Java代码审计之命令执行漏洞详解
字数 2768 2025-08-29 08:29:41

Java代码审计之命令执行漏洞详解

0x01 漏洞简介

在Java代码审计中,命令执行漏洞指应用程序未对用户输入进行严格过滤,直接将外部可控参数拼接到系统命令中执行,导致攻击者可注入恶意命令并获取服务器控制权。

核心原理

  • 开发者误用危险函数执行系统命令时,未对用户输入的参数进行安全校验或转义
  • 用户可通过构造特殊字符(如管道符 |、命令分隔符 ;)或参数注入(如 ${} 表达式)将恶意指令与原始命令拼接
  • 该漏洞常出现在参数动态拼接的场景,且受操作系统特性影响

操作系统特性影响

Windows系统命令注入表

  • & - 命令分隔符
  • && - 前一个命令成功时执行后一个命令
  • | - 管道符
  • || - 前一个命令失败时执行后一个命令
  • %0a - 换行符
  • %0d - 回车符
  • ^ - 转义字符

Linux系统命令注入表

  • ; - 命令分隔符
  • && - 前一个命令成功时执行后一个命令
  • | - 管道符
  • || - 前一个命令失败时执行后一个命令
  • $() - 命令替换
  • ` ` - 反引号命令替换
  • \n - 换行符
  • \r - 回车符

0x02 Java命令执行方法

2.1 Runtime.exec()

Runtime.exec() 是Java中执行系统命令的核心方法,提供多种重载形式,本质是启动子进程执行外部命令。直接拼接用户输入会导致命令注入漏洞,需使用参数数组形式并严格校验输入。

方法重载形式

// 方法1: 直接执行字符串命令
Process exec(String command);

// 方法2: 通过字符串数组传递命令和参数
Process exec(String[] cmdarray);

// 方法3: 指定环境变量执行字符串命令
Process exec(String command, String[] envp);

// 方法4: 通过数组传递命令并自定义环境变量
Process exec(String[] cmdarray, String[] envp);

// 方法5: 指定环境变量和工作目录执行字符串命令
Process exec(String command, String[] envp, File dir);

// 方法6: 通过数组传递命令,并指定环境变量、工作目录
Process exec(String[] cmdarray, String[] envp, File dir);

执行差异分析

  1. exec(String)exec(String[]) 的区别:

    • exec(String) 会将传入的命令字符串通过 StringTokenizer 进行分割,按照默认的空白分隔符(包括空格、制表符\t、换行符\n、回车符\r和换页符\f)进行分割
    • exec(String[]) 则直接使用传入的数组作为命令参数
  2. 漏洞利用差异:

    • exec(String) 中使用 & 拼接命令会异常,因为命令被错误分割
    • exec(String[]) 中使用 & 拼接命令可以成功执行,因为整个字符串会被Shell解析
  3. exec(String) 的漏洞利用方法:

    • 可以利用Shell的解析逻辑实现命令注入,直接拼接 cmd /c 即可

2.2 ProcessBuilder

ProcessBuilder 命令执行漏洞的核心在于通过 ProcessBuilder 类直接构造并执行系统命令时,若未对用户输入参数进行严格过滤或拆分,攻击者可注入恶意命令实现任意代码执行。

特点

  • ProcessBuilder 不支持以字符串形式传入命令,只能拆分成List或者数组的形式传入
  • 底层实现最终通过调用 ProcessImpl.start() 完成操作系统级别的进程创建

2.3 ProcessImpl

ProcessImpl 是Java中 Process 抽象类的具体实现类,其设计目的是为 ProcessBuilder.start() 方法提供底层支持,用于创建和管理操作系统进程。

特点

  • ProcessImpl 的构造函数被声明为 private,无法直接通过 new 实例化
  • 开发者通常需通过 ProcessBuilderRuntime.exec() 间接调用其功能
  • 若需直接操作 ProcessImpl,必须通过反射技术绕过访问限制

反射调用示例

package com.xmsec.controller.rce;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

public class ProcessImplExamples {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        String[] cmdarray = new String[]{"cmd", "/c", "calc"};
        Class clazz = Class.forName("java.lang.ProcessImpl");
        Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
        method.setAccessible(true);
        Process p = (Process) method.invoke(null, cmdarray, null, null, null, false);
    }
}

0x03 代码审计思路

核心思路是识别所有可能导致命令注入的代码路径,重点围绕"参数可控性"和"Shell调用方式"两个维度进行分析:

1. 定位危险入口点

识别所有可能执行系统命令的代码位置:

  • Runtime.getRuntime().exec()
  • ProcessBuilder.start()
  • 反射调用 ProcessImplUNIXProcess 等底层类的方法

审计技巧

  • 使用IDE全局搜索关键词:exec(ProcessBuilderstart(getRuntime()
  • 检查反射调用:搜索 Class.forName()Method.invoke() 等代码块,确认是否操作危险类(如 ProcessImpl)

2. 分析参数来源

判断命令参数是否完全或部分可控:

  • HTTP请求参数(GET/POST)、Headers、Cookies
  • 文件上传内容、数据库查询结果
  • 配置文件(如YAML/Properties)中的动态值
  • 是否存在字符串拼接(如 "sh -c " + userInput)
  • 是否通过 String.format()StringBuilder 动态生成命令

3. 验证调用方式与参数解析

确认是否通过Shell环境执行命令,以及参数解析是否安全:

  • Shell会解析命令中的特殊符号(如 ;&&$()),导致命令注入
  • 检查是否显式调用Shell,如使用 sh -cbash -ccmd.exe /c 等Shell解释器
  • 检查参数分割逻辑,如使用 exec(String command) 传递单个字符串命令
  • 检查反射绕过,通过反射直接调用 ProcessImpl.start(),绕过参数安全检查

0x04 防御与修复

1. 避免执行系统命令

优先使用Java原生API替代直接执行系统命令。例如:

  • 删除文件使用 File.delete() 而非 rm 命令
  • 网络请求使用 HttpClient 而非 curl 命令

这样可以规避命令注入风险,同时提升跨平台兼容性。

2. 使用安全的命令执行方式

无法避免系统命令执行时,优先使用:

// 不安全的字符串拼接方式
exec("cmd /c " + userInput);

// 安全的数组传参方式
exec(new String[]{"cmd", "/c", fixedCommand});

3. 避免Shell调用

禁止通过 sh -ccmd.exe /c 等方式创建Shell环境,直接调用可执行文件路径:

// 不安全的Shell调用
exec("sh -c ls " + dir);

// 安全的直接调用
exec(new String[]{"/bin/ls", dir});

4. 危险字符过滤

过滤 |&;$() 等Shell元字符,以及路径遍历符号(../),可使用OWASP ESAPI等安全库进行编码处理:

String safeInput = ESAPI.encoder().encodeForOS(new WindowsCodec(), userInput);

5. 其他防御措施

  • 实施最小权限原则,使用低权限账户执行命令
  • 对用户输入实施白名单验证
  • 对命令参数进行严格的类型和格式检查
  • 记录和监控命令执行操作

总结

Java命令执行漏洞是Web应用安全中的高风险漏洞,审计时需要重点关注命令执行函数的调用方式、参数来源和解析逻辑。通过使用安全的API调用方式、避免Shell调用、严格过滤用户输入等措施,可以有效防御此类漏洞。在代码审计过程中,应当全面检查所有可能的命令执行路径,确保没有可控参数能够注入恶意命令。

Java代码审计之命令执行漏洞详解 0x01 漏洞简介 在Java代码审计中,命令执行漏洞指应用程序未对用户输入进行严格过滤,直接将外部可控参数拼接到系统命令中执行,导致攻击者可注入恶意命令并获取服务器控制权。 核心原理 : 开发者误用危险函数执行系统命令时,未对用户输入的参数进行安全校验或转义 用户可通过构造特殊字符(如管道符 | 、命令分隔符 ; )或参数注入(如 ${} 表达式)将恶意指令与原始命令拼接 该漏洞常出现在参数动态拼接的场景,且受操作系统特性影响 操作系统特性影响 Windows系统命令注入表 : & - 命令分隔符 && - 前一个命令成功时执行后一个命令 | - 管道符 || - 前一个命令失败时执行后一个命令 %0a - 换行符 %0d - 回车符 ^ - 转义字符 Linux系统命令注入表 : ; - 命令分隔符 && - 前一个命令成功时执行后一个命令 | - 管道符 || - 前一个命令失败时执行后一个命令 $() - 命令替换 ` ` - 反引号命令替换 \n - 换行符 \r - 回车符 0x02 Java命令执行方法 2.1 Runtime.exec() Runtime.exec() 是Java中执行系统命令的核心方法,提供多种重载形式,本质是启动子进程执行外部命令。直接拼接用户输入会导致命令注入漏洞,需使用参数数组形式并严格校验输入。 方法重载形式 : 执行差异分析 : exec(String) 与 exec(String[]) 的区别: exec(String) 会将传入的命令字符串通过 StringTokenizer 进行分割,按照默认的空白分隔符(包括空格、制表符 \t 、换行符 \n 、回车符 \r 和换页符 \f )进行分割 exec(String[]) 则直接使用传入的数组作为命令参数 漏洞利用差异: exec(String) 中使用 & 拼接命令会异常,因为命令被错误分割 exec(String[]) 中使用 & 拼接命令可以成功执行,因为整个字符串会被Shell解析 exec(String) 的漏洞利用方法: 可以利用Shell的解析逻辑实现命令注入,直接拼接 cmd /c 即可 2.2 ProcessBuilder ProcessBuilder 命令执行漏洞的核心在于通过 ProcessBuilder 类直接构造并执行系统命令时,若未对用户输入参数进行严格过滤或拆分,攻击者可注入恶意命令实现任意代码执行。 特点 : ProcessBuilder 不支持以字符串形式传入命令,只能拆分成List或者数组的形式传入 底层实现最终通过调用 ProcessImpl.start() 完成操作系统级别的进程创建 2.3 ProcessImpl ProcessImpl 是Java中 Process 抽象类的具体实现类,其设计目的是为 ProcessBuilder.start() 方法提供底层支持,用于创建和管理操作系统进程。 特点 : ProcessImpl 的构造函数被声明为 private ,无法直接通过 new 实例化 开发者通常需通过 ProcessBuilder 或 Runtime.exec() 间接调用其功能 若需直接操作 ProcessImpl ,必须通过反射技术绕过访问限制 反射调用示例 : 0x03 代码审计思路 核心思路是识别所有可能导致命令注入的代码路径,重点围绕"参数可控性"和"Shell调用方式"两个维度进行分析: 1. 定位危险入口点 识别所有可能执行系统命令的代码位置: Runtime.getRuntime().exec() ProcessBuilder.start() 反射调用 ProcessImpl 、 UNIXProcess 等底层类的方法 审计技巧 : 使用IDE全局搜索关键词: exec( 、 ProcessBuilder 、 start( 、 getRuntime() 检查反射调用:搜索 Class.forName() 、 Method.invoke() 等代码块,确认是否操作危险类(如 ProcessImpl ) 2. 分析参数来源 判断命令参数是否完全或部分可控: HTTP请求参数(GET/POST)、Headers、Cookies 文件上传内容、数据库查询结果 配置文件(如YAML/Properties)中的动态值 是否存在字符串拼接(如 "sh -c " + userInput ) 是否通过 String.format() 、 StringBuilder 动态生成命令 3. 验证调用方式与参数解析 确认是否通过Shell环境执行命令,以及参数解析是否安全: Shell会解析命令中的特殊符号(如 ; 、 && 、 $() ),导致命令注入 检查是否显式调用Shell,如使用 sh -c 、 bash -c 、 cmd.exe /c 等Shell解释器 检查参数分割逻辑,如使用 exec(String command) 传递单个字符串命令 检查反射绕过,通过反射直接调用 ProcessImpl.start() ,绕过参数安全检查 0x04 防御与修复 1. 避免执行系统命令 优先使用Java原生API替代直接执行系统命令。例如: 删除文件使用 File.delete() 而非 rm 命令 网络请求使用 HttpClient 而非 curl 命令 这样可以规避命令注入风险,同时提升跨平台兼容性。 2. 使用安全的命令执行方式 无法避免系统命令执行时,优先使用: 3. 避免Shell调用 禁止通过 sh -c 、 cmd.exe /c 等方式创建Shell环境,直接调用可执行文件路径: 4. 危险字符过滤 过滤 | 、 & 、 ; 、 $() 等Shell元字符,以及路径遍历符号( ../ ),可使用OWASP ESAPI等安全库进行编码处理: 5. 其他防御措施 实施最小权限原则,使用低权限账户执行命令 对用户输入实施白名单验证 对命令参数进行严格的类型和格式检查 记录和监控命令执行操作 总结 Java命令执行漏洞是Web应用安全中的高风险漏洞,审计时需要重点关注命令执行函数的调用方式、参数来源和解析逻辑。通过使用安全的API调用方式、避免Shell调用、严格过滤用户输入等措施,可以有效防御此类漏洞。在代码审计过程中,应当全面检查所有可能的命令执行路径,确保没有可控参数能够注入恶意命令。