记一次 RCE 0day 的审计过程
大家好,我是 Sukali,首次在信安之路上投稿,并获得免费加入信安之路知识星球的机会,同时解锁了信安之路成长平台、文库以及 POC 管理系统的使用权限,今天来为大家分享一下自己挖掘的一个 RCE 0day 的审计过程。
RCE 是远程代码执行或者远程命令执行的缩写,在案例讲解之前,先来聊聊 java 代码审计的一些基础知识:
1、在 Java 代码审计过程中,我们的目标是:
- 识别 常见的安全漏洞
- 理解代码逻辑,寻找不安全的输入处理
- 分析漏洞可利用性,判断是否能造成实际攻击
- 提出 合理的修复方案
2、代码审计的基本方法
手工审计
- 查找 用户输入点
- 跟踪 变量的传递路径
- 分析 是否进入危险函数
自动化工具
- FindSecBugs(Java 代码安全分析插件)
- SonarQube(静态代码扫描)
- CodeQL(代码查询分析)
黑盒测试
- Fuzzing(模糊测试),通过随机输入探测漏洞
- 渗透测试,模拟真实攻击场景
3、Java 常见的命令执行函数
Java 提供多个执行系统命令的方法,如果使用不当,会造成远程命令执行漏洞(RCE)。以下是 Java 常见的高危命令执行 API:
危险 API | 描述 | 漏洞等级 |
---|---|---|
Runtime.getRuntime().exec(cmd) | 直接执行 cmd,如果 cmd 由用户输入控制,可能导致 RCE。 | 高 |
ProcessBuilder.start() | 启动进程并执行命令,如果参数未严格过滤,可能被利用。 | 高 |
ScriptEngine.eval(script) | 执行 JavaScript 代码,可能导致远程代码执行。 | 高 |
GroovyShell.evaluate(script) | 执行 Groovy 代码,如果 script 可控,则可能被攻击者利用。 | 高 |
RCE 0day 审计案例
在通过对其手工代码审计发现在dbBackUp方法中存在命令执行的功能。并将sb数组作为参数传入执行。Sb数组中在没有过滤的情况下添加了pathSql变量,该变量由backPath+backName组成。
详细解释
从图片中的代码可以看到
1、pathSql 变量可控
代码语言:javascript代码运行次数:0运行复制String pathSql = backPath + backName;
backPath 和 backName 由外部参数传入,如果 backPath 可控,攻击者可以拼接恶意命令。
2、未经安全处理
代码语言:javascript代码运行次数:0运行复制sb.append(" " + pathSql);
sb.append() 直接拼接了 pathSql ,如果 pathSql 由攻击者控制,就可以执行任意命令。
runtime.exec() 直接执行命令
代码语言:javascript代码运行次数:0运行复制Process process = runtime.exec("cmd /c " + sb.toString());
Process process = runtime.exec("/home/mysql/mysql/bin/" + sb.toString());
runtime.exec() 不会自动过滤 Shell 特殊字符,攻击者可以注入 &&、;、|| 等符号进行命令拼接,最终实现远程命令执行(RCE)
通过查找 dbBackUp 函数的调用链可以发现在 process 方法中调用了 dbBackUp 方法并给其提供了fileUrl 以及 backName 参数,backName 参数是直接生成了一个以时间命名的 .sql 文件,我们关注 fileUrl 参数即可,fileUrl 是由 MappingCache.getValue 获取 FILE_LOCATION 得到:
详细解释
fileUrl 变量
代码语言:javascript代码运行次数:0运行复制String fileUrl = MappingCache.getValue(DOMAIN_COMMON, FILE_LOCATION);
fileUrl 由 MappingCache.getValue() 方法获取FILE_LOCATION变量的值。
问题:如果FILE_LOCATION变量的值可被攻击者控制,攻击者可以构造恶意路径或命令,导致远程命令执行(RCE)。
val.split("&")
代码语言:javascript代码运行次数:0运行复制String[] split = val.split("&");
val 是从 MappingCache.getValue(DOMAIN_COMMON, BACKUP_DATABASE) 获取的配置项,包含数据库连接信息。
split("&") 解析用户输入,可能导致未受控的数据流,如果 val 由攻击者控制,也可能影响执行的 SQL 备份命令。
dbBackUp 方法调用
代码语言:javascript代码运行次数:0运行复制BackupDatabaseTemplate.dbBackUp(userName, password, databaseName, fileUrl, backName, port);
dbBackUp 方法的 fileUrl 直接来源于 FILE_LOCATION,如果 FILE_LOCATION 可控,fileUrl 也将被攻击者操控。
追踪 getValue 发现内容都是从 redis 中获取,现在只需要寻找在哪里写入 redis 中的即可
代码解析
getValue 方法主要做了什么?
连接 Redis
代码语言:javascript代码运行次数:0运行复制redis = getJedis();
getJedis() 方法用于获取 Redis 连接。
这意味着数据存储在 Redis,getValue 只是读取 Redis 里的数据。
拼接 Redis Key
代码语言:javascript代码运行次数:0运行复制redis.get((domain + key + _SUFFIX_MAPPING).getBytes());
domain + key + _SUFFIX_MAPPING 组成 Redis键名。
通过 get() 方法从 Redis 获取数据。
反序列化数据
代码语言:javascript代码运行次数:0运行复制Object object = SerializeUtil.unserialize(redis.get(...));
代码使用 SerializeUtil.unserialize() 反序列化数据。
如果 Redis 存储的是序列化对象,那么这里会将其还原为 Java 对象。
返回数据
代码语言:javascript代码运行次数:0运行复制Mapping mapping = (Mapping) object;
return mapping.getValue();
反序列化的数据被转换为 Mapping 类型,并返回 getValue()。
利用链跟踪
在前端寻找 FILE_LOCATION 发现在通用配置中可以配置 FILE_LOCATION 内容。
回到 process 方法我们查看调用链发现 startTask 中引用了 process,继续跟进
发现在 job.runJob 服务中的 doCmd 引用了 startTask 方法
继续查找调用可以找到 invokeListener 方法中引用 doCmd 方法
继续跟进上一个调用函数则是 multicastEvent 方法
省略其他冗余的调用方法追踪,最后是在 Service 的 post 请求方法下调用含有漏洞的
方法链
完整调用链
漏洞复现
tmp/ || calc.exe || 保存到
调用 job.runJob 服务成功命令执行
成功执行
修复方案
1. 使用 ProcessBuilder 替代 Runtime.exec()
代码语言:javascript代码运行次数:0运行复制ProcessBuilder pb = new ProcessBuilder(
"mysqldump",
"-h", "3306",
"-u", "root",
"-p", "password",
"-r", "/safe/path/backup.sql"
);
pb.start();
ProcessBuilder 能够隔离参数,防止命令拼接漏洞。
2. 限制 backPath 只能在安全目录
代码语言:javascript代码运行次数:0运行复制if (!backPath.startsWith("/var/backups/")) {
throw new SecurityException("非法备份路径!");
}
3. 过滤 backName 防止命令拼接
代码语言:javascript代码运行次数:0运行复制if (!backName.matches("[a-zA-Z0-9_-]+\\.sql")) {
throw new IllegalArgumentException("非法文件名!");
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-03-19,如有侵权请联系 cloudcommunity@tencent 删除变量漏洞数据序列化安全