> For the complete documentation index, see [llms.txt](https://simon-6.gitbook.io/simoncyber/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://simon-6.gitbook.io/simoncyber/portswigger-web-academy/crsf/lab-6.md).

# Lab 6

#### SameSite cookies simply

SameSite is a cookie setting that tells the browser:

> "Only send this cookie if the request is coming from the same site"

#### Why it matters for CSRF

Remember CSRF works because the browser auto-attaches cookies to requests.

SameSite breaks that:

```
evil.com tries to send request to bank.com
Browser checks: is this cross-site? YES
SameSite=Strict/Lax → don't attach the cookie
→ CSRF fails
```

#### Why you added `SameSite=None` earlier

In the cookie injection payload:

```
Set-Cookie: csrfKey=ATTACKER_KEY; SameSite=None
```

You forced `SameSite=None` so the injected cookie would be sent on your cross-site form submission. Without it the browser would block it.

In fact, Google Chrome automatically attaches a same-site lax. What is that?

* It only sends the cookie on GET-Request, not Post. This prevents actually changing data. However, the possible vulnerability is that it still sends it over GET&#x20;

### Headers

<figure><img src="/files/9Z2CSupI3PQtT2JbQXuz" alt=""><figcaption></figcaption></figure>

Samesite checks the scheme, TLD + 1, and the TLD. However, this is site, not origin. Origin is the full compass of the entire URL.

<figure><img src="/files/eju4QqoBqvtdx34YRqkU" alt=""><figcaption></figcaption></figure>

### How does it work?

Before SameSite:

Any request going to bank.com → attach bank.com cookies\
Didn't matter where the request came from\
evil.com → bank.com → cookie attached → CSRF works

After SameSite:

evil.com → bank.com → browser checks SameSite\
→ cross-site request → don't attach cookie\
→ server sees no cookie → rejects request\
→ CSRF fails

```
Strict → never attach on cross-site requests
         most secure, but breaks some legitimate flows
         (clicking email link to your bank logs you out)
         

Lax    → attach on cross-site GET only
         The request resulted from a top-level navigation by the user, such as clicking on a link. 
         balance between security and usability
         Chrome default since 2021

None   → always attach
         needed for third-party services (payment widgets etc)
         must use HTTPS
```

An example of why you would not set it to strict:

\
You get an email from bank.com\
Click "View your statement" link\
→ cross-site request (coming from email client)\
→ Strict blocks cookie\
→ you arrive at bank.com logged out\
→ have to log in again

If a cookie is set with the `SameSite=None` attribute, this effectively disables SameSite restrictions altogether, regardless of the browser. As a result, browsers will send this cookie in all requests to the site that issued it, even those that were triggered by completely unrelated third-party sites.

With the exception of Chrome, this is the default behavior used by major browsers if no `SameSite` attribute is provided when setting the cookie.

Tracking cookie = SameSite=None fine → no sensitive access\
Session cookie = SameSite=None dangerous → grants full account access

{% hint style="info" %}
To declare the SameSite security: we need to do something like this:

`Set-Cookie: session=0F8tgdOhi9ynR1M9wa3ODa; SameSite=Strict`
{% endhint %}

{% hint style="info" %}
If you encounter a cookie set with `SameSite=None` or with no explicit restrictions, it's worth investigating whether it's of any use. (since some websites break when SameSite is set)\
\
When setting a cookie with `SameSite=None`, the website must also include the `Secure` attribute, which ensures that the cookie is only sent in encrypted messages over HTTPS. Otherwise, browsers will reject the cookie and it won't be set.

`Set-Cookie: trackingId=0F8tgdOhi9ynR1M9wa3ODa; SameSite=None; Secure`
{% endhint %}

### Bypassing Lax using GET Requests&#x20;

Using a Get Request:

```
<script>
    document.location = 'https://vulnerable-website.com/account/transfer-payment?recipient=hacker&amount=1000000';
</script>
```

{% hint style="info" %}
What is Top Level Navigation?\
\
TOP LEVEL navigation changes the URL in your address bar. Resources that are loaded by iframe, img tags, and script tags do not change the URL in the address bar so none of them cause TOP LEVEL navigation.
{% endhint %}

This is just JavaScript for **"navigate the browser to this URL"**. As long as the request involves a top-level navigation, the browser will still include the victim's session cookie. The following is one of the simplest approaches to launching such an attack:

{% hint style="info" %}
Why doesn't img work for Lax?<br>

Because **Lax only allows cookies on top-level navigation GET requests**.\
\
EX: `<img src="https://bank.com/transfer?amount=1000&to=hacker">`\
\
This is a background image request\
It's cross-site\
SameSite=Lax → block cookie on cross-site subresource\
→ no cookie sent\
→ server sees unauthenticated request\
→ rejected
{% endhint %}

<figure><img src="/files/B14HSNKQkG35akqO8XmU" alt=""><figcaption></figcaption></figure>

Cross-site + top-level → document.location to bank.com ← Lax allows cookie\
Cross-site + subresource →  ← Lax blocks cookie\
Same-site + top-level → clicking link on bank.com ← always allowed\
Same-site + subresource → bank.com loads its own image ← always allowed

{% hint style="info" %}
Same-site/cross-site is about where the request is **going**, not where the page is. This is why top-level navigation GET is enabled but not cross-site subresource&#x20;
{% endhint %}

Cross-site subresource + session cookie = suspicious\
Why would evil.com need bank.com's cookie attached to a background request?\
Answer: only reason is CSRF or data theft

User clicks link → goes to bank.com\
→ user intentionally went there\
→ needs their session cookie to be logged in\
→ makes sense to send it

{% hint style="info" %}
Top-level = does the PAGE move? (yes/no)\
Cross-site = is the DESTINATION different? (yes/no). \
\
Lax only enables cross-site/top level GET requests.&#x20;
{% endhint %}

***

<figure><img src="/files/Gl3QOaLUUTh9dVizaAPp" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/P5yf9a2RsKHouWELeo7Q" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/XY7WCTbNTPN5a09wupdc" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/gF8w70Gm1yshLw3QtgLB" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/fesVR6SOhOHWPEzurQOa" alt=""><figcaption></figcaption></figure>

We encoded something like "@" because its used as a string, as to suppose &.\
\
1\. Where does `/my-account` come from?

You asked if it just "attaches that after the origin." Precisely.

In a standard URL like `https://example.com/my-account`, the browser breaks it into two main parts for the network request:

* The Host: Taken from the domain (`0aac0085...net`). This goes into the `Host:` header so the server knows which website you're talking to.
* The Path: Everything after the domain (`/my-account/change-email...`) becomes the first line of your request (the "Request Line").

The server receives this and says, "Okay, I am the Host listed here, and I need to look in my internal 'folders' or routing table for the logic associated with the path `/my-account/change-email`."

When you see a path like `/my-account/change-email`, it’s rarely a literal folder on a hard drive anymore. In modern web applications (like the ones in the PortSwigger labs), paths are handled by a Router.

Think of the router as a switchboard operator for the website.

#### 1. The Routing Table

The application has a "map" (usually a file or a block of code) that links specific URL patterns to specific functions.

| Path Pattern               | Function to Execute         | Required Permission |
| -------------------------- | --------------------------- | ------------------- |
| `/login`                   | `show_login_page()`         | Public              |
| `/my-account`              | `render_dashboard(user_id)` | Logged In           |
| `/my-account/change-email` | `update_user_email()`       | Logged In           |

When your request hits the server, the server looks at the Request Line (e.g., `GET /my-account...`), compares it against this table, and triggers the corresponding "Controller" or function.

#### 2. Static vs. Dynamic Paths

Sites generally store and handle paths in two ways:

* Static Routes: These are fixed. `/about-us` or `/contact` always point to the same place.

  <a class="button secondary">JumpCloud</a>
* Dynamic Routes: These use placeholders (often called "slugs"). For example, a path might be stored as `/post/:post_id`.

  <a class="button secondary">CodeSignal</a>

  * If you visit `/post/123`, the router recognizes the pattern, grabs `123`, and uses it to look up a specific article in the database.

<https://www.geeksforgeeks.org/python/flask-app-routing/>
