Practice
You can't talk about AJAX without mentioning one of the most important aspects of its implementation – protection against CSRF attacks.
CSRF (Cross-Site Request Forgery, also XSRF) – is a very dangerous attack that allows a hacker to perform a variety of actions on an unprotected site on behalf of other, registered visitors.
What these actions are – sending messages, transferring money from one account to another, or changing passwords – depends on the site, but in any case this attack is part of the essential knowledge every web developer must have.
The «classic» attack scenario is as follows:
Vasya is logged in to a site, let's say,
Vasya ends up on an «evil page», for example a hacker invited him to do so via an email or some other way.
The evil page contains a form like this:
When landing on the evil page, JavaScript calls
form.submit, thereby submitting the form to
mail.com.
The site
mail.com checks the cookies, sees that the visitor is authorized, and processes the form. In this example the form implies sending a message.
The result of the attack – Vasya, by visiting the evil page, unwittingly sent a message on his own behalf. The content of the message was crafted by the hacker.
ProtectionIn the example above, the attack exploited a weak link in authorization.
Cookies allow the site
mail.com to verify that it was indeed Vasya who came, but they say nothing about the data he's sending.
In other words, cookies do not guarantee that the form was created by Vasya himself. They only verify identity, not data.
A typical way sites protect against this – is a «secret key» (
secret), a special value that is generated randomly and stored in the visitor's session. Only the server knows it; we won't even show it to the visitor.
Then, based on the key, a «token» is generated (
token). The token is made so that on one hand it differs from the key – in particular, there can be many tokens for one key – and on the other hand it's easy to verify from the token whether it was generated from a given key or not.
For each token an additional random value is needed, called the «salt»
salt.
Token computation formula:
token = salt + ":" + MD5(salt + ":" + secret)
For example:
The session stores
secret="abcdef", this value is created once.For a new token, we generate
salt, for example let
salt="1234".token = "1234" + ":" + MD5("1234" + ":" + "abcdef") = "1234:5ad02792a3285252e524ccadeeda3401".This value is – on one hand, random, on the other hand – having such a
token, we can take its first part
1234 as the
salt and, knowing the
secret, verify using the formula whether it was computed correctly.
Without knowing the
secret, it is impossible to generate a token that the server will accept as correct.
Next, the token is added as a hidden field to every form generated on the server.
That is, the «honest» form for sending messages, created on
http://mail.com, will look like this:
When it's submitted, the server checks the
csrf field, confirms that the token is correct, and only then sends the message.
The «evil page», no matter how hard it tries, cannot generate a similar form, since it does not have the
secret, and the token will be incorrect.
Such a token is also called the form's «signature», which confirms that the form was generated specifically on the server.
Signature with form fieldsThis signature indicates that the author of the form is the server, but it guarantees nothing about its content.
There are situations when we want to be sure that some of the form's fields were not changed by the visitor on their own. Then we can include these fields in the MD5 for the token formula, for example:
token = salt + ":" + MD5(salt + ":" + secret + ":" + fields.money)
When the form is submitted, the server checks the signature, substituting into it the
secret it knows, and the submitted value
fields.money. If they don't match, either the
secret is wrong (a hacker), or
fields.money has been changed.
Token and AJAXNow let's move on to AJAX requests.
What if sending messages in our interface is implemented via XMLHttpRequest?
As with the form, we must «sign» the request with a token, to guarantee that its contents were sent to the server specifically by the site's interface, and not by an «evil page».
There are several options here, the simplest is an additional cookie.
During authorization, the server sets a cookie named
CSRF-TOKEN, and writes the token into it.
The code performing the XMLHttpRequest reads the cookie and sets the header
X-CSRF-TOKEN with it:
var request = new XMLHttpRequest(); var csrfCookie = document.cookie.match(/CSRF-TOKEN=([\w-]+)/); if (csrfCookie) { request.setRequestHeader("X-CSRF-TOKEN", csrfCookie ); }
The server checks whether the header is present and whether it contains the correct token.
This protection works because only JavaScript from the same domain can read the cookie. The «evil page» won't be able to «transfer» the cookie into the header.
If you need to not use XMLHttpRequest but, for example, dynamically generate a form from JavaScript – it is also signed in a similar way, a hidden field or an additional URL parameter is generated from the cookie.
SummaryA CSRF attack is when an «evil page» sends a form or request to a site where the visitor is presumably logged in.
If the site only checks cookies, it will accept such a form. And it should not do this, since the form was generated by an evil hacker.
To protect against the attack, the forms that
mail.com generates are signed with a special token. Not all forms need this – only those that perform actions on behalf of the visitor, that is, that could be targeted by the attack.
To sign the XMLHttpRequest, the token is additionally written into a cookie. Then JavaScript from the domain
mail.com can read it and add it to the header, and the server can check that the header is present and contains a valid token.
Dynamically generated forms are signed in a similar way: the token from the cookie is added as a URL parameter or an additional field.
Comments