说个我自己的经历。去年这个时候,我还是个只会蹲在Writeup区看热闹的围观群众,看到别人一上来就找到flag,总觉得是天赋差距。后来跟着NSSCTF的日常训练题刷了两个月才明白,CTF这东西说到底就是“知识面+套路熟练度”的比拼,没有多少玄学成分。这次赶上NSSCTF赛季N开打,我抱着检验自己的心态报名,前前后后花了两个周末,把Misc、Web、Crypto三个方向从头打到尾,收获比想象中大得多。这篇就把这次参赛的完整过程拆开写一写,特别是那些官方文档里不会写的判断思路和踩坑细节,希望能给刚入门的朋友一点参考。
1. 赛季N的基本盘:题型分布、flag规则和入场前要搞清的事
1.1 平台形式和赛季机制
NSSCTF本质上是一个CTF练习与竞赛平台,赛季N可以理解为某一轮周期性比赛。它和线下赛最大的区别是题目长期在线、随时可以提交flag,而且大部分题目都提供了明确的难度分级。对我这种上班族来说,这种“非同步赛”的节奏非常友好——白天没时间就晚上打,晚上没思路就周末补,不用被固定的几小时赛时逼到崩溃。
赛季N相比之前的日常训练有一点变化值得注意:动态积分机制更明显了。也就是说,一道题的分值会随着解出人数增加而下降,越早解出来分越高。这个机制直接改变了我的做题策略:先把容易的、别人也能做的题目快速清掉,再去啃难题,不然排名会被拉得很远。
1.2 五大方向怎么选:先认清自己的舒适区
CTF题目通常分五个大类,每类的入门门槛和工具链完全不同。我见过太多新人上来就冲Pwn,结果被堆栈溢出绕到怀疑人生,然后直接弃坑。下面这个表是我自己整理的方向对比,适合在报名前先做个自我评估:
| 方向 | 核心能力 | 入门工具 | 新手友好度 | 典型耗时 |
|---|---|---|---|---|
| Misc | 信息收集、编码、取证 | Wireshark、binwalk、strings | 高 | 30-90分钟 |
| Web | 漏洞原理、HTTP协议 | Burp Suite、浏览器F12 | 中高 | 1-3小时 |
| Crypto | 数学基础、编码识别 | Python、CyberChef | 中 | 1-3小时 |
| Reverse | 逆向思维、汇编基础 | IDA、Ghidra、GDB | 低 | 3小时以上 |
| Pwn | 内存布局、漏洞利用 | pwntools、gdb | 低 | 3小时以上 |
我的建议很直接:第一赛季把Misc当主战场,Web做副战场,Crypto随缘。Misc题几乎不考数学和汇编,考的是“细心”和“常识”,非常适合建立信心。等你能稳定在一场比赛中解出三四道题之后,再往Web和Reverse的方向深入也不迟。
1.3 flag格式和提交规则:拿不到分的第一个隐藏原因
很多人第一次参赛,明明找到了疑似答案,提交却显示错误,于是开始怀疑人生。这里有个特别容易被忽略的点:flag的格式。NSSCTF这类平台的flag一般遵循NSSCTF{...}的格式,但部分题目会要求带上特定的前缀或者做URL编码转换。
我这次就亲眼见到同组的朋友在Web题里找到了一个形如flag{...}的字符串,直接复制提交,结果一直报错。后来我在群里的提示下帮他加上了NSSCTF前缀,一下就过了。这种“格式不匹配”不是题目bug,恰恰是CTF里最基础的考察点——你是不是真的读懂了题目的上下文。
2. 我主攻的三条题线,以及为什么这么组合
2.1 选方向等于选工具链:别让工具拖慢你
方向选完之后,第一件事不是刷题,而是把工具链装齐。我自己这次比赛前重新整理了一遍环境,花了一个下午配置。Misc方向装了Wireshark、binwalk、010 Editor;Web方向装了Burp Suite Community版和Chrome开发者工具的常用插件;Crypto方向直接用Python的pycryptodome库和CyberChef网页版。工具不在多,顺手最重要。我见过有人用Wireshark看流量包看了半天,最后发现他过滤语法写错了,压根没看到关键TCP流,这种都属于工具熟练度问题,赛前就应当解决。
2.2 Misc题其实是“信息收集”基本功
Misc题的底层逻辑是:给你一个看似无用的东西,让你用各种手段从中提取出隐藏信息。最常见的载体有图片、压缩包、流量包、音频文件。它考的其实不是某门技术,而是你面对未知文件时有没有一套固定的“检查流程”。这套流程练好了,将来做Web题、Reverse题也有用,因为渗透测试的第一步同样是信息收集。所以我把Misc放在优先位置,就是为了先练这套流程。
2.3 Web题离实战最近,但也最容易钻牛角尖
Web题模拟的是真实网站漏洞,最常见的考点是SQL注入、文件上传、命令执行、反序列化。它比Misc多一层“交互感”,你需要向服务器发送特定请求,根据返回内容判断漏洞类型。我这次选择Web作为副战场,是因为它和我的日常工作(Web开发)有重叠,理解起来快。但Web题有个问题:容易钻牛角尖。一个payload改来改去都不出结果,很可能不是payload的问题,而是你漏看了响应头或者Cookie。所以打Web题一定要养成“每一步都看完整响应”的习惯。
3. Misc流量分析题的完整解题链路:从pcap文件到flag
3.1 拿到附件先做三件事
我这次遇到的第一道Misc题,附件是一个名为capture.pcapng的流量包。很多人拿到这类文件会直接打开Wireshark乱翻,我的习惯是先做三件机械式操作:
file capture.pcapng binwalk capture.pcapng strings capture.pcapng | grep -i -E "flag|ctf|nss|http|txt|zip"这三条命令分别告诉你三件事:文件真实类型是什么、里面有没有嵌套其他文件、以及有没有直接暴露的可读字符串。实测下来,大概有四成的Misc题在 strings 这一步就能看到线索。这道题也不例外:strings 输出里出现了一段可疑的base64字符串,后面跟着一个名为secret.zip的文件路径。
这里有个经验值得单独说一下:不要急着解码base64。先把它复制到文本文件里存好,继续往下走。因为流量包里的base64可能只是某个请求参数的编码值,解码得到的未必是flag,也可能是加密压缩包的密码,上下文很重要。
3.2 在Wireshark里追踪HTTP流,一个会话一个会话看
strings虽然快,但它只能看到明文内容,看不到请求和响应的对应关系。真正要看清楚整个链路,还得回到Wireshark。我的做法是先用过滤器把无关流量去掉:
http || dns || tcp.port == 80 || tcp.port == 443然后右键任意一个HTTP请求,选择“追踪TCP流”,会弹出一个完整的会话内容窗口。这个方法比一帧一帧翻包高效得多。在这个流量包里,我看到浏览器访问了一个下载站,先后请求了三个文件:index.html、image.jpg、secret.zip。其中image.jpg的响应头里有个奇怪的字段x-custom-header: ZmxhZ3twY3NfaXNfc29fZnVuXw==。
这个字段名一看就是题目故意埋的。我用echo "ZmxhZ3twY3NfaXNfc29fZnVuXw==" | base64 -d解码,得到一串半截flag前缀。到这里思路就清晰了:秘密一定被拆分成了几部分,剩余部分应该在图片或压缩包里。
3.3 从图片和压缩包走向终点:binwalk与伪加密
接下来处理image.jpg和secret.zip。先对图片跑binwalk:
binwalk image.jpg结果发现jpg末尾有一个ZIP档案。用dd或者binwalk -e把藏在图片里的ZIP分离出来,解压时却提示需要密码。这时候有三个常见方向尝试:压缩包文件名提示、图片里肉眼可见的异常、以及伪加密。
伪加密是CTF里最常见的套路:ZIP的加密标志位被改过,让你以为有密码,实际文件根本没加密。判断方法很简单,用十六进制编辑器打开ZIP,找到中央目录结构里General purpose bit flag字段,如果值为0x01而文件实际内容没有加密,就把这个位置改成0x00再解压。我用010 Editor改完之后,文件直接解开了,里面是一个txt文件,内容是第一部分的剩余flag。
把base64解码的前缀和txt内容拼起来,NSSCTF{...}就完整了。这道题整体的知识点覆盖了流量分析、编码识别、文件分离和ZIP结构,难度不高,但链路很长,非常考验细心程度。整个过程大概花了四十分钟,其中一半时间花在确认“base64不能先解”这个判断上。
4. Web题从探测到利用:一次SQL注入的完整记录
4.1 入口获取:URL参数就是攻击面
赛季N里有一道Web题,点击题目打开是一个简单的查询页面,输入商品ID就能返回商品名和价格。这类题目十有八九是SQL注入,入口就是id参数。我用Burp抓了一下请求,确认是GET请求,参数就是id=1。Burp的Repeater功能很适合做这种单参数测试,每次改动都看完整响应。
先随手输入一个id=1',页面返回SQL语法错误。看到报错的那一刻,我基本确定是注入点了。但这里要注意:不同数据库的报错格式不同。MySQL、SQLite、PostgreSQL的报错关键词各有区别,要根据报错信息推断数据库类型,再选对应的注入语句。这道题的报错格式像是SQLite,走常规的union注入路线。
4.2 用order by确定字段数,再一步步拿库名
确定字段数的经典手法是:
id=1 order by 1 id=1 order by 2 id=1 order by 3当order by 4报错时,说明查询只有3个字段。接下来用union select构造联合查询,让原始查询返回空集,把union的结果作为页面内容输出:
id=0 union select 1,2,3页面显示了2和3,说明这两个字段的输出位置。然后逐层拿库名和表名:
id=0 union select 1,sqlite_version(),3 id=0 union select 1,name,3 from sqlite_mastersqlite_master是SQLite的系统表,存了所有表结构。查询结果里看到了users表和flag_table表。到这里目标就很明确了,直接查表内容:
id=0 union select 1,sql,3 from sqlite_master where name='flag_table'返回的是一个建表语句,里面列出了字段名。再用字段名去查数据,flag就出来了。整个流程十分钟左右,属于Web入门里比较标准的送分题。
4.3 空格被过滤时的绕过方法
我的第二道Web题就没那么舒服了。同一套流程走到order by的时候,发现空格被过滤了,请求返回500。这种过滤很常见,绕过方式也很多。我在本地Python环境里快速验证了几种方案:
| 原始写法 | 绕过写法 | 适用场景 |
|---|---|---|
id=1 union select | id=1/**/union/**/select | 过滤空格但不过滤注释 |
id=1 union select | id=1%0bunion%0bselect | 允许垂直制表符 |
id=1 and 1=1 | id=1&&1=1 | 过滤关键字但允许符号 |
id=1 order by 3 | id=1 order%09by%093 | URL编码代替空格 |
实际测试下来,/**/注释替代空格在这个题目里有效。为什么注释能替代空格?因为MySQL和SQLite在解析SQL时会把/**/当作一个分隔符,效果等同于空白字符,但很多过滤正则只匹配了字面空格。这个知识点不难,但如果你没提前储备,现场临时想会很浪费时间。比赛就是这样,经验库越厚,临场操作越快。
5. 解题过程中最容易踩的四个坑
5.1 工具输出的编码坑
第一个坑是编码。strings输出的中文内容在终端里可能乱码,Windows下尤其严重。我这次在流量分析时,用strings导出的内容里有一串%E4%BD%A0%E5%A5%BD样子的文本,一眼能看出是URL编码,但它在终端里显示为乱码。正确做法是先把strings输出重定向到文件,再用编辑器打开,或者直接用Python处理。千万别在终端里肉眼解读编码字符串,这是新手最常犯的错误。
strings capture.pcapng > output.txt python3 -c "import urllib.parse; print(urllib.parse.unquote(open('output.txt').read()))"5.2 假flag与flag格式校验
第二个坑是假flag。有些题目会在文件里故意塞一个flag{fake_flag}或者类似的东西来迷惑你,等你高高兴兴提交,得到却是WA。我这次在图片的EXIF信息里看到了一个很像flag的字符串,仔细一看里面有两个字母反了。这种“一眼假”的flag其实是出题人发的好人卡,提醒你别眼瞎。判断方法很简单:提交前先检查格式、再检查大小写、最后思考逻辑上这个字符串是否真的有意义。
5.3 本地脚本能跑,平台提交不过
第三个坑是环境不一致。Crypto题里最常见:你在本地用Python算出一个结果,提交到平台却显示错误。原因往往是版本差异,比如Python的hashlib在不同版本下处理某些编码时的默认行为不同,或者题目要求的是MD5但你用了SHA1。我这次的Crypto题就吃了这个亏。赛前写好脚本时用的Python解析不了题目给的hex字符串,因为有个0x前缀没去掉。这种问题Debug起来快,但如果你不知道要去搜“环境差异”,可能卡半小时。我的经验是:本地验证通过 ≠ 答案正确,提交前把每一步中间结果都打印出来,跟题目给出的中间值做比对。
5.4 死磕难题的沉没成本
第四个坑是时间管理。赛季N有动态积分,死磕一道题超过两小时,情绪和分值都会崩。我给自己定的规则是:Misc题超过四十分钟没进展就直接看Hint,Web题超过一小时换下一道,Crypto题超过半小时先跳过。这不是不努力,而是把有限时间留给性价比更高的题目。CTF比的是总分,不是单题攻坚能力。
6. 赛后复盘:怎样把一场比赛变成自己的武器库
6.1 Writeup要怎么写才有价值
比赛结束不等于学习结束。我见过很多人打完比赛就停手,这其实浪费了最大的成长机会。正确的复盘姿势是:每一道你解出来的题,都写一篇给自己看的Writeup;每一道你没解出来的题,主动去看别人的Writeup,然后自己独立复现一遍。
我自己的Writeup模板很简单:题干信息、工具环境、完整操作步骤、关键报错、最终flag。不追求文笔,追求可复现性。一个月后你再回头看这篇笔记,如果照着它能在五分钟内重新走通,那这篇笔记才算合格。如果发现自己写的步骤缺了某个参数,或者命令复制之后根本跑不了,那就说明当时没有真正理解。
6.2 整理属于自己的checklist
复盘的第二件事是整理checklist。不是网上抄的通用列表,而是根据自己踩过的坑总结的个人清单。我这次的checklist长这样:
- 拿到附件先file、strings、binwalk三连。
- base64/hex/url编码先存文件,不急解码。
- Web题先看响应头,再看响应体,最后才改payload。
- ZIP解不开先检查伪加密。
- 提交前核对flag格式和大小写。
- 本地脚本跑通后,逐行打印中间值再提交。
你看,这六条没有一条是高深技术,但每条都对应着我这次比赛里实际踩过的坑。积累到十条、二十条之后,你的解题速度会有肉眼可见的提升。
6.3 下一阶段的训练路线
赛季N打完之后,我给自己排了下一步计划:第一,把Misc里没接触过的题型(如音频频谱图、内存取证)补一遍;第二,Web方向从“会注入”走向“懂原理”,去看SQL注入在数据库层面到底发生了什么;第三,开始接触简单的Reverse题,至少学会用Ghidra找main函数。训练不是漫无目的地刷题,而是每个阶段盯着一个明确短板。
我个人这两年最大的体会是:CTF的学习曲线其实是螺旋上升的。你可能在某一届比赛里觉得自己像个废物,但把那一届的题吃透之后,下一届就会突然发现很多题型眼熟。这次赛季N,我一开始也没想到自己能连续解出Misc和Web题,但靠着之前刷题积累的动作直觉,整个过程比预期顺利不少。
最后分享一个小技巧:每次比赛结束,不管成绩好坏,都挑一道自己花了最长时间的题,哪怕没解出来,也去把别人的Writeup逐行读完,然后关掉Writeup自己重新做一遍。这个习惯我坚持了三轮赛季,是解题速度和心态提升最快的方法,比盲目加练有用得多。下一届再战,这些经验都能派上用场。