C-SuRFing
In this week's article we'll explore the web vulnerability, Cross-Site Request Forgery, more commonly referred to as CSRF (pronounced "Sea Surf"). We'll cover what the vulnerability is and why it matters, as well as common prevention methods and bypasses to these defenses!
What is Cross-Site Request Forgery?
Cross-Site Request Forgery (CSRF) is a technique used by adversaries to send requests while impersonating another user, allowing them to execute unwanted actions on the users behalf.
This works when a malicious page is able to generate a request that includes the valid cookies of a user's session on another site. This cross-site request is usually targeted at actions that make stateful changes to the user's data, things like updating account information, making comments or posts on the users behalf, and more.
CSRF is typically used to target state-changing requests as it is highly unlikely that an adversary will be able to see the response to the request. Instead, if they are able to compromise a user's password, update their email address/email settings, or otherwise, an adversary may be able to achieve full account take over (ATO).
Why Should we care?
From an adversarial perspective, there are few things as impactful as arbitrary ATO. CSRF vulnerabilities potentially allow us an avenue to achieve this, and even if we're unable to fully compromise an account, we're still heavily able to impact the integrity of an application as we can act on behalf of other users.
Caveat of XSS:
It's also worth noting that CSRF protections fail entirely in the presence of XSS. To put it in more mathematical terms. The set of actions that are achievable via CSRF can be considered a subset of the actions achievable with XSS. It's important for us as security researchers to be mindful of this fact, as if we are reporting on a found XSS bug, we wouldn't also report that CSRF is possible unless the CSRF is possible without the XSS as well.
Simply put, you wouldn't report the symptom of a vulnerability as its own issue. Instead, you could include this in the initial report and emphasis the impact possible when all CSRF protections can be bypassed.
Finding CSRFs:
Looking for State-Changing Requests:
As mentioned earlier, state-changing requests are actions that change a value. Things like modifying account settings, creating new items, and updating existing items are all considered state-changing. By finding where in the application these stateful requests come from, we're able to build out a list of potential targets.
Identifying Missing Protections:
With a list of targets in hand, we can begin checking through the identified functionality and take a closer look at each request. Ideally, what we're looking for is for there to be no CSRF token and hopefully, SameSite=none.
Barring an ideal situation, we should take the time to check that if a token is present, is it being properly validated? Is it sufficiently random? If the cookies in use have SameSite=Lax, there are still ways around it. Are we able to create a GET request to any of our target endpoints? Do we have a way to deliver a malicious link to users?
Exploiting the Vulnerability:
After establishing our targets and refining our list against the security measures in place, our last step is to figure out how to exploit it. If you have access to Burp Suite Professional, you'll have the ability to use its CSRF-POC generator. A wonderful convenience tool that will write out your POC for you against a given endpoint. Barring that, the manual way isn't too terrible!
Manual POCs:
Alternatively, it's not too complex to create our POC manually. Using your favorite text editor, you can make a file, poc.html
:
<html>
<form method="POST" action="https://example.com/update_password" id="poc">
<input type="text" name="new_password" value="EruWasHere">
<input type="Submit" value="Submit">
</form>
<script>document.getElementById("poc").submit();</script>
</html>
With a simple HTML page, we can test our targets to see if they're vulnerable to CSRF.
In the above example, the key things to remember are to:
- Change the
form
to match your target request - Update the
name
of the input field to match what you've found on the target
It's also worth noting that the script
added at the end of this form will effectively make our POC execute as soon as a user visits the page, requiring no extra action on their part.
Preventing CSRFs:
CSRF prevention is most commonly handled by implementing CSRF tokens, unique strings that are embedded within forms on an application, that must be submitted with each relevant request.
CSRF Tokens:
Implementing these tokens on a per session and per form basis, we can ensure that an adversary won't be able to reuse tokens from a different session to forge their requests. Combining this with a high level of entropy such that tokens are not susceptible to frequency analysis can nearly guarantee that an adversary won't be able to use anything besides the correct CSRF token for a specific user.
Requiring the token to be submitted alongside a state-changing request, it becomes significantly harder for an adversary to forge a request that will be seen as valid, meaning they'll need to take a step back and work to discover a method of leaking a user's real-time CSRF tokens if they wish to pursue this vulnerability.
SameSite Flag:
Similar to the SameOrigin policy used by webpages, the SameSite flag allows us to enable extra protections for our cookies within a page. Consisting of three settings -- Strict, Lax, or None -- various levels of protections can be enabled.
SameSite=Strict:
Prevents cookies from being sent during any cross-site requests.
SameSite=Lax:
The current default in modern browsers, cookies are only allowed to be sent in requests that meet the following conditions:
- Are a result of a top-level navigation, such as clicking a link
- The request uses the GET method
SameSite=None:
Disables SameSite restrictions entirely, regardless of the browser. This means that regardless of where a request comes from, the cookie will be sent with it.
Bypassing CSRF Protections:
Bypassing SameSite Flags:
CSRF via a GET Request:
Even when an application employs SameSite flags on its session cookies, it's still possible to exploit a CSRF if the state-changing request allows for the GET method. For example, if an adversary is able to convince a user to click a malicious link, such as the following:
https://example.com/updated_password?new_password=EruWasHere
They're able to execute a state-changing GET request on the user's behalf, allowing them to bypass the SameSite=Lax protections. This works specifically because SameSite Lax is only checking that the request is a GET, and that it was triggered by a Top Level Navigation event.
Newly Issued Cookies:
Another point of note with the SameSite flag, when the application does not explicitly set SameSite=Lax, and instead relies on the browser to apply this by default, a delay is included in which cookies with the SameSite=Lax attribute are able to be sent via POST requests!
Largely made as an accommodation to prevent single sign-on mechanisms from failing, Chrome specifically will wait 120 seconds after a cookie is set before enforcing restrictions on top level POST requests. While manual exploitation of this may not be the most realistic, if an adversary is able to force a user to refresh their session -- and receive a new session cookie as a result -- there is the potential for bypassing the protections offered by SameSite Lax.
Flaws with CSRF Tokens:
Not Properly Validated:
Similar to most cookie based protections, it's important to remember that a person made it. Just because there are CSRF tokens implemented across an application, doesn't mean they're implemented properly. Some key things to test for are:
- Is the value of the token checked?
- Does the token need to exist?
- Can I reuse tokens from other locations?
- Can I use tokens from other users?
- Is the token sufficiently random?
Double-Submit Cookies:
Another common prevention method, some sites will require the CSRF token to be provided both as a cookie, and as a part of the request body. In this case, it's worth testing to see if the application is only checking to see if the values match each other and aren't validated against a value held by the server. If this is the case, we can treat this the same as any other client-side protection, and directly manipulate our request to bypass it.
The complexity of this bypass comes from the need to set your false cookie on behalf of the legitimate site. Likely, this will require us to have found some form of gadget to accomplish this task before we're able to employ it for our CSRFs.
Clickjacking:
Alternatively, if the identified endpoint seems to have sufficient protections in place to prevent CSRF, an adversary may attempt to exploit a clickjacking vulnerability if one is present. Ultimately, since clickjacking relies on tricking a user into submitting a hidden, iframed form, the same state-changing actions can be executed this way instead.
Escalating a CSRF:
Account Takeover:
Throughout this article, we've touched on CSRF being able to lead to full account takeover. In cases where sufficient protections are not in place on requests such as updating passwords, email addresses, or even sending password resets, it's possible to fully compromise the integrity, availability, and confidentiality of a user's account.
Self -> Stored-XSS:
It's also worth mentioning that CSRF has some use as a gadget even if we aren't able to achieve ATO. Take a scenario where you've discovered an injection point for Self-XSS within a field only visible to the user.
As is, this is likely to be marked as low, or potentially even informational due to the complexity and user interaction both being significantly higher on Self-XSS. That said, if the same field is vulnerable to CSRF, we're able to embed our XSS payload into our forged request and potentially remove the need for user interaction entirely. This effectively upgrades our Self-XSS into a Stored-XSS, significantly raising the impact of the vulnerability.
Breaching Confidentiality:
Even if we aren't able to fully takeover a user's account, if we can compromise requests surrounding their email settings, we can potentially force confidential information to be leaked. Take for instance, if you're able to modify an accounts email address to one you control and are then able to force some form of billing information email to be sent. More often than not, emails of this kind will include PII information about a user, addresses, full names, and more. Allowing information like this to leak is often a violation of GDPR standards, and almost always results in loss of trust of the application from users.