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

admin 2025年4月22日09:35:46评论3 views字数 4683阅读15分36秒阅读模式

0x01 漏洞简介

在Java代码审计中,命令执行漏洞指应用程序未对用户输入进行严格过滤,直接将外部可控参数拼接到系统命令中执行,导致攻击者可注入恶意命令并获取服务器控制权。其核心原理是开发者误用危险函数执行系统命令时,未对用户输入的参数进行安全校验或转义,导致用户可通过构造特殊字符(如管道符|、命令分隔符;)或参数注入(如${}表达式)将恶意指令与原始命令拼接。该漏洞常出现在参数动态拼接的场景,且受操作系统特性影响。

Windows系统命令注入表

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

Linux系统命令注入表

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

0x02 Java命令执行方法

2.1 Runtime.exec()

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

// 方法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);
Java代码审计之命令执行漏洞详解

从上面可知exec()方法在执行命令的时候,传入的参数有字符串和字符串数组两种形参,将这种方法封装到靶场上且限制必须包含ping来执行观察其效果,分析可利用的方式。

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

首先使用exec(String)执行ping命令,正常返回结果,执行其他命令则异常

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

使用&进行命令拼接,但是发现也异常了

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

切换为exec(String[])时,直接使用&拼接,发现两条命令都可以被执行。

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

思考下,为什么exec(String)使用了&会异常,而exec(String[])不会呢?来动态调试exec(String)的调用链加深理解,开始断点调试,发现调用的是exec(command, null, null);

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

继续跟进,发现这里最终也去调用的是exec(cmdarray, envp, dir),那为什么最终的命令执行的结果不一样呢,分析下面的代码可以发现,传入的command是经过了StringTokenizer进行处理的,那么问题的关键就是StringTokenizer是怎么去处理的

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

跟进StringTokenizer,发现这里对传入的命令做了一个分割,将传入的字符串str按照默认的空白分隔符(包括空格、制表符t、换行符n、回车符r和换页符f)进行分割,将字符串拆解为一系列连续的子字符串

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

String[] cmdarray = new String[st.countTokens()];的作用是创建一个字符串数组cmdarray,其长度等于StringTokenizer对象st中分割后的子字符串数量,最终命令变成了["calc&ping","baidu.com"]传入了exec(cmdarray, envp, dir)

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

最终交给ProcessBuilder去执行,命令被错误的分割成<font style="color:rgba(0, 0, 0, 0.88);">["calc&ping", "baidu.com"]</font>,Java会尝试执行第一个参数calc&ping,视为可执行程序名,calc&ping不是一个有效程序,因此抛出IOException: Cannot run program "calc&ping"

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

如果是exec(String[])呢?最终命令是["cmd", "/c", "calc&ping", "baidu.com"],Java调用cmd.exe,并传递参数/c和后续参数calc&ping baidu.comcmd.exe会将calc&ping baidu.com整体视为一个字符串,并按照 Shell 规则解析其中的&符号,将calc&ping baidu.com解析为两条命令,故而执行成功。

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

exec(String)怎么去进行漏洞利用呢?可以利用Shell的解析逻辑实现命令注入,直接拼接cmd /c即可

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

2.2 ProcessBuilder

ProcessBuilder命令执行漏洞的核心在于通过ProcessBuilder类直接构造并执行系统命令时,若未对用户输入参数进行严格过滤或拆分,攻击者可注入恶意命令实现任意代码执行,ProcessBuilder不支持以字符串形式传入命令,只能拆分成List或者数组的形式传入。

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

从靶场漏洞代码中可知道,是直接将用户输入的参数传入到ProcessBuilder进行命令执行,从而攻击者可以拼接恶意命令造成漏洞

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

跟进ProcessBuilder.start()方法,ProcessBuilder.start()是Java中启动外部进程的核心方法,其底层实现最终通过调用ProcessImpl.start()完成操作系统级别的进程创建。

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

2.3 ProcessImpl

ProcessImpl是 Java 中Process抽象类的具体实现类 ,其设计目的是为ProcessBuilder.start()方法提供底层支持,用于创建和管理操作系统进程 。由于ProcessImpl的构造函数被声明为private,无法直接通过new实例化,开发者通常需通过ProcessBuilderRuntime.exec()间接调用其功能 。若需直接操作ProcessImpl,必须通过反射技术绕过访问限制。ProcessImplstart()方法是静态的,可通过反射获取该方法并传入参数(如命令数组、环境变量等)创建进程。

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);
}
}

靶场效果

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

0x03 代码审计思路

学习完Java命令执行的一些方式,就可以知道需要审计哪些危险函数了,是否手动创建了shell,然后关注参数是否可控,若不可控则无法命令注入,若参数可控注意空格等会导致分割,可编码绕过(如${IFS}代替空格)。核心思路是识别所有可能导致命令注入的代码路径,重点围绕“参数可控性”和“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解释器,如new ProcessBuilder("sh", "-c", userCmd)

  • 检查参数分割逻辑,如使用exec(String command)传递单个字符串命令

  • 检查反射绕过,通过反射直接调用 <font style="color:rgba(0, 0, 0, 0.88);background-color:rgb(246, 246, 246);">ProcessImpl.start()</font>,绕过参数安全检查。

0x04 防御与修复

1、避免执行系统命令

优先使用Java原生API替代直接执行系统命令。例如:删除文件使用File.delete()而非rm命令 ,网络请求使用通过HttpClient而非curl命令等等,规避命令注入风险,同时提升跨平台兼容性。

2、无法避免系统命令执行时,优先使用Runtime.exec(String[] cmdarray)ProcessBuilder的数组传参方式,避免将命令与参数拼接为字符串

exec("cmd /c " + userInput)  // 容易被注入

exec(new String[]{"cmd", "/c", fixedCommand})  // 如果命令有白名单限制,相比上面安全

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

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

exec(new String[]{"/bin/ls", dir});   // 安全

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

String safeInput = ESAPI.encoder().encodeForOS(new WindowsCodec(), userInput);  
来源:https://www.freebuf.com/ 感谢【xmsec

原文始发于微信公众号(船山信安):Java代码审计之命令执行漏洞详解

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月22日09:35:46
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java代码审计之命令执行漏洞详解https://cn-sec.com/archives/3984822.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息