Laravel 是一个免费的开源 PHP Web 框架,旨在实现的Web软件的MVC架构。2021年1月13日,阿里云应急响应中心监控到国外某安全研究团队披露了 Laravel <= 8.4.2 存在远程代码执行漏洞。当Laravel开启了Debug模式时,由于Laravel自带的Ignition功能的某些接口存在过滤不严,攻击者可以发起恶意请求,通过构造恶意Log文件等方式触发Phar反序列化,从而造成远程代码执行,控制服务器。漏洞细节已在互联网公开。阿里云应急响应中心提醒 Laravel 用户尽快采取安全措施阻止漏洞攻击。
Laravel 框架 < 8.4.3
facade ignition 组件 < 2.5.2
<https://github.com/SNCKER/CVE-2021-3129.git>
docker-compose up -d
点击生成密钥出现如下图即成功创建漏洞环境
按照漏洞公开文章需要先有一个未知变量的错误,来点击 Make variable optional
通过点击“Make variableOptional”按钮,模板变量则会被修改。如果检查HTTP日志,我们就会看到被调用的端点
通过这些solutions,开发者可以通过点击按钮的方式,快速修复一些错误。本次漏洞就是其中的vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php
过滤不严谨导致的。首先我们到执行solution的控制器当中去,看看是如何调用到solution的
<?php
namespace Facade\\Ignition\\Http\\Controllers;
use Facade\\Ignition\\Http\\Requests\\ExecuteSolutionRequest;
use Facade\\IgnitionContracts\\SolutionProviderRepository;
use Illuminate\\Foundation\\Validation\\ValidatesRequests;
class ExecuteSolutionController
{
use ValidatesRequests;
public function __invoke(
ExecuteSolutionRequest $request,
SolutionProviderRepository $solutionProviderRepository
) {
$solution = $request->getRunnableSolution();
$solution->run($request->get('parameters', []));
return response('');
}
}
接着调用solution对象中的run()
方法,并将可控的parameters
参数传过去。通过这个点我们可以调用到MakeViewVariableOptionalSolution::run()
<?php
namespace Facade\\Ignition\\Solutions;
use Facade\\IgnitionContracts\\RunnableSolution;
use Illuminate\\Support\\Facades\\Blade;
class MakeViewVariableOptionalSolution implements RunnableSolution
{
...
public function run(array $parameters = [])
{
$output = $this->makeOptional($parameters);
if ($output !== false) {
file_put_contents($parameters['viewFile'], $output);
}
}
public function makeOptional(array $parameters = [])
{
$originalContents = file_get_contents($parameters['viewFile']);
$newContents = str_replace('$'.$parameters['variableName'], '$'.$parameters['variableName']." ?? ''", $originalContents);
$originalTokens = token_get_all(Blade::compileString($originalContents));
$newTokens = token_get_all(Blade::compileString($newContents));
$expectedTokens = $this->generateExpectedTokens($originalTokens, $parameters['variableName']);
if ($expectedTokens !== $newTokens) {
return false;
}
return $newContents;
}
protected function generateExpectedTokens(array $originalTokens, string $variableName): array
{
$expectedTokens = [];
foreach ($originalTokens as $token) {
$expectedTokens[] = $token;
if ($token[0] === T_VARIABLE && $token[1] === '$'.$variableName) {
$expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];
$expectedTokens[] = [T_COALESCE, '??', $token[2]];
$expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];
$expectedTokens[] = [T_CONSTANT_ENCAPSED_STRING, "''", $token[2]];
}
}
return $expectedTokens;
}
}
可以看到这里主要功能点是:读取一个给定的路径,并替换$variableName
为$variableName ?? ''
,之后写回文件中。由于这里调用了file_get_contents()
,且其中的参数可控,所以这里可以通过phar://
协议去触发phar反序列化。如果后期利用框架进行开发的人员,写出了一个文件上传的功能。那么我们就可以上传一个恶意phar文件,利用上述的file_get_contents()
去触发phar反序列化,达到rce的效果。