<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】Phar與Stream Wrapper造成PHP RCE的深入挖掘

    VSole2022-04-20 08:13:27

    Phar RCE

    今年HITCON上,baby cake這一題,涉及到了今年BlackHat大會上的Sam Thomas分享的File Operation Induced Unserialization via the “phar://” Stream Wrapper這個議題,見:https://i.blackhat.com/us-18/Thu-August-9/us-18-Thomas-Its-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It-wp.pdf 。它的主要內容是,通過phar://協議對一個phar文件進行文件操作,如file_get_contents,就可以觸發反序列化,從而達成RCE的效果。

    在文章開頭部分,讓我先對phar反序列化做一些小小的分析。我們直接閱讀PHP源碼。在 phar.c#L618 處,其調用了php_var_unserialize。

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

    因此可以構造一個特殊的phar包,使得攻擊代碼能夠被反序列化,從而構造一個POP鏈。這一部分已經太常見了,CTF比賽中都出爛了,沒什么值得繼續討論的。值得關注的是到底為什么file_get_contents能夠實現RCE。

    Steam AP|

    因此,為解決這個問題,我們需要首先閱讀此函數的源碼。大概在此處:https://github.com/php/php-src/blob/PHP-7.2.11/ext/standard/file.c#L548 ,重點關注此行:

    stream = php_stream_open_wrapper_ex(filename, "rb",
                    (use_include_path ? USE_PATH : 0) | REPORT_ERRORS,
                    NULL, context);
    

    可以注意,其使用的是php_stream系列API來打開一個文件。閱讀PHP的這篇文檔:Streams API for PHP Extension Authors,可知,Stream API是PHP中一種統一的處理文件的方法,并且其被設計為可擴展的,允許任意擴展作者使用。而本次事件的主角,也就是phar這個擴展,其就注冊了phar://這個stream wrapper。可以使用stream_get_wrapper看到系統內注冊了哪一些wrapper,但其余的沒什么值得關注的。

    php > var_dump(stream_get_wrappers());array(12) {  [0]=>  string(5) "https"  [1]=>  string(4) "ftps"  [2]=>  string(13) "compress.zlib"  [3]=>  string(14) "compress.bzip2"  [4]=>  string(3) "php"  [5]=>  string(4) "file"  [6]=>  string(4) "glob"  [7]=>  string(4) "data"  [8]=>  string(4) "http"  [9]=>  string(3) "ftp"  [10]=>  string(4) "phar"  [11]=>  string(3) "zip"}
    

    那么,注冊一個 stream wrapper,能實現什么功能呢?很容易就能找到其定義:https://github.com/php/php-src/blob/8d3f8ca12a0b00f2a74a27424790222536235502/main/php_streams.h#L132

    typedef struct _php_stream_wrapper_ops {
        /* open/create a wrapped stream */
        php_stream *(*stream_opener)(php_stream_wrapper *wrapper, const char *filename, const char *mode,
                int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);
        /* close/destroy a wrapped stream */
        int (*stream_closer)(php_stream_wrapper *wrapper, php_stream *stream);
        /* stat a wrapped stream */
        int (*stream_stat)(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb);
        /* stat a URL */
        int (*url_stat)(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context);
        /* open a "directory" stream */
        php_stream *(*dir_opener)(php_stream_wrapper *wrapper, const char *filename, const char *mode,
                int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);
        const char *label;
        /* delete a file */
        int (*unlink)(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
        /* rename a file */
        int (*rename)(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context);
        /* Create/Remove directory */
        int (*stream_mkdir)(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context);
        int (*stream_rmdir)(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
        /* Metadata handling */
        int (*stream_metadata)(php_stream_wrapper *wrapper, const char *url, int options, void *value, php_stream_context *context);
    } php_stream_wrapper_ops;
    

    因此,我們發現,一個 stream wrapper,它支持以下功能:打開文件(夾)、刪除文件(夾)、重命名文件(夾),以及獲取文件的meta。我們很容易就能斷定,類似unlink等函數也是同樣通過這個 streams api 進行操作。

    Sam Thomas 的 pdf 指出

    This is true for both direct file operations (such as
    “file_exists”) and indirect operations such as those that occur during external entity processing
    within XML (i.e. when an XXE vulnerability is being exploited).

    我們通過試驗也很容易發現,類似unlink等函數也均是可以使用的。

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

    • fileatime / filectime / filemtime
    • stat / fileinode / fileowner / filegroup / fileperms
    • file / file_get_contents / readfile / `fopen“
    • file_exists / is_dir / is_executable / is_file / is_link / is_readable / is_writeable / is_writable
    • parse_ini_file
    • unlink
    • copy

    僅僅是知道一些受影響的函數,就夠了嗎?為什么就可以使用了呢?

    尋找受害者

    當然不夠。我們需要先找到其原理,然后往下深入挖掘。

    先看file_get_contents的代碼。其調用了

    stream = php_stream_open_wrapper_ex(filename, "rb" ....);
    

    這么個函數。

    再看unlink的代碼,其調用了

    wrapper = php_stream_locate_url_wrapper(filename, NULL, 0);
    

    這么個函數。

    從php_stream_open_wrapper_ex的實現,可以看到,其也調用了php_stream_locate_url_wrapper 。這個函數的作用是通過url來找到對應的wrapper。我們可以看到,phar組件注冊了phar://這個wrapper, https://github.com/php/php-src/blob/67b4c3379a1c7f8a34522972c9cb3adf3776bc4a/ext/phar/stream.c

    其定義如下:

    const php_stream_wrapper_ops phar_stream_wops = {
        phar_wrapper_open_url,
        NULL,                  /* phar_wrapper_close */
        NULL,                  /* phar_wrapper_stat, */
        phar_wrapper_stat,     /* stat_url */
        phar_wrapper_open_dir, /* opendir */
        "phar",
        phar_wrapper_unlink,   /* unlink */
        phar_wrapper_rename,   /* rename */
        phar_wrapper_mkdir,    /* create directory */
        phar_wrapper_rmdir,    /* remove directory */
        NULL
    };
    

    接著,讓我們翻這幾個函數的實現,會發現它們都調用了phar_parse_url,這個函數再調用phar_open_or_create_filename -> phar_create_or_parse_filename -> phar_open_from_fp ->phar_parse_pharfile -> phar_parse_metadata -> phar_var_unserialize。因此,明面上來看,所有文件函數,均可以觸發此phar漏洞,因為它們都直接或間接地調用了這個wrapper。

    只是這些文件函數,就夠了嗎?

    當然不夠。這是一個所有的和IO有關的函數,都可能觸發的問題。

    前面我已經指出,它們都有一個共同特征,就是調用了php_stream_locate_url_wrapper。但是這個不那么好用,換php_stream_open_wrapper更合適點。讓我們搜索一下PHP源代碼吧,

    我們很快就能發現,操作文件的touch,也是能觸發它的。不看文件了,我們假設文件全部都能用。

    我們會驚訝(一點都不)地發現:

    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

    zip

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

    Bzip / Gzip

    這,夠了嗎?non non噠喲!

    如果題目限制了,phar://不能出現在頭幾個字符怎么辦?請欣賞你船未見過的船新操作。

    $z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';

    當然,它同樣適用于compress.zlib://。

    Postgres

    再來個數據庫吧!

    $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');
    

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

    MySQL

    還有什么騷操作呢?

    ……MySQL?

    走你!

    我們注意到,LOAD DATA LOCAL INFILE也會觸發這個php_stream_open_wrapper. 讓我們測試一下。

    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\'  IGNORE 1 LINES;');
    

    再配置一下mysqld。

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

    ……然后,走你!

    這就是我想要看到的舞臺!——長頸鹿

    很可惜,這不是默認配置;但是,嗯,很有意思。

    我相信,PHP代碼內部還有相當多的php_stream_open_wrapper等待挖掘,這只是關于stream wrapper利用的一小步。

    stringphp
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    這意味著如果變量名后面附加了圓括號,PHP 將尋找與變量求值結果同名的函數,并嘗試執行它。除其他事項外,這可用于實現回調、函數表等。利用包裝函數將這些構造中的任何一個用作變量函數。這可能是另一種規避阻止 PHP 函數名稱的規則的方法。
    這里根據紅日安全PHP-Audit-Labs對一些函數缺陷的分析,從PHP內核層面來分析一些函數的可利用的地方,標題所說的函數缺陷并不一定是函數本身的缺陷,也可能是函數在使用過程中存在某些問題,造成了漏洞,以下是對部分函數的分析
    crawlergo是一個使用chrome headless模式進行URL收集的瀏覽器爬蟲。它對整個網頁的關鍵位置與DOM渲染階段進行HOOK,自動進行表單填充并提交,配合智能的JS事件觸發,盡可能的收集網站暴露出的入口。內置URL去重模塊,過濾掉了大量偽靜態URL,對于大型網站仍保持較快的解析與抓取速度,最后得到高質量的請求結果集合。調研1.
    在文章開頭部分,先對phar反序列化做一些小小的分析。
    一款新的webshell管理工具
    文件包含利用思路
    2021-12-10 21:28:32
    開發人員通常會把可重復使用的函數寫到單個文件中,在使用某些函數時,直接調用此文件,而無需再次編寫,這種調用文件的過程一般被稱為包含。????為了使代碼更加靈活,通常會將被包含的文件設置為變量,用來進行動態調用,但正是由于這種靈活性,從而導致客戶端可以調用一個惡意文件,造成文件包含漏洞。
    文章首發在:奇安信攻防社區https://forum.butian.net/share/2142前言 如果存
    PHP官方確認,于6月9日,PHP官網發布了該漏洞的修復方案。
    正常來說一個合法的反序列化字符串,在二次序列化也即反序列化再序列化之后所得到的結果是一致的。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类