0.题前提示:
PHP中的md5怎么绕过呢?
我们已经知道,PHP是一种编程语言,它和C程序有着相似的语法。
md5是什么?
MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),是一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。
1. Level 1
我们点击“创建实例”,可以看到如下界面:
我们同时把完整的代码放在这里:
<?php
include("flag.php");
@error_reporting(1);
if(isset($_GET['a'])&&isset($_GET['b'])){
$a = $_GET['a'];
$b = $_GET['b'];
}
else{
highlight_file(__FILE__);
die("Please GET a and b");
}
if( $a!==$b && $a==$b ){
echo "level 1 complete\n";
}
else{
die('sorry, error');
}
if(isset($_GET['md1'])&&isset($_GET['md2'])){
$md1 = $_GET['md1'];
$md2 = $_GET['md2'];
}
else{
die("Please GET md1 and md2");
}
if( $md1 !== $md2 && md5($md1) == md5($md2) ){
echo "level 2 complete\n";
}
else{
die("NO No no ! ! !");
}
if(isset($_POST['md1'])&&isset($_POST['md2'])){
$md1 = $_POST['md1'];
$md2 = $_POST['md2'];
}
if( $md1 !== $md2 && md5($md1) === md5($md2) ){
echo "level 3 complete\n";
echo $flag; #这里的flag变量是包含在flag.php文件里的, 所以用highlight_file(__FILE__)看不到flag的内容是什么
}
else{
die("try again!");
}
?>
Please GET a and b
我们尝试解读这个代码:
if(isset($_GET['a'])&&isset($_GET['b'])){
$a = $_GET['a'];
$b = $_GET['b'];
}
我们来看这一段:
if(isset($_GET['a'])&&isset($_GET['b'])){
isset() 函数:这是 PHP 中的一个内置函数,用于检查变量是否已被设置并且非 null。在这里,它被用来分别检查 $_GET[‘a’] 和 KaTeX parse error: Expected 'EOF', got '&' at position 24: …] 是否存在且有值。 逻辑与(&̲&)操作符:表示只有当 iss…_GET[‘a’]) 和 isset($_GET[‘b’]) 这两个条件同时为真(即 $_GET 中同时存在名为 a 和 b 的参数且都不为 null)时,才会进入 if 语句块中执行后续的代码。
也就是说:这一段的目的是判断a,b两个值是否存在。
再来看if语句内的片段:
$a = $_GET['a'];
$b = $_GET['b'];
这里是将通过 GET 方式传递过来、且已经经过前面 isset()函数检查存在的参数值,分别赋给了两个普通的变量 $a 和 $b。
我们根据同样的道理解释接下来的几段:
else{
highlight_file(__FILE__);
die("Please GET a and b");
}
这一段是表明 $a,$b不存在的情况:程序会高亮显示FILE文件,同时返回Please GET a and b
的信息。
if( $a!==$b && $a==$b ){
echo "level 1 complete\n";
}
else{
die('sorry, error');
}
这块代码的意思是:如果$a变量与$b变量非全等且相等,就输出“level 1 complete”的信息。在这里,就是让我们输入数据类型不同且值相同的两个数,并使用GET方法将它们传递进去。
要读懂这段代码(同时也是做出这道题的核心技能),我们需要了解PHP代码中的几种“等于”。(也就是关系运算符)。
我们不做具体介绍,只简单指出来代码中各种关系运算符的含义:
!==(非全等比较运算符)
作用:该运算符用于比较两个值,会同时比较值和数据类型是否完全相同。只有当两个操作数的值相等且数据类型也一样时,才返回 false;只要值不同或者数据类型不同(或者两者都不同),就返回 true。
“==”(弱相等比较运算符)
作用:比较两个操作数的值是否相等,在比较过程中会进行自动的类型转换。例如,如果一个操作数是字符串类型的数字 “5”,另一个是整数类型的 5,使用 == 比较时会将字符串 “5” 自动转换为整数 5 后再比较,此时结果为 true。
“===”(强等于比较运算符)
作用:与“ !==” 相对应,用于判断两个操作数的值以及数据类型是否完全相等。只有值相等且数据类型也相同的情况下才返回 true,否则返回 false。
由此,我们可以直接做出第一步:向网站后面加上 “/?a=1.0&b=1”。这个方法向网站传递了两个数据类型不同(浮点型和整型)但值相同(均为1)的两个参数。
我们得到了这样的一句话:
level 1 complete Please GET md1 and md2
2. Level 2
我们继续解读这个程序。
if(isset($_GET['md1'])&&isset($_GET['md2'])){
$md1 = $_GET['md1'];
$md2 = $_GET['md2'];
}
同上,这里要求我们传入两个名为$md1和$md2的参数。
if( $md1 !== $md2 && md5($md1) == md5($md2) ){
echo "level 2 complete\n";
}
else{
die("NO No no ! ! !");
}
这里的结构同上面类似,不同的是:判断相等的部分变成了
md5($md1) == md5($md2)
。
这是什么意思?
在“题前介绍”中,我们已经知道,MD5算法可以产生出一个128位的散列值。所以这里的实际含义是:我们要输入值不同或者数据类型不同的两个变量,而且确保它们两个的散列值相等。
这是一个很复杂的任务。我们在这里介绍一种CTF竞赛中常用的手段:MD5弱绕过。
当传入的值特别大时,php语言默认会使用科学计数法存储它们。因此,当我们输入两个变量的MD5散列值十分接近时,它们被科学计数法简化后就会被判定为相等。
例如,$a=QNKCDZO,$b=240610708时:
$a的MD5散列值为0e830400451993494058024219903391;
$b的MD5散列值为0e462097431906509019562988736854;
我们注意到,这两个散列值的开头都是0e。也就是说,它们在被存储时,会首先被运算成md5(0)
,再进行计算。
我们遇到“==”(弱等于)时,一般就采用这个方法,构造0e开头的散列值,从而实现绕过。
因此,我们输入 “/?a=1.0&b=1&md1=QNKCDZO&md2=240610708”(为何要输入“/?a=1.0&b=1”?因为程序是从上往下依次执行的。我们必须满足上述的所有条件。)
我们得到了这句话:
level 1 complete level 2 complete try again!
3. Level 3
我们继续解读程序:
if( $md1 !== $md2 && md5($md1) === md5($md2) ){
echo "level 3 complete\n";
echo $flag; #这里的flag变量是包含在flag.php文件里的, 所以用highlight_file(__FILE__)看不到flag的内容是什么
}
else{
die("try again!");
}
我们注意到:先前的弱等于变成了强等于。这意味着,我们需要输入MD5散列值以及数据类型(这里不考虑)完全相等的两个值,并且它们的值不同或者数据类型不同!
这让我们犯了难:MD5的本意就是要保证信息传输完整一致。怎么会存在值不同而MD5散列值相同的数据呢?
我们再介绍一种方法:数组绕过。这种方法并非针对MD5算法,而是针对PHP语言本身。
如果PHP语言的版本低于8,我们可以利用一个漏洞:当传入的参数是一个数组时,md5()函数 会返回NULL(空) 值。而众所周知:NULL===NULL。
我们直接输入
/?a=1.0&b=1&md1[1]=1&md2[1]=2
这里传入了md1[],md2[]两个数组。这两个数组内的值有着相同的数据类型,而值不同。同时,它们的MD5散列值都是NULL。
我们最终得到了这样一行字:
level 1 complete level 2 complete level 3 complete Redrock{5c7c0765-072b-4189-88e3-55177c66c84b}
所以flag就是:
Redrock{5c7c0765-072b-4189-88e3-55177c66c84b}
学到了什么?
- MD5算法是什么;
- 简单解读PHP代码;
- 几种PHP关系运算符的含义;
- 利用PHP语言漏洞实现0e绕过与数组绕过。
事实上,不只是PHP语言的漏洞。在这个算法公开的4年后,该就算法被证实存在弱点,可以被加以破解;现在,MD5算法已经SHA-256、SHA-3、Blake2和SM3等哈希算法取代。
更多有关于MD5漏洞的信息可以参考这个帖子。