OpenRasp源码分析-java功能

本文详细分析了OpenRasp的Java功能,包括RaspIntall.jar的安装过程,启动执行流程,核心原理如加载java层的hook功能模块和运行时插桩。在启动流程中,重点讲述了如何通过类加载机制实现动态插桩,以及反序列化检测的逻辑。此外,还简要提及了未分析的native实现部分。
摘要由CSDN通过智能技术生成

OpenRasp源码分析-java功能

最近在做关于后台服务器和web的rasp这块工作,需要在OpenRasp的基础上做一个二次研发,为了能弄清其实现的技术细节,阅读了一下源码。记录的内容主要有以下几个点

  • RaspIntall.jar的功能
  • 启动执行流程
  • 核心原理
  • 检测规则
  • 其他
    整个OpenRasp的基本就是上述几个过程。其中重点关注的是启动执行流程,核心原理,检测规则三个点,下面就一个个的分析其实现。在分析的过程中我用了一个一些逆向工程的思路,当然也可以直接读源码,每个人都有自己的思路,能达到目的就可以了。

下面分析主要关注的java–>springboot–>DeserializationHook相关

RaspIntall.jar的功能

在其官方文档中,有提到了在启动服务之前,需要先安装agent,对于SpringBoot框架,使用如下方式安装

java -jar RaspInstall.jar -nodetect -install <spring_boot_folder> -backendurl http://XXX -appsecret XXX -appid XXX

其中backendurl appsecret appid可以通过启动rasp-cloud可以获取到,这里不是重点,先不去管。

这里使用jd-gui.jar查看RaspInstall.jar,根据参数

-nodetect -install <spring_boot_folder>

定位到MANIFEST.MF下的Main-Class值,如下

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: root
Created-By: Apache Maven 3.5.4
Build-Jdk: 1.8.0_232
Main-Class: com.baidu.rasp.App

找到关键类com.baidu.rasp.App,经过分析后,来到关键掉一共

public static void operateServer(String[] args) throws RaspError, ParseException, IOException {
   
    showBanner();
    argsParser(args);
    checkArgs();
    if ("install".equals(install)) {
   
      File serverRoot = new File(baseDir);
      InstallerFactory factory = newInstallerFactory();
      Installer installer = factory.getInstaller(serverRoot, noDetect);
      if (installer != null) {
   
        installer.install();
      } else {
   
        throw new RaspError(RaspError.E10007);
      } 
    } else if ("uninstall".equals(install)) {
   
      File serverRoot = new File(baseDir);
      UninstallerFactory factory = newUninstallerFactory();
      Uninstaller uninstaller = factory.getUninstaller(serverRoot);
      if (uninstaller != null) {
   
        uninstaller.uninstall();
      } else {
   
        throw new RaspError(RaspError.E10007);
      } 
    } 
  }

进入到install中的调用,经过分析可以知道,程序是将rasp目录下的所有程序复制到指定的springboot_path下的rasp目录下,主要有以下几个文件

.
├── conf
│   └── openrasp.yml
├── logs
│   ├── alarm
│   │   └── alarm.log
│   ├── plugin
│   │   └── plugin.log
│   ├── policy_alarm
│   │   └── policy_alarm.log
│   └── rasp
│       └── rasp.log
├── plugins
├── rasp-engine.jar
└── rasp.jar
log文件是运行后自动生成的,可以忽略。

RaspInstall.jar主要功能:

  • 在目标路径创建rasp目录
  • 复制rasp.jar,rasp-engine.jar
  • 复制conf目录
  • 复制plugins目录
  • 生成配置信息conf/openrasp.yml文件

启动执行流程

启动openrasp的时候,使用如下命令

java -javaagent:/opt/spring-boot/rasp/rasp.jar -jar xxx.jar

指定javaagent:参数,在java中启动时指定了agent,则可以完成以下功能

javaagent 的主要功能如下:

可以在加载 class 文件之前做拦截,对字节码做修改
可以在运行期对已加载类的字节码做变更,但是这种情况下会有很多的限制,后面会详细说
还有其他一些小众的功能
获取所有已经加载过的类
获取所有已经初始化过的类(执行过 clinit 方法,是上面的一个子集)
获取某个对象的大小
将某个 jar 加入到 bootstrap classpath 里作为高优先级被 bootstrapClassloader 加载
将某个 jar 加入到 classpath 里供 AppClassloard 去加载
设置某些 native 方法的前缀,主要在查找 native 方法的时候做规则匹配

#参考 JVM 源码分析之 javaagent 原理完全解读
https://www.infoq.cn/article/javaagent-illustrated/

根据参考中的分析,可以知道,在启动的时候指定了javaagent之后,java程序会根据META-INF/MANIFEST.MF新找到

Premain-Class:xxxx

指定的类,接着调用premain方法,使用jd-gui.jar查看rasp.jar中的META-INF/MANIFEST.MF,内容如下

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-it1rIYSE-1587637934982)(./imgs/rasp_manifest_mf.png)]

该类实现了premain方法,另一个是动态加载的方式,暂时不管,如下
在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EQLZf4WM-1587637934986)(./imgs/init.png)]

进入到init方法

add_rasp_engine_jar

程序首先调用JarFileHelper.addJarToBootstrap(inst);rasp.jar添加到bootstrap的路径内,这样系统会优先加载这个jar文件到系统路径中,后续的Hook能完成预先的类加载埋点插桩,后面的核心逻辑功能分析有细致分析
add_jar_to_bootstrap

接着调用ModuleLoader.load(mode, action, inst);rasp-engine.jar加载并执行。进入到ModuleLoader.load程序调用了ModuleLoader创建一个loader的实例对象
在这里插入图片描述

这里需要注意的是,在ModuleLoader内有一个静态代码块,这里负责初始化当前加载的根目录baseDirectory和系统加载器systemClassLoader
在这里插入图片描述

可以看到在获取 systemClassLoader的时候,有一个判断是否是sun.misc.Launcher$ExtClassLoader,不是就获取其父类的classLoader
默认情况下,我们在写一个main方法内获取到的ClassLoader

    private static void showClassLoader(){
   
        ClassLoader loader =ClassLoader.getSystemClassLoader();
        System.out.println("Defaule ClassLoader Name:"+loader.getClass().getCanonicalName()+" \nparant Name: "+loader.getParent().getClass().getCanonicalName()+"\ngrand parent Name:"+loader.getParent().getParent().getClass().getCanonicalName());
    }

sun.misc.Launcher.AppClassLoader
对应的`parent`是
sun.misc.Launcher.ExtClassLoader
对应的grand parent:NullPointer

可以看到sun.misc.Launcher.AppClassLoader对应的父类是sun.misc.Launcher.ExtClassLoader,这两个的父类都是URLClassLoader,具体参考(https://blog.csdn.net/briblue/article/details/54973413)

   private static void showClassLoader(){
   
        ClassLoader loader =ClassLoader.getSystemClassLoader();
//        System.out.println("Defaule ClassLoader Name:"+loader.getClass().getCanonicalName()+" \nparant Name: "+loader.getParent().getClass().getCanonicalName()+"\ngrand parent Name:"+loader.getParent().getParent().getClass().getCanonicalName());
        ClassLoader module = loader.getParent();
        if (module instanceof URLClassLoader){
   
            System.out.println("Got it  "+module.getClass().getCanonicalName());
        }
    }
输出的结果
Got it  sun.misc.Launcher.ExtClassLoader

初始化完成后,调用ModuleLoader的构造函数,ModuleLoader的构造函数是一个私有的,在构造函数内通过调用ModuleContainer的构造函数获取一个engineContainer对象,接着调用start方法启动程序。在ModuleContainer内部通过解析rasp-engine.jar获取MANIFEST.MF指定的Rasp-Module-Class类,调用反射方法获取到rasp-engine.jar的入口类,接着使用获取到的类加载器指定反射调用newInstance()方法,执行调用模块对应的start方法。

在这里插入图片描述

对应的Rasp-Module-Classrasp-engine.jarMETA-INF/MANIFEST.MF
在这里插入图片描述

由于系统加载器不一定就是默认的加载器,所以在加载的时候做了区分,
一个就是判断是否为java.net.URLClassLoader这个加载器,另一个就是调用ModuleLoader.isCustomClassLoader函数判断是否是weblogic或者jdk9、10和11。
源码内对应的注释
在这里插入图片描述
如果两个都不是,则这里就提示加载失败,根据前面的分析可以知道,此时调用的是URLClassLoader部分的逻辑,反之就利用反射初始化模块EngineBoot对象,最后调用start方法。由于EngineBoot实现的是Module接口,每个模块都有start release两个方法
在这里插入图片描述

在准备完成后,通过engineContainer.start()的方式调用模块对应的start()
在这里插入图片描述

启动正常的话,就能看到了如下输出
在这里插入图片描述

核心原理

根据前面的启动流程可以知道,程序最后交给了EngineBoot类并执行了start方法。EngineBoot类主要完成以下几个工作

  • 加载openrasp_v8.so (这个是暂时不做重点关注)
  • 记载config文件 (这个是暂时不做重点关注)
  • 初始化rasp编译信息 (这个是暂时不做重点关注)
  • 缓存RASP的build信息 (这个是暂时不做重点关注)
  • 加载js插件(这个是暂时不做重点关注)
  • 加载java层的hook功能模块(重点关注)
  • 初始化加载或者回滚类(重点)
加载java层的hook功能模块

EngineBoot.start()内,在完成一些js和系统信息初始化后,接着来到了检测点的初始化,通过CheckerManager.init();来实现。在其内部通过加载com.baidu.openrasp.plugin.checker.CheckParameter. Typ内的枚举类,包括了如下类型的hook点

  // js插件检测
        SQL("sql", new V8AttackChecker(), 1),
        COMMAND("command", new V8AttackChecker(), 1 << 1),
        DIRECTORY("directory", new V8AttackChecker(), 1 << 2),
        REQUEST("request", new V8AttackChecker(), 1 << 3),
        READFILE("readFile", new V8AttackChecker(), 1 << 5),
        WRITEFILE("writeFile", new V8AttackChecker(), 1 << 6),
        FILEUPLOAD("fileUpload", new V8AttackChecker(), 1 << 7),
        RENAME("rename", new V8AttackChecker(), 1 << 8),
        XXE("xxe", new V8AttackChecker(), 1 << 9),
        OGNL("ognl", new V8AttackChecker(), 1 << 10),
        DESERIALIZATION("deserialization", new V8AttackChecker(), 1 << 11),
        WEBDAV("webdav", new V8AttackChecker(), 1 << 12),
        INCLUDE("include", new V8AttackChecker(), 1 << 13),
        SSRF("ssrf", new V8AttackChecker(), 1 << 14),
        SQL_EXCEPTION("sql_exception", new V8AttackChecker(), 1 << 15),
        REQUESTEND("requestEnd", new V8AttackChecker(), 1 << 17),
        DELETEFILE("deleteFile", new V8AttackChecker(), 1 << 18),
        MONGO("mongodb", new V8AttackChecker(), 1 << 19),
        LOADLIBRARY("loadLibrary", new V8AttackChecker(), 1 << 20),
        SSRF_REDIRECT("ssrfRedirect", new V8AttackChecker(), 1 << 21),
        RESPONSE("response", new V8AttackChecker(false), 1 << 23),

        // java本地检测
        XSS_USERINPUT("xss_userinput", new XssChecker(), 1 << 16),
        SQL_SLOW_QUERY("sqlSlowQuery", new SqlResultChecker(false), 0),

        // 安全基线检测
        POLICY_LOG("log", new LogChecker(false), 1 << 22),
        POLICY_MONGO_CONNECTION("mongoConnection", new MongoConnectionChecker(false), 0),
        POLICY_SQL_CONNECTION("sqlConnection", new SqlConnectionChecker(false), 0),
        POLICY_SERVER_TOMCAT("tomcatServer", new TomcatSecurityChecker(false), 0),
        POLICY_SERVER_JBOSS("jbossServer", new JBossSecurityChecker(false), 0),
        POLICY_SERVER_JBOSSEAP("jbossEAPServer", new JBossEAPSecurityChecker(false), 0),
        POLICY_SERVER_JETTY("jettyServer", new JettySecurityChecker(false), 0),
        POLICY_SERVER_RESIN("resinServer", new ResinSecurityChecker(false), 0),
        POLICY_SERVER_WEBSPHERE("websphereServer", new WebsphereSecurityChecker(false), 0),
        POLICY_SERVER_WEBLOGIC("weblogicServer", new WeblogicSecurityChecker(false), 0),
        POLICY_SERVER_WILDFLY("wildflyServer", new WildflySecurityChecker(false), 0),
        POLICY_SERVER_TONGWEB("tongwebServer", new TongwebSecurityChecker(false), 0),
        POLICY_SERVER_BES("bes", new BESSecurityChecker(false), 0);

加这些插件添加到一个EnumMap

/**
 * Created by tyy on 17-11-20.
 *
 * 用于管理 hook 点参数的检测
 */
public class CheckerManager {
   

    private static EnumMap<Type, Checker> checkers = new EnumMap<Type, Checker>(Type.class);

    public synchronized static void init() throws Exception {
   
        for (Type type : Type.values()) {
   
			
				
            checkers.put(type, type.checker);
        }
    }

    public synchronized static void release() {
   
        checkers = null;
    }

    public static boolean check(Type type, CheckParameter parameter) {
   
        return checkers.get(type).check(parameter);
    }
}

添加的检测类为

[Loopher]: ===> loading checker name: com.baidu.openrasp.plugin.checker.v8.V8AttackChecker
[Loopher]: ===> loading checker name: com.baidu.openrasp.plugin.checker.v8.V8AttackChecker
[Loopher]: ===> loading checker name: com.baidu.openrasp.plugin.checker.v8.V8AttackChecker
[Loopher]: ===> loading checker name: com.baidu.openrasp.plugin.checker.v8.V8AttackChecker
[Loopher]: ===> loading checker name: com.baidu.openrasp.plugin.checker.v8.V8AttackChecker
[Loopher]: ===> loading checker name: com.baidu.openrasp.plugin.checker.v8.V8AttackChecker
[Loopher]: ===> loading checker name: com.baidu.openrasp.plugin.checker.v8.V8AttackChecker
[Loopher]: ===> loading checker name: com.baidu.openrasp.plugin.checker.v8.V8AttackChecker
[Loopher]: ===> loading checker name: com.baidu.openrasp.plugin.checker.v8.V8AttackChecker
[<
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值