最近在CTFshow刷WEB方向的爆破专题,一个系列刷下来收获比想象中多。爆破在CTF Web里属于那种看着简单、上手才发现水深得很的题型——说它简单,是因为核心思路就一句话:枚举加判定;说它水深,是因为题目里往往塞满了验证码、Token、加密参数、频率限制这些拦路虎,稍不注意就卡死在第一步。这篇刷题日记想把我这段时间做爆破题总结出来的判断思路、工具链选型逻辑,以及踩过的几个典型坑完整整理出来。适合刚入门CTF、准备打校内赛或者对Web安全攻击面感兴趣的读者,不需要很强的代码基础,但最好先会抓包改包。
1. 爆破题在CTF Web里的定位:为什么值得专门刷一遍
1.1 爆破的本质是什么
很多人对爆破(暴力破解)有误解,觉得这就是拿个大字典怼着登录接口一顿猛撞,没什么技术含量。但实际上,爆破在CTF里更像是一种“兜底逻辑”:当你找不到SQL注入、XSS、文件上传这类精确漏洞时,爆破往往能成为突破口。它的本质可以拆成两个动作——枚举所有可能取值,再通过某种判定条件筛选出正确答案。
拿开密码锁打比方:我不知道密码是多少,但我可以一个个试。如果锁具能告诉我“前两位对了”或者“最后一位错了”,我就能大幅缩小范围。Web爆破也一样,服务器返回的响应里藏着大量信息:状态码变了说明请求被不同处理,响应长度变了说明页面内容有差异,返回了不同的Cookie说明登录逻辑已经走通。我们要做的就是把这些差异变成判定条件。
也正是因为这样,爆破题特别考验人对HTTP协议的熟悉程度。你至少得看得懂请求头、响应体、Session机制,知道GET和POST的参数怎么传,明白Set-Cookie和Location响应头的作用。这些基础知识一旦打通,后面做SQL注入和RCE题也会顺手很多。
1.2 这类题能训练什么能力
我觉得爆破专题是Web方向性价比最高的训练场之一,原因有三个。
第一,它把“读代码”的需求降到了最低。很多Web题需要审计源码才能找到漏洞点,爆破题通常只需要观察响应差异,不需要你精通某个语言。对刚起步的人来说,这种反馈是即时的,成就感来得快,不容易劝退。
第二,它能培养对请求细节的敏感度。爆破过程中你会反复抓包、看响应、分析差异,渐渐地你就能一眼看出某个接口的参数名是不是可枚举的,某个验证码是不是真的会刷新,某个Token是不是藏着时间戳。这种敏感度在做任何Web题时都有用。
第三,它的题型变种很多。同一个爆破主题,可以出成弱口令、验证码绕过、Token预测、加密参数枚举、时序攻击等完全不同的方向。刷一遍爆破专题,等于把Web安全里“穷举思维”的各种用法都过了一遍,后面遇到新题时脑子里的思路库会宽很多。
2. 工具选型与请求库取舍:Burp和脚本各管哪一段
2.1 Burp Suite的Intruder模块怎么用才高效
爆破题首选工具大部分时候是Burp Suite的Intruder模块,因为它能可视化地修改请求包、配置字典、观察排序结果,非常直观。但很多人用Intruder时只会简单设置一个payload位置,然后直接开跑,这样遇到稍微复杂一点的题目就会踩坑。
先说Position(位置)的选择。用Burp抓包后,通常会有多个参数需要爆破:用户名、密码、验证码、Token。如果是用户名和密码同时要试,就要用Cluster bomb模式,它会做笛卡尔积组合;如果只有一个参数要爆破,用Sniper模式就够了。这个选择直接影响爆破量——Cluster bomb的时间复杂度是n×m级,如果不控制字典大小,跑起来会非常痛苦。
然后是Payload的设置。很多人喜欢直接在Payload Options里手打几个弱口令,这当然可以,但更好用的是加载字典文件。爆破题能不能出结果,百分之七八十取决于字典质量。你可以在网上找到公开的常用密码字典,也可以自己按规则生成——比如把月份、日期、年份、常见英文单词排列组合起来,配合大小写规则和数字后缀,能覆盖很大一部分弱口令题目。
Options选项卡里同样有讲究。Intruder默认的请求并发线程是1,意味着它是串行发请求的。串行的好处是稳定,不容易被服务器封IP,但缺点是慢。在靶场和CTF环境中,通常可以把线程调到10左右,同时把请求失败后的重试次数调低,不然一旦服务器延迟,整个队列会积压。另外,结果列表里记得按Length排序,这一步几乎是爆破题的标配操作。
2.2 什么时候必须换Python脚本
Burp Intruder虽然方便,但它有一个致命短板:无法对每次请求做动态计算。比如题目要求密码先做一次MD5再提交,或者请求头里带一个每隔几秒就过期的Token,再或者需要对上一轮的响应值做提取并拼接到下一次请求中——这些场景Intruder都不好处理,至少处理起来很别扭。
这种时候就该上脚本。我习惯用Python加requests库来写爆破逻辑,简单场景一个for循环就够了。比如爆破一个普通登录接口:
import requests url = "http://target/login.php" dict_file = "passwords.txt" with open(dict_file, "r", encoding="utf-8", errors="ignore") as f: passwords = [line.strip() for line in f] session = requests.Session() for pwd in passwords: resp = session.post(url, data={"username": "admin", "password": pwd}) if resp.status_code == 200 and "密码错误" not in resp.text: print(f"[+] Found: {pwd}") break else: print(f"[-] {pwd} -> {resp.status_code} len={len(resp.text)}")这段代码可以做很多变体:把用户名也放进字典做双层循环,就是弱口令爆破;把密码先拿到哈希库跑一遍再提交,就是加密参数爆破;把响应的Set-Cookie提取出来拼到下一次请求头里,就能处理登录态的爆破。
脚本最大的优势是可定制性。你可以对任何响应做任意判断,可以在循环里加延时,可以处理多步请求链路。比如有的题是先请求一个验证码图片接口拿到校验值,再带着校验值去登录,这时就必须用脚本来维护状态。
2.3 字典准备是第一生产力
说实话,爆破题刷多了以后,我最大的感悟是:工具只是放大器,字典才是爆破的灵魂。字典质量直接决定结果,再快的爆破工具配上烂字典也白搭。
常见的字典来源有几个方向。第一是网络上公开收集的常用弱口令表,里面的密码基本覆盖了“123456”“password”“admin888”这类人类最爱用的组合。第二是自己按规则生成,比如根据题目提示的格式(“6位数字密码”“生日是2000年之前”),用脚本快速生成针对性的字典。第三是参考题目本身的异常响应,比如有的题会提示“密码必须包含大写字母和数字”,那么你的字典就应该在这种规则下生成,而不是拿个原始弱口令表硬怼。
做字典生成时,Python的itertools模块很好用。比如生成六位数字密码:
import itertools for combo in itertools.product("0123456789", repeat=6): pwd = "".join(combo) print(pwd)如果题目给了密码范围和规则,这种定向生成比网上找字典高效得多。
3. 登录接口弱口令爆破:从抓包到出flag的完整链路
3.1 一道经典题的拆解过程
登录接口弱口令爆破是WEB爆破专题里最基础的题型,也是后面各种变形的基础。我拿某道题举个例子,整个过程分四步走。
第一步是抓包分析。在登录页面随便输入一个用户名和密码,提交后用Burp抓到POST请求,观察参数名。这一步很多人会跳过,直接开跑,但其实非常关键——你得弄清楚服务器到底接收哪几个字段,有没有隐藏字段,参数名是不是常规的username和password。有一次我在一道题里卡了半小时,就是因为实际参数名是wtf_username,字典和逻辑都没问题,但参数名对不上,永远爆破不成功。
第二步是确定爆破点。如果题目提示了账号名(比如“admin”),那用户名字段固定,密码字段设为payload位置,用Sniper模式跑;如果账号名也要猜,就两个位置都标记,用Cluster bomb模式。
第三步是加载字典。这里我会先跑一个小的测试字典,比如二十来个常见弱口令,目的是观察响应格式,确认“密码错误”时页面有什么特征,这样后续筛选时才有参照物。
第四步才是正式爆破。跑完不是盯着结果乱看,而是按Length(响应长度)排序,找出长度明显与众不同的那个请求。因为绝大多数“密码错误”的响应内容长度是相同的,只有密码正确时页面才会多返回一些内容,比如跳转脚本、Cookie设置、甚至直接是flag。
注意:不要一上来就开大字典暴力乱跑。先用小字典确认响应模式,再用大字典跑,能节省大量时间。
3.2 怎么让正确口令自己“跳”出来
爆破完成后判断哪个是正确答案,有一些技巧可以显著提高效率。
最常用的是响应长度排序。前面说了,错误响应的长度通常一致,正确答案的长度会有偏差。你可以在Intruder的结果面板里直接点Length列排序,偏差值呈台阶状的那个请求就是答案。这个办法在绝大多数弱口令题里都有效,属于第一优先级。
第二个方法是关键词匹配。有的题目虽然密码错误也会返回200状态码,但响应体里包含“用户名或密码错误”的提示文案。正确密码返回的页面没有这段文案,而是“登录成功”之类的字样。如果你用的是脚本,判断关键词很方便;如果用Burp,可以在Options里配置Grep Extract,把响应中的特定字符串提取出来,这样结果列表里每一行都能直接看到这个关键词,一眼扫过去就能找到异常项。
第三个方法是观察状态码和响应头的细微变化。比如错误时会返回200,正确时会返回302并跳转到首页;或者错误时不在Set-Cookie,正确时才会下发一个新的会话Cookie。这些差异在日志里可能不明显,但批量扫描时看排序和头部信息就能察觉到。
3.3 三个容易翻车的小细节
第一是前端加密。很多登录接口并不是明文提交密码,而是先在前端用JavaScript做一次MD5或SHA1哈希,再以哈希值作为密码参数提交。你要是直接拿明文密码字典去爆破,永远不可能成功。判断方法很简单:抓包看提交参数,如果密码字段的值是32位十六进制字符串,那基本可以确认做过MD5。解决办法是用脚本在循环里先哈希再提交,或者先离线批量把字典处理成哈希文件,再用Burp加载处理后的字典。
第二是验证码卡关。有的题在爆破一次后会刷新验证码,提交时要求带上验证码参数。这种情况用Burp直接跑是不现实的,因为验证码每次都变。要么先分析验证码接口,寻找绕过方法(下面章节详细说),要么用脚本配合打码方式处理。CTF里多数验证码不会做得太复杂,绕过概率很高。
第三是Session与Cookie的干扰。爆破过程中如果你复用了同一个会话,服务器可能因为连续失败触发了锁定机制,后续怎么跑都是“账号已锁定”。这种情况下可以在脚本里每隔一段时间换一个新Session,或者每跑几十次就重置Cookie,避免被同一会话标记。
4. 验证码、Token与签名:爆破题里最常见的“卡人”机制
4.1 验证码:先判断能不能绕过,再考虑识别
验证码是爆破题里出现频率最高的拦截手段,但CTF环境下的验证码十有八九是可以绕过的。问题在于绕过方式依赖于具体的实现逻辑,所以第一步永远是分析验证码是怎么设置的。
我一般按照一个顺序来判断。第一次请求页面时验证码会不会过期?有的题验证码只在用户刷新时才变,那么爆破期间只要你不刷新页面,验证码就一直有效,直接携带首次抓到的验证码参数跑完整轮爆破即可。第二种情况是验证码仅为前端校验,服务端根本不检查,你就能直接删掉验证码参数或者填一个固定值提交。第三种是验证码可识别,图片简单到可以直接肉眼分辨——那就用Burp里的人工介入,或者稍微看一眼就能填进去,不构成实际障碍。
真正麻烦的是验证码会跟随每次登录尝试刷新。这种情况下先别急着打码,先观察验证码是不是隐藏在请求或响应中。有的题会在返回的头里附带一个md5值,这个值对应的明文就是验证码;还有的题是把验证码放在Cookie里做校验,那么直接读取请求包就能找到它。
我整理了一个简单的判断清单:
| 现象 | 判断方向 | 应对手段 |
|---|---|---|
| 验证码只在刷新时变 | 全程不复用,携带旧验证码 | Burp直接爆破 |
| 服务端不校验验证码 | 删掉参数即可 | 直接绕过 |
| 验证码明文出现在响应头或Cookie | 提取后拼接 | 加Grep Extract提取 |
| 验证码图片简单可读 | 人工识别 | 配合识别提交 |
| 验证码随每次请求刷新 | 寻找生成逻辑 | 脚本提取后循环带入 |
4.2 Token爆破:多数时候是在和时间戳较劲
Token在爆破题里一般被用来防止重复提交,它的实现很多是“当前时间戳”或者“时间戳加固定字符”的变体。这类Token看起来每个请求都不一样,导致直接用固定payload爆破没法成功,但只要你看出它的生成规律,爆破反而会变成一道很简单的题。
举个例子。有的登录接口会在返回包的Cookie里塞一个类似token=MjAyNTAxMDExMjAwMA==的东西,看着像随机字符串,但Base64解一下就会发现是时间戳“20250101120000”。这种情况下你的脚本就可以做三件事:取当前时间生成Token、拼进请求、再试密码。循环里每次请求都动态更新Token,服务器就无法用“Token过期”来拦截你。
如果Token不是时间戳而是自增ID,那就更简单了。比如订单查询接口里的ID,或者用户编号参数,从1开始试到几万,配合响应差异判断就能找到正确答案。这种思路在爆破题里经常出现,本质上是从“爆破密码”变成了“枚举资源”,覆盖的范围比密码爆破更广。
4.3 弱比较与哈希爆破:PHP弱类型是出题人的最爱
还有一类爆破题,表面上让你爆破参数,实际上考的是语言特性。最常见的PHP弱类型比较问题,比如md5($password) == "0"这类条件,只要传入的字符串经过MD5后是以“0e”开头的科学计数法表示,进行比较时就会被当作数字0,从而绕过密码校验。这类“magic hash”在爆破题里经常作为隐藏考点出现。
如果你在刷题时发现密码爆破怎么都不出结果,但页面里有明显的md5()函数痕迹、或者响应提示跟哈希相关,那就要转换思路:不是去跑密码字典,而是去构造MD5值以0e开头的输入。这类输入在互联网上有现成的列表,也可以在本地脚本里跑一段循环快速找出来。用脚本生成候选值,一个个提交,命中后就能拿到进入后台的权限。
签名爆破则是另一个进阶方向。比如某个接口的请求数据末尾带一个hash值,作用是对参数做完整性校验。如果你能确定签名的算法(MD5、SHA1、HMAC-CRC之类的组合)和参与签名的字段顺序,那么就可以用脚本枚举签名密钥,把每个尝试值作为字段拼接后做哈希,再和题目给出的签名做对比。这种爆破在JWT伪造题里也很常见,密钥是弱密钥时几乎就是直接送分。
5. 响应差异判断:状态码、长度与跳转才是爆破的“眼睛”
5.1 长度排序是最稳定的筛选器
前面提到长度排序,这里展开细讲。爆破的结果列表里,大部分错误请求的响应体长度是基本一致的,因为服务端处理逻辑走的是同一个分支,返回的是同一个模板。而正确的那个请求往往会走到不同的处理分支,响应的HTML结构发生变化,长度自然不相同。
实际操作中,长度排序之后你可能会看到两组数据:一组是固定的几百字节,另一组可能多出来几百字节。多出来的那个基本就是突破口。但也有特殊情况:有的题错误响应反而比正确响应长,比如错误时会返回一个完整的错误提示页面,正确时只有一个302跳转头。所以不要只盯着“更长的一定是答案”这个惯性,要学会结合状态码和响应头综合判断。
一个比较稳妥的做法是把长度、状态码和响应头三者放到一起看。你可以用脚本把每次响应的这三个维度都打印出来,找一个明显偏离主流的请求,再通过手动浏览器访问验证。
5.2 参数顺序、请求头与Set-Cookie的隐藏信号
除了响应体,请求返回的响应头也是一个重要信号源。我发现很多爆破题的正确答案并不会体现在页面文字上,而是藏在两个地方:一个是Location跳转地址,一个是Set-Cookie。
典型的场景是:密码错误时返回200,页面重新加载登录表单;密码正确时返回302,Location指向首页或者flag页面。这种情况下用Burp看结果时,可以直接关注状态码列,把302标出来,通常就是正确答案。如果你用的是脚本,判断条件可以写成:
if resp.status_code == 302 or "flag" in resp.url or ("admin" in resp.headers.get("Set-Cookie", "")): print(f"[+] Found: {pwd}, status={resp.status_code}, location={resp.headers.get('Location')}")Set-Cookie则更多用于判断身份切换。爆破成功后服务端往往会下发一个新的SessionId,因此你在结果列表里如果发现某个响应的Set-Cookie内容和之前的都不一样,那很可能就是这个请求被服务端特殊处理了。
有一种情况要提醒:有的题返回300系列状态码是因为设置了重试或者反爬,而不是登录成功。所以看到302不要激动,先看Location指向哪里、目标页面里有没有flag。只有确认跳转到了可访问的后台页面,才能确定爆破成功。
5.3 精力放在“可控变量”上
爆破题的难点通常不在“跑”上,而在“判断参数怎么设置”上。比如有些题要求你同时爆破用户名、密码、权限级别三个参数,其中权限级别是一个隐藏字段,默认是普通用户,改成管理员权限就可以看到不一样的响应。这种题已经不是单纯的爆破,而是对业务逻辑的理解。
我做这类题时有个习惯:把题目的每一个输入参数先过一遍,问自己“哪些参数的值是可控且可枚举的”。有可能是用户名、密码、邀请码、角色ID、订单号,甚至某个回调地址。把这些参数都梳理出来后,再逐个尝试爆破,往往会有意外收获。爆破的范围远不止登录口令,接口路径、文件名、目录名、隐藏参数名,这些都可以作为爆破目标。
6. 高频翻车点复盘:字典、并发与加密参数的真实坑
6.1 字典问题:为什么同样的题别人能出你不能
爆破题翻车第一大原因就是字典不行,但这个“不行”有很多具体表现。最常见的是字典全是纯小写字母,而题目密码包含了数字和特殊符号;另一种是字典太大,几百万行跑了一晚上都没跑完;还有一种更隐蔽——字典虽然有目标密码,但因为编码问题导致提交后的内容错乱,服务器那边收到的是乱码。
我的建议是,准备字典时至少做三件事。第一,按用途分类,不要让弱口令通用表和数字组合表混在一起,不同题目用不同字典;第二,做编码处理,Burp和脚本里统一使用UTF-8,避免中文或特殊字符乱码;第三,先跑小样本验证“链路能通”,确认每发一个请求都能被服务器正常响应,再放大跑量。
如果你自己生成密码,可以用规则组合的方式。比如把常见姓名列表、生日列表、数字后缀、特殊字符后缀做交叉组合,几百MB的字典很快就出来了。但注意生成后做去重和排序,不然无效请求会浪费大量时间。
6.2 并发控制与频率限制:别把靶场跑挂
爆破本质上是大量的重复请求,一旦控制不好,很容易触发反爬或者把靶场服务跑挂。我在刷题时就遇到过几次:线程设太高,服务直接拒绝连接,后面所有请求都变成了429或503,还得等一段时间才能恢复。
稳妥的做法是控制并发数。Burp Intruder的线程一般设置在5到15,脚本里可以用线程池配合time.sleep()做限速。对于本地靶场题,速度稍微慢一点没关系,追求的是稳定出结果,而不是跑得有多猛。如果题目明确有IP封锁机制,那就要考虑换IP——但在CTF环境里更多是等待冷却时间,或者查看是否可以通过修改请求头(如X-Forwarded-For)绕过限制。
还有一点:有的题做了账号锁定,连续失败五次就锁定五分钟。遇到这种情况,刷得多不如刷得准,先仔细分析响应,确认哪些参数是真正需要枚举的,再决定跑法,比盲目暴力有效得多。
6.3 加密参数爆破:从密码到完整请求指纹
最后说一类较特殊的翻车场景——加密参数的爆破。这类题目中,需要爆破的往往不是用户口令,而是一个被加密或编码后的参数值。
比如有的接口带一个sign参数,取值为当前请求参数的某种哈希。如果你猜不到签名算法,常规爆破无论如何都过不了签名校验。这时候要回到题目源码和提示去找线索。有的题会在页面源码注释里留算法提示,有的题会在报错信息中把计算过程暴露出来。
如果确认是MD5且参与签名的字符串较短,你可以直接枚举。比如签名是md5("flag" + 某个数字),那么写一个循环从1试到10000,每次计算哈希再对比目标值,很快就能还原出那个数字。这种爆破比密码爆破目标更具体,可控性更高,但前提是你已经理解了整个请求的构成。
我在实际刷题中发现,很多“爆破不出来”的问题,最终都出在对请求格式的判断上。参数拼接顺序错了一位、编码忘了做URL编码、提交的是JSON格式却用了表单格式发送——这些细节问题在报错信息上通常都有提示,只是大家习惯性忽略了响应内容里的那一行小字。
7. 刷完爆破专题后,我对Web安全的新理解
这个话题到最后,我不太想做什么宏大总结,只想说点实际的体会。爆破专题刷完之后,我对Web安全里“不安全的对象枚举”和“请求差异分析”有了更直观的认识。以前觉得爆破就是个力气活,现在才发现它其实是在训练一种思维方式:任何输入只要有规律、可枚举、可比较,就值得试一试。
后面我打算把刷题方向延伸到JWT伪造、验证码机器识别、以及API接口枚举这几个方向,因为它们本质上跟爆破是一家人——都是靠大量请求去试探边界,靠响应差异去判断内部逻辑。如果你也在这个阶段,建议别急着跳去做难题,先把爆破专题吃透,把Burp和脚本配合练到顺手,再往后面走会轻松很多。
最后分享一个刷题时的小技巧:不管题多简单,做记录的时候把请求包、响应关键特征和最终payload都截图保存下来。回头整理时会发现,很多题的出题逻辑是重复的,这些记录就是你最好的字典和思路库。