1

Vulnerability

Login CSRF

⚠️

Vulnerability

Most CSRF defenses instinctively focus on actions performed by an already authenticated user.

That makes login CSRF easy to overlook.

In a conventional CSRF attack, the victim is already logged in and the attacker causes their browser to perform some action using the victim's existing authenticated session.

Login CSRF reverses that situation.

The attacker forges a request that logs the victim's browser into an account controlled by the attacker.

Traditional CSRF:
Victim already logged in
        ↓
Attacker forges a request
        ↓
Victim's account is modified

Login CSRF:
Victim not necessarily logged in
        ↓
Attacker forges a login request
        ↓
Victim's browser becomes logged into the attacker's account

A simplified attack might look like this:

Attacker creates an account
        ↓
Attacker knows the credentials:

username = attacker@example.com
password = attacker-password
        ↓
Attacker creates a page that submits those credentials to the real site's
login endpoint
        ↓
Victim visits attacker-controlled page
        ↓
Victim's browser submits login request
        ↓
Target application authenticates:  Attacker's account
        ↓
Victim's browser now holds a session for the attacker's account

For example, a vulnerable login flow might be targeted with a forged form:

<form action="https://app.example.com/login"
      method="POST"
      id="login-form">

  <input type="hidden"
         name="username"
         value="attacker@example.com">

  <input type="hidden"
         name="password"
         value="attacker-controlled-password">
</form>

<script>
  document.getElementById('login-form').submit();
</script>

The important point is that the attacker is not trying to authenticate as the victim.

They are deliberately authenticating the victim's browser as the attacker.

Why This Can Be Dangerous:

The victim may not immediately notice that anything unusual happened.

Depending on how the application behaves, they may begin entering information, creating content, or performing actions while authenticated to the attacker's account.

For example:

Victim thinks:

"I am using my account."
        ↓
Actually authenticated as: Attacker's account
       ↓
Victim enters:

Searches
Profile information
Messages
Saved data
Other account-associated content
        ↓
Data is stored in:

Attacker's account
        ↓
Attacker may later view that data through their own legitimate access

The exact impact depends heavily on the application.

Some applications clearly display the active account identity, making the attack easier for the victim to notice. Others may have interfaces where the currently authenticated identity is less obvious, allowing the account confusion to persist longer.

Why Login Endpoints Are Often Missed:

Developers sometimes reason about the login endpoint like this:

CSRF protection is needed because:

An authenticated user's session could be abused
        ↓
Login endpoint requires no authenticated session
        ↓
Therefore:

"No CSRF protection needed."

But that reasoning misses the actual security property being protected.

A login request changes the browser's security context.

Before request:

No authenticated identity
        ↓
Login request succeeds
        ↓
Browser now represents:
Authenticated identity X

If an attacker can choose identity X, that state transition itself can have security consequences.

Defending Against Login CSRF:

The synchronizer-token mechanism from CSRF-Tokens section can be applied to the login form just as it is to other sensitive state-changing requests.

Victim requests legitimate login page
        ↓
Server provides: 
csrf_token=random-value
        ↓
Login form includes token
        ↓
Victim submits credentials
        ↓
Server validates:

Credentials
        +
CSRF token
        ↓
  Login succeeds
Attacker needs:

username        ✓ Known
password        ✓ Known
csrf_token      ✗ Cannot obtain from the target origin

The forged login therefore fails.

2

Vulnerability

Logout CSRF

⚠️

Vulnerability

Logout is another state-changing action that applications sometimes leave unprotected because its immediate impact appears minor.

An attacker who can trigger a victim's logout cannot directly take over the victim's account. At first glance, the result is simply:

Victim is logged in
        ↓
Attacker forges a logout request
        ↓
Victim's session is destroyed
        ↓
❌ Victim must log in again

On its own, this is usually a low-severity issue primarily an annoyance or a denial-of-service condition affecting that user's current session.

The problem is that forced logout can become useful as part of a larger attack chain.

Forced Logout Followed by Phishing:

A common scenario is to deliberately interrupt the victim's session and then take advantage of their expectation that they simply need to authenticate again.

Victim is using the real application
        ↓
Attacker silently triggers logout
        ↓
Victim suddenly loses their session
        ↓
Victim is presented with a convincing fake login page
        ↓
Victim believes: "My session expired—I need to log in again."
        ↓
Victim enters credentials
        ↓
Credentials sent to attacker

The CSRF vulnerability did not steal the credentials itself.

Its role was to create a believable situation in which the victim expects to see a login prompt. The actual credential theft occurs when the victim enters their password into the phishing page.

This distinction matters:

Logout CSRF
        ↓
Creates an unexpected authentication state

Phishing
        ↓
Exploits that state to capture credentials

Neither step necessarily compromises the account alone, but together they can form a more convincing attack.

Combining Logout CSRF with Login CSRF:

Logout CSRF can also be chained with the login CSRF technique discussed above.

The attacker first ensures that the victim is no longer authenticated to their own account:

Victim logged into:

Victim's account
        ↓
Forged logout request
        ↓
Victim's session destroyed

The attacker then causes the victim's browser to submit a login request using credentials for an account controlled by the attacker:

Victim now logged out
        ↓
Forged login request
        ↓
Browser authenticates as: Attacker's account
        ↓
Victim continues using application
        ↓
⚠ Account confusion

Protecting the Logout Endpoint:

Logout is still a state-changing action and should be handled deliberately.

For applications where unexpected logout has meaningful security or usability consequences, the logout request can be protected using the same request-integrity mechanisms discussed throughout this section:

Logout request
        ↓
Valid authenticated session?
        ↓
  Yes
        +
Valid CSRF token or other appropriate
request-origin validation?
        ↓
  Yes
        ↓
Destroy session
3

Vulnerability

Stored CSRF

⚠️

Vulnerability

Every CSRF example so far has assumed that the attacker needs to get the victim to visit a separate, attacker-controlled page.

Stored CSRF removes that requirement.

Instead of hosting the request trigger on evil.example.com, the attacker manages to store that trigger inside the target application's own content.

Traditional CSRF:

Attacker-controlled site
        ↓
Victim must be lured there
        ↓
Forged request triggered

Stored CSRF:

Attacker submits content to the target application
        ↓
Application stores it
        ↓
Victim later visits the legitimate application
        ↓
Forged request triggered

Possible injection points include user-controlled content such as:

  • Forum posts.

  • Comments.

  • Profile fields.

  • User-generated pages.

  • Rich-text content.

  • Any field where attacker-controlled HTML can be stored and later rendered as active markup.

For example, imagine an application incorrectly allows HTML submitted by a user to be rendered directly inside a profile:

<!-- Attacker-controlled content stored by the application -->
<img src="https://app.example.com/api/account/delete?confirm=true"
     width="1"
     height="1">

When another user views that profile, their browser processes the stored HTML:

Victim visits:

https://app.example.com/users/attacker
        ↓
Application serves profile page
        ↓
Browser encounters:
<img src="https://app.example.com/api/account/delete?confirm=true">
        ↓
Browser requests:
https://app.example.com/api/account/delete?confirm=true

If that endpoint incorrectly performs a state-changing action through GET, and the victim's authentication cookies are sent with the request under the applicable cookie rules, the request may execute using the victim's authenticated context.

Victim browsing trusted site
        ↓
Stored attacker-controlled markup loads
        ↓
Request sent to application
        ↓

Victim's authentication context may accompany the request
        ↓
Server sees authenticated request
        ↓
State-changing action executes if no effective CSRF defense exists

The victim never needs to:

  • Click an external phishing link.

  • Visit an obviously malicious domain.

  • Intentionally interact with attacker-controlled content.

They may simply browse a page on an application they already know and trust.

Why Stored CSRF Can Be More Dangerous:

The major advantage for the attacker is persistence.

Attacker
        ↓
Phishing email / malicious link / attacker-controlled website
        ↓
Victim must visit

Stored CSRF changes the delivery model:

Attacker stores trigger once
        ↓
Trigger remains inside the legitimate application
        ↓
User 1 visits affected page
        ↓
Request triggered

User 2 visits affected page
        ↓
Request triggered

User 3 visits affected page
        ↓
Request triggered

The application itself becomes the distribution mechanism.

This removes one of the main obstacles of conventional CSRF: convincing individual victims to visit a separate attacker-controlled page.

The Relationship to Stored XSS:

Stored CSRF and stored XSS can sometimes look superficially similar because both involve attacker-controlled content being saved and later delivered to other users.

The difference is what the browser is being asked to do.

Stored XSS:

Stored content
        ↓
Attacker-controlled JavaScript executes
        ↓
Script can perform actions within the application's origin
Stored CSRF:

Stored content
        ↓
Browser automatically triggers a request
        ↓
Server may process the request using the victim's authentication
context

Stored XSS is generally the more powerful vulnerability because arbitrary script execution can often perform many actions directly from within the application's origin.

Stored CSRF does not require JavaScript, however.

A simple HTML element can sometimes be enough when the target application exposes a state-changing action through a browser-triggerable request such as GET.

<img src="https://app.example.com/api/action?value=attacker-choice">

Defending Against Stored CSRF:

The first layer is preventing untrusted content from becoming active HTML in the first place.

User-controlled input
        ↓
Treat as data
        ↓
Correctly encode before rendering
        ↓
Not interpreted as browser markup

If an application genuinely needs to support user-supplied HTML, it should be carefully sanitized so that dangerous elements and attributes cannot be used to trigger unintended requests or execute script.

The application should also follow the CSRF defenses already discussed throughout this section:

 Do not use GET for state-changing actions

 Require synchronizer tokens or another
  appropriate request-integrity mechanism

 Validate tokens correctly

 Apply protection consistently across every state-changing endpoint

 Use appropriate SameSite cookie settings as an additional layer

 Safely handle and sanitize user-controlled content

4

Vulnerability

SameSite bypass scenarios

⚠️

Vulnerability

SameSite cookie attributes are one of the primary modern browser-level defenses against CSRF. They significantly reduce the browser's tendency to automatically attach authentication cookies to requests initiated from an unrelated site.

They are not, a complete replacement for secure request design.

Understanding where SameSite does and does not apply is important, because treating the attribute as an absolute guarantee can leave applications exposed through the gaps around its intended behavior.

Lax Mode Still Allows Some Top-Level Navigations:

SameSite=Lax blocks cookies from many cross-site requests, particularly background requests and cross-site form submissions.

It still allows cookies to be sent in certain top-level cross-site navigations using safe HTTP methods, most importantly GET.

Victim is logged into:

app.example.com
        ↓
Victim visits:evil.example
        ↓
Attacker provides a link:
<a href="https://app.example.com/action">

If the victim follows that link and the browser performs a qualifying top-level GET navigation, a SameSite=Lax cookie can still accompany the request.

This is exactly why SameSite is not a substitute for correct HTTP method design.

SameSite=Lax
        +
State-changing GET endpoint
        ↓
Victim follows attacker-controlled link
        ↓
Top-level GET navigation
        ↓
Session cookie may be sent
        ↓
State-changing action may execute

For example:

<a href="https://app.example.com/api/account/delete?confirm=true">
  Click here
</a>

If visiting that URL deletes an account, SameSite=Lax does not solve the underlying problem.

The real fix is:

GET
    ↓
Reads only

POST / PUT / PATCH / DELETE
    ↓
State-changing operations
        +
CSRF protection where applicable

This connects directly to where CSRF vulnerabilities occur state-changing actions should not be exposed through GET in the first place.

Sibling Subdomains Can Be Same-Site:

A particularly important distinction is that same-site and same-origin are not the same thing.

Under the Same-Origin Policy, these are different origins:

https://app.example.com

https://admin.example.com

They have different hosts, so JavaScript running on one cannot automatically read arbitrary responses from the other.

For SameSite purposes, however, they can still belong to the same site because they share the same registrable domain.

Same-Origin Policy:

app.example.com ≠ admin.example.com
Different origins

SameSite:

app.example.com = admin.example.com
Same site

That means an attacker who gains control of, or can execute content from, a sibling subdomain may be operating from a context that SameSite does not treat as cross-site.

For example:

Attacker controls:

attacker.example.com
        ↓
Target application:
app.example.com
        ↓
Both belong to: example.com

Requests from the attacker-controlled subdomain toward the main application may therefore carry cookies that a purely cross-site attack would not.

This can arise through several situations:

  • A subdomain takeover.

  • A forgotten or poorly secured legacy application.

  • An internal tool exposed under the same parent domain.

  • A hosting configuration that allows attacker-controlled content on a sibling subdomain.

The Short-Lived Lax-Allowing-Unsafe Grace Window:

There is also a browser compatibility behavior worth understanding when a cookie is set without an explicit SameSite attribute.

Modern browsers commonly apply Lax-by-default behavior to such cookies, but some implementations include a temporary compatibility window in which a newly created cookie may still be sent with certain top-level cross-site requests using an otherwise unsafe method such as POST.

The important distinction is that this is not a general bypass of an explicitly configured SameSite=Lax cookie.

Explicit:

SameSite=Lax
        ↓
Normal Lax rules apply

Omitted SameSite attribute
        ↓
Browser may apply Lax-by-default behavior
        ↓
Some browsers may include a temporary compatibility exception

Set the intended SameSite value explicitly rather than relying on browser defaults or compatibility behavior.

Set-Cookie: session=abc123;
            Secure;
            HttpOnly;
            SameSite=Lax

SameSite Is a Layer, Not the Entire Defense:

The common pattern across all of these scenarios is that SameSite only controls when the browser automatically sends a cookie in particular request contexts.

It does not fix:

* State-changing GET endpoints
* Missing CSRF token validation
* Attacker-controlled same-site subdomains
* Incorrect cookie Domain scoping
* Authentication flows with separate request-integrity weaknesses
* Application logic that accepts dangerous requests regardless of how they were initiated

A stronger model is layered:

State-changing request
        ↓
Correct HTTP method
        +
Valid CSRF token or other appropriate request-integrity check
        +
Origin / request validation where applicable
        +
Appropriate SameSite policy
        ↓
Request accepted
5

Vulnerability

Multi-step CSRF

⚠️

Vulnerability

Sensitive actions are sometimes deliberately split across multiple requests:

Initiate action
        ↓
Review or prepare
        ↓
Confirm action

The assumption is often that requiring several steps makes the operation inherently harder to forge.

That assumption is unsafe.

A multi-step flow is only more resistant to CSRF if the server treats each security-relevant transition as something that must be independently authorized and validated.

If an attacker can forge every required request—or can bypass or directly invoke a weak later step—the fact that the normal user interface presents those requests across multiple pages provides no security by itself.

The Weakest Step Determines the Security of the Flow:

Consider a transfer process:

Step 1:

POST /api/transfer/initiate
        ↓
Server creates a pending transfer
        ↓
Step 2:

POST /api/transfer/confirm
        ↓
Server executes the transfer

A vulnerable design might correctly protect the first request but assume that reaching the confirmation endpoint proves the user has already completed the earlier steps legitimately.

Victim completes step 1
        ↓
Application creates:

pending transfer
        ↓
Confirmation endpoint assumes:

"If this request reaches me, the earlier checks must have happened."
        ↓
 Attacker directly invokes confirmation

That is the core mistake. The server must verify the state of the flow itself.

Chaining Requests:

Where each step is independently forgeable and does not require the attacker to know an unpredictable value generated by a previous response, an attacker may be able to trigger the entire sequence automatically.

Testing Multi-Step Flows:

Testing should therefore examine each request individually, as well as the complete sequence.

For every sensitive multi-step operation, ask:

Can Step 2 be called directly?

Can Step 3 be called directly?

Does every relevant state-changing request enforce CSRF protection?

Does the server verify that the required previous state actually exists?

Is that state bound to the correct authenticated user or session?

Can a request from one flow confirm, modify, or consume an object created
by another flow?

Are generated identifiers predictable, reusable, or otherwise attacker-controlled?