Java中DoS to RCE失败的探索

写一篇水文,没有什么技术,记录一次失败的探索

前言

拒绝服务漏洞本身是鸡肋洞,但研究如何通过DoS到RCE是有意义的,我进行了一些尝试,虽然最终失败,但我认为有必要记录一下过程,算是学习的笔记

Java理论上不存在通过拒绝服务到RCE的可能性,因为无法操作底层,不能通过栈溢出写shellcode

但我找到了鸡肋的办法,确实能够从DoS到RCE,虽说鸡肋,但也可以拿来写一下

定位JVM的危险函数

hotspotos模块的C++代码,找到system()函数的调用

int os::fork_and_exec(char* cmd) {
  const char * argv[4] = {"sh", "-c", cmd, NULL};
  // ...
  NOT_IA64(syscall(__NR_execve, "/bin/sh", argv, environ);)
  IA64_ONLY(execve("/bin/sh", (char* const*)argv, environ);)
  // ...
}

全局搜索fork_and_exec函数,在vmError.cpp找到一处有意思的地方

void VM_ReportJavaOutOfMemory::doit() {
  static char buffer[O_BUFLEN];
  // ...
  char* cmd;
  const char* ptr = OnOutOfMemoryError;
  while ((cmd = next_OnError_command(buffer, sizeof(buffer), &ptr)) != NULL){
    // ...
    os::fork_and_exec(cmd);
  }
}

发现命令字符串来自于OnOutOfMemoryError变量,在globals.hpp中找到:

在程序出现OOM的时候,执行用户自定义的某些脚本或命令

product(ccstrlist, OnOutOfMemoryError, "",                                
        "Run user-defined commands on first java.lang.OutOfMemoryError")

通过DoS触发RCE

阅读Oracle相关文档后发现了触发的方式

java -XX:OnOutOfMemoryError=calc.exe com.test.Start

简单写一个OOME的类(模拟实际中可能出现的DoS漏洞)

public class Start {
    public static void main(String[] args) throws Exception {
        int[] a = new int[Integer.MAX_VALUE];
    }
}

成功通过DoS触发RCE

寻找修改参数方式

以上内容是启动JVM时修改参数,我想找到一种在运行时修改参数的方式

昨天橙子酱师傅分享了一篇文章:如何在运行时修改JVM参数

我尝试阅读JVM源码找到其中的说明(在globals.hpp中)

// manageable flags are writeable external product flags.
//    They are dynamically writeable through the JDK management interface
//    (com.sun.management.HotSpotDiagnosticMXBean API) and also through JConsole.
//    These flags are external exported interface (see CCC).  The list of
//    manageable flags can be queried programmatically through the management
//    interface.

其中提到com.sun.management.HotSpotDiagnosticMXBean类可以修改参数

发现这是一个接口,提供了dumpHeapget/set参数的方法

@Exported
public interface HotSpotDiagnosticMXBean extends PlatformManagedObject {
    void dumpHeap(String var1, boolean var2) throws IOException;
    List<VMOption> getDiagnosticOptions();
    VMOption getVMOption(String var1);
    void setVMOption(String var1, String var2);
}

使用该接口的实现类修改参数

new HotSpotDiagnostic().setVMOption("OnOutOfMemoryError","calc.exe");

报错如下

Exception in thread "main" java.lang.IllegalArgumentException: VM Option "OnOutOfMemoryError" is not writeable
	at sun.management.HotSpotDiagnostic.setVMOption(HotSpotDiagnostic.java:94)
	at com.example.testenv.Start.main(Start.java:10)

失败的绕过

跟踪上文的报错信息,发现某处判断了修改参数类型不能是String

if (!(var4 instanceof String)) {
    throw new IllegalArgumentException("VM Option \"" + var1 + "\" is of an unsupported type: " + var4.getClass().getName());
}

Flag.setStringValue(var1, var2);

通过反射直接操作Flag类(该类的私有的)

Class<?> clazz = Class.forName("sun.management.Flag");
Method method = clazz.getDeclaredMethod("getFlag", String.class);
method.setAccessible(true);
Object flag = method.invoke(null,"OnOutOfMemoryError");
Method getVal = clazz.getDeclaredMethod("getValue");
getVal.setAccessible(true);
String value = (String) getVal.invoke(flag);
// 成功打印之前设置的calc.exe
System.out.println(value);

尝试通过反射修改

Class<?> clazz = Class.forName("sun.management.Flag");
Method setMethod = clazz.getDeclaredMethod("setStringValue", String.class, String.class);
setMethod.setAccessible(true);
setMethod.invoke(null,"OnOutOfMemoryError","notepad.exe");

报错如下

Caused by: java.lang.IllegalArgumentException: This flag is not writeable.
	at sun.management.Flag.setStringValue(Native Method)
	... 5 more

错误来自于native方法

    static synchronized native void setStringValue(String var0, String var1);

报错的底层原理

找到JNI相关的代码

JNIEXPORT void JNICALL
Java_sun_management_Flag_setStringValue
  (JNIEnv *env, jclass cls, jstring name, jstring value)
{
   jvalue v;
   v.l = value;

   jmm_interface->SetVMGlobal(env, name, v);
}

flagJVM中已经固定,无法修改,所以在Java层面无论如何也是无法修改的

JVM_ENTRY(void, jmm_SetVMGlobal(JNIEnv *env, jstring flag_name, jvalue new_value))
  // ...
  // 如果flag不可写则抛出IllegalArgumentException
  if (!flag->is_writeable()) {
    THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(),
              "This flag is not writeable.");
  }
  // ...
JVM_END