
别的不说Bugku平台上这类“小而精”的Web题真的非常适合入门因为题目虽然只有短短几行提示背后却能把网络协议和PHP特性串得很完整。今天拆的这道“你从哪里来MD5”就是典型例子第一关考HTTP头里的Referer字段第二关考PHP里经典到不能再经典的MD5弱类型比较绕过。整体难度不高但如果没搞清楚原理光在网上抄Payload很容易卡在参数名、环境差异这些地方。这篇文章我会把完整的解题思路、每一步请求的构造逻辑、以及复现时遇到的实际问题全部过一遍新手可以直接跟着跑有点基础的也可以当查漏补缺。1. 题目回顾一张页面里的两个关卡1.1 “你从哪里来”到底在暗示什么刚打开题目的页面时通常看到的文案非常简洁类似一句提示或者一个很简单的页面结构周围没有任何多余信息。这时候一定不要急着在网上找Flag先看页面返回的内容本身。题目名“你从哪里来”其实已经把第一个考点写在脸上了它问的是“你的来访来源”对应到HTTP协议里就是Referer请求头。Referer这个字段的含义很直白当浏览器从一个页面跳转到另一个页面时会把来源页面的URL放进请求头一并发送。比方说你在搜索引擎点击了一个搜索结果跳转到目标站点目标站点的服务器就能从请求头里看到你是从哪个地址点过来的。不少偏传统的网站会拿这个字段做简单的来源校验比如图片防盗链、表单提交来源判断。这道题模拟的正是这种场景。题目说“你从哪里来”等于告诉你要伪造一个来源地址发过去。一般这类题会要求Referer指向一个具体站点比如https://www.google.com但具体值要看题目本身不要上来就套死。这一步解法很直接用一个能自定义请求头的工具把Referer改成题目要求的值重新请求即可。1.2 MD5弱比较PHP里最经典的旁门左道过了第一关之后页面会进入第二个场景提示你输入一个字符串并且这个字符串要满足某个MD5相关条件。这里涉及的考点就是PHP的“弱类型比较”。简单说PHP里和的行为不一样。会比较值和类型要求两者完全一致而是松散比较如果两边是字符串PHP会尝试把它们转成数值再比较。这里有个非常经典的“陷阱”如果字符串符合科学计数法的格式比如0e123PHP会把它当成0乘以10的123次方数值结果正好是0。MD5的十六进制哈希值恰好也有以0e开头的情况。一个字符串的MD5值如果是0e加上一串数字那它在松散比较里就会等同于整数0。所以题目里如果写着类似“传入一个字符串使其MD5值等于某值”的校验很多情况下就是让你找一个MD5值为0e开头的字符串来绕过。网上流传的经典字符串有输入字符串MD5值QNKCDZO0e830400451993494058024219903391s878926199a0e545993274517709034328855841020s155964671a0e342768416822977524524239673701s214587387a0e848240070830368651067225321549s150211347a0e861580163291561247404381396064s1885207154a0e5093672134182067008420087633282406107080e462097431906509519562118736429我当年第一次看到这张表也觉得很神奇但这些字符串是有人专门跑脚本撞出来的目的就是利用PHP的松散比较漏洞。解题的时候直接可用复制粘贴就行不需要现场去找。2. 解题思路推演从提示反推考点2.1 两关之间通常有固定的顺序观察页面返回时你会发现第一关和第二关通常是被“卡”住的没设置正确的Referer页面就一直停留在来源校验的提示设置对了才会暴露出关于MD5的下一个验证逻辑。这个顺序很像实际业务里的访问控制流程先判断来源合法性再进入业务逻辑校验。因此解题的节奏就是“先补头再补参”。不要一上来就带着MD5的Payload去打那样大概率会因为后面的逻辑根本没加载而失败。先把Referer处理干净让服务端认为你是来自“正确地方”的访客再去看它抛出的第二个条件。我当时第一次打这道题时就是上来直接把各种MD5 Payload塞进URL结果页面纹丝不动。后来静下心用Burp看请求才发现第一关就挡在外面了。这个经验对新手特别重要CTF里不是所有校验都同时生效必须跟着页面的状态逐步推进。2.2 定位MD5校验的参数名和比较方式过了Referer这一关后页面往往会直接或间接地给出一个提示比如“请输入一个md5值等于xxx的字符串”或者直接把一段PHP代码片段吐出来给你看。如果是后者那几乎就是送分题按源码里的逻辑构造参数即可。常见的形式是if (md5($_GET[a]) $_GET[a]) { // 输出flag }注意这里是不是。按照前面提到的弱比较逻辑只要传入的a是一个MD5值为0e开头的字符串比如QNKCDZO那么md5($_GET[a])的结果就是0e830400451993494058024219903391两者进行比较时都会被解析为数值0于是条件成立。如果遇到的是md5($_GET[a]) md5($_GET[b])这种就要考虑另一层绕法了——用数组。md5()函数处理数组时不会正常返回字符串而是返回NULL。两边都是数组的话md5(array)的结果都是NULLNULL NULL成立。这种数组绕过在强比较里也能用这里先提一下后面延伸部分会展开讲。做题的时候先确定参数名再确定比较方式。参数名通常在提示里写了有时候是a有时候是value反正跟着题目走就行。至于比较方式如果是就用0e字符串如果是则大概率要靠数组绕过。3. 完整实操从零开始拿到Flag3.1 环境准备本地有curl就够了这类题不依赖本地环境你只需要一个能发自定义HTTP请求的工具。图形化的话Burp Suite最顺手拦截请求后直接在Raw报文里改Referer命令行的话curl是首选干净利落而且便于写成一个可复现的脚本。我平时打CTF的习惯是先用浏览器打开目标页面观察页面文案和状态码确定题目的“当前阶段”。这些信息拿到后再用curl精准构造请求比一直在浏览器里点来点去效率高得多。原因很简单F12里改请求头虽然也可以但遇到要连续测试多个参数时命令行的可复用性更强。下面所有步骤都基于curl。如果你用的是Burp Suite思路完全一致只是操作界面不同。3.2 先用curl过掉Referer校验第一步向题目地址发送一个带自定义Referer的请求。假设题目要求的来源是https://www.google.com命令如下curl -v -H Referer: https://www.google.com http://题目地址-H表示追加一个请求头-v可以打印完整的请求和响应信息方便你确认请求头是否真的带上了。我强烈建议你在跑这条命令时打开-v因为有的平台在未通过校验时会返回302跳转你如果不看过程光看最终输出可能会误以为题目坏了。加了-v之后Referer会明明白白出现在请求体里响应状态码和Location也一目了然。如果一切正常响应体会从“你从哪里来”变成第二关的提示比如要求输入一个满足MD5条件的参数。这个时候再进入下一步。3.3 构造MD5弱比较的Payload参数假设第二关的提示是要求传入一个名为a的参数并且满足md5(a) a这种弱比较逻辑。那么直接选用QNKCDZO作为参数值即可。原因是QNKCDZO的MD5值为0e830400451993494058024219903391这是一个典型的科学计数法格式字符串。在PHP的比较中它会被解析为数值0而QNKCDZO本身不是0所以关键点在于等号左边是md5(QNKCDZO)结果也是0e开头的字符串。两边都变成数值0比较就通过了。有朋友可能会问那为什么不能随便传一个字符串让它的MD5值等于它自己因为理论上不存在md5(x) x的正常字符串这里利用了类型转换的漏洞而不是寻找自反字符串。理解这一点才算真正弄懂了弱比较绕过。3.4 两关同时打组合请求直接拿Flag实际操作中你可以把两步合并成一个请求。先设置Referer再在URL查询字符串里带上参数a一次拿到Flag。curl -v \ -H Referer: https://www.google.com \ http://题目地址?aQNKCDZO这里要注意参数名、参数位置GET还是POST一定要和题目提示一致不要照抄网上的现成Payload就完事。我见过很多新人卡在这一步其实不是方法不会而是把参数名a换成了param或者value导致绕过条件一直不触发。如果题目要求的是POST则需要额外加-d参数curl -v \ -H Referer: https://www.google.com \ -d aQNKCDZO \ http://题目地址拿到Flag之后这道题就算是完整解完了。整个流程总结下来就是看清提示→改Referer→定位参数→用0e字符串绕过MD5弱比较。4. 常见坑点与排查实录4.1 Referer值不匹配大小写、精确URL、空Referer这一关最常见的报错表现是设置了Referer但页面仍然提示来源不正确。我排查下来主要三种原因。第一题目要求的是完整URL而你只填了域名。比如需要https://www.google.com你却只写了www.google.com服务端做字符串匹配的时候自然不过。第二大小写或者协议写错。有的题目匹配得非常死https和http都会被区分对待。建议先看页面给出的提示原文比如提示里写了www.google.com就先试试带https://的完整形式如果还不行再试http://开头。第三你用的工具有一定特殊情况。比如某些插件或代理会自动改写Referer甚至直接移除它。这时候可以在curl的-H里再次明确指定确保最终发出去的报文确实带着对应的头。一个小技巧是用-v打印请求头确认这一行确实存在且值正确 Referer: https://www.google.com如果打印出来没这个头多半是命令的引号或转义出了问题。4.2 MD5绕过不生效参数名和比较逻辑要看清如果Referer已经通过但加了aQNKCDZO还是不出Flag重点检查两个地方。第一参数名。这道题第二关的变量名不一定叫a有些版本叫value有些叫input还有的写成了md5。判断依据是页面输出的提示或参考代码而不是网上题解的截图。看提示里写的是哪个变量名就用哪个。第二比较逻辑。如果是md5($_GET[a]) $_GET[b]这类需要两个参数的情况只传一个a肯定不够。同时要判断两个变量是否都是GET参数也有可能一个是GET另一个是POST混合传参时会更加隐蔽。另外如果题目给的比较条件是md5($a) 0这种那么直接传aQNKCDZO也可以因为0e开头的MD5值被解析为0。如果比较条件是md5($a) md5($b)这种强比较0e字符串就失效了得换用数组绕过。4.3 0e字符串不是“试一次就能过”的万能钥匙QNKCDZO这套Paylaod虽然经典但并非所有题都适用。如果题目用强比较或者对参数类型做了is_string()检查0e字符串就成了废棋。还有一点容易被忽略PHP不同版本对字符串转换为数值的行为基本一致但某些PHP 8.x版本在特定场景下会抛出更严格的类型错误。CTF平台通常为了体现考点会保留老行为但真实项目中这类弱比较漏洞已经在不断收紧。做题时如果遇到版本相关的问题先确认题目的PHP版本和使用框架再决定绕过策略。4.4 常见问题速查表现象可能原因解决方向页面一直提示来源错误未设置Referer或值不匹配加上完整来源URL核对协议/域名Referer设置后无变化请求被缓存或工具自动改写了头使用curl -H强制指定禁用浏览器缓存MD5参数加了但页面无响应参数名错误或比较逻辑不同看提示原文用正确的参数名0e Payload不生效可能是强比较或类型检查改用数组绕过如a[]1b[]2请求返回302跳转校验失败重定向到提示页观察Location和响应头定位哪一关没过想复现但本地找不到环境平台要求特定User-Agent等检查题目是否还对其他Header有限制5. 从这道题延伸出去更多PHP特性考点5.1 MD5强比较绕过数组才是真正的万能钥匙标题里的“MD5”在很多CTF题里并不会只考弱比较更常出现的是强比较也就是md5($_GET[a]) md5($_GET[b])。这里的要求值和类型都相等0e字符串就完全没有用武之地了。这时候要用PHP的另一个坑md5()函数只接受字符串参数如果传入数组会直接返回NULL。所以md5(array)的值是NULL两个NULL用比较结果是true。构造方法非常简单curl http://题目地址?a[]1b[]2只要后端比较的是md5($a) md5($b)传两个数组进去就能绕过。这个方法同样适用于sha1()等其它哈希函数。不过用了数组之后很多题目会伴随报错信息md5(): Argument #1 ($string) must be of type string, array given这是正常的不影响比较结果。第一次看见报错别慌只要页面输出里带出了Flag就说明绕过成功了。5.2 不只是MD5PHP松散比较的其它“危险”场景0e绕过的本质是PHP松散比较里字符串转数值的行为这个坑不止存在于MD5比较里。比如0 foo在PHP 7里的结果也是true因为foo转数值失败时变成了0。再比如经典的0e1 0也会成立因为两边都转成了数值0。这类问题如果出现在登录判断、支付判断等场景里后果往往不只是拿Flag这么简单。做题的时候可以举一反三一旦在后端源码里看到连接的比较表达式就要下意识地思考参数的输入能不能被构造为特殊字符串或数组改变它的类型转换结果。这也是一个Web安全从业者最基本的代码审计直觉。5.3 真实开发里如何避免这类问题回到现实世界。虽然CTF题里的写法很“幼稚”但类似漏洞在真实项目里并不罕见尤其是早期用PHP写的业务系统里偶尔还是能看到这种代码if ($input $db_password) { ... }正确的修复方案有几条使用hash_equals()函数比较哈希值这是PHP官方提供的安全比较函数能避免时序攻击也杜绝了松散比较问题。全部使用严格比较MD5值先统一转小写再做字符串匹配。接收输入时做类型校验比如is_string($_GET[a])把数组类型直接挡在外面。参数绑定和类型转换要前置不要等进入比较逻辑时才暴露类型。放在更大的视角看这道题的延伸价值不只是帮你在CTF里拿分更重要的是帮你建立一种条件反射看到比较运算符先想类型看到请求头参数先想来源校验。实际的渗透测试里这种排查思路比背一百个Payload都管用。等到你自己去审计一套老PHP代码时会发现当年在Bugku上踩过的坑全都是经验。