Java代码审计的第四章- 命令执行漏洞

       定义

  • 命令执行漏洞是指应用有时需要调用一些执行系统命令的函数,如果系统命令代码未对用户可控参数做过滤,则当用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击。

•          原因

  • 命令执行攻击主要存在以下几个危害:继承Web服务程序的权限去执行系统命令或读/写文件,反弹shell,控制整个网站甚至控制服务器,进一步实现内网渗透。

•          黑盒测试

  • 在黑盒测试中,我们需要以一下这几个方向去探测命令执行漏洞
    • 参数注入测试: 输入特殊字符如单引号、双引号、分号、反斜杠等来检查应用程序是否对用户输入进行了适当的过滤和验证。尝试输入:
    • graphqlCopy code’; whoami; #
    • 路径遍历: 尝试在文件路径中使用”../”来尝试访问其他目录。检查应用程序是否正确地处理路径,防止目录遍历攻击。尝试输入:
    • bashCopy code../../../../../etc/passwd
    • 操作系统命令: 尝试在输入中插入操作系统命令,并观察应用程序的响应。尝试输入:
    • bashCopy code; cat /etc/passwd
    • 注入变量: 检查应用程序是否会在后台解释执行用户输入的变量。尝试在URL参数、表单字段等中注入变量:
    • bashCopy codeinput=$(ls); echo $input
    • 系统函数调用: 尝试通过在输入中插入系统函数调用,观察应用程序的响应。尝试输入:
    • bashCopy code`id`
    • 文件上传功能: 如果应用程序具有文件上传功能,攻击者可能会试图上传恶意脚本文件,然后通过执行该文件来实现命令执行。攻击者可能会尝试上传包含系统命令的恶意脚本,然后执行它,从而在服务器上执行命令。为了验证此漏洞,您可以按照以下步骤进行:
      • 尝试上传一个包含命令的恶意脚本文件(例如 PHP、ASP 或 JSP 文件)。
      • 确保上传的文件类型和大小受到服务器限制的约束。
      • 检查上传文件的存储位置和访问路径,看是否可以直接访问。
      • 尝试通过访问上传文件的URL来执行其中的命令,例如:http://example.com/uploads/malicious_script.php。
    • 特殊字符绕过: 某些应用程序会过滤或转义特殊字符,以防止命令注入。攻击者可能会尝试使用各种编码、转义和编码方式来绕过这些过滤机制。为了验证此漏洞,您可以:
      • 尝试使用不同编码和转义方式,如URL编码、Unicode编码等,来插入恶意命令。
      • 观察应用程序的响应是否显示了解码后的恶意命令。
    • 限制字符集: 某些应用程序可能会限制输入的字符集,以过滤掉特殊字符。攻击者可能会尝试使用其他字符集来绕过这些限制。为了验证此漏洞,您可以:
      • 尝试使用其他字符集,如全角字符、特殊符号等,来构造恶意命令。
    • 错误消息泄露: 在某些情况下,应用程序可能会返回有关命令执行错误的详细错误消息,从而为攻击者提供有关应用程序配置和漏洞的信息。为了验证此漏洞,您可以:
      • 尝试构造一个引起命令执行错误的输入,然后观察应用程序返回的错误消息是否透露了敏感信息。
      • 利用错误消息中的信息来更进一步地攻击目标系统。
    • 环境变量: 在某些情况下,应用程序可能会解释和执行环境变量。攻击者可能会尝试在输入中插入环境变量,从而执行恶意命令。为了验证此漏洞,您可以:
      • 尝试在输入中使用环境变量,并观察应用程序的响应,看是否成功执行了相关命令。
      • 真实环境下类似于
        • 假设我们有一个虚拟的应用程序,该应用程序接受用户输入并执行一个简单的命令。用户输入会被作为环境变量传递给执行的命令。应用程序的代码可能类似于以下内容:pythonCopy codeimport os

          def execute_command(command):
          system(command)

          user_input = input("Enter a command: ")
          execute_command(user_input)

        • 在这种情况下,应用程序没有对用户输入进行适当的过滤或转义,这可能导致命令执行漏洞。假设攻击者尝试输入恶意的环境变量来执行一个恶意命令:
        • arduinoCopy codeUSER_INPUT='; ls'
        • 当这个输入被传递给应用程序并作为环境变量执行时,它可能会执行以下命令:
        • luaCopy codeos.system('; ls')
        • 这可能导致应用程序执行了 ls 命令,列出当前目录的内容。

 fuzz

  • 在绕过一些参数限制时,我们可以参考一下思路
  • | &、%0a、%0d、%0A、%0D #注意URL编码不能在网页中使用
     (1)去掉ls,发现直接输入管道符也报敏感字符。这里可以使用如下绕过方式:
     &、%0a、%0d、%0A、%0D #注意URL编码不能在网页中使用
     l”s、l””s、l\s
  • (1)猜测这里过滤了cat命令,这里我们可以尝试如下命令进行绕过:
       tac、more、less、head、tail、ca\t、ca”t、ca””t
  • (2)过滤了php:
      、key.、k{e}y.php、k?k.txt
  • 0.0.1%0al\s -la ../k?y.* 或者 127.0.0.1%0al\s -la ../
     chmod命令加权限
       chmod 777 ../*
       这里应该是过滤了od字符,所以绕过时需要在od中间加\,尝试绕过:

 白盒审计

  • 在PHP开发语言中有system()、exec()、shell_exec()、eval()、passthru()等函数可以执行系统命令。
  • 在Java开发语言中可以执行系统命令的函数有
    • getRuntime.exec
  • getRuntime.exec是在Java1.5之前提供的,Java1.5之后则提供了Pro-cessBuilder类来构建进程
    • Process-Builder.start
–          ProcessImpl
–          Groovy执行命令
  • 在审计过程中,需要检查发现如下代码
•          ProcessBuilder方式
  • 原理
    • lang.ProcessBuilder类用于创建操作系统进程,每个ProcessBuilder实例管理一个进程属性集。start()方法利用这些属性创建一个新的Process实例,可以利用ProcessBuilder执行命令。
    • 正常使用
      • 执行结果为
      • b6c31d01d420240606160220

    • // new ProcessBuilder(command).start()
      // 功能是利用ProcessBuilder执行ls命令查看文件,但攻击者通过拼接; & |等连接符来执行自己的命令。

      public static String processbuilderVul(String filepath) throws IOException {
      String[] cmdList = {"sh", "-c", "ls -l " + filepath};
          ProcessBuilder pb = new ProcessBuilder(cmdList);
          redirectErrorStream(true);
          Process process = pb.start();

          // 获取命令的输出
          InputStream inputStream = process.getInputStream();
          BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
          String line;
          StringBuilder output = new StringBuilder();
          while ((line = reader.readLine()) != null) {
              output.append(line).append("\n");
          }
          return output.toString();
      }                

    • 在上述案例中,输入可控,并且没有过滤,使用; 做分隔符,执行了后面的命令
    • 7142b8354c20240606160316

  • 修复
  • 直接自定义黑名单过滤分隔符
  • // 自定义黑名单,这里过滤了常见的管道符,可自行添加
  • public static boolean checkOs(String content) {
    String[] black_list = {"|", ",", "&", "&&", ";", "||"};
        for (String s : black_list) {
            if (content.contains(s)) {
                return true;
            }
        }
        return false;

05266877d720240606160354

 

 Runtime方式
  • 原理
  • 当利用exec()进行命令执行时,如果参数没有经过过滤就可能通过命令拼接符进行命令拼接执行多条命令
  • 典型代码
  • 传入cmd却没有任何过滤,命令会被执行
  • // Runtime.getRuntime().exec(cmd)

    public static String vul(String cmd) {
    StringBuilder sb = new StringBuilder();
        try {
            Process proc = Runtime.getRuntime().exec(cmd);
            InputStream fis = proc.getInputStream();
            InputStreamReader isr = new InputStreamReader(fis);
            BufferedReader br = new BufferedReader(isr);
    ...

  • 命令被直接执行
  • 1ed045465c20240606160422

  • 还有其他代码形式
  • 95106a90f420240606160450

  • 过程也如上 通过cmd参数输入相关命令,通过“Run-time.getRuntime().exec”执行命令,此处的cmd参数可控而且没有经过任何过滤就传入exec()方法中执行,因此造成了命令执行漏洞。我们先看一下程序输入正常的cmd参数时是如何执行系统命令的,输入“cmd=ls”,返回了执行ls命令后的数据信息
  • 560d4514bc20240606160520

  • 同时也可以被拼接 当输入“cmd=ls;cat/etc/passwd”后,返回“java.io.IOException:Cannot run program”ls;id”:error=2,No such file or directory”这个错误信息
  • 1e1b1bee9a20240606160545

  • 主要原因是 Java通过“Runtime.getRuntime().exec”执行命令并不是启动一个新的shell,所以就会有报错信息,需要重新启动一个shell才能正常执行此命令 启动一个新的shell执行多个命令,输入“cmd=sh-c ls;id”,发现命令执行成功,返回了ls和id命令的信息
  • 914fbb519f20240606160609

  • 同时会发现输入 输入“cmd=sh-c ls;cat/etc/passwd”,发现浏览器一直在请求的状态,无法正常执行此命令
  • 主要原因是 如果exec方法执行的参数是字符串参数,参数中的空格会经过StringTokenizer处理,处理完成后会改变原有的语义导致命令无法正常执行。要想执行此命令要绕过StringTokenizer才可以,只要找到可以代替空格的字符即可,如${IFS}、$IFS$9等
  • 输入“cmd=sh-c ls;cat${IFS}/etc/passwd”后会有以下报错
  • 4d7cf3347120240606160634

  • 也就是说,我们的请求中包含无效的字符。查看RFC规范知,url中只允许包含英文字母(a~z和A~Z)、数字(0~9)、“-_.~”这4个特殊字符,以及保留字符(!*’();:@&=+$,/?#[])共84个字符,刚才的请求中出现了{}大括号,导致了报错,如图
  • 124d35f05e20240606160657

  • 对{}进行url编码,输入“cmd=sh%20-c%20ls;cat$%7BIFS%7D/etc/passwd”,发现可以正常执行ls和cat/etc/passwd两个命令
  • 72dab1b52720240606160732

  • 直接修复,可以设立输入白名单
  • // 使用白名单替换黑名单。黑名单需要不断更新,而白名单只需要指定允许执行的命令,更容易维护。

    public static String safe(String cmd) {
    // 定义命令白名单
        Set<String> commands = new HashSet<\>();
        add("ls");
        commands.add("pwd");

        // 检查用户提供的命令是否在白名单中
        String command = cmd.split("\\s+")[0];
        if (!commands.contains(command)) {
            return "命令不在白名单中";
        }
        ...

  • 796f24079f20240606160809

  • 也可以使用 使用位于mail的可用Java API

 ProcessImpl

  • // ProcessImpl 是更为底层的实现,Runtime和ProcessBuilder执行命令实际上也是调用了ProcessImpl这个类
    // ProcessImpl 类是一个抽象类不能直接调用,但可以通过反射来间接调用ProcessImpl来达到执行命令的目的

经典漏洞写法

public static String vul(String cmd) throws Exception {
    // 首先,使用 Class.forName 方法来获取 ProcessImpl 类的类对象
    Class clazz = Class.forName("java.lang.ProcessImpl");
 
    // 然后,使用 clazz.getDeclaredMethod 方法来获取 ProcessImpl 类的 start 方法
    Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
 
    // 使用 method.setAccessible 方法将 start 方法设为可访问
    method.setAccessible(true);
 
    // 最后,使用 method.invoke 方法来调用 start 方法,并传入参数 cmd,执行命令
    Process process = (Process) method.invoke(null, new String[]{cmd}, null, null, null, false);
}

脚本引擎代码注入

原理

 // 通过加载远程js文件来执行代码,如果加载了恶意js则会造成任意命令执行

//远程恶意js: var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec(“open -a Calculator”);}
// ⚠️ 在Java 8之后移除了ScriptEngineManager的eval

例如

public void jsEngine(String url) throws Exception {
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
    Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
    String payload = String.format("load('%s')", url);
    engine.eval(payload, bindings);
}

2d09fce4f820240606160931

Groovy

原理

  基于 Groovy 造成的命令执行通常是通过Groovy代码中的漏洞或不安全的代码执行来实现的

import groovy.lang.GroovyShell;
@GetMapping("/groovy")
public void groovy(String cmd) {
    GroovyShell shell = new GroovyShell();
    shell.evaluate(cmd);
}
     

 

Java代码审计的第四章- 命令执行漏洞-棉花糖会员站
Java代码审计的第四章- 命令执行漏洞
此内容为付费阅读,请付费后查看
1积分
付费阅读
已售 8
© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 共1条

请登录后发表评论

    请登录后查看评论内容