ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CTF赛后复现全攻略:从Web到Pwn的实战复盘思路

CTF赛后复现全攻略:从Web到Pwn的实战复盘思路 1. 三场比赛连轴转的赛后状态题目可以不会复盘必须到位虎符、红明谷、ctfshow渔人杯这几场比赛扎堆出现在赛历上之后我身边大多数人的状态都差不多比赛那两天高度紧张白天盯着题目发呆晚上躺床上还在想某个报错是不是绕过姿势不对等到比赛一结束又在各种群里蹲官方题解和选手writeup。我自己也经历了这么一轮但真正让我觉得有收获的不是赛中的灵光一现而是赛后那一个星期集中做的复现。说实话CTF比赛比的从来不只是知识点会不会而是在有限时间内能不能把会的东西用出来。很多题目放在题库里你慢慢磨总能做出来可一到了比赛题目一变长、环境一变复杂、时间一变紧原本熟练的操作突然就卡壳了。我这次在三个比赛里的最大感受就是我的失分点大多不是因为题目超纲而是因为自己的排查链路不够流畅——赛后才意识到那些丢分题只要按照平时刷题的节奏慢慢理其实都能做出来。所以这篇文章不打算做那种全场题目逐个详解的writeup整理而是想从赛后复现的角度出发聊聊我在这三场比赛结束后是怎么把Web、Pwn、Misc几个方向重新捡起来盘的以及哪些工具、哪些步骤、哪些思考方式是我觉得最值得沉淀下来的。如果你也是那种比赛结束就不知道该干嘛的选手这篇应该能给你一个还算明确的复现方向。1.1 虎符杯留给我的几个比赛时根本想不起来的考点虎符杯的题目风格给我留下一个很深的印象它不太喜欢出一个单知识点的小题而是喜欢把两三个基础考点叠在一起放进同一个场景里。比如一道Web题你得先通过信息收集发现备份文件再从备份文件里找到数据库连接信息接着才能进入真正有注入点的页面如果你只盯着入口页面的参数看大概率会错过后面一大串东西。这种套娃式出题方式恰恰是比赛中最容易让人心态崩掉的。因为平时在ctfshow刷题时题目编号基本已经把考点暗示得很清楚了比如web入门 sql注入你看到标题就知道该往注入方向试但比赛里没有这种提示拿着一个看似正常的登录框你得自己去判断该测SQL注入、弱口令、还是逻辑越权。赛后复现虎符的题目时我给自己定的规矩是不急着看别人的writeup先把比赛当时的所有操作记录翻出来找到自己第一次思路断掉的位置。比如有一道题我看到页面注释里有一个奇怪的路径当时犹豫了一下没点开后来发现那就是整道题的入口。复盘时我专门把这个动作记了下来遇到注释信息、奇怪的响应头、JS文件里的隐藏路由第一反应不是继续测当前页面参数而是先收集这部分信息。它不一定每道题都有用但一旦有用就是决定性的。1.2 红明谷题目与现实业务的贴近程度让我意识到差距红明谷给我最大的冲击不是题目有多难而是它很多题目场景的真实感比普通CTF高很多。平时我们刷题面对的很多靶场都是为CTF专门设计的端口就一个功能很单一漏洞点非常清晰。但红明谷的部分题目更像是把一个简化版的业务系统丢给你界面上有各种功能按钮数据库、缓存、日志模块都有你需要自己判断漏洞可能藏在哪一层。这种风格对日常训练的要求就变了。如果你只在ctfshow这类题库里按编号刷题习惯了拿到题目直接测参数的节奏遇到业务仿真的环境会很容易不知道从哪入手。赛后复现红明谷题目时我发现自己的问题出在信息收集太粗糙上我只关注了HTTP请求里肉眼可见的参数没有仔细看Cookie结构、没有检查是否存在多个同类型接口、也没有去对比登录前后返回内容的差异。因此我在复现这类题目时额外做了一个动作先把整个应用的目录结构、路由列表、接口参数全部过一遍形成一个功能地图然后再逐个功能去看它是否存在安全风险。这个习惯放到Web方向所有题目里都很受用后面我也一直在提醒自己别急着打先看清楚战场长什么样。1.3 ctfshow渔人杯萌新题库和比赛题之间的那座桥ctfshow平台上有一个很大的题库体系从萌新入门到web应用安全与防护、从misc入门到pwn基础题号非常多。渔人杯这种比赛很大程度上就是把这些日常训练内容做了一次混编你不会再知道某道题对应哪个题号但所有考点的原型都能在题库里找到。我注意到网络上一搜ctfshow web入门 sql注入这类关键词的人特别多这说明很多人其实就是从ctfshow开始接触CTF的。渔人杯对这批人来说是个很合适的中间站它的题目难度跨度比纯萌新题大又比虎符这类大赛的决赛题温和用来检验自己刷题刷到什么水平了非常合适。赛后复现渔人杯题目时我的处理方式和前两个比赛不太一样我更多是把它当成查漏补缺用的。哪道题没做出来我就回ctfshow去找同考点的基础题先确认自己对基础知识的理解没有偏差再回来看比赛里的变形。比如HTTP头注入如果我只知道它大概和SQL注入有关但不知道具体在哪个Header里触发那我就会把题库里相关题目翻出来重新做一遍把这个知识点彻底打通。后面我会详细说说我在Web方向复现时梳理的完整思路。2. Web题复现绕不开的SQL注入、HTTP注入与过滤盲区Web方向永远是我赛后复现的第一站原因很实在它环境最好搭本地起一个服务就能反复试payload而且Web题目的知识点和流行关键词高度重合搜一下ctfshow web入门 sql注入能找到一大堆练习材料非常利于对照复现。我在这次三个比赛中Web方向暴露的问题比较集中一是SQL注入的过滤绕过不够系统二是对HTTP请求层的一些隐式入口不敏感三是在拿到一个不熟悉的源码后缺少一套固定的过滤规则分析路径。赛后复现的时候我针对这三个问题分别做了补课。2.1 整理失败点先从sqlmap的日志开始有一种情况很常见比赛时遇到一个疑似SQL注入点时间紧迫直接上了sqlmap结果它跑了很久也没有跑出数据最后时间耗尽就放弃了。赛后千万别直接把日志删了sqlmap的日志是很有价值的复现线索。我一般会用sqlmap -u http://target/news.php?id1 --batch --level3 --risk2 --log-filesqlmap.log这类命令重跑一遍然后去看日志里它到底测试了哪些payload、在哪个阶段停止、是因为响应异常还是检测规则把它拦了。很多时候你会发现sqlmap不是没有找到注入点而是因为需要某种绕过条件它的默认模板没有触碰到。比如它可能在日志里尝试了AND 11、AND 12但目标过滤了空格或者过滤了AND这种情况下即使存在注入sqlmap默认的检测也可能失效。复现时如果发现这种情况就要手动用注释符/**/代替空格或者使用%0a等替代空白字符先确定注入是否存在再谈后续的利用。手动测试的基本思路是先提交一个正常参数看响应基线再提交单引号等特殊字符观察报错变化然后尝试布尔条件表达式看响应是否出现差异。2.2 SQL注入从能用poc到懂原理的差距很多新手拿到SQL注入第一反应是 or 11#进去能过就过不能过就陷入迷茫。赛后复现时最值得花时间补的基础就是搞懂后端查询大概长什么样。比如一个登录查询后台逻辑很可能是SELECT * FROM users WHERE username$username AND password$password我传入admin or 11后拼接出来的语句就变成SELECT * FROM users WHERE usernameadmin or 11 AND password...这里的核心是闭合前一个单引号并让后续条件恒真。接着就要练习各种过滤绕过思路我按照输入被过滤的地方把它们分成几类过滤点常见绕过手法空格被过滤使用/**/、%0a、反引号或Tab字符替代or/and被过滤尝试select被过滤使用内联注释/*!50000select*/或拆分字符串information_schema被过滤尝试使用sys.schema或通过join方式探测引号被过滤考虑宽字节注入例如在GBK编码下使用%df%27宽字节注入是个很经典的坑。如果程序使用了GBK编码并且把用户输入的单引号转义为\那么在单引号前加上%df%df\就会被当作一个中文字符从而让单引号逃逸出来。复现这类题目时我通常用Burp Suite改包把URL里的参数改成%df%27观察页面是否出现SQL报错来判断宽字节注入是否生效。除了布尔盲注和报错注入堆叠注入也是近年比赛的高频考点。比如在MySQL环境里如果目标允许一次执行多条语句那么?id1;show tables;--就可能直接在响应中带出表名列表。赛后复现时我会专门在本地搭建一个MySQL环境把这类带分号的payload跑一遍从而理解堆叠注入能用、能带回显、为什么默认情况下页面不回显这些细节。2.3 HTTP请求层的隐式入口Header注入的复现思路网络热词里有一个ctfshow http注入这么完成我猜测有不少人搜这个是因为在题目里发现往URL参数里注入不管用但问题其实出在HTTP头部。这是很多人做Web题的一个盲区明明把参数值翻了个底朝天却从来没想过改动User-Agent、Referer、X-Forwarded-For这些请求头。HTTP头注入的常见场景是后端日志或数据库会记录用户的浏览器信息、来源页面、IP地址等信息。比如登录成功后页面会显示欢迎你来自xxx的用户如果你把User-Agent改成User-Agent: OR 11而后端把UA的内容拼进了SQL查询就会产生注入点。类似地X-Forwarded-For如果被用于写入数据库也可能存在同样的隐患。复现这类题目我一般用Burp Suite的重放器在Raw视图里直接修改请求头然后观察响应是否出现异常或变化。重点测试的请求头包括User-AgentRefererX-Forwarded-ForX-Real-IPClient-IP自定义头如X-Custom-Header如果后端是记录访问日志并输出到页面的场景还可以尝试头注入叠加XSS比如在UA里构造scriptalert(1)/script看是否被原样输出。这类题目在ctfshow题库里其实也有原型复现时只要把重点从URL参数切换到整个HTTP报文思路一下子就打开了。2.4 ctfshow题库编号之后的通用总结过滤绕过像一个排列组合很多人在群里问ctfshow web165怎么做ctfshow web82的考点是什么但我个人觉得题号本身并不重要重要的是从这些题目里抽出来的通用规律。ctfshow的Web题库编号看起来又多又杂实际上核心Web漏洞类型就那些SQL注入、文件上传、命令执行、文件包含、反序列化、SSRF、XXE、条件竞争等。以搜索热度很高的ctfshow web入门 sql注入类题目为例你刷到后面会发现真正的拦路虎往往不是不知道注入语法而是不知道后端过滤规则。过滤规则千奇百怪可大体上就是一个排列组合问题后端过滤了关键字、空格、注释符、引号、逗号、等号中的某几项你需要找到漏掉的那一项。我在复现时会把每道题的后端过滤规则猜一遍并记录成笔记。比如某道题过滤了select和空格那么动态构造的payload可能就是id1/**/union/**/select/**/1,2,3#如果这一步过了再继续试字段数、回显点、查表名。整个流程就是一个探测-构造-再探测的循环。不要指望一道题能一步到位更不要因为一个payload失败就立刻怀疑自己没找到注入点。先确认过滤的是什么再用排列组合的思路去构造你会发现大多数过滤问题都能解。3. Pwn方向复现七道题里至少有四道是同样的栈利用思路Pwn方向是我以前最怕的因为一上来要面对的不是页面回显而是汇编指令、内存布局、保护机制。但这次赛后复现让我心态好了不少实际上很多Pwn题目考来考去就是栈溢出、格式化字符串、堆利用几种核心套路。像ctfshow题库里的pwn07、pwn074这类题目本质上也逃不出这些范畴。3.1 复现前的环境准备Pwn复现绝不能直接在比赛环境下瞎试需要一套可重复搭建的工具链。我目前习惯用的环境是Ubuntu 20.04虚拟机为了兼容老题目也可以再装一个Ubuntu 18.04Python3 pwntoolsgdb pwndbg插件checksec查看二进制保护机制one_gadget、ROPgadget、LibcSearcher装上之后先跑一遍checksec它能告诉我这个二进制开了哪些保护比如栈执行保护NX、地址随机化PIE、栈保护canary等。这一步非常关键因为保护机制直接决定了利用思路开了NX就不能直接执行栈上shellcode开了PIE就需要先泄露程序基址开了canary就得先绕过canary检查。3.2 从checksec到泄露地址栈题的不变套路典型的栈溢出题目我一般按这样一个固定流程复现用file命令确认二进制架构和位数。用checksec查看保护机制。用IDA或Ghidra打开二进制定位存在危险函数的地方比如gets()、strcpy()、read()这些不检查输入长度的函数。用cyclic 200生成溢出序列在gdb里运行程序输入序列崩溃后cyclic -l确认偏移。根据保护机制设计payload。比如最常见的ret2libc思路如果程序没有开PIE、但开了NX那我就先通过puts或write这类函数泄露一个真实地址再用LibcSearcher匹配出libc版本最后计算system函数和/bin/sh字符串的地址构造ROP链拿到shell。exp骨架类似from pwn import * p process(./pwn) elf ELF(./pwn) libc LibcSearcher(puts, puts_addr) payload bA * offset payload p64(pop_rdi_ret) payload p64(puts_got) payload p64(puts_plt) payload p64(main_addr) p.sendline(payload) ...赛前我总觉得ROP很难但实际上只要把偏移算对、把gadget地址找对整个过程机械性比较强。赛后反复练三五道类似题目后看到栈题第一反应就不再是怎么开始而是直接进入确认保护-算偏移-找gadget的流程。3.3 格式化字符串类题目为什么pwn07会让萌新崩溃ctfshow pwn07这类的格式化字符串题是很多Pwn新手的第一道心理阴影。明明代码看起来很简单就是printf(user_input)但程序里既没有明显的栈溢出又不知道怎么控制流程。格式化字符串的核心是printf的格式化参数是从栈上取值的而你的输入往往也在栈上。通过构造%p、%s、%n你可以实现两个能力读任意地址的内容、写任意地址的内容。复现时我一般先在本地试出输入内容在格式化参数中的位置。比如输入AAAA%p.%p.%p.%p.%p.%p观察输出中哪一段出现了0x41414141也就是AAAA的十六进制就能确定偏移量。假如它出现在第6个参数位置那么AAAA%6$s就可以把某个地址作为字符串读出。如果要利用%n写数据还需要理解%n会把已经输出的字符数量写到指定地址。比如构造一个payload先让前面输出的字符串长度等于目标值再通过%n把该值写入某个GOT地址从而改变程序流程。这种利用用手工构造非常痛苦pwntools提供了fmtstr_payload函数自动生成payloadpayload fmtstr_payload(offset, {target_addr: target_value})但自动化工具能用好不代表可以不懂原理。赛后复现时我会先手工构造一个最简单的写单个字节的payload再对比fmtstr_payload生成的结果这样才能在工具失灵时及时发现问题。4. Misc方向复盘流量包和文件分离才是送分题的重灾区很多人觉得Misc就是靠运气我觉得可能是因为平时刷ctfshow misc入门这类题刷得不够系统。Misc看似杂其实也有高频套路。这次三场比赛的Misc题我总结下来大多数都没离开两个大类一个是文件与隐写一个是流量包分析。4.1 第一步永远是binwalk、foremost、strings三连遇到Misc题我给自己定的规矩是不管题目描述说什么先做三件事。第一件用file查看文件真实类型。很多题目会给你一个看似是图片的文件实际上里面塞了一个压缩包或者扩展名被改掉了。file能直接告诉你文件的真实格式。第二件用binwalk扫一遍文件看看能否检测到内部嵌入的其他文件。比如一张PNG图片里可能藏了一个ZIP压缩包binwalk会列出偏移位置。接着用foremost或者dd根据偏移把隐藏文件提取出来。第三件用strings查找文件中的可见字符串。有时候flag本身就藏在某个角落根本不需要复杂隐写分析。常用命令strings flag.png | grep -i ctf\|flag\|key如果这一步没结果再考虑LSB隐写、EXIF信息、文件尾附加数据等更复杂的隐写手段。复现时我习惯把文件分离和特征搜索这两步放在最前面因为它们的成本很低、收益很高很多人比赛时连这两步都没做就去做像素级隐写分析纯属浪费时间。4.2 流量包分析从HTTP流回到TCP流流量包题是Misc的重点也是比赛现场最容易让人手忙脚乱的题目。拿到一个.pcap文件我会先打开Wireshark快速浏览协议分级统计看看这个包主要是HTTP流量、DNS流量还是TCP裸流量。如果是HTTP流量优先查看是否有文件传输。在Wireshark里可以通过文件 - 导出对象 - HTTP直接导出HTTP响应中的文件这种方式比手动去翻流快得多。如果是DNS流量就要关注是否有DNS隧道或者比较可疑的解析记录比如大量看起来像base64编码的子域名。进阶一点的题目可能把flag藏在TCP流里需要你跟踪TCP流去还原完整数据传输。有时候数据是分多个包传输的只看某一个包会漏掉上下文。用tshark命令行也能快速做统计和过滤比如tshark -r traffic.pcap -Y http.request -T fields -e http.host -e http.request.uri这条命令可以把所有HTTP请求的域名和URI打印出来快速定位可疑请求。4.3 编码迷宫从base64到培根密码不要急着解Misc题目里还有一种很常见的干扰项一串看起来完全不像flag的字符串。它可能是base64、base32、十六进制、URL编码也可能是一串培根密码风格的二进制组合。复现时不要一上来就随便选一个编码去解而是先观察特征如果字符串只包含A-Za-z0-9/且尾部可能有优先考虑base64。如果只包含A-Za-z2-7优先考虑base32。如果只包含0-9a-f优先考虑十六进制。如果全是大小写A/B的序列比如BAABB AABBB考虑培根密码。如果能看到%XX优先URL解码。解码工具我推荐CyberChef它可以把多个解码步骤串联起来比如先base64解码再zlib解压不用来回切工具。复现时把每一步解码结果都记录下来因为有些题目在中间步骤里还嵌着另一层隐写。5. 我的赛后复现工具链与时间安排复盘了具体方向之后说说我自己的整体复现流程。以前我复盘是随缘式的今天觉得这道题有意思就看看明天没时间就扔一边结果两周后什么也没沉淀下来。这次我改成了一套固定流程效果比以前好很多。5.1 本地靶场用Docker还是虚拟机Web题复现我优先用Docker。很多赛题官方会提供Dockerfile或者部署环境直接在本地把容器拉起来端口映射到本机就能无限次测payload不怕把环境打坏。即使官方没提供也可以用phpstudy、LNMP这类一键环境快速搭建。Web题要反复改代码看效果Docker的轻量特性比虚拟机舒服很多。但Pwn题就不一样了它依赖特定的libc版本和系统环境。我习惯在虚拟机里装一个Ubuntu 18.04和20.04双环境比赛题目要求哪个版本就切到哪个版本避免本地libc和远程不一致导致exp在本地能通、远程不通的尴尬情况。复现时我会把目标服务的地址直接指向本地容器或虚拟机IP这样既不影响正常上网又能随时抓包调试。5.2 Payload的归档方式按过滤条件建目录我以前存payload的方式非常随意——散落在聊天记录、浏览器历史、临时文件里等到要用时根本找不到。后来我改成在本地建一个目录按过滤条件或者漏洞类型归档。比如payloads/ ├── sqli/ │ ├── space_filter.txt │ ├── keyword_filter.txt │ └── hex_encoding.txt ├── rce/ │ ├── preg_match_bypass.txt │ └── length_limit.txt └── upload/ ├── ext_bypass.txt └── content_type_bypass.txt每个文件里不仅记录payload还要写上适用条件和验证结果。这样下次比赛遇到相似过滤规则直接去翻对应文件比现场空想快得多。5.3 复现时写题解顺手把exp改成可复用模板复现过程中我强烈建议把写好的exp顺手改成可复用模板。比如一个SQL注入脚本不要只在某个靶场上能用而是把它抽成函数传入URL、参数名、注入类型返回结果。再比如Pwn的利用脚本我会把连接目标和本地调试写成一个变量切换这样赛后复现和下次比赛都能直接用。写题解也不是为了发博客而是为了让自己理解更清楚。我一般会用Markdown记录题目背景、漏洞点、绕过方式、完整exp和最终flag再把自己当时卡住的地方单独标出来。过几个月回头再看这份记录比重新做一遍题有价值得多。5.4 给萌新的一条复现顺序建议最后聊一下复现顺序。我自己的建议是先Web、再Misc、最后Pwn。理由是Web环境最容易被复现遇到不会的题很快就能动手验证Misc需要一些文件分析工具但不需要太多环境搭建Pwn则对环境和工具链要求最高前期容易卡在exp跑不起来这个坎上。时间上我的习惯是每道题最多给自己两个小时。第一个小时不看任何writeup完全按照比赛时的思路继续尝试第二个小时可以查阅官方题解或者别人的writeup搞清楚思路后再回到题目里自己重新走一遍。每次都直接看答案的复现没有意义因为你需要锻炼的是如何自己想出来的能力。如果你是刚入门、还在刷ctfshow萌新题的朋友完全可以按照这个套路来做完一道题哪怕做出来了也要在赛后把相关考点扩展练一遍。比如今天做了一道SQL注入题就回题库把sql注入分类下的其他题目都过一遍今天遇到一个HTTP头注入的坑就把Burp的Repeater用熟、把常见Header都测一遍。这种以点带面的复现方式比单纯追求题数要扎实得多。赛后复现是一件短期看不到收益、但长期非常划得来的事。比赛的名次是一时的从比赛里沉淀下来的工具、笔记、节奏感才是能一直带走的东西。
返回列表