从Java源码来看Native命令执行方法

开发 前端
在Java中盛行的反序列化漏洞中,如果将RCE的功能简单的通过Runtime.getRuntime().exec(cmds)这种结构来进行实现可能大概率也不能达到我们的目的,所以探索一下Runtime的底层实现,使用更加底层且复杂的调用来进行RCE功能的实现相对来说更加的可行

概述

在RASP等安全产品防护严密的现在,普通的寻找Runtime.getRuntime().exec(cmds)的调用已经成为了一件不现实的事情。

同样的,在Java中盛行的反序列化漏洞中,如果将RCE的功能简单的通过Runtime.getRuntime().exec(cmds)这种结构来进行实现可能大概率也不能达到我们的目的,所以探索一下Runtime的底层实现,使用更加底层且复杂的调用来进行RCE功能的实现相对来说更加的可行。

这里主要是对Java中多种命令执行的方式跟踪源码进行原理分析、构造利用代码、集成自研工具。

前置

首先需要对Java中的反射机制有着基本的掌握

通过反射的方式,我们可以获取到任何类的构造方法,类方法,成员变量,且能够获取对应类对象进行对应方法的调用等等目的

  • 获取Class类对象对于类对象的获取,主要可以通过Class.forName / loadClass的方式来获取,值得注意的是,在调用Class.forName进行类的加载的时候,将会调用static方法
Class.forName("java.lang.Runtime")

image-20230418202156861.png

  • 获取对应类的构造方法对于获取类的构造方法,主要可以通过getConstructor或者getDeclaredConstructor这两种方法来进行实现

image-20230418202554295.png

两者的区别主要是前者不能够反射获取private修饰的构造方法,而后者能够获取。

所以通常使用后者进行构造函数的获取,传入的参数就是对应构造方法的参数类。

clazz.getDeclaredConstructor(type.class)
clazz.getConstructor(type.class)
  • 反射获取成员变量和构造方法类似的,存在有getField和getDeclaredField两个不同的获取方法,区别和构造函数类似。
clazz.getField(name)
clazz.getDeclaredField(name)
  1. 反射获取类方法同样具有getMethod和getDeclaredMethod两种。
  2. ...............

一个普通的命令执行是

Runtime.getRuntime().exec("calc");

如果使用反射机制可以是

Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(runtime, "calc");

或者是其他的一些使用反射机制的变形。

command

跟踪Runtime

首先我们跟踪Runtime执行命令的过程。

image-20230418203905152.png

在这里接收一个String类型的参数,调用exec的另一个重在方法对参数进行处理,将其通过分隔符,将其封装成了数组对象(这里就是一个字符串)。

image-20230418204046203.png

之后通过参数是String[]类型的另一个重载方法,通过调用ProcessBuilder类的方法进行执行。

image-20230418204233337.png

在ProcessBuilder#start方法中,将命令传递给了ProcessImpl#start方法进行处理

image-20230418204613461.png

windows

在windows中主要是在ProcessImpl的构造方法中调用了create方法。

image-20230418210009655.png

。这个create方法是通过win32的方式创建了一个进程。

image-20230418210027743.png

linux

在linux下,在ProcessImpl#start的调用中将会创建一个UNIXProcess对象并返回

image-20230418213534618.png

image-20230418213547533.png

在UNIXProcess类的构造方法中,调用了forkAndExec这个native方法

image-20230418213735497.png

创建了一个一个进程,并返回了对应进程的pid

构造命令执行

ProcessBuilder#start

在上面的流程分析中,知道了在Runtime.getRuntime().exec()方法调用的下一层就是使用ProcessBuilder#start方法。

如果hook掉了我们可以通过使用ProcessBuilder类来进行命令执行的构造。

new ProcessBuilder("calc").start();

或者使用反射的思路构造

//method_1
        Class pro = Class.forName("java.lang.ProcessBuilder");
        ((ProcessBuilder) pro.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();

        //method_2
        Class pro = Class.forName("java.lang.ProcessBuilder");
        pro.getMethod("start").invoke(pro.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));

        //method_3
        Class pro = Class.forName("java.lang.ProcessBuilder");
        ((ProcessBuilder) pro.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();

        //method_4
        Class pro = Class.forName("java.lang.ProcessBuilder");
        pro.getMethod("start").invoke(pro.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));

ProcessImpl

从上面的分析可以知道,在windows环境下的JDK。

ProcessImpl类的构造方法将会调用create方法执行native方法进行命令执行。

image-20230418215221068.png

所以我们只需要反射获取ProcessImpl类的构造方法并实例化就会执行我们的恶意逻辑。

UNIXProcess

上面是针对windows的方式

针对linux,在前面的分析中知道主要是在其start方法中调用了UNIXProcess类的构造方法。

image-20230419094058481.png

执行forkAndExec这个native方法进行命令执行。

other

甚至于,我们知道最后主要是在create方法(windows)、forkAndExec方法(linux)中执行命令,我们同样可以通过反射这两个方法进行命令执行。

本文作者:superLeeH, 转载请注明来自

责任编辑:武晓燕 来源: FreeBuf.COM
相关推荐

2023-07-10 07:40:14

2020-06-15 08:07:30

命令Linux字符

2022-12-30 09:08:29

JDK源码工具

2014-09-26 15:41:51

2009-09-04 09:36:17

Java调用

2020-05-07 19:46:18

LinuxMySQLMariaDB

2011-06-07 14:47:19

JAVA

2010-04-20 14:15:49

负载均衡产品

2024-06-12 13:36:24

2023-12-25 16:10:34

Linuxdconfig命令

2012-07-04 13:28:51

2022-09-27 10:07:01

要使用 source

2010-08-31 14:01:00

DB2SQL脚本

2021-07-14 09:48:15

Linux源码Epoll

2023-04-04 13:40:36

2021-04-15 08:15:27

Vue.js源码方法

2021-04-15 09:07:52

hotspotJavaC++

2021-09-08 10:47:33

Flink执行流程

2019-07-22 15:59:21

2015-04-08 09:34:13

点赞
收藏

51CTO技术栈公众号