深入源码对OpenRASP进行原理和设计思想的剖析
2023-5-31 21:25:53 Author: www.freebuf.com(查看原文) 阅读量:13 收藏

启动阶段

OpenRASP的启动类Jar包是rasp.jar在他的MANIFEST.MF中有如下配置

image-20230518111515816.png

确定了Premain-Class / Agent-Class / Main-Class / Can-Redefine-Classes / Can-Retransform-Classes这些关键属性值

具体表示什么功能这里就不过于描述了

首先在启动是将会加载的是Agent#premain方法

image-20230518111836563.png

image-20230518111936615.png

这里存在一个步骤是将我们的rasp.jar包加入到了BootStrapClassLoader下,这是因为Java的一个双亲委派机制,如果不将其加入根下,当我们hook的点是通过BootStrapClassLoader加载的类的时候,将不能正确的检测

后面就是一些模块的加载

image-20230518113521879.png

image-20230518113534687.png

首先是如果是JBoss的服务器,将会进行一些相关配置

image-20230518113644866.png

这里的ENGINE_JARrasp-engine.jar,之后就是对rasp-engine这个模块的加载

image-20230518113832588.png

简单的描述一下流程就是

  1. 获取rasp-engine.jar这个包的绝对路径

  2. 获取在rasp-engine.jar包中的MANIFEST.MF文件中的Rasp-Module-Class这个模块的主要类名

  3. rasp-engine.jar添加进入classpath下,使得能够加载包中的类文件

  4. 加载Rasp-Module-Class属性对应的类,并实例化

image-20230518114411660.png

紧接着就是调用我们前面获取到的engine类的start方法进行启动

image-20230518201646210.png

首当其充的就是V8引擎的加载,这里是为了方便OpenRASP跨平台以及热部署使用V8引擎进行部分规则的编写

有关V8的详细源码可以在github中找到

后面就是一些插件的初始化

image-20230518202551259.png

这里设置了是通过JSON的格式来保存栈信息

之后就是设置了一些保存信息用的key值

image-20230518202833740.png

紧接着就是对插件的更新

image-20230518203634417.png

image-20230518203659329.png

  1. 首先获取对应的插件的绝对路径

  2. 之后获取对应目录下的js文件,官方内置的为official.js进行插件的配置

  3. 读取文件内容并将其和文件名保存在scripts中,之后使用V8引擎进行信息的提取

image-20230518204719102.png

之后就是调用InitFileWatcher方法来实现对js配置文件的监听事件,使得能够实时的更新OpenRasp的配置

image-20230518210152286.png

之后就是调用CheckerManager.init方法进行检测类型的加载

image-20230518210719367.png

image-20230518210730056.png

image-20230518210737850.png

有 js插件检测,java本地检测和安全基线检测

紧接着就是使用initTransformer(inst)方法进行字节码转换器的初始化,进行插桩的配置

image-20230518211224362.png

image-20230518211336781.png

通过调用addAnnotationHook进行插桩

image-20230518211728823.png

  1. 通过获取在com.baidu.openrasp.hook包下的所有类,提取这里面中使用了@HookAnnotation注解的类

  2. 实例化类之后调用addHook进行插桩

接着调用retransform方法进行转换

image-20230518214230146.png

这里存在有一个判断,通过isClassMatched的调用判断是否是Hook的类

image-20230518214307199.png

实现分为两种

而如果存在这样的将会对这些类进行Hook点的加载

最后结束了初始化的过程

image-20230518220948531.png

检测阶段

有了前面的一些基础,我们简单的认识了OpenRASP的再启动阶段的一些初始化的配置

我们这里具体看看其中的原理,这里使用XXE的检测作为例子

前面也提及了,在OpenRASP初始化的阶段就会对应一些危险类的危险函数进行Hook,并使用了javassist这个ASM框架在危险方法中动态加入了一些特定的检测逻辑来判断是否是异常行为,如果是,将会进行拦截,不是,就会放行

正如XXE的Hook流程

XXEHook#isClassMatched方法是用来进行筛选,可能出现的XXE漏洞的sink点

image-20230522220039184.png

这三个类都是可能出现XXE的漏洞点,当然,这里过滤得不是很完全,对于XXE得sink点还有这其他的类,这里就不扩充了

CustomClassTransformer#transform方法就是重写字节码的具体实现

image-20230522220713817.png

  1. 将原类文件写入classpath中

  2. 调用AbstractClassHook#transformClass进行检测代码的添加并返回修改后的字节码

image-20230522220947611.png

这里主要是通过不同的实现类的hookMethod方法来进行检测代码的添加

这里针对XXE就是在XXEHook#hookMethod方法中

image-20230522221228503.png

主要是使用getInvokeStaticSrc获取了checkXXE方法的字节码并将其写入了sink点的运行前,这里也就是在XML进行解析的必经之路上expandSystemId / setDescription等两个方法

image-20230522222032146.png

在我们的漏洞靶场中,这里选用的是openRASP的官方靶场

在配置成功RASP之后,进行XXE攻击

image-20230523172111222.png

根据jsp中的代码,这种XXE是通过DocumentBuilder类进行xml的解析的

image-20230523172506201.png

在解析过程中,将会调用我们已经hook了的org/apache/xerces/impl/XMLEntityManager类的关键方法expandSystemId

而经过前面的分析,我们知道在初始化的过程中已经在调用expandSystemId方法之前添加了checkXXE方法的调用,这些流程的调用栈如下

checkXXE:102, XXEHook (com.baidu.openrasp.hook.xxe)
invoke:-1, GeneratedMethodAccessor9 (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
expandSystemId:-1, XMLEntityManager (org.apache.xerces.impl)
addExternalEntity:-1, XMLEntityManager (org.apache.xerces.impl)
scanEntityDecl:-1, XMLDTDScannerImpl (org.apache.xerces.impl)
scanDecls:-1, XMLDTDScannerImpl (org.apache.xerces.impl)
scanDTDInternalSubset:-1, XMLDTDScannerImpl (org.apache.xerces.impl)
dispatch:-1, XMLDocumentScannerImpl$DTDDispatcher (org.apache.xerces.impl)
scanDocument:-1, XMLDocumentFragmentScannerImpl (org.apache.xerces.impl)
parse:-1, XML11Configuration (org.apache.xerces.parsers)
parse:-1, XML11Configuration (org.apache.xerces.parsers)
parse:-1, XMLParser (org.apache.xerces.parsers)
parse:-1, DOMParser (org.apache.xerces.parsers)
parse:-1, DocumentBuilderImpl (org.apache.xerces.jaxp)

之后就是检查是否是危险操作

image-20230523174433428.png

image-20230523174729793.png

在检测过程中,将会有安全对业务让步的操作

之后真正的检查逻辑在doRealCheckWithoutRequest方法中

image-20230523175128461.png

image-20230523175212691.png

使用不同类型的检查方式,这里XXE是V8AttackChecker

image-20230523175547599.png

使用checkParam来对payload进行检测

image-20230523175640536.png

也即是通过插件来进行检测

更多的检查方式可以在official.js中了解,这里跟一下XXE的检测方法

image-20230523214214111.png

这分别是针对XXE检测的一些内置的手段,比如说禁用外部实体 / 禁用特殊协议 / 直接禁用file协议等等

其中的action字段就是判断是否启用

至于如何进行检测,如下图

image-20230523214432631.png

这两种是对特殊协议的禁用的检测,一旦使用前面配置的协议,将会被视为恶意请求,message字段也就是将会在告警中打印的提示符

image-20230523214710880.png

至于针对file协议的使用检测如下

image-20230523214847735.png

  1. 如果启用了这个算法,首先将会file:///xxx中后面的内容进行提取

  2. 检查是否进行了跨目录访问

  3. 检查是否使用了url的锚点

  4. 检查是否使用file协议读取了内部文件

  5. 如果都不满足,将会返回一个常量clean
    image-20230523215201845.png

总结

上面针对OpenRASP这一个开源RASP产品进行了详细的流程分析,通过跟踪代码,分析流程,对RASP产品有了更深的体验

从V8引擎进行检测,到使用js进行热部署,已经hook点的定位的手法和思路也有了更深的体会

总的来说,RASP产品的优势和缺点同样的突出

  1. 优势

    • 有着很好的精准性,误报率不高

    • 能够进行0Day的一定程度的防御,(甚至可以拿来做蜜罐

    • 相比于WAF,能够对加密的流量进行防御

  2. 劣势

    • 有可能造成资源的浪费

    • 需要维护多个sink点

    • 容易造成sink点检测不全的风险,比如OpenRASP在对webshell的检测的时候,针对市面上常见的大型攻击框架(ysoserialbehinder)等等进行了包名的检查,但是针对随机类名将会被绕过,又比如前面分析到的XXE的sink点,针对常见的XXE漏洞点进行了检测,但是前段时间公布了一个sink点因项目未更新,可能也会存在绕过的风险

    • 通用性的解决方法也是一个难题

参考

https://forum.butian.net/share/1959

https://github.com/baidu-security/openrasp-v8

https://rasp.baidu.com/doc/


文章来源: https://www.freebuf.com/articles/web/368133.html
如有侵权请联系:admin#unsafe.sh