MD5和sha1绕过总结
弱比较类型
php中的等比较运算符有两种,
==和===。==:先将左右两边类型转化成一致,再进行比较值是否一致===:先比较左右类型是否一致,若一致再比较值是否一致
字符串与数字比较
1
2
3var_dump('a' == 0); //bool(true)
var_dump('1a' == 1); //bool(true)
var_dump('12a' == 1); //bool(false)字符串在和数字比较的时候会将字符串转化为数字,如果字符串是以数字开头的,则会转换成数字再做比较,纯字符串则转换失败成false
例:
1
2
3
4
5
6
7
8
9$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}构造payload:
1
?key=123
布尔值与任意值比较
1
2
3var_dump(True == 0); //bool(false)
var_dump(True == 'False'); //bool(true)
var_dump(True == 2); //bool(true)hash值和字符串“0”比较
因为当hash开头为0e后全为数字的话,进行比较时就会将其当做科学计数法来计算,用计算出的结果来进行比较。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16$str1 = "a";
echo md5($str1); //0cc175b9c0f1b6a831c399e269772661
var_dump(md5($str1) == '0'); //bool(false)
$str2 = "s224534898e";
echo md5($str2); //0e420233178946742799316739797882
var_dump(md5($str2) == '0'); //bool(true)
$str3 = 'a1b2edaced';
echo md5($str3); //0e45ea817f33691a3dd1f46af81166c4bool
var_dump(md5($str3) == '0'); //bool(false)
var_dump('0e111111111111' == '0'); //bool(true)第二、四条由于0e后全为数字,按科学计数法即0乘e的幂次方为0,所以比较为true
绕过
strcmp函数绕过
1 |
|
strcmp(string1,string2),该函数返回:
0 - 如果两个字符串相等
<0 - 如果 string1 小于 string2
>0 - 如果 string1 大于 string2
对于传入非字符串类型的数据的时候,strcmp函数会报错,将返回0。所以,strcmp()在比较字符串和数组的时候直接返回0,这样通过把目标变量设置成数组就可以绕过该函数的限制,构建payload:
1 | ?a[]=1 |
MD5/sha1弱类型绕过
0e绕过
弱比较会把0exxxx当做科学计数法,不管后面的值为任何东西,0的任何次幂都为0
一些字符串md5值以0e开头:
1
2
3
4
5
6QNKCDZO
240610708
s878926199a
s155964671a
s21587387a
0e215962017sha1值以0e开头:
1
2
3
4
5
610932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729
0e1290633704: 0e19985187802402577070739524195726831799数组绕过
md5()函数计算的是一个字符串的哈希值,对于数组则返回false,如果是
md5((string)$apple) == md5((string)$banana))则不能用数组,因为转换成字符串后都为NULL
MD5/sha1强类型绕过
因为强类型比较,不仅比较值,还比较类型,0e会被当做字符串,所以不能用0e来进行
但是我们可以用MD值完全相同的字符来进行绕过。
数组绕过
方法同上
MD5强碰撞
即找到两个不同输入产生相同哈希值的过程。
如:
1
2a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a21
2a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A21
2a=0e306561559aa787d00bc6f70bbdfe3404cf03659e704f8534c00ffb659c4c8740cc942feb2da115a3f4155cbb8607497386656d7d1f34a42059d78f5a8dd1ef
b=0e306561559aa787d00bc6f70bbdfe3404cf03659e744f8534c00ffb659c4c8740cc942feb2da115a3f415dcbb8607497386656d7d1f34a42059d78f5a8dd1ef
特定条件下的MD验证绕过:ffifdyop
1 | Select * from ’admin’ where password=md5($pass,true) |
即MD5报文将以原始 16字符二进制格式返回
ffifdyop 字符串经过MD5加密后为276f722736c95d99e921722cf9ed621c
再转换成字符串为’or’6乱码
Select * from ’admin’ where password=‘or’6乱码 相当于万能密码
extract变量覆盖
1 |
|
extract():函数从数组中将变量导入到当前的符号表
trim(string[charlist]):移除字符串两侧的空白字符或其他预定义字符,若省略后面一个参数,则去除\0、\t、\n、\x0B、\r和空格。
运用extract()将GET方式获得的变量导入到当前的符号表中,然后判断$flag和$shiyan两个变量的内容是否相等。那么我们将$flag和$shiyan这两个变量的内容都会被设置成空字符串。构建payload:
1 | Payload:?flag=&shiyan= |
限制传入的匹配
1 |
|
ereg():限制传入内容,例如上面就是限制了只能传入数字以及大小写字母。
strpos():查找字符串在另一字符串中第一次出现的位置。
对传入进行限制,但是又要求密码中含有–,因此我们这里有两种绕过方法:
数组法
同样strpos()如果传入数组,会返回NULL,从而绕过对
--的检测,构建payload:1
?password[]=1
截断法
在
%00后的函数无法识别,因此构建payload:1
?password=1%00--
JSON
1 |
|
输入一个数组进行json解码,解码后的message与key值相同才会得到flag,使用弱类型进行绕过,key肯定是字符串,两个等号时会转化成同一类型再进行比较,直接构造一个0就可以相等了,通过0==”admin”这种形式绕过,构建payload:
1 | ?message={"key":0} |
长度限制绕过
1 |
|
要求输入password的长度小于8位,且值要大于9999999,并且需要匹配到-,这里就要用到科学计数法了:
- 1e10 = 10^10
而匹配-可用截断法,所以构造payload:
1 | ?password=1e10%00- |
urldecode二次编码绕过
1 |
|
使用GET传参时,浏览器就已经把hakerDJ进行了一次解码了,然后又用了urldecode函数又再次进行了一次解码。所以我们要将hakerDJ进行二次编码:
1 | Payload: ?id=%25%36%38%25%36%31%25%36%33%25%36%42%25%36%35%25%37%32%25%34%34%25%34%41 |
$a==md5($a),md5($a)==md5(md5($a))
0e215962017 的 MD5 值也是由 0e 开头,在 PHP 弱类型比较中相等:
1 | $md5 md5($md5) |
爆破脚本:
1 | # -*- coding: utf8 -*- |
MD5和双MD5都是0e开头的:
1 | CbDLytmyGm2xQyaLNhWn |
哈希长度扩展攻击
概念
哈希长度扩展攻击,简单的说就是由于hash的生成机制使得我们可以人为的在原先明文基础上添加新的拓展字符,从而使得原本的加密链变长,进一步控制加密链的最后一节,使得我们得以控制最终的结果。
为了更好的解释该原理,下面以MD5算法举个例子
MD5加密
MD5
MD5算法是典型的一种信息摘要算法,它是由md2、md3和md4演变而来的。无论是哪一种的md算法都是将一个任意长度的字符串加密为一串固定长度的密文。在这整个加密过程中,会将明文字符串转换为一个128位的消息摘要,接直把消息摘要转换为一个十六进制的字符串就会得到32位的字符串,也就是我们平时见到的MD5密文
因为MD5加密过程经过了压缩,加密和hash算法,所以MD5加密的内容是不可逆的
MD5算法过程
要了解算法的原理过程,肯定需要一个流程图便于理解:
大致步骤为:
- 把消息分为n个分组
- 对最后一个消息分组进行填充
- 和输入量进行运算,运算结果位下一个分组的输入量
- 输出最终结果
下面举个例子好理解一点:
比如我们需要对1234567890123456进行加密,首先我们需要将它转化为二进制,即:
1 | #转换结果 00110001 00110010 00110011 00110100 00110101 00110110 00110111 00111000 00111001 00110000 00110001 00110010 00110011 00110100 00110101 00110110 |
因为一个ASCIl码对应8位二进制字符,所以我们得到了128位二进制字符,下面我们在winhex(需要转为十六进制)上观察:
对于MD5算法来说,我们需要将原数据进行分块处理,以512位个二进制数据为一块,而最后一块的处理分为以下两种情况:
- 明文数据的二进制数据长度<=448,通过填充padding(无意义占位)数据使其长度为448,再添加原始明文数据的二进制长度信息(64)使其长度为512位即可。
- 明文数据的二进制数据长度大于448但小于等于512,填充padding数据至下一块的448位,而后再添加原始明文数据的二进制长度信息(64位)使其长度为512位即可。
对于padding数据(长度不定)来说:首位二进制位1,其余位为0.
对于我们的数据只有128位二进制字符,所以我们需要填充到448位,又因为**10000000(2)=80(16),**补充上去即可:
对于长度信息位(长度8Byte=64bit)来说,我们还需补位8byte,从低位走向高位,原始明文为1234567890123456共16byte,有16*8=128bit,转换为十六进制为80,写入倒数第八个byte位,之后补7byte的0x00:
接下来需要用这64byte的数据进行计算,与初始向量进行计算
计算信息的摘要需要用补位结果的数据进行运算,也就是补位后的512bit的消息,在计算时候有个初始的向量,这里初始的向量是一个固定的值:
1 | A 01 23 45 67 0x67452301 |
由于在计算机存储中采用的是小端存储方式,所以上面初始化向量在程序中的初始化代码为后面的0x部分。
然后将刚才的512bit消息和初始化向量进行第一轮的运算,之后初始化向量会被新的值覆盖,最后一轮的向量经过高低位互换后就是计算出的MD5值。
1 | 高低位互换: |
高低位互换的过程如下:
1 | 假如最后一轮的运算后的向量值为: |
进行拼接得到最后加密结果12efcdab12efcdab12efcdab12efcdab
攻击工具
上面我们也了解到了MD5的加密过程,而我们只要知道一个hash值,知道原来数据的数据长度,那么我们就可以算出原数据+填充数据到512+任意内容的hash值。
而有些ctf就是需要我们找填充的数据及填充后的hash值,当然我们不可能手搓,下面是关于该攻击的一个脚本
脚本
1 | # -*- coding: utf-8 -*- |
项目
hashpump
hashpump是一个专门生成MD5长度拓展攻击payload的工具。
1 | Github仓库:https://github.com/bwall/HashPump |
现在被作者删了找不到了
hash-ext-attack
推荐中文项目:https://github.com/shellfeel/hash-ext-attack
MD5前缀构造法
工具:fastcoll
原理是构造两个不一样的字符串,但是MD5是一样
通过构造前缀碰撞法”(chosen-prefix collisions)来进行哈希碰撞(也可以构造两个MD5相同的文件):
1 | fastcoll_v1.0.0.5.exe -p 2.pdf -o 3.pdf 4.pdf |
文件内的内容为你想构造的前缀
输出两个txt文本后:在php环境中运行Md5collision.php
可以看到,二进制的hash一样。 但是实际内容不一样。
Md5collision.php
1 |
|
可以看到前面相同的内容是我们构造的前缀,最后形成了两个字符串不同但二进制哈希值相同的字符串
适用题型
适用于字符串拼接类型,如:
1 |
|
NAN和INF
NAN和INF,分别为非数值和无穷大。当一个运算无法计算结果时,就会产生NAN,可以通过0/0产生;而当数值超过PHP_FLOAT_MAX或者使用一些数学函数产生极大或极小的结果时,就会得到INF。
但是var_dump它们数据类型却是double
那么在MD5函数处理他们时,是将其直接转化为字符串”NAN”和字符串”INF”使用的,但他们有个特殊的性质,他们与任何数据类型(除了true)做强类型或弱类型比较均为false,甚至NAN===NAN都是false(INF===INF为true),但md5('NAN')===md5('NAN')和md5('INF')===md5('INF')为true.
1 |
|
截断比较
md5截断爆破
1 | substr(md5(?),0,5)==='8ffb1' |
爆破脚本:
1 | import hashlib |
sha256截断爆破
1 | substr(sha256(?),0,5)==='8ffb1' |
爆破脚本:
1 | import hashlib |












