OpenRasp源码分析(一)
Agent
入口是com.baidu.openrasp
1 | public static void premain(String agentArg, Instrumentation inst) { |
1 | public static void premain(String agentArg, Instrumentation inst) { |
在JarFileHelper.addJarToBootstrap(inst);
中使用inst.appendToBootstrapClassLoaderSearch(new JarFile(localJarPath));
将本Jar文件加入jdk的根路径下。使用appendToBootstrapClassLoaderSearch
参数后,JVM将会在启动时将指定的目录或JAR文件添加到引导类加载器的搜索路径的最后,使得引导类加载器可以加载这些额外的类和资源。这样,自定义的类或资源可以与Java平台核心类库或扩展库一起使用。
readVersion();
从MANIFEST.MF文件中读取agent的版本信息,构建时间,git版本。
ModuleLoader
在静态类中初始化了 baseDirectory
和 moduleClassLoader
moduleClassLoader
是对系统类加载器的父类进行迭代,得到的name为sun.misc.Launcher$ExtClassLoader
的加载器。
1 | public static void premain(String agentArg, Instrumentation inst) { |
sun.misc.Launcher$ExtClassLoader的作用和使用方法
sun.misc.Launcher$ExtClassLoader
是Java虚拟机中的一个类加载器,它是扩展类加载器(Extension Class Loader)的具体实现。作为类加载器的一种,
sun.misc.Launcher$ExtClassLoader
主要用于加载Java虚拟机的扩展类库。扩展类库是指由Java虚拟机提供的,位于JRE的lib/ext
目录下的一些核心类库和扩展功能的实现。扩展类加载器在Java类加载的委派模型中处于中间位置,位于应用类加载器和引导类加载器之间。当需要加载某个类时,扩展类加载器首先尝试自己去加载,如果找不到则委托给引导类加载器来加载。它负责加载
java.ext.dirs
系统属性指定的扩展类库路径下的类。扩展类加载器的作用包括:
- 加载扩展类库:它加载位于
lib/ext
目录下的类库,为Java虚拟机提供一些核心功能的扩展和补充。- 提供自定义的扩展类库:开发人员可以将自己编写的类库放置在扩展类库路径下,由扩展类加载器进行加载和管理。
- 实现类加载的委派机制:扩展类加载器在加载类时会先尝试自己加载,如果找不到则委托给引导类加载器,通过这种委派机制可以保证类的唯一性和一致性。
如果需要加载自定义的类或资源,可以使用应用类加载器或自定义的类加载器来完成。例如,可以使用
ClassLoader.getSystemClassLoader()
来获取应用类加载器,然后使用它的方法(如loadClass()
和getResource()
)来加载类或资源
如下为ModuleLoader.load
的部分代码
1 | private ModuleLoader(String mode, Instrumentation inst) throws Throwable { |
在ModuleContainer
的构造函数中,首先获取rasp-engine.jar 的Manifest
文件,获取moduleName
和moduleEnterClassName
1 | this.moduleName = attributes.getValue("Rasp-Module-Name"); |
然后使用ExtClassLoader
加载Rasp-Module-Class,即com.baidu.openrasp.EngineBoot
,结果设置为modue,然后调用module.start(mode, inst)
,即com.baidu.openrasp.EngineBoot#start()
EngineBoot
rasp-engine
启动
在com.baidu.openrasp.EngineBoot#start()
中进行了如下操作:
- 输出banner
- 加载V8引擎,V8为native实现,具体代码在openrasp-v8。
loadConfig
加载config,比如初始化log4j的logger和为appender增加限速
1 | public static void ConfigFileAppender() throws Exception { |
- CheckerManager.init(); 用于管理 hook 点参数的检测
- initTransformer(inst); 初始化类字节码的转换器
- CloudUtils.checkCloudControlEnter():检查云控配置信息
- LogConfig.syslogManager():读取配置信息,初始化syslog服务连接
插件加载
然后JS插件初始化JS.Initialize()
1 | #加载插件 |
1 | UpdatePlugin:237, JS (com.baidu.openrasp.plugin.js) |
在UpdatePlugin中,通过Config中的getScriptDirectory()获取js插件目录,为apache目录下的rasp\plugins
File pluginDir = new File(Config.getConfig().getScriptDirectory());
通过V8引擎,将官方插件读取成JsonString的格式,利用Config.getConfig()
Config.getConfig().setConfig(ConfigItem.ALGORITHM_CONFIG, jsonString, true);
使用item.setter.setValue(value)
将JsonSting格式插件设置到ConfigItem.ALGORITHM_CONFIG
中
初始化检测器
CheckerManager.init();
从com.baidu.openrasp.plugin.checker.CheckParameter.Type
中获得(type,checker)
并且加入CheckerManager.checker
检测器有三种
- js插件检测
- java本地检测
- 安全基线检测
区别在于,js插件检测的checker来自于插件目录下的plugin.js,java本地检测的checker来自于源码checker/local路径下,这样做的目的是方便扩展。添加/更新插件可以直接通过修改js文件实现。插件的开发文档:OpenRasp插件开发
JS插件检测
在CheckerManager初始化时,遍历Type
类,并将其中所有的checker加入到CheckerManager的checkers中,
如下图所示,js插件的checker类都是V8AttackChecker,而java本地检测的cheker都是单独实现的类。
以ssrf为例,来看OpenRasp插件检测的流程。在plugin.js.JS的Check函数第一行下断点,然后触发SSRF。
此时堆栈如下
1 | Check:161, JS (com.baidu.openrasp.plugin.js) |
可以看到在进行url请求时首先触发了HttpClientHook
,接着调用checkHttpUrl
之后一路到
isBlock = CheckerManager.check(type, parameter);
通过CheckerManager根据type进一步调用了SSRF js插件的检测方法。具体的检测在JS类的Check函数中(js插件的检测都在这里触发),涉及到V8引擎。
返回结果转码后是一个json数组,包含可信度,插件名等。
1 | [{"action":"log","message":"SSRF - Requesting known DNSLOG address: 127.0.0.1.xip.io","confidence":100,"algorithm":"ssrf_common","name":"official"}] |
插桩
initTransformer(inst);
initTransformer(inst)->transformer = CustomClassTransformer(inst)
通过 inst.addTransformer,向 Inst
对象注册一个 ClassFileTransformer
即该类CustomClassTransformer
,那么该类的transform会hook其他类捕获其他的类。
1 | public CustomClassTransformer(Instrumentation inst) { |
transformer.retransform();
1 | //CustomClassTransformer的构造函数中啊包含addAnnotationHook |
在transform
中遍历hooks中的所有hook,判断当前类是否为hook的关注类,判断成功后交给该hook类的transformClass
方法。
1 | for (final AbstractClassHook hook : hooks) { |
总结
OpenRasp通过java agent的premain 方式,在程序执行之前加载rasp,hook住一些可能触发恶意行为的函数,通过Javaassit修改jvm中的类,然后checker进行进一步的检测。
OpenRasp的checker主要使用js插件的方式实现,方便扩展。在openrasp\plugins\official\plugin.js中是官方实现的checker函数。可以据此参考实现自定义checker。在不方便使用V8引擎或需要深度自定义开发的情况下,可以使用java本地检测的方式实现checker。