前言

最近有一些小伙伴遇到md5类型题目求解,所以我打算收集一些相关题目做成一个小靶场分享给各位师傅来玩玩,并且自己动手也是十分有趣的一件事情,如果对各位师傅有用的话在github上给本萌新点一下star哟❤️
靶场地址:https://github.com/dota-st/md5bypass

writeup

预备技能

弱类型比较

弱类型比较,也叫松散比较。字符串和数字进行比较时,会把字符串强制转换为数字(如果字符串开头有数字,则转换为开头数字,没有则转换为0)

==:只比较数据值——>数据值相同,不比较数据类型
!=: 只比较数据值——>数据值不同

1
2
3
4
5
6
var_dump(123=="123a");
#bool(true)
var_dump(123=="1234a");
#bool(false)
var_dump(0=="abc");
#bool(true)

强类型比较

强类型比较,也叫严格比较。不仅要比较数据的值也要比较数据的类型,例如str和int两种数据类型就不会相等

===:比较数据类型——>数据类型相同 && 比较数据值——>数据值相同
!==:比较数据类型——>数据类型不同 || 比较数据值——>数据值不同

1
2
3
4
5
6
var_dump(123==="123a");
#bool(false)
var_dump(123==="123");
#bool(false)
var_dump(123===123);
#bool(true)

pass1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <?php
error_reporting(0);
highlight_file("pass-01.php");

if(isset($_GET["pass"])){
if($_GET["pass"] != hash("md4", $_GET["pass"])){
die('fail~~~');
}else{
echo "success!!!<br>";
echo file_get_contents('flag.txt')."\n";
echo "<a href='pass-02.php'>下一关</a>";
}
}else{
echo "please input the pass";
}
?>

考点是md4弱类型,注意观察,if判断中是用的!=也就是弱类型比较,我们可以使用科学计数法0e(需要纯数字)进行绕过,我们写一个脚本得到一个md4加密前和md4加密后都为纯数字的科学计数法表示的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
/*
* User: dota_st
* Date: 2021/2/5
*/
for ($i = 0; ; $i++) {
$r = "0e" . $i;
$md4 = hash("md4", $r);
if (preg_match("/^0e[0-9]*$/", $md4)) {
echo ("md4加密前:".$r)."\n";
echo("md4加密后:".$md4);
break;
}
}

运行后得到结果

md4加密前md4加密后
0e2512880190e874956163641961271069404332409

成功绕过

pass2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);
highlight_file("pass-02.php");

if(isset($_GET['user']) && isset($_GET['pass'])){
$user = $_GET['user'];
$pass = $_GET['pass'];

if($user != $pass && md5($user) == md5($pass)){
echo "success!<br>";
echo file_get_contents('flag.txt')."\n";
echo "<a href='pass-03.php'>下一关</a>";
}else{
echo "fail~~~";
}

}else{
echo "please input the user and pass!"."\n";
}
?>

考点是md5弱类型比较,注意看到==符号,关于md5函数的弱比较,有多种绕过手法

科学计数法绕过
列举几种脚本爆破出的类型

1
2
3
4
5
6
7
8
9
IHKFRNS 0e256160682445802696926137988570
QLTHNDT 0e405967825401955372549139051580
QNKCDZO 0e830400451993494058024219903391
3908336290 0e807624498959190415881248245271
4011627063 0e485805687034439905938362701775
4775635065 0e998212089946640967599450361168
0e215962017 0e291242476940776845150308577824
aabg7XSs 0e087386482136013740957780965295
aabC9RqS 0e041022518165728065344349536299


数组绕过
php中的md5()函数无法处理数组类型数据,对于数组类型数据返回NULL,当我们传入两个数组时,就会变成两个NULL,也就是NULL==NULL,成功绕过

md5碰撞

利用md5碰撞,得到两个md5值相同但内容完全不一样的字符串

1
2
3
一.%D89%A4%FD%14%EC%0EL%1A%FEG%ED%5B%D0%C0%7D%CAh%16%B4%DFl%08Z%FA%1DA%05i%29%C4%FF%80%11%14%E8jk5%0DK%DAa%FC%2B%DC%9F%95ab%D2%09P%A1%5D%12%3B%1ETZ%AA%92%16y%29%CC%7DV%3A%FF%B8e%7FK%D6%CD%1D%DF/a%DE%27%29%EF%08%FC%C0%15%D1%1B%14%C1LYy%B2%F9%88%DF%E2%5B%9E%7D%04c%B1%B0%AFj%1E%7Ch%B0%96%A7%E5U%EBn1q%CA%D0%8B%C7%1BSP

二.%D89%A4%FD%14%EC%0EL%1A%FEG%ED%5B%D0%C0%7D%CAh%164%DFl%08Z%FA%1DA%05i%29%C4%FF%80%11%14%E8jk5%0DK%DAa%FC%2B%5C%A0%95ab%D2%09P%A1%5D%12%3B%1ET%DA%AA%92%16y%29%CC%7DV%3A%FF%B8e%7FK%D6%CD%1D%DF/a%DE%27%29o%08%FC%C0%15%D1%1B%14%C1LYy%B2%F9%88%DF%E2%5B%9E%7D%04c%B1%B0%AFj%9E%7Bh%B0%96%A7%E5U%EBn1q%CA%D0%0B%C7%1BSP

pass3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);
highlight_file("pass-03.php");

if(isset($_GET['user']) && isset($_GET['pass'])){
$user = $_GET['user'];
$pass = $_GET['pass'];

if(!ctype_alpha($user) && !is_numeric($pass) && md5($user) == md5($pass)){
echo "success!<br>";
echo file_get_contents('flag.txt')."\n";
echo "<a href='pass-04.php'>下一关</a>";
}else{
echo "fail~~~";
}

}else{
echo "please input the user and pass!"."\n";
}
?>

这关还是==进行弱类型比较,但这次多了两个函数

ctype_alpha(): 用于检测是否只包含[A-Za-z]
is_numeric(): 用于检测变量是否为数字或数字字符串

我们可以采用科学计数法进行绕过,但一个需要全为数字,一个需要全为字母

1
2
3
4
数字
240610708 0e462097431906509019562988736854
字母
QNKCDZO 0e830400451993494058024219903391

pass4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 <?php
error_reporting(0);
highlight_file("pass-04.php");

if(isset($_GET['user']) && isset($_GET['pass'])){
$user = $_GET['user'];
$pass = $_GET['pass'];

if($user != $pass && md5($user) == md5(md5($pass))){
echo "success!<br>";
echo file_get_contents('flag.txt')."\n";
echo "<a href='pass-05.php'>下一关</a>";
}else{
echo "fail~~~";
}

}else{
echo "please input the user and pass!"."\n";
}
?>

这次还是==弱类型比较,但仔细观察,发现有一个变量md5()函数外还套了一层md5()也就是双层md5,我们可以采用数组形式进行绕过

1
2
3
4
分析逻辑:
md5(NULL)——>d41d8cd98f00b204e9800998ecf8427e

md5(md5(array()))——>md5(NULL)——>d41d8cd98f00b204e9800998ecf8427e

所以我们给user设为空,而pass给予一个数组类型数据

pass5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 <?php
error_reporting(0);
highlight_file("pass-05.php");

if(isset($_GET['user']) && isset($_GET['pass'])){
$user = $_GET['user'];
$pass = $_GET['pass'];

if($user != $pass && md5($user) === md5(md5($pass))){
echo "success!<br>";
echo file_get_contents('flag.txt')."\n";
echo "<a href='pass-06.php'>下一关</a>";
}else{
echo "fail~~~";
}

}else{
echo "please input the user and pass!"."\n";
}
?>

这次在上一关的基础上把==弱类型变成了===强类型比较,虽然是强类型比较,但我们null经过md5加密的值是相等的,所以依然可以使用数组的方法进行绕过

pass6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);
highlight_file("pass-06.php");

if(isset($_GET['user']) && isset($_GET['pass'])){
$user = (string)$_GET['user'];
$pass = (string)$_GET['pass'];

if($user != $pass && md5($user) == md5(md5($pass))){
echo "success!<br>";
echo file_get_contents('flag.txt')."\n";
echo "<a href='pass-07.php'>下一关</a>";
}else{
echo "fail~~~";
}

}else{
echo "please input the user and pass!"."\n";
}
?>

这关为==弱类型比较,但和前面不同的是对于传入的参数user和pass都加了(string)函数进行强制转换为字符串,所以我们采用科学计数法进行绕过,但需要注意的是,有双层md5,所以我们需要寻找第二层md5加密后依然值为科学计数法0e表示的数据
写一个多线程爆破脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Author: dota_st
# Date: 2021/2/6 17:15
# blog: www.wlhhlc.top
import string
import hashlib
import multiprocessing
def calc_md5(s):
md5 = hashlib.md5(s.encode("utf-8")).hexdigest()
md5_double = hashlib.md5(md5.encode("utf-8")).hexdigest()
if (md5_double[0:2] == "0e" and md5_double[2:].isdigit()):
print(s)


def getstr(payload, s, slen):
if (len(s) == slen):
calc_md5(s)
return s

for i in payload:
sl = s + i
getstr(payload, sl, slen)


if __name__ == '__main__':
for i in range(3, 40):
payload = string.ascii_letters + string.digits
getstr(payload, '', i)
cpus = multiprocessing.cpu_count()
stop_event = multiprocessing.Event()
processes = [multiprocessing.Process(target=getstr, args=(payload,
'', i))
for i in range(cpus)]
for p in processes:
p.start()
for p in processes:
p.join()

这里我只爆出四个就停止脚本了,有兴趣的师傅接着继续运行

1
2
3
4
aawBzC
aabsbm9
aaaabGG5T
aaaabKGVH

所以这里变量user我们就给单层md5加密值为科学计数法0e表示的字符串,pass给我们爆破出来双层MD5加密值为科学计数法表示的字符串

pass7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 <?php
error_reporting(0);
highlight_file("pass-07.php");

if(isset($_GET['user']) && isset($_GET['pass'])){
$user = $_GET['user'];
$pass = $_GET['pass'];

if($user !== $pass && md5($user) === md5($pass)){
echo "success!<br>";
echo file_get_contents('flag.txt')."\n";
echo "<a href='pass-08.php'>下一关</a>";
}else{
echo "fail~~~";
}

}else{
echo "please input the user and pass!"."\n";
}
?>

这次为===强类型比较,所以我们采用数组形式进行绕过,原理前面说过,这里不再赘述

pass8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 <?php
error_reporting(0);
highlight_file("pass-08.php");

class auth{
public $user;
public $pass;

public function __destruct(){

$this->user = (string)$this->user;
if(strlen($this->user) > 3 || strlen($this->pass) >3){
echo "level1 is fail~~~";
}
if($this->user !== $this->pass && $this->user != $this->pass && md5($this->user) === md5($this->pass)){
echo "success!<br>";
echo file_get_contents("flag.txt");
}else{
echo "level2 is fail~~~";
}
}
}
unserialize($_POST['auth']);
?>

1.传入的变量user和pass长度不能大于3
2.对传入的变量user强制转换为字符串,注意只是user
3.user和pass进行md5加密后强比较

这里我们采用php中两个特定数据类型值进行绕过,一个是INF,一个是NAN

INF具有无穷大的含义,而NAN是代表着一个在浮点数运算中未定义或不可表述的值。除了与True之外,拿NAN与其他任何值进行松散比较或者严格比较返回结果都是FALSE。
因为他们都是不确定的值,所以在与自身做比较时,会返回false

这里我们运行一下程序进行比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var_dump('INF'==INF);
#bool(false)

var_dump('INF'!==INF);
#bool(true)

var_dump(md5('INF')===md5(INF));
#bool(true)

echo md5('INF');
#9517fd0bf8faa655990a4dffe358e13e

echo md5(INF);
#9517fd0bf8faa655990a4dffe358e13e

进行序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

/*
* User: dota_st
* Date: 2021/2/6
*/

class auth
{
public $user;
public $pass;
}

$a = new auth();
$a->user = INF;
$a->pass = INF;
echo serialize($a);
#运行后得到O:4:"auth":2:{s:4:"user";d:INF;s:4:"pass";d:INF;}


题型补充

sha1强碰撞比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);

$flag=getenv('fllag');
if (isset($_GET['user']) and isset($_GET['pass']))
{
if ($_GET['user'] == $_GET['pass'])
echo 'no no no no way for you to do so.';
else if(is_array($_GET['user']) || is_array($_GET['pass']))
die('There is no way you can sneak me, young man!');
else if (sha1($_GET['user']) === sha1($_GET['pass'])){
echo "Hanzo:It is impossible only the tribe of Shimada can controle the dragon<br/>";
die('Genji:We will see again Hanzo'.$flag.'<br/>');
}
else
echo 'Wrong!';
}else
echo 'Just G1ve it a try.';
highlight_file(__FILE__);
?>

首先不能使用数组,其次是采用了===强比较,搜索后发现已存在 sha1 哈希碰撞,即payload为

1
2
3
user=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1

pass=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1

常用收集

md5

1
2
3
4
5
6
7

text MD5 Hash
0e215962017 ==> 0e291242476940776845150308577824
0e1284838308 ==> 0e708279691820928818722257405159
0e1137126905 ==> 0e291659922323405260514745084877
0e807097110 ==> 0e318093639164485566453180786895
0e730083352 ==> 0e870635875304277170259950255928

md4

1
2
3
4
         text	                                MD4 Hash
0e001233333333333334557778889 ==> 0e434041524824285414215559233446
0e00000111222333333666788888889 ==> 0e641853458593358523155449768529
0001235666666688888888888 ==> 0e832225036643258141969031181899

SHA1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
               text	                                         SHA1 Hash
0e00000000000000000000081614617300000000 ==> 0e65307525940999632287492285468259219070
0e00000000000000000000721902017120000000 ==> 0e94981159498252295418182453841140483274
0e01011001101011010001101110101100101000 ==> 0e48906523151976751117677463787111106598
0e11001000001010011000100000010001101000 ==> 0e63407184960930419027062777705081379452
0e01000001100000001010011011001000000100 ==> 0e55962072388397083814346733718698213796
0e10011110000101101000011101011010100100 ==> 0e31188585417285828785355336774237712792
0e01010111000111111010101011010111010100 ==> 0e45906344569616659428808892091261969181
0e00100001110000001111010000010011101100 ==> 0e14860258669052332549568607710438132953
0e11110000111010001001101111111110010010 ==> 0e12174258436385758552874426941686538483
0e10111110011100101100010101111010000110 ==> 0e99774398282593376043462038572281385389
0e11001111110111110010111010000011110110 ==> 0e63185221301034624940345471074357888797
0e00001010010101100100101011101110001110 ==> 0e90943988772171749054413593888105986782
0e01011101110010111011110010010010101110 ==> 0e01608800192659803576771346607441737826
0e10111110101111001000000100011101101110 ==> 0e49094541458479495263034294421025186938
0e11100111101110011010111001010101111110 ==> 0e55770706149948760086200713505841887543
0e11111001010101100110011001010001110001 ==> 0e91120687121163809515824135435029958137
0e01000111101111110010010010000001001001 ==> 0e78071797328546369301825420848872451849
0e00100110100010100110001101110110110101 ==> 0e06077380408260614659219920561082767632
0e11111100001011000011110100100010111101 ==> 0e12149120354415335220758399492713921588
0e00111100110101101001000101011011111101 ==> 0e38661126569790555598431905065403870516
0e10100011100101000001110010100110100011 ==> 0e55745078154640212511596259055910278070
0e10011110011111001001100100000111011011 ==> 0e20319731123101477913295720812414482217