前言

最近有一些小伙伴遇到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;}