Java命令注入原理并结合Java Instrument技术
字数 2296 2025-08-15 21:31:09
Java命令注入原理与Java Instrument技术应用
一、命令注入概述
命令注入是指恶意用户构造特殊请求,对执行系统命令的功能点进行注入攻击,从而达到执行任意系统命令的效果。在Java中,常见的命令执行方式包括:
java.lang.Runtime.getRuntime().exec()java.lang.ProcessBuildercom.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为命令创建环境
- Linux下调用
四、命令注入利用技术
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过滤 | 不进行过滤 |
| 命令执行成功率 | 可能失败 | 成功率更高 |
| 安全风险 | 相对较低 | 风险更高 |
实际测试结果
-
String类型入参:
unixprocess_result:prog:/bin/sh cmd:-cecho"xxxx">/tmp/xxx- 执行失败(空格被过滤)
-
String[]类型入参:
unixprocess_result:prog:/bin/sh cmd:-cecho "xxx">/tmp/yyy- 执行成功
七、防御建议
- 避免直接使用用户输入拼接命令
- 使用String[]或List类型而非String类型传递命令
- 对用户输入进行严格过滤和验证
- 使用白名单限制可执行的命令
- 实施最小权限原则,限制执行命令的权限
八、参考资源
源码地址:
- 演示环境:https://gitee.com/cor0ps/java-range.git
- Java Agent:https://gitee.com/cor0ps/Agent.git