In this article I would like to talk about how you can combine small flaws in the processing of cookie-values in a chain, and to make at the expense of this attack on users of popular web applications.

The story began over a year ago when I tested the DOMinator program for searching for DOM-Based XSS on Bug Bounty program sites. One of the first warnings I received was a cookie injection vulnerability in JavaScript analytics Google Analytics.
When accessing a site with Google Analytics, the script processes the HTTP value of the Referer header and extracts the host and the path to the script from it to track where the user came from. In the future, this data gets into the cookie parameter
__utmz
.
')
It looks like this:
__utmz=123.123.11.2.utmcsr=[HOST]|utmccn=(referral)|utmcmd=referral|utmcct=[PATH]
By changing the script path on your site from which the user navigates to the site with Google Analytics, you can influence the end of the
__utmz
cookie value and try to change the cookie attributes, since the path is not processed before it hits the value. However, attributes will be overwritten by subsequent values that Google Analytics substitutes.
blackfan.ru/x/injection;injection=injection?r=http://site.com/
Result:
document.cookie=__utmz=blah...|utmcct=/x/injection;injection=injection; path=/; domain=.site.com
At the moment, it was difficult to call it a vulnerability, but nevertheless, the behavior of the script is quite interesting, besides, it is incredibly common.
Bug Bounty and Cookie
A small digression. Cookie vulnerabilities in vulnerability rewards programs are notable in that you might have two main scenarios:
- Found the ability to overwrite and create arbitrary cookie settings.
Answer: This is just a cookie, what will it give you?
- Found XSS via cookie parameter.
Answer: You cannot create an arbitrary cookie parameter, it means that this is not a vulnerability!
And only if both options were in the same Bug Bounty program, then you, so be it, will be paid.
The need to prove each time the threat from cookies is often discourages people from finding them. As for the time spent on this, you can find a dozen more vulnerabilities that will be taken without question. Basically, only the most basic things are checked, such as session fixing and the correctness of the secure attributes set, httpOnly ...
Cookie
Take a look at the structure of the cookie headers:
Set-Cookie: par=val; path=/; httpOnly; secure;
Cookie: par=val; par2="val2"; par3=val3;
The structure is quite complex and the browser, JavaScript scripts and the web server work with it every time you request. Each handles it in its own way, and the same line can produce different results.
Analyzing the processing of cookies, you must ask the following questions:
- Do you need spaces after;
- What characters can be used instead;
- What value will result in the case of identical keys
- Is the key case important?
- How many attributes can a parameter have
- What value will be the result in case of identical attributes?
- How to encode special characters
Cookie handling
The first and most famous feature - Safari allows you to declare several parameters through a single Set-Cookie header.
Set-Cookie: param1=value1; path=/, param2=value2; httpOnly;
Returning to the problem of Google Analytics, let's check this feature in setting cookies through JavaScript. We get the first version of operation:
blackfan.ru/x/injection;,injection_cookie=injection;?r=http://site.com/
Result
document.cookie=__utmz=blah...|utmcct=/r/injection;,injection_cookie=injection; path=/; domain=.site.com
Safari will create two cookie parameters
__utmz
and
injection_cookie
.
That is, for a Safari user on any site with Google Analytics, you can create an arbitrary cookie setting. It remains only to think of why ...
CSRF
Protection against CSRF can be divided into 3 types:
- Different tokens for each action. Stored on server.
- One session token for all actions. Stored on the server in a user session.
- One session token for all actions. Stored in the cookie parameter.
The third option is based on the fact that the value of the token in the user's cookie is not accessible to the attacker. To pass the test, simply send the same token value in the cookie and post parameters. That is, a simple rewriting of the value through Google Analytics is perfect here.
Cookie handling features # 2
What if you deploy an attack at 180 degrees? It is not necessary that the browser actually had a cookie parameter with a CSRF token, the value of which we know. Enough to think so web server.
This will help another discovered cookie processing feature.
RFC2109
Note: For backward compatibility, the separator in the cookie header
is semi-colon (;) everywhere. A server should also accept comma (,)
as the separator between cookie-values for future compatibility.
Many web servers support cookies not only through a semicolon, but also separated by a comma.
Cookie: par=val; par2="val2"; par3=val3;
Cookie: par=val, par2="val2", par3=val3,
Moreover, in some cases, the space is not required.
Cookie: par=val;par2="val2";par3=val3;
Cookie: par=val,par2="val2",par3=val3,
In turn, for most browsers, characters such as space and comma are quite normal. And if you install:
Set-Cookie: par=val, csrftoken=val2;
document.cookie="par=val, csrftoken=val2;";
For the browser, this will be one value, but for some server implementations, two cookie parameters. However, in the case of using these features to bypass the CSRF protection, it is necessary to remember that we do not overwrite the old token, but add another one, that is, the order of processing cookies is important.
Cookie: csrftoken=realvalue; par=val, csrftoken=fakevalue;
In total, there are already two chains of exploitation of vulnerability:
Safari -> WebApp (GA & Double Submit Cookies)
-> WebApp (GA & Double Submit Cookies & , cookie)
Exploitation
After preparing a good base, you need to check in real conditions. Almost immediately, the ideal option is
mobile.twitter.com
.
It implements CSRF cookie-based protection, the server supports a comma-separated cookie without a space, but ... It does not have Google Analytics. But he is on
translate.twitter.com
! It's time to check the processing of cookie attribute values, or more precisely, to check the possibility of discarding path and domain values added at the end if an injection occurs in the cookie value.
It turned out that Google Chrome, in the case of a large number of cookie attributes, at some point simply ceases to parse them and does not reach the last valid values.
That is, in this case:
Set-Cookie: test=test; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; [...]; domain=blah.blah.blah.google.com;
The cookie will be installed on
.google.com
, not
blah.blah.blah.google.com
.
Thus, one more operation chain is added:
Chrome -> WebApp 1 (Double Submit Cookies & , cookie) & WebApp 2 (GA)
Form PoC:
<html> <body> <form style="display:none;" id="csrf" action="https://mobile.twitter.com/api/tweet" method="POST"> <input type="hidden" name="tweet[text]" value="PoC" /> <input type="hidden" name="m5_csrf_tkn" value="x" /> <input type="submit" value="Submit request" /> </form> <script> function xxx() { setTimeout("document.getElementById('csrf').submit();",5000); } </script> <a target="_blank" href="http://blackfan.ru/x/,m5_csrf_tkn=x,;domain=.twitter.com;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;?r=http://translate.twitter.com/" onclick="xxx()"> Tweet "PoC" </a> </body> </html>
Description:
- User is logged in to
twitter.com
- Mobile version of
mobile.twitter.com
picks up the session of the main site even if the user did not visit it - We assume that the user was not on
translate.twitter.com
and does not have the cookie __utmz
on it - We transfer the Referer with the path that contains the injection into the cookie to the site
translate.twitter.com
- Google Analytics creates cookies
__utmz=blah...|,m5_csrf_tkn=x,
- Due to the large number of attributes, Chrome overwrites the domain on
.twitter.com
- We are waiting for the end of the request processing and send another request to create a tweet “PoC” using the token “x”
- In accordance with the order of the sent cookie,
mobile.twitter.com
takes our value of the token "x" and makes sure that the value in the post request and in the cookie are the same - User has PoC tweet
Cookie handling features # 3
The next goal was
instagram.com
, or rather all the sites on Django. CSRF protection in Django is also cookie-based. To successfully pass the scan, it is enough to send the same values in the
csrftoken
cookie and the post parameter to
csrfmiddlewaretoken
, or in the HTTP header
X-CSRFToken
. However, there is an additional check that may further hinder. If the site is working over HTTPS, Django checks the Referer header and, in case of a mismatch, blocks the request, even if it contains the correct token. Post requests without Referer are also blocked.
During the study of the processing of cookies in Django, the following features were discovered:
- It is not necessary to use a semicolon as a separator; any whitespace character between the parameters is sufficient.
Cookie: test=test test2=test2
- If the cookie value is
[ \ ]
, then the first part of the cookie is discarded.
Cookie: test=test]test2=test2
.
As a result, only test2
.
It turned out that this problem is not even in Django, but in Python. The cookie library processes values in accordance with
RFC2109 . And it turns out that the use of
[ \ ]
characters is not provided if the value is not framed with double quotes. For browsers, the use of these symbols is quite normal.
PoC for instagram is almost identical to the previous one:
<html> <body> <form action="http://instagram.com/web/friendships/[user_id]/follow/?ref=emptyfeed" id="csrf" method="POST"> <input type="hidden" name="csrfmiddlewaretoken" value="x" /> <input type="submit" value="Submit request" /> </form> <script> function xxx() { document.getElementById('csrf').submit(); } </script> <iframe src="http://blackfan.ru/x/]csrftoken=x,;domain=.instagram.com;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;?r=http://blog.instagram.com/" onload="xxx()"/> </body> </html>
Blog.instagram.com is used for forwarding cookies
__utmz
, and /
/r/]csrftoken=x,;domain=.instagram.com;
.
Cookie handling features # 4
After talking with Google, Twitter, Facebook, Django and Python, I again decided to try to get around their fixes.
The following condition changes have occurred:
- Google Analytics began dropping the part after the semicolon and forcing to replace the space with% 20
- Python fixed incorrect processing
[ \ ]
However, there was still the possibility of enumerating cookies through whitespace in Django, which was greatly hampered by the replacement used by Google.
The whitespace check showed the following:
- Internet Explorer replaces \ x09 \ x0b \ x0c with _
- Chrome does not set a cookie if it contains \ x09 \ x0b \ x0c characters
- FireFox considers these characters normal
As a result, the following version of operation for FireFox is obtained
instagram.com/?utm_source=1&utm_medium=2&utm_campaign=3&utm_term=4&utm_content=5%09csrftoken%3dx
Cookie handling features # 5
I also found another interesting option in some server implementations, but so far I have not found a use in real conditions. When using special characters in cookies, the value is framed in double quotes. This behavior can be used as follows:
Set-Cookie: test="test
Set-Cookie: foo=bar
Set-Cookie: test2="
For browsers, double quotes are not some special character and as a result you will get the following title:
Cookie: test="test; foo=bar; test2="
But some web servers can process these values as one test parameter, as a result of which the
foo=bar
parameter will not be created. With the coincidence of an incredible amount of conditions, this feature can also be used.
results
Operating options
Safari -> WebApp (GA & Double Submit Cookies)
-> WebApp (GA & Double Submit Cookies & , cookie)
Chrome -> WebApp 1 (Double Submit Cookies & , cookie) & WebApp 2 (GA)
FireFox -> WebApp (GA & Double Submit Cookies & , cookie)
Fixes
- Google Analytics added% 20 blank space for cookies (dubious improvement)
- Google Analytics fixed the ability to change cookie attributes by dropping everything that comes after the ";"
- Google Chrome will NOT correct overwriting using a large number of attributes, since the client should not be able to set arbitrary attributes initially.
- Python fixed a problem with characters
[ \ ]
( https://hg.python.org/cpython/rev/270f61ec1157 )
- Twitter changed the type of CSRF protection on
mobile.twitter.com
When dealing with Google, I was faced with a total lack of understanding of the essence of the vulnerability. They demanded an example from me on Google sites, which I did not have, and they were not at all interested that Google Analytics could be a threat on other sites. And only after a dozen letters did my report get to Krzysztof (apparently, it was
@kkotowicz ), who figured out what was happening and conveyed the information to the appropriate developers.