laravel5.7漏洞复现
文章目录
- laravel环境搭建
- pop链分析
- 构造反序列化payload
laravel环境搭建
这个不用多说了,网上很多大佬都写过很详细的博客,可以借鉴pop链分析
laravel5.7漏洞主要利用vendor/laravel/framework/src/Illuminate/Foundation/Testing文件夹下的一个PendingCommand类,该类中有几个私有特性1
2
3
4public $test; //一个实例化的类 Illuminate\Auth\GenericUser
protected $app; //一个实例化的类 Illuminate\Foundation\Application
protected $command; //要执行的php函数 system
protected $parameters; //要执行的php函数的参数 array('id') - 用于命令执行的函数为PendingCommand.php中的run()函数还有可以触发run函数的析构函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public 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;
}1
2
3
4
5
6
7
8
9public 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函数
其中也就是返回了Illuminate\Foundation\Application对象;即我们可以将任意对象赋值给 $this->instances[$abstract] ,这个对象最终会赋值给[Kernel::class]最后会进入到getconcret函数.1
return $this->instances[$abstract];=$this->instances["Illuminate\Contracts\Console\Kernel"];
首先会判断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数组是我们可控的。最终也就是执行了到这pop链就已经分析完成了!!1
call_user_func_array('xxx',array('xxx'));
构造反序列化payload
直接放exp如下
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!