Introducing Qwiet AI AutoFix! Reduce the time to secure code by 95% Read More

# Cross-Site Request Forgery

**Cross-site request forgery** (CSRF) vulnerabilities can be used to trick a user into performing an unwanted action on your website.

Websites consist of a combination of client-side and server-side code. The client-side code is the HTML and JavaScript that is rendered by and executed in the browser. This client-side code allows users to navigate to other URLs, submit HTML forms, and trigger AJAX requests via JavaScript. Your server-side code will intercept the data sent in the resulting HTTP requests, and act upon it appropriately.

These server-side actions can also be triggered by *forged* HTTP requests unless you explicitly put in protective measures. A forged request is when a malicious actor tricks a victim into clicking on a link, submitting a form, or running some code that triggers an unexpected action. This malicious code is typically hosted on a website controlled by the attacker, on another domain – hence the *cross-site* part of the name.

Protecting against CSRF requires two things: ensuring that `GET` requests are **side effect free**, and ensuring that other types of request originate from your client-side code by using **anti-forgery tokens**.

## Making GET Requests Side Effect Free

Hyperlinks on the internet trigger a `GET` request to the destination URL. This means that links into your website from external domains will almost always be `GET` requests. To make sure your users don’t experience unexpected actions when clicking on links, `GET` requests should only retrieve resources from the server – never *change state* on the server. Anything that changes state – a *side effect* – should be handled by `POST`, `PUT` and `DELETE` requests, depending on whether the request is adding, updating or deleting state.

Here are some examples of side effects that should **not** be performed with `GET` requests:

* Logging in
* Logging out
* Password resets
* Sign-ups
* Posting or editing content
* Account deletion

For example, consider the following implementations of a “Logout” function:

=== Angular

“`typescript
export class LogoutService {
constructor(private http: HttpClient) {}logout(): void {
// Send a GET request to the server to trigger a logout.
this.http.get(‘/api/auth/logout’).subscribe()
}
}
“`

=== React

“`jsx
class Logout extends React.Component {
constructor(props) {
super(props)this.updateLoginStatus = props.updateLoginStatus
}

async logout() {

// Invalidate the session on the server.
const response = await fetch(“/logout”, {
method: ‘GET’
})

if (response.ok) {

// Set the login status to “Logged out” and the username to null in the component state.
this.updateLoginStatus(false, null)
}
}

render() {
return (
<a onClick={() => { this.logout() }}>
Logout
</a>
)
}
}
“`

If an attacker tricks a user into clicking on a link to `www.yoursite.com/logout`, that user will be logged out before they are aware of what is happening. For this reason, it’s recommended to implement logout functionality via `PUT` or `DELETE` requests. The situation becomes even more dire in the event of e.g. account deletion functionality: you will have real problems if attackers can trick users into accidentally deleting their accounts.

## Anti-Forgery Tokens

Once you have restricted your side effects are to non-`GET` requests, you need to protect against malicious HTTP requests that use the other HTTP verbs. `POST`, `PUT` and `DELETE` requests can still be sent to your site from HTML forms and JavaScript code hosted on other domains. It is harder for an attacker to forge these type of requests – they will need the user to do more than click on a single malicious link – but it is still possible.

To ensure that you only handle valid requests for these HTTP verbs, you need to include a **secret** and **unique** token with each HTTP response, and have the server verify that token when it is passed back in subsequent requests that has side effects. These tokens are called **anti-forgery tokens**.

Anti-forgery tokens are typically (strongly) random numbers that are stored in a cookie or on the server as they are sent to the browser. The server will compare the token attached to any inbound requests with the original value. If the values are identical, the server will accept the valid HTTP request.

Anti-forgery tokens should be generated on the web-server and provided to client-side code in some fashion – typically they are written out as `<meta>` tags in the HTML header, or loaded asynchronously via an AJAX call. Any subsequent AJAX calls should pass the token back in an HTTP header, so the server can validate it:

=== Angular

Angular allows you to add anti-forgery tokens to HTTP requests using the `HttpClientXsrfModule` module:

“`typescript
@NgModule({
declarations: [
],
imports: [
BrowserModule,
HttpClientModule,
HttpClientXsrfModule.withOptions({
cookieName: ‘XSRF-TOKEN’,
headerName: ‘X-CSRF-TOKEN’
}),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
“`

The name of the header and the name of the hidden form field will depend on what library you are using to generate your anti-forgery tokens on the server-side. Consult the library’s documentation for details, and add the appropriate corresponding names in your Angular configuration.

=== React

“`jsx
class Logout extends React.Component {
constructor(props) {
super(props)this.updateLoginStatus = props.updateLoginStatus
}

async logout() {

// Invalidate the session on the server, passing back the anti-forgery token
// in an HTTP header. The browser will attach a matching value in a cookie.
const response = await fetch(“/logout”, {
method: ‘DELETE’,
headers: {
‘CSRF-Token’ : this.props.csrf
}
})

if (response.ok) {

// Set the login status to “Logged out” and the username to null in the component state.
this.updateLoginStatus(false, null)
}
}

render() {
return (
<a onClick={() => { this.logout() }}>
Logout
</a>
)
}
}
“`

If you generate HTTP requests using HTML forms, you should add the anti-forgery token in a hidden field of the form:

“`jsx
function Comment(props) {
return (
<div className=”post”>
<form action=”/comment” method=”POST”>
<textarea name=”comment” placeholder=”What’s on your mind?”/>
<input type=”hidden” name=”_csrf” value={props.csrf} />
<button type=”submit”>Add Comment</button>
</form>
</div>
)
}
“`

The name of the header and the name of the hidden form field will depend on what library you are using to generate your anti-forgery tokens on the server-side. Consult the library’s documentation for details, and update your client-side code accordingly.

### Ensure Cookies Are Sent With The SameSite Cookie Attribute

The `Same-Site` cookie attribute instructs the browser whether to send cookies with requests initiated by third-party domains. Since anti-forgery tokens are typically validated by comparing the token value sent in the HTML form with a value in a cookie, they are only effective when the `Same-Site` attribute is set appropriately.

A `Same-Site` value of `Strict` will mean than *any* request initiated by a third-party domain to *your* domain will not have any cookies attacked. This is the most secure setting, since it prevents malicious sites attempting to perform harmful actions under a user’s session.

A value of `Lax` permits `GET` request from a third-party domain to *your* domain to have cookies attached – but *only* `GET` requests. With this setting a user will not have to sign in again to your site if they follow a link from another site (say, Google search results). This makes for a friendlier user-experience – but make sure your `GET` requests are side effect free!

## Further Considerations

* Make sure your cookies cannot be accessed in JavaScript, and are only sent over HTTPS. This is done by adding the `HttpOnly` and `Secure` attributes to each cookie.

* Many sites require a secondary authentication step, or require re-confirmation of login details when the user performs a sensitive action. (Think of a typical password reset page – usually the user will have to specify their old password before setting a new password.) Not only does this protect users who may accidentally leave themselves logged in on publicly accessible computers, but it also greatly reduces the possibility of CSRF attacks.

* Ensuring `GET` requests are side effect free is part of a series of design principles called *Representation State Transfer (REST)* that assign certain types of action (view, create, delete, update) to different HTTP verbs. REST insists that GET requests are used only to *view* resources.

## CWEs

* [CWE-352](https://cwe.mitre.org/data/definitions/352.html)

About ShiftLeft

ShiftLeft empowers developers and AppSec teams to dramatically reduce risk by quickly finding and fixing the vulnerabilities most likely to reach their applications and ignoring reported vulnerabilities that pose little risk. Industry-leading accuracy allows developers to focus on security fixes that matter and improve code velocity while enabling AppSec engineers to shift security left.

A unified code security platform, ShiftLeft CORE scans for attack context across custom code, APIs, OSS, containers, internal microservices, and first-party business logic by combining results of the company’s and Intelligent Software Composition Analysis (SCA). Using its unique graph database that combines code attributes and analyzes actual attack paths based on real application architecture, ShiftLeft then provides detailed guidance on risk remediation within existing development workflows and tooling. Teams that use ShiftLeft ship more secure code, faster. Backed by SYN Ventures, Bain Capital Ventures, Blackstone, Mayfield, Thomvest Ventures, and SineWave Ventures, ShiftLeft is based in Santa Clara, California. For information, visit: www.shiftleft.io.

Share

See for yourself – run a scan on your code right now