Cross Site Request Forgery (CSRF) is a pretty straightforward flaw to take advantage of. Explaining it can be more difficult, due to the number of conditions that have to be met. This post isn’t meant to be a primer on CSRF, but here are the conditions that must occur.
- The targeted app has a critical transaction that uses predictable inputs
- An attacker knows what those inputs are and is targeting the vulnerable app
- The attacker has a way to get a victim user to visit a page under the attacker’s control
- A victim user has an authenticated session with the targeted app and visits the attacker’s web page
That is a lot of requirements to be met and they must all be met at the same time. In spite of this, CSRF can be a damaging attack if a critical transaction is targeted and successfully exploited. Determining what is a critical transaction requires us to evaluate the context of the flaw in the app. For example, using CSRF to create a new user in a web app that manages internal business processes and data would be critical. Creating a new user in an app that someone could just register for would not be critical. Anyone can register already. But I digress.
For the sake of argument, lets say that we’ve found a critical transaction that is vulnerable to CSRF. The transaction does not accept GET requests, so simply doing something like <img src=”http://victim.site/page?adduser=joe&pass=password” /> isn’t going to work. As a result, we are going to need to do more work. To exploit this flaw, we use some code that looks like that shown below.
The problem with using an XML HTTP Request (XHR) in this attack is that Cross Origin Request Sharing (CORS) will come into play.
CORS Preflight Checks
CORS is meant to provide some access control for cross site HTTP requests. This includes our XHR request used to try to exploit CSRF in a site. A CORS preflight check is an HTTP request that is performed by browsers prior to making the specified cross site HTTP request. The browser send a HTTP OPTIONS request to the targeted site to get the CORS policy. If the site’s policy allows our attack site to make cross site requests, then we are golden. If not, we need to see if we can make our request fit into a “simple request” and avoid the CORS preflight check.
According to Mozilla’s documentation on CORS, a simple request only allows the use of GET, HEAD, and POST requests. Since we are using a POST request in our CSRF attack, we meet these requirements. The documentation goes on to specify the requirements for the “Content-Type” HTTP header. The content type must be one of these three to avoid the preflight check.
Any other content types will trigger the CORS preflight. If we see that the application is using a different content type than the three above, then our attack will not work. Well, it shouldn’t work. Even if you see a request using “application/json” within the app, you should try attacks using the three above. The server side code may not be checking the content type or be really liberal in what it is willing to accept. You may find that it will accept “text/plain” instead, the CORS preflight check isn’t made, and your attack works.
The take away here is really aimed at not wasting time during testing. If you are using XHR on a request that doesn’t accept these content types and the required HTTP methods, then you are going to invoke a CORS preflight check. If the victim has set their CORS policy correctly, your malicious site should not be be able to make the cross site HTTP request. At this point, it is time to move on from testing for CSRF.