If HttpOnly You Could Still CSRF… Of CORS you can!

Graph-X
5 min readSep 26, 2019

--

The Background

Before we get into the meat and potatoes of this post, we need to take a look at a couple of concepts in appsec. The first one deals with Cross-Origin Resource Sharing (CORS). This is a complex security mechanism that is built into all modern browsers. If you are not familiar with what it is and how it works, you should read this very good article on it. The second concept deals with HttpOnly cookies. If unfamiliar, you can read up on that here.

The Setup

For this exercise we’re going to use an app/API that I described in a previous blog post here as an example. To paint this picture we will assume the following:
1) We have an endpoint that properly validates session cookies.
2) Session cookies are properly protected with httponly and secure flags
3) CSRF tokens are not used by the app
4) Full collection of server headers are as follows:

Response headers received from the victim server

As you can see from the above image we have two flags set on our session cookie; secure and HttpOnly. The HttpOnly flag set on our session cookie means that we are not allowed by the browser to access the cookie using JavaScript. Or can we?

Enter XMLHttpRequest

XMLHttpRequest is a wonderful function of JavaScript that allows developers to interact with the server and allow for dynamic updates of a page without having to refresh the page. It makes for clean, flashy web apps. Below is the malicious website code with part of the JavaScript used to execute the attack:

Malicious XMLHttpRequest JS Code

However, since it’s JavaScript, we should not be allowed via client-side script to access the cookie, according to the OWASP article on the subject. Normally this would be the case, but the XMLHttpRequest has a property called withCredentials. According to the article, the withCredentials property “Is a boolean that indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies or authorization headers”. So according to everything we have thus far, we can use session cookies programatically unless the HttpOnly flag is set, right?

Wrong. There appears to be an exception to the client-side script access prohibition for XMLHttpRequest that will still allow you to access the session cookie even with the HttpOnly flag set as long as the withCredentials property is set to true. This means that we can still execute CSRF using a victims session cookie that is properly secured with the HttpOnly flag.

Of CORS There’s Always A Catch

If you looked at the article on CORS that I linked in the background section, you will see that we have another hurdle to this CSRF execution. CORS is a complicated security concept that is the bane of many a web developer. It’s also the bane of many an attacker. When I first attempted to execute the CSRF on the web app, I kept on seeing the below error in the console when I tested my POC. “Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource…”.

Console Error From CSRF Attempt.

Since the victim server doesn’t have the Access-Control-Allow-Origin header set, it looks as though we’re stuck. Well, sort of. As it turns out, we still send our request to the server AND the request is still executed on the server using the secured session cookie! The error implies that the request was blocked by the browser. However, upon closer examination we see that it’s only the response that the browser discards. What does this mean?

Putting It All Together

Let’s use this PHP as the server example of simulating an update to user information. I’ll link the victim and attacker code in full at the end of this post so you can play with this yourself.

PoC Victim Server Code

Let’s say that we have an admin user that logs in to the application then receives a phishing email with a link to our malicious server. When the admin navigates to the malicious server, an XMLHttpRequest is executed against the victim server that updates the adminId 1 with the attacker’s email address and a password of their choosing.

Malicious Server Code

Now as a result of CORS restrictions we’ll end up with an error, but the error is generated by the browser itself. NOT the server. This means that the server received the request and faithfully executed it complete with expected response. However, we’re unable to read the response. So as long as we aren’t counting on the data in the response like in this particular scenario we can still execute CSRF. Even with HttpOnly and CORS protections in place.

The Fix

As it happens, this gap has been noticed and a fix is in. There’s a cookie flag called SameSite. This is a relatively new flag and Mozilla lists it as experimental. Additionally, PHP as of 7.3 includes this capability.

However, this might present some design hurdles with existing web apps that should be taken into consideration when reviewing the implementation of this flag.

The Code

--

--

Graph-X

I’m the last person you should be taking advice from