CSRF 跨站请求伪造完全实战手册学习平台PortSwigger Web Security Academy完成日期Day 11前置知识已完成 XSS 基础与进阶Day 9-10目录什么是 CSRFCSRF 与 XSS 的核心区别实验室 1无防御的 CSRF实验室 2Referer 验证失效实验室 3Token 未绑定会话CSRF 防御方案总结核心收获一、什么是 CSRF1.1 定义CSRFCross-Site Request Forgery跨站请求伪造是一种攻击者诱导已登录用户在不知情的情况下以其身份执行非预期操作的攻击方式。1.2 核心原理┌─────────────────────────────────────────────────────────────┐ │ 用户登录银行网站 A │ │ Cookie: sessionxxx浏览器自动保存 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 用户访问攻击者控制的网站 B钓鱼邮件/恶意广告 │ │ 网站 B 中隐藏了一个自动提交的表单 │ │ form actionhttps://银行A.com/转账 methodPOST │ │ input nameto value黑客账号 │ │ input nameamount value10000 │ │ /form │ │ scriptdocument.forms[0].submit();/script │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 浏览器自动带上银行 A 的 Cookie 发送请求 │ │ 银行 A 看到合法的 Session → 执行转账 │ │ 用户完全不知情 │ └─────────────────────────────────────────────────────────────┘1.3 为什么叫“跨站”因为攻击代码托管在攻击者的网站跨站但利用的是目标网站受害站点上用户的已登录状态。1.4 攻击成立的两个必要条件条件说明用户已登录目标网站浏览器中保存了目标网站的 Session Cookie目标网站没有 CSRF 防御没有 Token、SameSite Cookie 或 Referer 验证二、CSRF 与 XSS 的核心区别对比项CSRFXSS全称Cross-Site Request ForgeryCross-Site Scripting攻击目标利用用户已登录状态执行操作在受害者浏览器执行恶意脚本是否需要用户已登录✅ 必须❌ 不一定能否读取 Cookie❌不能✅可以通过document.cookie攻击代码位置攻击者控制的第三方网站目标网站本身的页面中用户交互要求访问攻击者网页即可零操作访问含恶意脚本的页面浏览器行为自动带上目标站 Cookie 发请求执行注入的 JavaScript典型危害转账、改密码、改邮箱窃取 Cookie、键盘记录、钓鱼防御方法CSRF Token、SameSite Cookie输出编码、CSP、HttpOnly关系XSS可以绕过CSRF Token 防御—2.1 一句话区分CSRF黑客借用你的身份去干坏事你不知情浏览器自动帮忙XSS黑客注入恶意代码到你的页面里执行代码在目标站运行2.2 XSS 如何绕过 CSRF Token如果目标网站同时存在XSS 漏洞和CSRF Token 防御script// XSS 读取页面上的 CSRF Tokenvartokendocument.getElementsByName(csrf)[0].value;// 用获取到的 Token 构造合法请求fetch(/change-email,{method:POST,body:emailhackedevil.comcsrftoken});/script结论XSS 的危害比 CSRF 更大因为 XSS 可以读取页面上的 Token从而绕过 CSRF 的所有防御。三、实验室 1无防御的 CSRF3.1 攻击目的理解最基础的 CSRF 攻击目标网站没有任何防御攻击者可以直接构造恶意请求让受害者执行。3.2 攻击思路登录目标网站找到修改邮箱功能正常修改一次观察请求方式GET/POST和参数构造包含恶意参数的 URL 或自动提交表单诱骗已登录的受害者访问 → 邮箱被篡改3.3 实施步骤Step 1访问实验室并登录访问地址https://portswigger.net/web-security/csrf/lab-no-defensesStep 2进入“我的账户”页面点击页面顶部的“我的账户”My account。Step 3正常修改邮箱观察请求在邮箱输入框输入新邮箱如testexample.com点击“更新邮件”。关键观察按F12 → Network 面板查看请求详情。Step 4构造攻击代码根据抓包结果构造自动提交的表单formmethodPOSTactionhttps://YOUR-LAB-ID.web-security-academy.net/my-account/change-emailinputtypehiddennameemailvaluepwnedevil.com/formscriptdocument.forms[0].submit();/scriptStep 5在 Exploit Server 中部署点击页面顶部的“前往漏洞服务器”Go to exploit server在Body文本框中粘贴上面的代码点击“商店”Store保存Step 6发送给受害者点击“将利用权传递给受害者”Deliver exploit to victim。PortSwigger 会自动模拟一个已登录的受害者访问你的攻击页面表单自动提交 → 受害者邮箱被篡改。Step 7验证成功等待 3-5 秒点击“返回实验室”。3.4 为什么能成功原因说明无 Token 防御请求中没有任何 CSRF Token无 Referer 验证后端不检查请求来源无 SameSite CookieCookie 随跨站请求自动发送浏览器自动带 Cookie受害者已登录浏览器自动带上 Session Cookie四、实验室 2Referer 验证失效的 CSRF4.1 攻击目的理解“有防御但防御逻辑有缺陷”的场景。目标网站检查了 Referer 头但验证逻辑不严格可以被绕过。4.2 攻击思路正常修改邮箱抓包观察 Referer 头尝试修改 Referer 头发现后端拒绝分析后端验证逻辑可能只检查 Referer 中是否包含目标域名构造攻击页面让 Referer 头看起来合法利用history.pushState()修改当前 URL使 Referer 包含目标域名4.3 实施步骤Step 1访问实验室并登录Step 2正常修改邮箱抓包观察 Referer按F12 → Network → 点击 change-email 请求 → 请求标头找到Referer字段Referer: https://0a0500c80393b5da802a03cf00eb00d5.web-security-academy.net/my-account?idwienerStep 3分析验证逻辑根据 Solution 提示后端验证逻辑类似// ❌ 错误只检查 Referer 字符串中是否包含目标域名$referer$_SERVER[HTTP_REFERER];if(strpos($referer,web-security-academy.net)!false){// 认为合法changeEmail($_POST[email]);}漏洞只要 Referer 中包含目标域名即可通过不要求 Referer 必须来自目标域名本身。Step 4构造绕过代码利用history.pushState()修改当前页面 URL不刷新页面使 Referer 头包含目标域名!-- Head 部分添加 Referrer-Policy --Referrer-Policy: unsafe-url!-- Body 部分 --formmethodPOSTactionhttps://0ad3009203b71ab680b00380004d00ca.web-security-academy.net/my-account/change-emailinputtypehiddennameemailvaluepwnedevil.com/formscripthistory.pushState(,,/?0ad3009203b71ab680b00380004d00ca.web-security-academy.net);document.forms[0].submit();/scriptStep 5为什么需要Referrer-Policy: unsafe-url现代浏览器Chrome、Firefox 等默认会剥离 Referer 头中的查询字符串安全策略。策略Referer 行为默认无策略跨站请求时 Referer 只保留域名去掉查询字符串unsafe-url保留完整 URL包括查询字符串如果不加这个头Referer 会变成Referer: https://exploit-xxx.exploit-server.net/ ← 没有查询字符串验证失败加了之后Referer 变成Referer: https://exploit-xxx.exploit-server.net/?0a0500c...web-security-academy.net后端strpos检查 → 发现包含目标域名 →验证通过Step 6Store → Deliver to victim点击“商店”Store保存点击“将利用权传递给受害者”Deliver exploit to victim#### Step 7验证成功等待 3-5 秒返回实验室。4.4 为什么能成功原因说明Referer 验证逻辑缺陷后端用strpos检查“是否包含”而非严格匹配域名URL 构造绕过让攻击页面的 URL 包含目标域名作为查询字符串浏览器安全策略绕过Referrer-Policy: unsafe-url强制保留完整 Referer受害者无感知页面加载后 JS 自动执行受害者完全不知情4.5 正确的 Referer 验证应该怎么做// ✅ 正确严格解析并匹配域名$refererparse_url($_SERVER[HTTP_REFERER]);if($referer[host]web-security-academy.net){changeEmail($_POST[email]);}五、实验室 3Token 未绑定会话的 CSRF5.1 攻击目的理解 CSRF Token 防御的关键缺陷Token 虽然存在但没有和用户会话绑定导致攻击者可以用自己的 Token 攻击其他用户。5.2 攻击思路登录自己的账号获取自己的 CSRF token分析 Token 是否全局通用换账号是否还是同一个 Token构造攻击页面使用自己的 Token受害者的 Session诱骗受害者访问 → 后端验证 Token 存在但不验证归属 → 攻击成功5.3 实施步骤Step 1访问实验室并登录Step 2获取自己的 CSRF Token进入我的账户页面按F12 → 查看源码找到表单中的隐藏字段formmethodPOSTaction/my-account/change-emailinputrequiredtypeemailnameemailvaluewienernormal-user.netinputrequiredtypehiddennamecsrfvalue27rOOffDeqA7g84qnRrhwRrBUY1w6k6tbuttontypesubmitUpdate email/button/form记下csrf的值CWBNqHO5I0r4m83MduToFZ58sgP7SFoGStep 3验证 Token 是否全局通用换一个浏览器/隐私模式用另一个账号如carlos登录查看其页面上的 Token。如果两个账号的 Token 相同→ 确认 Token 未绑定会话。Step 4构造攻击代码使用自己的 Token构造攻击页面formmethodPOSTactionhttps://0a710081043ca1f11800f8a8100ba00bd.web-security-academy.net/my-account/change-emailinputtypehiddennameemailvaluehackedevil.cominputtypehiddennamecsrfvalueCWBNqHO5I0r4m83MduToFZ58sgP7SFoG/formscriptdocument.forms[0].submit();/scriptStep 5Store → Deliver to victim点击“商店”Store保存点击“将利用权传递给受害者”Deliver exploit to victimPortSwigger 模拟受害者carlos访问攻击页面请求携带carlos 的 Session Cookie请求携带wiener 的 CSRF Token后端验证Token 存在且格式正确 →通过后端没有验证这个 Token 是否属于 carlosStep 6验证成功等待 3-5 秒返回实验室。5.4 为什么能成功原因说明Token 未绑定用户所有用户共用同一个 Token或 Token 不验证归属后端验证逻辑缺陷只检查 Token 是否存在/格式正确不检查 Token 是否属于当前 Session攻击者获取自己的 Token合法用户可以轻松获取自己的 Token跨用户复用用 A 用户的 Token 攻击 B 用户5.5 正确的 Token 防御应该怎么做session_start();// ✅ 正确Token 和用户 Session 绑定if(!isset($_SESSION[csrf_token])){$_SESSION[csrf_token]bin2hex(random_bytes(32));}// 验证时检查 Token 是否属于当前 Sessionif($_POST[csrf]!$_SESSION[csrf_token]){die(Invalid CSRF token);}每个用户的 Token 都是独立生成且存储在 Session 中的攻击者无法预测其他用户的 Token。六、CSRF 防御方案总结6.1 防御层级从弱到强防御层级方法效果绕过可能第一层Referer 验证⚠️ 弱验证逻辑缺陷可被绕过第二层CSRF Token未绑定 Session⚠️ 弱Token 可复用第三层CSRF Token绑定 Session✅ 强XSS 可绕过第四层SameSite Cookie✅ 强需配合其他防御第五层双重 Cookie Token✅ 很强几乎无法绕过第六层敏感操作二次确认✅ 很强用户体验稍差6.2 各防御方案详解方案 1CSRF Token最常用原理每次请求附带一个随机生成的 Token攻击者无法预测。正确实现session_start();$_SESSION[csrf_token]bin2hex(random_bytes(32));// 表单中嵌入input typehiddennamecsrfvalue?php echo$_SESSION[csrf_token]; ?// 提交时验证if($_POST[csrf]!$_SESSION[csrf_token]){die(Invalid token);}关键点Token 必须随机生成不可预测Token 必须绑定用户 Session不能全局通用Token 必须每次请求更新或定期更新方案 2SameSite Cookie原理设置 Cookie 的SameSite属性控制 Cookie 在跨站请求中的发送行为。SameSite 值行为适用场景Strict完全禁止跨站发送 Cookie最高安全级别Lax允许安全的跨站 GET 请求如点击链接禁止 POST平衡安全与体验None允许所有跨站请求发送 Cookie需配合 Secure第三方登录等设置方法setcookie(session,$value,[samesiteStrict,securetrue,httponlytrue]);方案 3Referer 验证辅助手段原理检查请求的来源页面是否合法。正确实现$refererparse_url($_SERVER[HTTP_REFERER]);if($referer[host]!example.com){die(Invalid referer);}注意Referer 头可能被浏览器禁用或篡改不能作为唯一防御手段。方案 4双重 Cookie 验证原理利用 Cookie 的天然同源策略在请求中同时携带 Cookie 和 Token。1. 用户访问页面时服务器设置 Cookie: csrf_tokenrandom_value 2. 前端 JS 读取 Cookie 中的 csrf_token 3. 发送请求时将 csrf_token 放入请求头或参数中 4. 后端验证请求中的 Token 是否等于 Cookie 中的 Token优点不需要服务器存储 Token纯前端实现。方案 5敏感操作二次确认原理对于转账、改密码等敏感操作要求用户再次输入密码或验证码。修改邮箱 → 弹出确认框 → 要求输入当前密码 → 验证通过后才执行优点即使 CSRF 攻击成功也无法通过二次验证。6.3 防御方案选择建议场景推荐方案普通表单提交CSRF Token SameSiteLax高安全性要求银行、支付CSRF Token SameSiteStrict 二次确认API 接口Token 验证JWT/ OAuth CORS 白名单第三方登录SameSiteNone Secure七、核心收获7.1 三个实验室对比实验室防御机制漏洞点绕过方法无防御无—直接构造恶意请求Referer 失效Referer 验证验证逻辑不严格strpos 检查包含history.pushStateReferrer-Policy: unsafe-urlToken 未绑定CSRF TokenToken 未和用户 Session 绑定用自己的 Token 攻击其他用户7.2 关键认知CSRF 的本质利用浏览器自动带 Cookie的特性让受害者在不知情的情况下执行操作。防御的核心让攻击者无法构造合法的请求。Token 防御攻击者无法预测 TokenSameSite 防御攻击者无法让浏览器带上 CookieReferer 防御攻击者无法伪造来源XSS CSRF如果存在 XSS所有 CSRF 防御都无效。因为 XSS 可以在目标页面内执行代码直接读取 Token 或执行任意操作。Referer 验证的坑❌ 错误strpos($referer, domain.com)只检查是否包含✅ 正确parse_url($referer)[host] domain.com严格匹配域名Token 设计的坑❌ 错误全局通用 Token、硬编码 Token、可预测的 Token✅ 正确每个用户独立、随机生成、绑定 Session、定期更新