Java命令注入原理并结合Java Instrument技术
字数 2296 2025-08-15 21:31:09

Java命令注入原理与Java Instrument技术应用

一、命令注入概述

命令注入是指恶意用户构造特殊请求,对执行系统命令的功能点进行注入攻击,从而达到执行任意系统命令的效果。在Java中,常见的命令执行方式包括:

  1. java.lang.Runtime.getRuntime().exec()
  2. java.lang.ProcessBuilder
  3. com.jcraft.jsch.ChannelExec
  4. 特殊情况下method.invoke()也可用于命令执行

二、演示环境搭建

使用SpringBoot+Swagger搭建的模拟Web环境,主要包含三个接口:

  1. /command/exec/string - 使用Runtime.getRuntime().exec(),入参为String
  2. /command/exec/array - 使用Runtime.getRuntime().exec(),入参为String[]
  3. /command/processbuilder - 使用ProcessBuilder,入参为List

源码地址:https://gitee.com/cor0ps/java-range.git

三、Java执行系统命令的方法

1. Runtime.getRuntime().exec()

分为两大类:

  • 入参为String类型:public Process exec(String command)
  • 入参为String[]类型:public Process exec(String cmdarray[])

String入参执行流程

  1. 代码进入exec(String command, String[] envp, File dir)方法
  2. 使用StringTokenizer类处理传入的字符串
    • 默认分隔符为:空格、\t\n\r\f
  3. 将字符串转换为String数组
  4. 最终调用ProcessBuilder执行命令

关键点

  • String入参会被转化为String[]数组
  • Runtime最终通过ProcessBuilder执行命令
  • StringTokenizer会对特殊字符进行处理,可能影响命令执行

2. ProcessBuilder

分为两大类:

  • 入参为List类型:public ProcessBuilder(List command)
  • 入参为String可变参数类型:public ProcessBuilder(String... command)

执行流程

  1. String可变参数会被转化为List类型
  2. 进入start()方法后,存在prog变量(即cmdarray[0]的值)
  3. 如果有SecurityManager,会进入checkExec()
  4. 最终进入ProcessImpl.start()
    • Linux下调用java.lang.UNIXProcess类执行命令
    • 通过forkAndExec为命令创建环境

四、命令注入利用技术

1. Shell符号利用

要执行系统管道(|)、;、&&等符号,必须创建shell来执行命令:

符号/形式 说明
cmd1|cmd2 管道,前一个命令结果重定向给后一个命令
cmd1;cmd2 多语句分隔符
cmd1&&cmd2 逻辑与,两个都为真时返回真
cmd1||cmd2 逻辑或,有一个为真返回真
{cmd1,cmd2} 花括号扩展,命令依次执行
cmd1,cmd2 反引号命令替换
$(cmd1,cmd2) 变量替换

2. 绕过技术

当使用String入参时,可以利用bash的base64编码绕过特殊字符限制:

/bin/sh -c {echo,dG91Y2glMjAvdG1wL3p6eno=}|{base64,-d}|{bash,-i}

五、Java Instrument技术监控命令执行

1. 原理

通过Java Instrument技术监控java.lang.UNIXProcess类的构造函数,可以捕获执行的命令参数。

2. 实现代码片段

if ("java.lang.UNIXProcess".equals(className)) {
    try {
        ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
        CtBehavior[] ctBehaviors = ctClass.getDeclaredConstructors();
        for (CtBehavior cb : ctBehaviors) {
            if (cb.getName().equals("UNIXProcess")) {
                String src="{" +
                    "String prog_1=new String($1);" +
                    "String cmd_1=new String($2);" +
                    "System.out.println(\"unixprocess_result:\"+prog_1+\" \"+cmd_1);" +
                "}";
                cb.insertBefore(src);
            }
        }
        bytesCode = ctClass.toBytecode();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

3. 使用方式

在VM Options中添加:

-javaagent:"/path/agent.jar"

注意:路径必须加双引号,否则会出现异常。

4. 检测结果

成功运行后会输出:

Instrument Agent start!

执行命令时会输出类似:

unixprocess_result:prog:/bin/sh cmd:-cecho "xxx">/tmp/yyy

六、安全对比分析

Runtime.getRuntime().exec()不同入参对比

特性 String入参 String[]入参
特殊字符处理 会被StringTokenizer过滤 不进行过滤
命令执行成功率 可能失败 成功率更高
安全风险 相对较低 风险更高

实际测试结果

  1. String类型入参:

    unixprocess_result:prog:/bin/sh cmd:-cecho"xxxx">/tmp/xxx
    
    • 执行失败(空格被过滤)
  2. String[]类型入参:

    unixprocess_result:prog:/bin/sh cmd:-cecho "xxx">/tmp/yyy
    
    • 执行成功

七、防御建议

  1. 避免直接使用用户输入拼接命令
  2. 使用String[]或List类型而非String类型传递命令
  3. 对用户输入进行严格过滤和验证
  4. 使用白名单限制可执行的命令
  5. 实施最小权限原则,限制执行命令的权限

八、参考资源

  1. Java命令执行深入研究
  2. Java执行管道命令
  3. Java Instrument技术
  4. Javassist使用指南

源码地址:

  • 演示环境:https://gitee.com/cor0ps/java-range.git
  • Java Agent:https://gitee.com/cor0ps/Agent.git
Java命令注入原理与Java Instrument技术应用 一、命令注入概述 命令注入是指恶意用户构造特殊请求,对执行系统命令的功能点进行注入攻击,从而达到执行任意系统命令的效果。在Java中,常见的命令执行方式包括: java.lang.Runtime.getRuntime().exec() java.lang.ProcessBuilder com.jcraft.jsch.ChannelExec 特殊情况下 method.invoke() 也可用于命令执行 二、演示环境搭建 使用SpringBoot+Swagger搭建的模拟Web环境,主要包含三个接口: /command/exec/string - 使用 Runtime.getRuntime().exec() ,入参为String /command/exec/array - 使用 Runtime.getRuntime().exec() ,入参为String[ ] /command/processbuilder - 使用 ProcessBuilder ,入参为List 源码地址:https://gitee.com/cor0ps/java-range.git 三、Java执行系统命令的方法 1. Runtime.getRuntime().exec() 分为两大类: 入参为String类型: public Process exec(String command) 入参为String[]类型: public Process exec(String cmdarray[]) String入参执行流程 代码进入 exec(String command, String[] envp, File dir) 方法 使用 StringTokenizer 类处理传入的字符串 默认分隔符为:空格、 \t 、 \n 、 \r 、 \f 将字符串转换为String数组 最终调用 ProcessBuilder 执行命令 关键点 String入参会被转化为String[ ]数组 Runtime最终通过ProcessBuilder执行命令 StringTokenizer会对特殊字符进行处理,可能影响命令执行 2. ProcessBuilder 分为两大类: 入参为List类型: public ProcessBuilder(List command) 入参为String可变参数类型: public ProcessBuilder(String... command) 执行流程 String可变参数会被转化为List类型 进入 start() 方法后,存在 prog 变量(即cmdarray[ 0 ]的值) 如果有SecurityManager,会进入 checkExec() 最终进入 ProcessImpl.start() Linux下调用 java.lang.UNIXProcess 类执行命令 通过 forkAndExec 为命令创建环境 四、命令注入利用技术 1. Shell符号利用 要执行系统管道(|)、;、&&等符号,必须创建shell来执行命令: | 符号/形式 | 说明 | |----------|------| | cmd1\|cmd2 | 管道,前一个命令结果重定向给后一个命令 | | cmd1;cmd2 | 多语句分隔符 | | cmd1&&cmd2 | 逻辑与,两个都为真时返回真 | | cmd1\|\|cmd2 | 逻辑或,有一个为真返回真 | | {cmd1,cmd2} | 花括号扩展,命令依次执行 | | cmd1,cmd2 | 反引号命令替换 | | $(cmd1,cmd2) | 变量替换 | 2. 绕过技术 当使用String入参时,可以利用bash的base64编码绕过特殊字符限制: 五、Java Instrument技术监控命令执行 1. 原理 通过Java Instrument技术监控 java.lang.UNIXProcess 类的构造函数,可以捕获执行的命令参数。 2. 实现代码片段 3. 使用方式 在VM Options中添加: 注意:路径必须加双引号,否则会出现异常。 4. 检测结果 成功运行后会输出: 执行命令时会输出类似: 六、安全对比分析 Runtime.getRuntime().exec()不同入参对比 | 特性 | String入参 | String[ ]入参 | |------|-----------|-------------| | 特殊字符处理 | 会被StringTokenizer过滤 | 不进行过滤 | | 命令执行成功率 | 可能失败 | 成功率更高 | | 安全风险 | 相对较低 | 风险更高 | 实际测试结果 String类型入参: 执行失败(空格被过滤) String[ ]类型入参: 执行成功 七、防御建议 避免直接使用用户输入拼接命令 使用String[ ]或List类型而非String类型传递命令 对用户输入进行严格过滤和验证 使用白名单限制可执行的命令 实施最小权限原则,限制执行命令的权限 八、参考资源 Java命令执行深入研究 Java执行管道命令 Java Instrument技术 Javassist使用指南 源码地址: 演示环境:https://gitee.com/cor0ps/java-range.git Java Agent:https://gitee.com/cor0ps/Agent.git