JAVA 安全学习笔记(二)JAVA 命令执行
九涅·烧包包儿 / / 程序员 / 阅读量
>  Author: shaobaobaoer
>  Codes : https://github.com/ninthDevilHAUNSTER/JavaSecLearning
>  Mail: shaobaobaoer@126.com
>  WebSite: shaobaobaoer.cn
>  Time: Thursday, 24. July 2020 07:29PM

JAVA 命令执行类

Java原生提供了对本地系统命令执行的支持,对于开发者来说执行本地命令来实现某些程序功能(如:ps 进程管理、top内存管理等)是一个正常的需求,而对于黑客来说本地命令执行是一种非常有利的入侵手段。

Runtime

    /**
     * 利用 Runtime 执行命令
     *
     * @throws IOException
     */
    public static void execCommandRuntime() throws IOException {
        System.out.println(IOUtils.toString(Runtime.getRuntime().exec("cmd /c dir").getInputStream(), "GBK"));
    }

故意引入一个错误,可以查看Runtime的调用栈。内容较多,就不贴出来了,最终可以查到,Runtime.exec调用的最低层函数为java.lang.ProcessImpl.create,打开IDEA可以看到较为清晰的调用链(选中create函数,然后Navigate》Call Hierarchy)

图片标题
PS:图中最后一项没有展开,展开中有一项就是 Runtime.exec

综上,Runtime()的调用是这样的。

  1. Runtime.exec(xxx)
  2. java.lang.ProcessBuilder.start()
  3. new java.lang.ProccessImpl(xxx)
  4. new ProccessImpl()调用操作系统级别create(Windows)执行命令并返回createPID

JDK9的时候把UNIXProcess合并到了ProcessImpl当中了,但是我并没有找到javasec所说的forkAndExecnative方法,我推测Windows系统没有这个方法。

ProcessBuilder

    /**
     * 利用 ProcessBuilder 执行命令
     *
     * @throws IOException
     */
    public static void execCommandProcessBuilder() throws IOException {
        System.out.println(IOUtils.toString(new ProcessBuilder("cmd /c dir").start().getInputStream(), "GBK"));
    }

从之前Runtime的调用链可以发现,Runtime实际上是对ProcessBuilder进一步分装而已。

反射 JAVA 命令执行类

反射 Runtime类,并利用更加迷惑性的写法

之前有讨论过反射的相关写法,现在我们可以把反射Runtime写的更加迷惑性一些,利用 new String(new byte[]{})的方法动态创建String

    /**
     * 动态调用 Runtime 执行命令
     */
    public static void execCommandWithoutRuntime() {
        try {
            // 获取Runtime类对象
            String cmd = "cmd /c dir";
            // 获取 Runtime类

            Class<?> evil_class = Class.forName(new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101}));
            // 获取 .getRuntime方法
            Method getRuntime = evil_class.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101}));
            // 执行 .getRuntime方法 由于这是个静态方法,无输入参数,所以第一个参数为Null
            Object obj0 = getRuntime.invoke(null);
            // 获取 .exec 方法
            Method exec = evil_class.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class);
            // 执行 Runtime().getRuntime().exec() 方法 该方法是动态方法,第一个为类名 Runtime()类,第二个参数为字符串
            Object obj1 = exec.invoke(obj0, cmd);
            // 获取 .getInputStream 方法,
            Method getInputStream = obj1.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109}));
            getInputStream.setAccessible(true);
            Object obj2 = getInputStream.invoke(obj1);
            System.out.println(IOUtils.toString((InputStream) obj2, "GBK"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

反射 ProccessImpl 类

在之前分析Runtime的调用链时候,就已经发现了Runtime的核心是调用了ProcessImpl类下的start方法来执行命令,而这个类是final的,意味着只能通过反射来执行

final class ProcessImpl extends Process {...}

但是没有关系,可以通过setAccessible()来强行调用该类。另外,通过阅读源码,还能够发现ProcessImpl.start函数是一个静态函数,其接口如下所示

    static Process start(String cmdarray[],
                         java.util.Map<String,String> environment,
                         String dir,
                         ProcessBuilder.Redirect[] redirects,
                         boolean redirectErrorStream)

那么如果要反射,只要照虎画猫,构造好Method接口,就可以反射了。具体实现代码如下所示

    public static void execCommandProcessImpl() throws IOException {
        try {
            String[] cmds = new String[]{"cmd", "/c", "dir"}; // 这里不能写一起
            Class<?> clazz = Class.forName("java.lang.ProcessImpl");
            // 按照start的样子构造方法
            Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
            method.setAccessible(true);
            Process e = (Process) method.invoke(null, cmds, null, ".", null, true);
            System.out.println(IOUtils.toString(e.getInputStream(), "GBK"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

利用JNI来命令执行

通过JNI接口C/C++和Java可以互相调用(存在跨平台问题)。Java可以通过JNI调用来弥补语言自身的不足(代码安全性、内存操作等)。

关于如何去生成一个动态链接库,我不在该文中赘述了。详细可以参考代码库中的该篇REAME

我非常简单得写了一个 CommandExecution 类。并生成了动态链接文件 cmd.dll

package jni_sec;

public class CommandExecution {
    public static native String exec(String cmd);
}

之后,可以调用如下代码来完成JNI的命令执行。

/**
     * 利用JNI文件,调用动态链接库来执行结果
     *
     * @throws Exception
     */
    public static void execCommandJNIFile() {
        String cmd = "whoami";// 定于需要执行的cmd

        try {
            ClassLoader loader = new ClassLoader(JavaLocalCmdExec.class.getClassLoader()) {
                @Override
                protected Class<?> findClass(String name) throws ClassNotFoundException {
                    try {
                        return super.findClass(name);
                    } catch (ClassNotFoundException e) {
                        return defineClass(COMMAND_CLASS_NAME, COMMAND_CLASS_BYTES, 0, COMMAND_CLASS_BYTES.length);
                    }
                }
            };

            // 测试时候换成自己编译好的lib路径
            File libPath = new File("D:\\java_box\\java_sec_learning\\lib\\cmd.dll");

            // load命令执行类
            Class<?> commandClass = loader.loadClass("jni_sec.CommandExecution");
            Method loadLibrary0Method = ClassLoader.class.getDeclaredMethod("loadLibrary0", Class.class, File.class);
            loadLibrary0Method.setAccessible(true);
            loadLibrary0Method.invoke(loader, commandClass, libPath);

            String content = (String) commandClass.getMethod("exec", String.class).invoke(null, cmd);
            System.out.println(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

END

支付宝捐赠
请使用支付宝扫一扫进行捐赠
微信捐赠
请使用微信扫一扫进行赞赏
有 0 篇文章