第二十九天(PHP 弱类型脆弱&Hash加密&Bool类型&Array数组&函数转换比较)

1、== 和 ===

两个等号==是弱比较,使用==进行对比的时候,php解析器就会做隐式类型转换,如果两个值的类型不相等就会把两个值的类型转为同一类型进行对比。

例子:

var_dump(0 == 'pay');//true
var_dump('0e123456789'==0);// bool(true)
var_dump('0e123456789'=='0');// bool(true)
var_dump('0e1234abcde'=='0');// bool(false)
var_dump('1e123456fwe'==0);// bool(false)
var_dump('0e12345fwe'==0);// bool(true)

PHP 在使用 ==(松散比较)时,会尝试将字符串转换为数值类型后再比较。但这种转换有严格规则:

1.只有当字符串完全符合 " 科学计数法格式"(如 0e123、1.2e3)时,才会被解析为数值(浮点数)。

2.格式要求:字符串必须以数字开头,包含 e 或 E,且 e 前后必须是有效的数字(整数或小数)。

3.当两种不同类型之间比较时,如果不满足科学计数法格式,字符串就会转换成0后跟等号后的数字对比

不同类型时会执行下面规则:

字符串转整数的规则是:

1.仅提取字符串开头的有效数字部分;后面字母部分会被去掉,字母后面的数字也不会计入一并去掉

2.如果字符串完全不包含数字(或开头不是数字),则转换结果为 0

2、MD5对比缺陷

进行hash加密出来的字符串如存在0e开头进行弱比较的话会直接判定为true

QNKCDZO

0e830400451993494058024219903391

240610708

0e462097431906509019562988736854

s878926199a

0e545993274517709034328855841020

s155964671a

0e342768416822451524974117254469

s214587387a

0e848240448830537924465865611904

s214587387a

0e848240448830537924465865611904

s878926199a

0e545993274517709034328855841020

s1091221200a

0e940624217856561557816327384675

s1885207154a

0e509367213418206700842008763514

当密码的加密值以0e开头,如果用相同的加密方式加密出来的值也是以oe开头,那么也可以登录进去

例如:

密码:s1885207154a

md5加密后:0e509367213418206700842008763514

a = _GET['pwd'];
password = "0e50936721341820084200876514"; *//**注意:**这里管理员密码md5**的值是以0e**开头的,**如果没有看到0e**而直接去解md5**九成是解不出来的* **if**(md5(a) == $password){ *//**注意:**这里是两个等号"=="**进行判断,*若是"==="则不存在弱类型hash比较缺陷

echo 'you are logined!';

}else{

echo 'fuck';

}

当以同样是oe开头的值时,可以成功进入

当密码的加密值以0e开头并且后面都是数字时,就是符合科学计数法时;用不同类型比较时,直接输入一个0,也能进入 (需要前提条件较多,实战可能遇不上)

a = _GET['pwd'];

$password = "0e50936721341820084200876514"; //注意:这里管理员密码md5的值是以0e开头的,如果没有看到0e而直接去解md5九成是解不出来的

if((int)md5(a) == password){ //注意:这里是两个等号"=="进行判断,若是"==="则不存在弱类型hash比较缺陷

echo 'you are logined!';

}else{

echo 'fuck';

}

3 函数strcmp类型比较缺陷

低版本的strcmp比较的是字符串类型,如果强行传入其他类型参数,会出错,出错后返回值0,正是利用这点进行绕过

  • PHP 7.3 及以下
    输出警告Warning: strcmp() expects parameter 1 to be string, array given,但仍会执行判断逻辑(如果$password是"Array",则输出成功信息)。
  • PHP 8.0 及以上
    直接抛出致命错误:
    Fatal error: Uncaught TypeError: strcmp(): Argument #1 ($string1) must be of the type string, array given
    脚本终止,不会输出任何成功 / 失败信息。

代码:

password="\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*"; **if**(**isset**(_GET['password'])) {

if (strcmp(_GET\['password'\], password) == 0) {

echo "Right!!!login success";

exit();

} else {

echo "Wrong password..";

}

}

输入错误密码测试: 判断错误 输出 wrong password

输入正确密码:判断成功 输出: Right!!!login success

当将输入的类型改成数组时,低版本可以绕过,高版本会报错

低版本:

高版本: php8版本

4、函数Bool类型比较缺陷

在使用 json_decode() 函数或 unserialize() 函数时,部分结构被解释成 bool 类型,也会造成缺陷,运行结果超出研发人员的预期

json_decode() 函数

代码:

str=_GET['s'];
data = json_decode(str,true);
if (data\['user'\] == 'xiaodi' \&\& data['pass']=='xiaodisec')

{

print_r(' 登录成功! '."\n");

}else{

print_r(' 登录失败! '."\n");

}

正常提交数据 是 s={"user":"xiaodi","pass":"xiaodisec"}

验证逻辑是将用户输入的值和==后面的值对比,同时为真则进入if,否则返回登录失败

当输入这个值为这个时 , s= {"user":true,"pass":true} 也能通过if判断

原因: 输入的两个值都是true 是一个bool类型的值;当判断时就是 :true == "xiaodi" && true =="xiaodisec" 非空字符串都会被转换为 true 那么 true ==true成立 从而实现绕过

unserialize() 函数

str=_GET['s'];
data = unserialize(str);
if (data\['user'\] == 'root' \&\& data['pass']=='xiaodi')

{

print_r(' 登录成功! '."\n");

} else{

print_r(' 登录失败! '."\n");

}

a 是参数 ;2是 提交数量 ;s 代表string类型 ;4 代表字符个数 ;b 代表 bool 类型

预期提交格式:a:2:{s:4:"user";s:4:"root";s:4:"pass";s:6:"xiaodi";}

绕过:$str = 'a:2:{s:4:"user";b:1;s:4:"pass";b:1;}';

正常登录

绕过登录 用 bool 类型的值替换 用户输入的值root ,将bool值 设置为 1 ,bool值为1 时 是true,这里后面的验证和上面一致

5、函数switch 类型比较缺陷

当在switch中使用case判断数字时,switch会将参数转换为int类型计算

代码:

num =_GET['n'];
switch ($num) {

case 0:

echo "say none hacker ! ";

break;

case 1:

echo "say one hacker ! ";

break;

case 2:

echo "say two hacker ! ";

break;

default;

echo "I don't know ! ";

}

正常输入值:

在正确值后面输入字符串 ,也可以输出

6、函数in_array数组比较缺陷

当使用in_array()或array_search()函数时,如果第三个参数没有设置为true,则in_array()或array_search()将使用松散比较来判断

代码:

array=\[0,1,2,'3'\]; var_dump(in_array('abc', array));//true
var_dump(in_array('3gege', array));*//false* var_dump(array_search('abc', array));//0:*下标
var_dump(in_array('1dsdsdsbc', array));*//true* var_dump(array_search('1bc', array));
//1:*下标
var_dump(in_array('1dsdsdsbc', $array,true));//false

当没设置第三个参数值为true时, 验证方式和== 是一样的,采用弱比较方式,所以会有漏洞

7、===数组比较缺陷

注意此时遇到的是 "===" ,不过也不是代表无从下手。在md5()函数传入数组时会报错返回NULL,当变量都导致报错返回NULL时就能使使得条件成立。

代码: 获取flag值

验证逻辑 : 当输入的username 值和password 值一样时,输出 Your password can not be your username.

当输入的值不一样时 ,输出 Invalid password

想要获取flag 值既要不让他们相等,又要让他们加密后的值相等

error_reporting(0);
flag = 'flag{test}'; **if** (**isset**(_GET['username']) and isset($_GET['password'])) {

if (_GET\['username'\] == _GET['password'])

print 'Your password can not be your username.';

else if (md5(_GET\['username'\]) === md5(_GET['password']))

die('Flag: '.$flag);

else

print 'Invalid password';

}

当传输的数据为数组是md5函数就会报错返回一个null值,当两边都为null时 ,条件成立,拿到flag值

案例:

CTF题目:

矛盾 - Bugku CTF平台

逻辑:

!is_numeric(num) 是一个逻辑判断表达式,用于检查变量 num 是否不是数字或数字字符串

num 值还要等于1

就是输入值既要是一个字符串,还要在判断中等于1

MD5 - Bugku CTF平台

逻辑:

对一个密码进行了md5加密,然后又对输入的密码进行md5值加密, 然后输入值不能等于md51未加密前的值,还要让md5加密后的两个值相等

QNKCDZO 加密后是以0e开后,所以 只要拿一个同样以0e开头的值就可以进入逻辑拿到flag值

代码审CMS:

某Info CMS代码审计