凌晨两点的房间里我盯着终端上滚动的报错信息整个人处于一种既兴奋又抓狂的状态。SUSCTF 2018线上赛已经进行了二十个小时作为一支临时凑起来的三人小队我们手头还有四道题没解出来而最让我在意的是那道报错信息看起来特别奇怪的Web题。SUSCTF是西南科技大学主办的大学生CTF夺旗赛2018年这一届采用线上初赛加线下决赛的模式线上赛持续24小时题型覆盖Web、Misc、Crypto、Reverse和Pwn五个方向。对当时刚入坑CTF不到半年的我来说这场比赛既是一次综合检验也是一次难得的选题练习。如果你想了解大学生CTF赛事是怎么玩的、新手应该如何分配精力这篇文章应该能给你一些参考。1. 赛前摸底赛制拆解与破题顺序1.1 线上赛24小时题量和题型到底怎么分布SUSCTF 2018的线上赛持续24小时题目总数大概在25道左右分布在Web、Misc、Crypto、Reverse、Pwn五个方向。我记得很清楚比赛前我们队三个人拉了群群公告第一条就是我写的“先看题别急着做”。为什么先看题这么重要因为CTF线上赛的出题风格通常是“签到题中等题难题”三段式。签到题可能一上来就是一道Base64变形或者一个简单注入五分钟就能做出来难题则可能要花好几个小时往往是多个知识点的组合。如果不先通读所有题目很容易在某一类题上死磕一下午最后回头发现签到题都没做。还有一个现实因素线上赛的靶机环境是不稳定的。有些题目会因为大量选手同时访问而卡顿甚至服务直接挂掉。先大概浏览一遍题目列表可以让你对服务器状态有个心理预期不至于做到一半发现环境崩了干着急。1.2 我给自己定的破题顺序先易后难先快后慢我当时的策略很简单分了三轮走。第一轮把每个方向的题目标题、附件类型、描述信息全部过一遍凡是看到flag格式明确、附件能直接下载、描述里有明显提示的都归为“快题”。第二轮开始做Crypto和Misc因为这两类题目往往不需要环境交互纯本地分析不依赖服务器的稳定性半夜网络波动也不影响。Web题放在第三轮因为Web题通常需要现扫目录、现测注入服务端可能还会临时修漏洞或者重启环境干扰因素最多。Reverse和Pwn那一届我基本是放弃状态只挑了一两道最简单的逆向尝试把时间留给有把握的方向。这种方式也许拿不到前三名但对于目标只是“把能拿的分都拿到”的队伍来说性价比非常高。1.3 容易被忽略的计分规则与flag格式这里有个细节很多人会忽略比赛平台的计分规则是动态的题目分值会随着解出人数增加而下降但每种题型的基础分不同。也就是说先做签到题还能拿到相对高的分拖到后面再做分值可能被稀释得很厉害。所以“先易后难”不只是心理上的轻松而是实实在在的分数差异。另一个容易被忽略的是flag格式。SUSCTF 2018的flag统一是flag{...}但有些附件的文件名或者题目描述里会隐晦提示“flag格式是SUSCTF{...}”如果没留意拼写错了就很难受。我的习惯是开局就把所有题目的flag格式抄到记事本里做一题填一题最后统一提交。这种小习惯看似不起眼但能避免很多低级的丢分。2. Web题实战报错注入、命令执行与过滤绕过2.1 小小查询系统的报错注入Web方向有一道题给我留下的印象最深名字就叫“小小查询系统”。页面只有一个输入框提示输入id即可查询到信息。输入1、2、3都能正常返回用户名和内容看起来是一个非常普通的前端查询。当我输入1的时候页面报错弹出来一行SQL语法错误而且错误信息里直接把MySQL版本号都带出来了。那一刻我意识到后台大概率是拼接SQL字符串直接查询数据库的报错信息也原样返回到前端。这种“看起来好奇怪的报错”恰恰是出题人故意留下的路标。判断是字符型注入之后我用1 and 11和1 and 12做了对比前者正常返回后者查询结果为空。再到1 order by 3看能不能正常排序通过逐步试探确定当前查询的列数。确定列数后最常见的思路是用union select直接拼接查询但当时这道题目把union过滤了所以我转去用了报错注入。MySQL的报错注入常用函数有extractvalue和updatexml原理都是让函数在计算时触发XPATH语法错误并把我们拼接的查询结果带进错误信息里。最典型的payload是1 and updatexml(1,concat(0x7e,(select database()),0x7e),1) --其中0x7e是波浪号~的十六进制用于把查询结果和错误提示隔开方便阅读。用这个套路我先后拿到了数据库名、当前库下的表名、目标表里的字段名最终在某个字段里读出了flag。这里有个小坑要提醒一下报错信息有长度限制通常一次只能显示32个字符左右所以如果flag很长需要用substr分段截取。我当时就是分了三段才把完整的flag拼出来。很多人第一次用报错注入看到flag只有一半就以为题目有问题其实只是长度限制。2.2 命令注入当空格和关键字都被过滤第二道Web题是个ping功能。页面让你输入一个IP后台会执行ping -c 4 $input并把结果打印出来。正常的127.0.0.1会返回ping的统计信息但如果我在后面加上; ls就会看到老实的报错提示Hacker?。这说明题目对命令做了黑名单过滤或者说某些字符被替换了。我第一个想到的是空格可能被过滤于是尝试127.0.0.1|ls${IFS}/居然真的列出了根目录。${IFS}在bash里是内部字段分隔符默认包含空格、tab、换行所以可以用它替换命令里的空格绕过只针对空格的过滤。根目录里躺着一个/flag文件接下来就是想办法读取它。cat /flag明显会被过滤因为cat在关键词列表里几乎铁定存在。我试了tac、more、less、head、tail发现tail没被过滤于是用127.0.0.1|tail${IFS}/flag直接把flag的最后几行打了出来。这题到这儿就结束了。事后看官方writeup这题其实想让大家熟悉PHP里的命令执行函数包括system、exec、shell_exec、passthru等出题的触发点就埋在“输入IP执行ping”这个场景里。很多新手卡住不是因为不知道命令注入而是没想到空格过滤之后还有${IFS}这种替代方案。命令注入题的核心始终是先探测过滤规则再针对规则构造绕过链。2.3 从报错内容到路径泄露的小技巧Web方向还有一个小细节让我长了记性。有一道题的报错信息里暴露了网站的物理路径比如/var/www/html/query.php。很多人看到报错只想着“哦这是注入”但忽略了路径本身就是重要信息。如果题目后续允许文件读取或者日志包含这条路径能直接帮你少走半天弯路。我当时做的另一道文件包含题就是因为先知道了物理路径再用伪协议php://filter/convert.base64-encode/resourcequery.php读出了源码结果发现源码里还藏着一个预留的后门文件。Web题里信息收集永远是第一优先级任何一条看起来无用的报错信息都可能是在提示下一步该看哪里。3. Misc杂项二维码修复、隐写提取与流量分析3.1 一张缺了角的二维码Misc方向第一题给了一张二维码图片但二维码的三个定位角里缺了两个用手机和常见的扫码工具都扫不出来。这种题型在CTF里很常见考察的就是选手对二维码结构的基本理解。二维码之所以能快速定位是因为左上、右上、左下三个角的“回字形”定位图案。扫描器先识别出这三个角才能确定二维码的方向和尺寸。缺了任何一个角扫码基本上都会失败。修复的思路很简单找一个完整二维码图片把缺的定位角区域用图像处理工具裁下来贴回原图再重新保存。为了保证识别成功率最好严格控制粘贴位置的比例和大小。我当时用的是Python的OpenCV库先读取图片再用cv2.rectangle把三个定位角画上去。对直接用画的方式替代复制粘贴因为定位角就是固定的黑白方块图案知道坐标就能画。画完再保存用微信扫一下出来了一段base64字符串。后续解base64又是好几层编码嵌套最后才看到flag。这道题的重点在于杂项题里很多线索不是“藏得深”而是“长得眼熟”。看到二维码先别急着解先看看它结构是不是完整的。3.2 LSB隐写与zsteg的妙用另一道Misc题是一张看起来完全正常的风景图文件体积也不大直接打开肉眼完全看不出异常。这种题十有八九是LSB隐写。LSB隐写的原理可以这样理解图片每个像素的颜色由RGB三个通道组成每个通道是0到255的一个数。把通道值转成二进制后最低的那一位Least Significant Bit对最终颜色影响极小人眼几乎察觉不到颜色变化。隐写工具就利用这1比特把秘密信息按二进制位嵌进去。如果信息是纯文本一个字符8位一张尺寸稍大点的图能藏好几百个字符。我当时先跑了一遍binwalk没发现附加文件再用Stegsolve逐通道翻看RGB的0号平面隐约能看到图案上有不自然的条纹。按理说手工提取也能解但更快的办法是直接用zstegzsteg -a image.png它会自动遍历所有通道和所有位平面把隐藏在LSB里的信息直接打印出来。我记得输出里立刻出现了一行像是密码的字符串推测是后续压缩包的解压密码。后来解压出来的文本文件里放着flag正好是这张图片的文件名加上一串随机字符。Misc题里的“工具流”思路特别重要先看binwalk再翻Stegsolve不行就上zsteg一套组合拳打下来大部分图片隐写都跑不掉。3.3 流量包里的压缩包WireShark提取之旅Misc还有一道题给了个.pcapng文件题目描述只有一句“抓到了奇怪的网络流量”。这种流量分析题在CTF里也是常客主要考察选手对WireShark或tshark的熟悉程度。打开文件后我习惯先看Statistics - Protocol Hierarchy也就是协议层级统计。这一步能快速判断流量里主要是什么协议是HTTP还是USB是DNS还是SMTP。那次统计结果显示HTTP请求数量特别多于是我去File - Export Objects - HTTP把所有HTTP传输对象导出来发现里面藏着一个压缩包。用file命令一看确实是zip压缩包但解压需要密码。密码哪里来回到WireShark里翻HTTP的请求参数发现某个POST请求里的请求体是一串base64解码之后就是解压密码。整个过程其实不复杂真正容易卡住的地方是很多人拿到pcapng就直接盯着一个个报文看而没有先做协议统计和对象导出。流量分析的第一原则永远是“先把大结构看清楚再去找细节”。如果换成USB流量思路也类似先用tshark把USB HID数据提取出来再写个Python脚本把键盘按键的HID码对照表还原成字符。我后来在别的比赛里就用过这个思路解出过键盘输入类的题目那次SUSCTF虽然没有USB流量题但工具和脚本都提前备好了。4. Crypto密码学编码识别与古典密码爆破4.1 一串乱码背后的多层编码Crypto方向第一题是很经典的多层编码。题目给了一段满满当当的字符串有大小写字母、数字和一些加号、斜杠末尾还带着等号。看到这个形态第一反应就是base64。但base64解码之后出来的还是一堆看起来有规律但读不懂的字符里面甚至出现了0x开头的片段。这时候要意识到解码不是终点而是下一层编码的起点。我当时的习惯是用CyberChef的Magic功能让它自动尝试各种编码组合或者用随波逐流的编码工具直接识别文本特征。CyberChef的Magic会从输入端自动推测可能用到的编码组合一键解出结果对新手非常友好。那次最终的解码链是base64 - 十六进制 - 栅栏密码 - 得到flag。整个过程没有复杂的数学知识考验的只是对常见编码的敏感度和耐心。很多人在第一步base64解出来还是乱码之后就放弃了其实多观察一下解码结果的字符集比如是不是全是十六进制字符是不是长度是偶数就能判断下一步该解什么。编码题的通用解题法就是这样不断解码每次解码后都重新判断文本形态直到看见flag格式为止。4.2 古典密码从频率分析到维吉尼亚第二道Crypto题给了一篇看起来像英文的密文但所有单词都拼不对题目提示只写了“key”两个字母。这种带key的英文密文最经典的就是维吉尼亚密码。维吉尼亚密码本质上是对凯撒密码的扩展它不是把所有字母固定偏移一位而是根据一个密钥循环决定每个字母的偏移量。破解的关键在于先确定密钥长度。常用的方法是Kasiski检验在密文里找到重复出现的字符串计算它们位置之间的间距然后求这些间距的最大公约数往往就接近密钥长度。这道题的密文不长我用Python写了个小脚本统计重复子串算出密钥长度大概是5然后再用重合指数法确定具体密钥内容最后把密钥代入维吉尼亚解密就得到了明文。如果你懒得手写CyberChef的Vigenère Decode功能也支持自动爆破密钥长度但前提是你理解背后的原理不然调参的时候会一头雾水。古典密码的难点从来不是解密算法的实现而是如何确定密码类型和密钥参数。4.3 一道基础的RSA题目最后一道Crypto是一道基础RSA题给了n、e、c三个值。看到这种结构本质上就是靠分解n来求私钥。这道题里的n并不大我用Python的gmpy2库几步就搞定了import gmpy2 from Crypto.Util.number import long_to_bytes # 通过工具分解n得到p、q p ... q ... n p * q phi (p - 1) * (q - 1) e 65537 d gmpy2.invert(e, phi) m pow(c, d, n) print(long_to_bytes(m))如果n再大一些可以用yafu或在线工具分解。RSA基础题的核心逻辑就一句话拿到了p和q题目就结束了。很多新手看到RSA就害怕其实在CTF里给n、e、c的基础题往往是最简单的送分题只要熟悉Python的密码库十分钟以内能拿下。5. 做题之外工具链、复盘与个人体会5.1 我常用的CTF工具清单经历了整个SUSCTF 2018之后我发现一个规律真正拉开差距的往往不是脑力而是工具档案的完整度。这里把我当时常用的工具按方向整理了一下方向工具用途WebBurp Suite抓包、改包、重放Websqlmap注入检测辅助Web蚁剑连接WebshellMiscbinwalk、foremost文件分离与隐藏内容提取MiscStegsolve、zstegLSB隐写、通道查看MiscWireShark、tshark流量分析与数据提取Misc随波逐流CTF编码工具编码自动识别与解码CryptoCyberChef编码转换、密码爆破CryptoPython的gmpy2/pyCryptodomeRSA、离散对数等数学计算ReverseIDA、Ghidra静态分析Pwnpwntools漏洞利用脚本编写工具不在多在于知道每个工具“在什么时候用”。比如binwalk在Misc题里几乎可以首先无脑跑一遍因为很多题目会把一个文件藏进另一个文件里但如果一个图片本身没有附加数据跑完binwalk什么都没有也不要浪费时间反复跑直接换思路看隐写。工具是死的思路是活的。5.2 赛后复盘把writeup变成自己的武器库比赛结束后还有一个关键动作复盘。那届SUSCTF的官方writeup和选手自己写的题解比赛结束后的几天内会在各大安全社区陆续放出来。我会把自己做出来的和没做出来的题都重新做一遍每道题整理出一个独立的Markdown文件内容包括题目描述、我的原始思路、卡住的点、最终解法、涉及到的知识点。整理完之后这些文件就是自己的“武器库”。以后遇到类似题目先翻武器库而不是先翻搜索引擎。我后来在实战中发现90%的新题都是旧知识点的组合命令过滤和${IFS}是一套base64和十六进制是一套二维码修复和图片隐写又是一套。把套路记下来比临时临场发挥靠谱得多。复盘还有一个容易被忽略的好处你能看到出题人的思路。比如那道“小小查询系统”为什么报错信息会原样回显因为出题人故意没有关闭错误显示就是为了引导选手往里走。一旦站在出题人的角度想问题很多原本觉得“藏得太深”的线索都会变得合理起来。5.3 给新手的三条实在建议如果你准备参加SUSCTF或者任何一场大学生CTF赛事我有三条实在建议。第一组队时要互补。三个人最理想的配置是Web一人、Misc/Crypto一人、Reverse/Pwn一人哪怕后面两个方向只能做简单题也能保证不浪费题目。CTF不是个人英雄主义的游戏分工明确才能最大化拿分效率。第二赛前把基础工具装好并至少各用过一次。比赛时没有时间现场学工具尤其是WireShark的基本过滤语法、Burp Suite的抓包流程、Python的密码库调用这些都要提前练到条件反射的程度。很多队伍在比赛前半小时还在装环境心态就已经输了一半。第三善用官方writeup但一定要自己动手敲一遍解题脚本。只看题解觉得“我会了”和真正跑通拿flag之间差的是一次一模一样的实战。尤其是密码学和Misc脚本能不能跑通决定了你是不是真的掌握了。最后再分享一个小习惯。每次比赛结束我会把自己解出来的flag和题目对应的解题步骤整理成一份文档按比赛名称归档。SUSCTF 2018的题我后来重新完整做了一遍发现当初两个多小时才解出来的命令注入其实就是过滤绕过的一个变体。CTF的成长速度往往就藏在这些反复拆解和整理里。如果下一届SUSCTF我还有机会参加大概会比第一次线上赛从容很多。