Recently I was pentesting a web app that had an unauthenticated XSS vulnerability but there was some heavy filtering in place. Nonetheless I was able to achieve session fixation using a combination of a technique I previously explained and some fun filter workarounds – including using the application’s own defensive HTML encoding to create a working XSS payload!
The application used a bespoke session management cookie. I’ll call it MYSESSIONID. On login, it wasn’t renewed. I couldn’t push a session cookie onto the victim in a classic session fixation attack. However, I had XSS in an unauthenticated page – but not the login page. The filtering in place used a combination of removal and encoding. Characters that were stripped out included:
+ ; ( ) ? < >
Characters that were allowed included:
" ' = [ ] / , .
So even though MYSESSIONID wasn’t protected with the HttpOnly flag, I just couldn’t construct a payload to steal it. Instead I looked to set one of my own. Here’s a breakdown of the attack:
1. Get a valid cookie
The application did not accept arbitrary session management cookies so the attacker sends a request to get a valid one. In this case, simply having no MYSESSIONID wasn’t enough, the cookie had to be present but an invalid value did the trick:
Set-Cookie: MYSESSIONID=NDnQrZ6JsMHyJTBCw8n:xx01; Path=/; Domain=.example.com
2. The XSS
The malicious link looked something like this (the highlighted bits are explained below):
var a = "foo"
The %0a at the front of the XSS payload was used to start a new line and this was sufficient to act as a statement terminator after
var a = "foo" (semi-colons were being stripped). But in order to inject a
The colon at the front was used because it looked like the session cookie was delimited in that way. That “xx01″ might refer, for example, to an internal server for load-balancing. Anyway, whatever it did, the application tolerated the unusual suffix to the session cookie. So that explains the :%0d appended to the cookie value in the XSS payload. Now for the
3. The victim logins in
So, at this point, the attacker has set the MYSESSIONID cookie on the victim to be
via a reflected XSS attack. Now the victim goes to the login page at https://www.example.com/app/login or is bounced there by navigating to a part of the site that enforces authentication. At login two MYSESSIONID cookies are passed up. This is because one had been set earlier in a
Set-Cookie response header the first time the victim hit the site, even if that was by visiting the XSS’ed page. The genuine MYSESSIONID has a
path of / and a
domain of .example.com. If I had set a cookie by XSS with no attributes my cookie would have had a
path of /app/folder/ (to match the path of the page which set the cookie) and a
domain of www.example.com (to match the domain of said page). This would mean my cookie would never be sent up to /app/login for authentication, hence the need to set a
path as part of the XSS.
Furthermore, when two MYSESSIONID values were sent up, the application took the first value so I had to make sure my cookie was first. By setting a
path of /app/, it trumped the real MYSESSIONID for having a better path match to /app/login. Thus it was listed first in the POST request with the credentials and became authenticated:
Cookie: MYSESSIONID=NDnQrZ6JsMHyJTBCw8n:xx01: MYSESSIONID=4GRc4jiKNeQIfsqh2:xx01
In contrast, the
domain of a cookie does not govern precedence in a standardised way, it varies between browser. From memory I think my cookie (with a more specific domain match) was sent up first by IE but second by Chrome and Firefox. It’s not something you want to rely on. Neither could I overwrite the cookie because for that to happen the name, path and domain must match. That would mean having to change both attributes from their defaults but in this case I could only change one. This is because I’d need a second semi-colon to set a second attribute and in doing so, using the encoding trick above, the first attribute would be spoilt, e.g. I’d get
var a = "foo"
document.cookie="MYSESSIONID=NDnQrZ6JsMHyJTBCw8n:xx01: path=/app/ domain=.example.com";
Developing this proof-of-concept for this specific injection point was quite fiddly and took some persistence but it was worth it. For all of their filtering – and because they did not change the session cookie after authentication – this was a nice practical attack using an unauthenticated XSS. One take-away thought then: be sure to probe the XSS defences in full because you never know what might come back and how it could be of help!