0x00 前言
md5碰撞只是一种掌握php弱类型的方式,弱类型的内容有很多,数组、字符串比较等等,但不论以哪种方式考,涉及的知识点都是相通的,希望通过对基础知识的分享与大家一同学习进步。
0x01 什么是md5
“MD5,即消息摘要算法(英语:MD5 Message-Digest Algorithm)。是一种被广泛使用的密码散列函数,将数据(如一段文字)运算变为另一固定长度值,是散列算法的基础原理,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。
显然128位不足以把世界上所有消息的摘要毫不重复的计算出来,当然现在16字节(128位)、32字节(256位)的md5也都有,选择位数多的方式可以在一定程度上减少硬碰撞(collision)的可能性。
在php语言中调用中md5的使用方式为:
<?php
$str = "hello world!";
echo "str_md5-->",md5($str),"<br/>"; //为字符串计算摘要值
$files = "02.php";
echo "file_md5-->",md5_file($files);//为文件计算摘要值
?>
得到默认32字节的密文。
由于php中的md5默认返回32字节的结果,所以要得到16字节的需要使用字符串截取。
<?php
$str = "hello world!";
echo "32byte-->",md5($str),"<br/>"; //为字符串计算摘要值
$md5Str = substr(md5($str),8,16);//获取16字节的摘要值
echo "16byte-->",$md5Str;
?>
这是因为32字节的字符串中间8-24字节与16字节加密的结果是相同的,所以在php中我们可以通过这一方式来获得16字节的摘要值。
0x02 什么是php弱类型
php是一门弱类型的语言,它不会严格检验变量类型,变量可以不显示地声明其类型,而是在运行期间直接赋值。
在php中比较是否相等有两种符号:==
和===
。
其中==
在比较时会将不同类型的变量或值转换为相同类型再进行比较。
而===
则直接比较类型是否相同,如果同类型,再比较值。
那么php弱类型这里的“弱”,指的不是某个类型有什么问题,而是整个php语言中某些函数在处理赋值、字符串比较、变量比较的过程中对类型
似乎并不关心,弱化了类型带来的影响,在使用某个变量时不需要我们定义变量的类型,而是根据内容判断这是什么类型,从而导致了各种漏洞的发生。
例如:
<?php
var_dump("a"==0); //true
var_dump("1a"==1); //true
var_dump("a1"==1); //false
var_dump("a1"==0); //true
var_dump("0e123456"=="0e234567"); //true
var_dump(0=="1a"); //false
?>
其结果为:
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
上述判断输出的原因是,在php中当一个字符串被当作一个数值来取值时,如果该字符串没有包含'.','e','E'
,并且其数值值在整形的范围之内时,该字符串被当作int来取值,其他所有情况下都被作为float来取值,而该字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0。
所以在上面的情况中,1a
转换为1
,a1
转换为0
,而"0e123456"=="0e234567"
相互比较的时候,会将0e
这类字符串识别为科学技术法的数字,0的无论多少次方都是零,所以相等。
0x03 md5==绕过(0e比较)
<?php
$flag = 'ook!';
$a = $_GET['a'];
if ($a != 'QNKCDZO' && md5($a) == md5('QNKCDZO')) {
echo $flag;
}else{
echo('你的答案不对0.0');
}
上面这段代码中就是上述0e开头的所有字串都被认为是0,所以我们先看看md5('QNKCDZO')
的结果是0e830400451993494058024219903391
,那么所有0e开头的md5串都可以满足上面的条件。
常用的有:
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
包含了纯数字、纯字母、数字字母组合三种类型的结果,在没有条件限制的情况下,无论使用哪一个都可以成功绕过。在上面的代码中使用?a=240610708
,即可打印flag值。
0x04 md5===绕过(数组比较)
在php中的hash函数md5、sha1等处理中若传入一个数组的值,则会报错返回NULL
,而返回的值在类型和内容上都是相同的,所以可以用来绕过某些两边参数可控的场景,上面只能控制一边的值传入,所以数组类型不适用。
<?php
$flag = "ook!";
$a = $_GET['a'];
$b = $_GET['b'];
if ($a != $b && md5($a) === md5($b)) //这里==也可以使用数组绕过。
echo $flag;
?>
上述的例子中传入?a[]=a&b[]=b
即可满足既不相等,md5后又相等的条件,虽然报错,但仍然输出了正确的值。
Warning: md5() expects parameter 1 to be string, array given in 1.php on line 5
Warning: md5() expects parameter 1 to be string, array given in 1.php on line 5
ook!
0x05 md5===绕过(硬碰撞)
前面我们也提到了md5无论是32位还是16位,都不可能不重复的表示所有信息,这种重复的例子就称为 硬碰撞 ,有如下代码:
<?php
$s1 = $_GET['a'];
$s2 = $_GET['b'];
$s3 = $_GET['c'];
echo md5($s1),"<br/>";
echo md5($s2),"<br/>";
echo md5($s3),"<br/>";
?>
当传入的值为
?a=%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab
&b=%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%5f%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%f3%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%e9%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%13%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%a8%1b%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%39%05%39%95%ab
&c=%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%ed%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%a7%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%e6%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%16%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%33%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%6f%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab
三个返回相同的md5值,可以通过其有限计算的特性绕过特定的条件。
ea8b4156874b91a4ef00c5ca3e4a4a34
ea8b4156874b91a4ef00c5ca3e4a4a34
ea8b4156874b91a4ef00c5ca3e4a4a34
0x06 json解码绕过
php在处理传入的json串时使用json_decode
将其解码,再进行比较时,我们不需要知道比较字串的内容,也可以利用字符串与0比较为真的特点绕过。
<?php
$flag = 'ook!';
$a = $_GET['a'];
$b = json_decode($a);
echo $b->abc;
var_dump($b->abc == $flag);
if ($b->abc == $flag)
echo $flag;
else
echo "error!!";
?>
当传入?a={"abc":0}
时,分别输出
0
bool(true)
ook!
注意这里的{"abc":0}
,0是数字,而加双引号{"abc":"0"}
之后两边都是字符,就不相等了。
0x07 array_search 绕过
原理都是类型转换的问题,函数的原型为:
mixed array_search ( mixed $needle , array $haystack [, bool $strict = false ] )
其中$needle,$haystack
必需,$strict
可选 函数判断$haystack
中的值是存在$needle
,存在则返回该值的键值(数组的下标,例如该值在第一位返回0,第二位返回1),第三个参数默认为false,如果设置为true则会进行严格过滤(带类型的比较)。
<?php
$a = array(0,1,2,3);
var_dump(array_search("a",$a));
var_dump(array_search("1a",$a));
var_dump(array_search("2a",$a));
var_dump(array_search("1a",$a,true));
?>
其结果为:
int(0)
int(1)
int(2)
bool(false)
0x08 小结
从上面我们可以看出,所有绕过的形式都是基于弱类型的比较,或值对于不合规参数类型的错误处理,数组与字符串、字符串与整数等等情形,我们不能保证每个用户都乖乖输入我们想要的值,因此在php中限定用户输入的类型和值就显得尤为重要。
这里感谢 Mrsm1th 师傅的分享,自己动手接受知识会更快。