PHP弱类型比较是CTF比赛中的常见考点,当使用==进行比较时,类型转换可能导致非预期的结果。
如0 == "abc"会返回false,但"0e12345" == "0e67890"会被认为是相等的,因为都被视为科学计数法的0。另外,像true和任何非空字符串比较可能为真,如true == "non-empty string"是true。
还有MD5和SHA1的弱碰撞。如两个不同的字符串经过哈希后以0e开头,比较时会视为相等。例如,MD5('240610708')是0e462097431906509019562988736854,和MD5('QNKCDZO')的结果类似,这样比较时会相等。
switch和in_array的松散比较。当switch使用松散类型时,比如字符串和数字比较,比如case 0会匹配字符串"abc",因为字符串转换为数字是0。而in_array默认不开启严格模式,所以可能被绕过。比如查找字符串"abc"在数组中是否有0的情况,因为类型转换后可能匹配。
还有strcmp和数组的漏洞。如果strcmp的参数是数组,会返回0,比如strcmp($_GET['password'], 'abc'),如果传入password[]=x,就会导致返回0,从而绕过检查。同样,md5(数组)会返回null,如果比较md5($_GET['a']) == md5($_GET['b']),传入数组的话,两边都为null,从而相等。
JSON解析中的问题,比如json_decode解析数字字符串为整数,可能导致类型比较问题。例如,如果后端期望的是字符串类型的值,但传入的是数字,可能被误判。例如,{"num": "123"}和{"num": 123}在PHP中会被视为不同,但如果处理不当,可能被利用。
还有is_numeric的问题,比如字符串中包含十六进制或科学计数法会被认为是数字,可能导致注入或溢出。例如,0x61646d696e会被当作数字,转换为十进制后是admin的ASCII值,可能用于绕过检查。
另外,switch的松散比较和类型转换,比如当switch传入的是字符串,而case是数字时,可能导致非预期的分支执行。比如传入"1abc",转换为整数1,会匹配case 1。
还有关于===的严格比较和==的区别,在CTF中常被用来设计陷阱,比如要求使用==时可以通过类型转换绕过,而===则需要完全匹配。
还有数组和字符串的转换,比如当数组被当作字符串使用时,会变成"Array",而某些情况下可能被利用,比如在拼接SQL查询时,但可能这个点更多是注入相关,但弱类型可能也有涉及。
还有关于NULL和空字符串的比较,比如使用==时,NULL == "" 为true,而===则为false。这可能在某些条件判断中被利用。
另外,可能涉及十六进制和八进制的字符串转换,比如"0x1a"被转换为26,或者"0123"被当作八进制,但PHP中可能在某些函数处理时需要注意。
使用sha1处理数组返回false,如果比较sha1($a) == sha1($b),当$a和$b都是数组时,两边都是false,从而相等。或者使用===比较时,两边都是false,但题目可能设计成松散比较,导致false == 0或者其他情况。
还有,当变量被用来进行数学运算时,自动转换为整数或浮点数,可能造成溢出或截断,比如大数字字符串转换为整数时变成负数,或者科学计数法表示,如"1e3"被转换为1000。
另外,在正则表达式匹配时,使用弱类型可能导致绕过,比如使用^0在PHP中可能因为类型转换导致匹配失败。
可能还需要考虑强制类型转换,比如在条件判断中使用加法或其他运算符,导致类型转换,例如"123abc" + 0得到123,从而绕过某些检查。
总结下来,常见的考点应该包括:
1. 松散比较(==)的类型转换问题,包括科学计数法、字符串转数字等。
2. 哈希比较中的0e开头字符串。
3. switch和in_array的松散比较。
4. strcmp等函数处理数组时的漏洞。
5. JSON解析中的类型混淆。
6. is_numeric的检测问题(十六进制、科学计数法)。
7. 类型转换在条件判断或数学运算中的副作用。
8. 某些函数返回值的类型问题,如md5数组返回null。
9. 十六进制和八进制的转换问题。
10. 使用恒等(===)和相等(==)运算符的不同。
可能还要注意PHP版本的不同,比如某些行为在PHP5和PHP7中的差异,例如在PHP7中,0e开头的数字字符串在比较时会当作数字,而某些函数的行为可能变化,但CTF题目可能仍以PHP5的环境为主。
以下是典型考点及示例:
1. 松散比较(==
)的类型转换
-
原理:PHP在
==
比较时会自动转换类型,导致非预期结果。 -
科学计数法绕过:
"0e12345" == "0e67890"
→True
(视为0)。 -
字符串与数字比较:
"123abc" == 123
→True
(字符串转为整型123)。 -
布尔值陷阱:
true == "non-empty"
→True
,false == "0"
→True
。
CTF应用:
if($_GET['a']=='admin'){/* 传入a=0可绕过 */}
2. 哈希弱碰撞(0e开头的MD5/SHA1)
-
原理:某些字符串哈希后以
0e
开头,松散比较时视为0。 -
MD5示例:
md5('240610708')=0e462097431906509019562988736854
-
md5('QNKCDZO')=0e830400451993494058024219903391
-
// 满足 $_GET['a'] != $_GET['b'],但 md5($_GET['a']) == md5($_GET['b'])
-
SHA1示例:
sha1('aaroZmOk')
和sha1('aaK1STfY')
均以0e
开头。
3. 函数参数类型漏洞
-
strcmp数组绕过:
strcmp($_POST['pass'], 'password')
中,若pass
为数组(pass[]=1
),返回NULL
,松散比较时NULL == 0
。 -
md5/sha1处理数组:
md5(array())
返回NULL
,若比较md5($_GET['a']) == md5($_GET['b'])
,传入数组使两边均为NULL
。
4. switch/in_array的松散比较
-
switch类型转换:
$input="1abc";
-
switch($input){
-
case1:// 匹配成功,字符串转为整型1
-
// 执行敏感操作}
-
in_array默认松散:
in_array('abc', [0, 1, 2])
→True
('abc'转为整型0)。
5. JSON解析类型混淆
-
原理:
json_decode
可能将字符串数字解析为整型。$data=json_decode('{"is_admin": "0"}');
-
if($data->is_admin==true)
-
{// "0"转为整型0,0 == false// 被绕过}
6. is_numeric的陷阱
-
十六进制/科学计数法:
is_numeric('0x1a')
→True
(PHP5),is_numeric('1e3')
→True
。 -
利用场景:传入
0x61646d696e
(hex转ASCII为admin
)绕过字符串过滤。
7. 类型转换的数学运算
-
字符串转数字截断:
"123abc" + 456 = 579
,"abc"
部分被忽略。 -
科学计数法计算:
"1e3" == 1000
→True
。
8. 严格比较(===)与松散比较(==)
-
绕过技巧:若代码误用
==
,可通过类型转换构造Payload。
if($_POST['code']==12345){
// 传入code=12345abc 可绕过(转为整型12345)}
9.strpos 返回值的类型问题
strpos()
函数用于查找子字符串在目标字符串中首次出现的位置,其返回值类型存在特殊的类型问题,尤其在**松散比较(==
)**场景下可能导致安全漏洞。返回子字符串在目标字符串中的位置索引(从 0 开始计数),若未找到则返回 false
。PHP 的松散比较(==
)会将 0
(整数)和 false
(布尔值)视为相等。// 认为未找到'ctf',
但若 data=ctf123,strpos返回0,0 == false → 成立!
}
// 应使用严格比较:strpos(...) === false
10. 空字符串与零的隐式转换
-
原理:空字符串
""
在比较/运算中可能转为0
。if($_POST['code']==0){// 传入 code= 或 code="",空字符串转为0,绕过验证}
11. 十六进制/八进制字符串转换
-
原理:字符串中的十六进制/八进制格式可能被转换为数字。
$input='0x61646d696e';// hex("admin")if(is_numeric($input)&&$input=='admin'){// 0x61646d696e 转为十进制后与字符串比较,可能绕过}
12. 浮点数精度问题
-
原理:浮点数比较时精度损失导致非预期结果。
$a=0.99999999999999999;$b=1;if($a==$b){// true,精度丢失后均为1 }
13. empty()
和 isset()
的陷阱
-
原理:运算符(如
.
和+
)强制转换变量类型。$a="123abc"+456;// 579(字符串转整型123后相加)$b="123".456;// "123456"(拼接为字符串)
-
原理:
empty()
认为"0"
、0
、null
均为“空”。if(empty($_GET['auth'])){
// 传入 auth=0 时,empty(0) → true,绕过身份检查}
防御建议
-
使用严格比较(
===
)。 -
函数启用严格模式,如
in_array(..., true)
。 -
避免直接比较哈希值,优先对比原始字符串。
-
对用户输入显式类型转换(如
(int)$_GET['id']
)。
原文始发于微信公众号(小话安全):CTF:PHP弱类型常见考点总结
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论