• Openrasp源码分析


    Openrasp是百度关于rasp技术的开源项目,由于工作需要,之前对rasp的源码进行了简单的分析。文章是之前就写好的,现在放出了,希望对大家有写帮助。

    OpenRASPjava引擎的源码分析

    安装包解压后目录结构如下:

     

    RaspInstall.jarOpenRASPjava引擎的安装(卸载)运行文件,在终端中执行以下命令进行安装(tomcat为例)

    java -jar RaspInstall.jar -install <tomcat_root>

    值得注意的是:<tomcat_root>不是webapps目录,而是tomcat的根目录

    rasp目录夹中java引擎的资源文件,目录结构如下图:

     

    conf文件夹下是rasp.properties配置文件,里面的配置参数用于指定是否开启基线检测、拦截后跳转地址等等。

    plugins文件夹下是official.js文件,OpenRASP是通过JavaScript插件来实现检测逻辑的,而official.js就是这个检测插件。

    rasp.jarrasp-engine.jarjava引擎的核心文件

    一:RaspInstall.jar

    反编译RaspInstall.jar文件后目录如下:

     

    简单看下App.class的代码

     

    以安装操作为例,获取命令行第二个参数(安装时指定web容器的根目录)后,先调用了newInstallerFactory()方法对操作系统类型进行判断,然后返回不同系统的安装工厂类(都继承了InstallerFactory类)

     

    随后调用了工厂类父类InstallerFactorygetInstaller(File  ServerRoot)方法

     

    父类中getInstaller(File  ServerRoot)就是对web容器做了一个判断,然后再调用子工厂类的getInstaller(String aerverName,String serverRoot)方法,方法中调用了不同的web容器安装类

     

    后面就是具体的安装操作了,包括在web服务器下创建rasp目录,将资源文件放入进去;添加环境变量;配置容器启动参数(例如java引擎是通过java agent机制实现的,应用启动时需要指定-javaagent参数)。

     

    二:rasp.jar

    rasp.jar文件就是一个javaagent,会在web应用的main函数运行之前先运行。目录结构如下:

    之前也说了rasp.jar文件就是一个javaagent,而Agent.java就是这个javaagent的入口类,内容如下:

     

    Premain(String agentArg, Instrumentation inst)就是javaagent的入口函数。

    函数中先调用了JarFileHelper.addJarToBootstrap(inst)

     

    函数中将当前jar文件加入到了jdk的根路径下,优先加载。这是因为当去 hook java.io.File 这样由 BootstrapClassLoader 加载的类的时候,无法从该类调用非 BootstrapClassLoader 加载的类中的接口,所以 agent.jar 会先将自己添加到 BootstrapClassLoader ClassPath下,这样 hook BootstrapClassLoader 加载的类的时候就能够成功调用到 agent.jar 中的检测入口。

    随后调用了ModuleLoader.load(agentArg, inst),用于加载所有的rasp模块,目前只有一个模块就是rasp-engine.jar.(目前还不是很能理解多个模块的用意)

     

    方法中new了一个ModuleLoader(agentArg, inst),用于构造所有的模块。

     

    大概意思就是调用各rasp模块入口类中的start方法。

    三:rasp-engine.jar

    先从rasp-engine.jar的入口文件EngineBoot.java中的入口方法start看起。

     

    先是判断应用的conf目录下是否有日志配置文件rasp-log4j.xml,如果不存在则释放log4j日志配置文件,也就是将资源文件夹resources下的rasp-log4j.xml.default文件中的注释去掉,文件名修改后复制到conf文件夹下。

    readVersion()用于通过/META/MANIFEST.MF文件读取当前openrasp的版本。

    接下来调用了JsPluginManager.init(),用于初始化插件系统以及更新插件,跟进去看看:

     

    JSContextFactory.init()主要用于JS上下文类的初始化,由于涉及到Rhino框架(一个开源的脚本引擎框架)的一些东西,没有深入了解了。目前知道的是有加载了资源文件夹resourcesenvironment文件夹下的js文件,以及初始化一些属性。updatePlugin()方法中更新插件引擎。initFileWatcher()初始化一个文件时间的监视器,监测plugin目录下的js文件是否有更新。

    回到start方法中,初始化JS引擎后,接下来调用了CheckerManager.init(),该方法用于初始化Checker管理器,也就是将所有的checker类实例都放入EnumMap中,方便之后通过Type来判断调用各自的Check类中的check()方法。最后就是调用initTransformer(inst)之后整个启动流程就结束了,这个方法用于初始化类字节码转换器,里面主要做了两件事(分为两部分):1.给类加载操作进行了插桩操作,当类加载的时候会先进入agent进行处理 2.对于初始化前就已经加载的类中执行了retransform处理。跟入函数:

     

    第一部分:函数内先构造了一个自定义类字节码转换器,该转换器的构造函数中会自动扫描所有标有@HookAnnotation的类,并放入HashMap中,方便后面比较类加载器加载的类是否是我们想要hook的类。

    接下来通过addTransformer将这个定义类字节码转换器添加到Instrumentation中,(这里涉及到javaagent的知识点,当Instrument读取字节码文件后会自动调用回调函数ClassFileLoadHook,该回调函数中做了很多事,其中一件就是调用ClassFileTransformer接口实现类对象中的transform方法)在这里也就是调用了CustomClassTransformer类中的transform方法,跟进该方法

     

    该方法中主要是对目前类加载器所加载的类是否属于众多我们想要hook类的一种,如果是则调用对应hook类中的transformClass方法。在for循环中,取出了所有的hook类并通过hook类中各自的isClassMatched方法域当前类加载器所加载的类相比较,如果是我们想要hook的类,则通过AbstractClassHook(所有hook类的父类)中的transformClass方法来调用对应hook类中的hookMethod方法,该方法就是用于将待转换的类转换后以字节码数组的形式返回,这里的转换就是通过操作字节码在想要hook的类的关键函数前后加入安全检测代码。

    这里以XXEHook这个hook类为例,跟进它hookMethod方法:

     

    先是调用了getInvokeStaticSrc方法,该方法通过输入的参数获取静态方法的代码字符串并返回(在原方法基础上加了一些捕获异常的代码),接下来的insertBefore方法是对javassit框架中CtBehavior类的insertBefore方法做了包装,主要内容是通过字节码的形式在指定方法前加入指定的安全检测代码(也就是插桩技术,具体如果操作字节码是交给了javassit框架在做,我们只需要调用它的接口并指定想要插桩的函数插入的代码就行),该方法的第一个参数当前类加载器所加载的类的字节码(这个地方有点说不清楚!),第二个参数指定该类中想要进行插桩操作的方法的方法名,第三个参数是对该类中想要进行插桩操作的方法的参数以及返回类型的描述((Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V代表该方法有四个参数且都是String类型,返回void),第四个参数就是我们想要插入的代码字符串了。

    具体的插入的什么安全检测代码,我们之后再回过来看,先不继续跟进了。这里附上一张官网关于Hook Class流程的说明:

     

    回到initTransformer(inst)函数中来看第二部分:

     

    显示调用了instrumentation.getAllLoadedClasses()用于获取所有加载过的类,for循环中对这些类做了一个判断,是否为需要hook的类,如果是则调用inst.retransformClasses(Class cls)jvm发起重新转换的请求。

    到这里整个openrasp启动流程已经结束了,流程图如下:

     

    现在再回过头看看具体插入的安全检测代码是什么样子的,还是以XXEHook这个hook类为例。XXEHook类中插入的代码是checkXXE这个方法:

     

    先对expandedSystemId参数做了校验,是否为空或者是否为当前线程已触发过检测的expandedSystemId

    如果是第一次触发检测通过XXEHook.getLocalExpandedSystemIds().add(expandedSystemId)将当前expandedSystemId加入到HashSet中,代表已经检测过。。。。(未完待续)

    整个rasp-engine.jar的目录结构如下

     

    hook文件夹目录结构如下

    Plugin文件夹目录如下

    Tool文件夹目录如下

     

  • 相关阅读:
    ssh免密钥登陆的两种方式
    python 项目实战之装饰器
    python 项目实战之随机杀死程序
    python paramiko外部传参和内部调用命令的方法
    linux screen 工具
    shell删除三天前或者三天内的文件
    CentOS7.3安装Go运行和开发环境
    4.Linq to Xml
    30.第一个Linq 数据库查询
    html 图标库
  • 原文地址:https://www.cnblogs.com/jinqi520/p/10271337.html
Copyright © 2020-2023  润新知