OpenRASP浅析

最近在学习RASP相关内容,正好OpenRASP开源,大概分析了一下流程。参考了网上师傅们的分析加上自己的理解就有了这篇文章。如有错误或不足,还望指正。

RASP技术

RASP,全称“Runtime application self-protection”,运行时应用程序自我保护技术。它通过JavaAgent将代码注入到某些方法中,与应用程序融为一体,使应用程序具备自我防护能力,当应用程序遭受到实际攻击伤害时,能实时检测和阻断安全攻击,而不需要进行人工干预。

RASP与WAF的比较

WAF拦截原始http数据包,然后使用规则对数据包进行匹配扫描,如果不满足预设的所有规则,则视为安全,可通过WAF。这样就会造成规则缺失,放行危险行为;同时WAF只是对程序外层进行防护,当数据进入程序后,无法获取后续的行为。
WAF依靠特征检测攻击,但会造成一定的误报率,而RASP审查的是最终要执行的代码。
RASP插桩到代码层面,可以记录详细的栈堆跟踪信息
正是因为RASP需要侵入到代码层面,导致必然会造成一定的性能损耗,并且一个不合格的rasp更容易影响到业务代码

DAST、SAST和IAST

DAST:动态应用程序安全测试(Dynamic Application Security Testing)技术在测试或运行阶段分析应用程序的动态运行状态。它模拟黑客行为对应用程序进行动态攻击,分析应用程序的反应,从而确定该Web应用是否易受攻击。
SAST:静态应用程序安全测试(Static Application Security Testing)技术通常在编码阶段分析应用程序的源代码或二进制文件的语法、结构、过程、接口等来发现程序代码存在的安全漏洞。
IAST:交互式应用程序安全测试(Interactive Application Security Testing)是2012年Gartner公司提出的一种新的应用程序安全测试方案,通过代理、VPN或者在服务端部署Agent程序,收集、监控Web应用程序运行时函数执行、数据传输,并与扫描器端进行实时交互,高效、准确的识别安全缺陷及漏洞,同时可准确确定漏洞所在的代码文件、行数、函数及参数。IAST相当于是DAST和SAST结合的一种互相关联运行时安全检测技术。

IAST与RASP

正如Thoughtworks官网所描述的那样:
IAST:交互式应用程序安全测试。监控应用程序在运行时的安全漏洞——测试时间。
RASP:运行时应用程序自我保护。监控应用程序,以检测其运行时的攻击——生产时间。
IAST 和 RASP 是应用程序运行时寻找问题的安全工具。对于 IAST,作为测试过程的一部分,扫描漏洞。,RAST 检测生产环境中的攻击。

OpenRASP

OpenRASP是百度在2017年针对RASP概念推出的一款开源免费的自适应安全产品,官网
这个项目使用v8引擎载入js并实现热加载,通过编写js规则对攻击进行检测,这一点还是比较吸引人的。
另一个引入JS的原因是OpenRASP会支持PHP、DotNet、NodeJS、Python、Ruby等多种开发语言,为了避免在不同平台上重新实现检测逻辑,所以引入了插件系统,选择JS作为插件开发语言。

这里我主要分析了对于Java版本的系统架构与具体的执行流程。

java 版本使用 javaagent 机制来实现。在服务器启动时,可动态的修改Java字节码,对敏感操作的函数进行hook,比如:

  • 数据库操作
  • 文件读取、写入操作
  • 命令执行

当服务器发生攻击,就会触发这些Hook点,此时RASP agent就可以获取到函数的参数,比如要读取的文件名、要执行的命令等等。

系统架构 – Java 版本

Java 版本的系统架构可以参考官方手册

启动流程

这部分的源码在openrasp/agent/java/boot

  1. OpenRASP使用了 on load 的方式进行代理。启动时首先会进入 com.baidu.openrasp.Agent 的 premain 函数进行初始化操作(init()),该函数会在 main 函数之前预先执行。
//com.baidu.openrasp.Agent
public static void premain(String agentArg, Instrumentation inst) {
    init(START_MODE_NORMAL, START_ACTION_INSTALL, inst);
}
public static synchronized void init(String mode, String action, Instrumentation inst) {
    try {
        JarFileHelper.addJarToBootstrap(inst);
        //读取MANIFEST.MF相关信息
        readVersion();
        ModuleLoader.load(mode, action, inst);
    } catch (Throwable e) {
        System.err.println("[OpenRASP] Failed to initialize, will continue without security protection.");
        e.printStackTrace();
    }
}
  1. init()方法中首先利用inst.appendToBootstrapClassLoaderSearch(new JarFile(localJarPath))将自身添加到 BootstrapClassLoader 的ClassPath下。
    这是因为双亲委派机制的存在,类加载器在加载类时无法往下委派加载。当被hook的类需要调用漏洞检测方法的代码时,如果该hook类为BootstrapClassLoader加载的,则无法从该类调用非 BootstrapClassLoader 加载的类中的代码。要解决这个问题,就应该想办法把这种类通过BootstrapClassLoader进行加载。OpenRASP中通过调用appendToBootstrapClassLoaderSearch方法,可以把一个jar包放到Bootstrap ClassLoader的搜索路径。这样的话,当Bootstrap ClassLoader检查自身加载过的类,发现没有找到目标类时,会在指定的jar文件中搜索。官方文档也做了如下解释:
    当去 hook 像 java.io.File 这样由 BootstrapClassLoader 加载的类的时候,无法从该类调用非 BootstrapClassLoader 加载的类中的接口,所以 agent.jar 会先将自己添加到 BootstrapClassLoader 的ClassPath下,这样 hook 由 BootstrapClassLoader 加载的类的时候就能够成功调用到 agent.jar 中的检测入口
//com.baidu.openrasp.JarFileHelper
public static void addJarToBootstrap(Instrumentation inst) throws IOException {
    String localJarPath = getLocalJarPath();
    inst.appendToBootstrapClassLoaderSearch(new JarFile(localJarPath));
}
  1. 加载和初始化引擎模块(rasp-engine.jar)
    ModuleLoader类的静态方法将moduleClassLoader设置为ExtClassLoader,在ModuleLoader构造函数中实例化ModuleContainer,并调用其start()方法。
    在ModuleLoader构造函数中还会执行setStartupOptionForJboss();这里按照源码的解释应该是判断当前进程是否为jboss7 版本,并设置相关属性和预加载包。
//com.baidu.openrasp.ModuleLoader
static {
    // juli
    try {
        Class clazz = Class.forName("java.nio.file.FileSystems");
        clazz.getMethod("getDefault", new Class[0]).invoke(null);
    } catch (Throwable t) {
        // ignore
    }
    Class clazz = ModuleLoader.class;
    // path值示例: file:/opt/apache-tomcat-xxx/rasp/rasp.jar!/com/fuxi/javaagent/Agent.class
    String path = clazz.getResource("/" + clazz.getName().replace(".", "/") + ".class").getPath();
    if (path.startsWith("file:")) {
        path = path.substring(5);
    }
    if (path.contains("!")) {
        path = path.substring(0, path.indexOf("!"));
    }
    try {
        baseDirectory = URLDecoder.decode(new File(path).getParent(), "UTF-8");
    } catch (UnsupportedEncodingException e) {
        baseDirectory = new File(path).getParent();
    }
    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
    while (systemClassLoader.getParent() != null
            && !systemClassLoader.getClass().getName().equals("sun.misc.Launcher$ExtClassLoader")) {
        systemClassLoader = systemClassLoader.getParent();
    }
    moduleClassLoader = systemClassLoader;
}

/**
 * 构造所有模块
 *
 * @param mode 启动模式
 * @param inst {@link java.lang.instrument.Instrumentation}
 */
private ModuleLoader(String mode, Instrumentation inst) throws Throwable {
    if (Module.START_MODE_NORMAL == mode) {
        setStartupOptionForJboss();
    }
    engineContainer = new ModuleContainer(ENGINE_JAR);
    engineContainer.start(mode, inst);
}

/**
 * 加载所有 RASP 模块
 *
 * @param mode 启动模式
 * @param inst {@link java.lang.instrument.Instrumentation}
 */
public static synchronized void load(String mode, String action, Instrumentation inst) throws Throwable {
    if (Module.START_ACTION_INSTALL.equals(action)) {
        if (instance == null) {
            try {
                instxance = new ModuleLoader(mode, inst);
            } catch (Throwable t) {
                instance = null;
                throw t;
            }
        } else {
            System.out.println("[OpenRASP] The OpenRASP has bean initialized and cannot be initialized again");
        }
    } else if (Module.START_ACTION_UNINSTALL.equals(action)) {
        release(mode);
    } else {
        throw new IllegalStateException("[OpenRASP] Can not support the action: " + action);
    }
}

在ModuleContainer构造方法中,获取了rasp-engine.jar中MANIFEST.MF文件的Rasp-Module-Name、Rasp-Module-Class字段。然后使用ExtClassLoader加载Rasp-Module-Class,即com.baidu.openrasp.EngineBoot,并将其实例化,赋值给module变量,然后ModuleLoader构造函数会调用new ModuleContainer(ENGINE_JAR).start(),继而调用module.start(mode, inst);com.baidu.openrasp.EngineBoot#start(),启动引擎。

private Module module;
private String moduleName;
//jarName = rasp-engine.jar
public ModuleContainer(String jarName) throws Throwable {
    try {
        File originFile = new File(baseDirectory + File.separator + jarName);
        JarFile jarFile = new JarFile(originFile);
        Attributes attributes = jarFile.getManifest().getMainAttributes();
        jarFile.close();
        this.moduleName = attributes.getValue("Rasp-Module-Name");
        String moduleEnterClassName = attributes.getValue("Rasp-Module-Class");
        if (moduleName != null && moduleEnterClassName != null
                && !moduleName.equals("") && !moduleEnterClassName.equals("")) {
            Class moduleClass;
            if (ClassLoader.getSystemClassLoader() instanceof URLClassLoader) {
                Method method = Class.forName("java.net.URLClassLoader").getDeclaredMethod("addURL", URL.class);
                method.setAccessible(true);
                method.invoke(moduleClassLoader, originFile.toURI().toURL());
                method.invoke(ClassLoader.getSystemClassLoader(), originFile.toURI().toURL());
                moduleClass = moduleClassLoader.loadClass(moduleEnterClassName);
                module = (Module) moduleClass.newInstance();
            } else if (ModuleLoader.isCustomClassloader()) {
                moduleClassLoader = ClassLoader.getSystemClassLoader();
                Method method = moduleClassLoader.getClass().getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
                method.setAccessible(true);
                try {
                    method.invoke(moduleClassLoader, originFile.getCanonicalPath());
                } catch (Exception e) {
                    method.invoke(moduleClassLoader, originFile.getAbsolutePath());
                }
                moduleClass = moduleClassLoader.loadClass(moduleEnterClassName);
                module = (Module) moduleClass.newInstance();
            } else {
                throw new Exception("[OpenRASP] Failed to initialize module jar: " + jarName);
            }
        }
    } catch (Throwable t) {
        System.err.println("[OpenRASP] Failed to initialize module jar: " + jarName);
        throw t;
    }
}

public void start(String mode, Instrumentation inst) throws Throwable {
    module.start(mode, inst);
}

OpenRASP的执行流程

这部分的源码在openrasp/agent/java/engine
这部分我主要是参考三梦师傅的分析流程进行对比分析。
代码入口就是上文获取的com.baidu.openrasp.EngineBoot#start(),代码虽然有改动,但流程没变:

  1. 输出banner信息
  2. Loader.load():V8引擎的加载,用于解释执行JavaScript
  3. loadConfig():初始化配置
    • LogConfig.ConfigFileAppender():初始化log4j
    • CloudUtils.checkCloudControlEnter():检查云控配置信息
    • LogConfig.syslogManager():读取配置信息,初始化syslog服务连接
  4. Agent.readVersion();BuildRASPModel.initRaspInfo():缓存rasp的build信息
  5. JS.Initialize():初始化插件系统
    • 为V8配置java的logger以及栈堆信息Getter(用于在js中获取当前栈堆信息)
    • UpdatePlugin():读取plugins目录下的js文件,过滤掉大于10MB的js文件,然后全部读入,最后加载到V8引擎中
    • 这里有一个commonLRUCache,主要是用于在hook点去执行js check的时候,进行一个并发幂等
    • InitFileWatcher():初始化一个js plugin监视器((JnotifyWatcher实现文件监控),在js文件有所变动的时候,重新去加载所有插件,实现热更新的特性
  6. CheckerManager.init():初始化所有的checker,从枚举类com.baidu.openrasp.plugin.checker.CheckParameter.Type中读取所有的checker,包含三种类型的checker,一是js插件检测,意味着这个checker会调用js plugin进行攻击检测,二是java本地检测,意味着是调用本地java代码进行攻击检测,三是安全基线检测,是用于检测一些高风险类的安全性基线检测,检测其配置是否有安全隐患。
    下面要讲到的doCheck()方法中的type参数就是这里的Type。
// 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),
LOADLIBRARY("loadLibrary", new V8AttackChecker(), 1 << 20),
//js插件检测-删除
DUBBOREQUEST("dubboRequest", new V8Checker(), 1 << 4),
//js插件检测-新增
DELETEFILE("deleteFile", new V8AttackChecker(), 1 << 18),
MONGO("mongodb", new V8AttackChecker(), 1 << 19),
SSRF_REDIRECT("ssrfRedirect", new V8AttackChecker(), 1 <<21),
RESPONSE("response", new V8AttackChecker(false), 1 << 23),
LINK("link", new V8AttackChecker(), 1 << 24),
JNDI("jndi", new V8AttackChecker(), 1 << 25),
DNS("dns", new V8AttackChecker(), 1 << 26),

// java本地检测
XSS_USERINPUT("xss_userinput", new XssChecker(), 1 << 16),
SQL_SLOW_QUERY("sqlSlowQuery", new SqlResultChecke(false), 0),
// 安全基线检测
POLICY_SQL_CONNECTION("sqlConnection", newSqlConnectionChecker(false), 0),
POLICY_SERVER_TOMCAT("tomcatServer", newTomcatSecurityChecker(false), 0),
POLICY_SERVER_JBOSS("jbossServer", new JBossSecurityChecke(false), 0),
POLICY_SERVER_JBOSSEAP("jbossEAPServer", newJBossEAPSecurityChecker(false), 0),
POLICY_SERVER_JETTY("jettyServer", new JettySecurityChecke(false), 0),
POLICY_SERVER_RESIN("resinServer", new ResinSecurityChecke(false), 0),
POLICY_SERVER_WEBSPHERE("websphereServer", newWebsphereSecurityChecker(false), 0),
POLICY_SERVER_WEBLOGIC("weblogicServer", newWeblogicSecurityChecker(false), 0),
POLICY_SERVER_WILDFLY("wildflyServer", newWildflySecurityChecker(false), 0),
POLICY_SERVER_TONGWEB("tongwebServer", newTongwebSecurityChecker(false), 0),
// 安全基线检测-新增
POLICY_LOG("log", new LogChecker(false), 1 << 22),
POLICY_MONGO_CONNECTION("mongoConnection", newMongoConnectionChecker(false), 0),
POLICY_SERVER_BES("bes", new BESSecurityChecker(false), 0);
  1. initTransformer(inst):核心代码,通过加载class,在加载前使用javassist对其进行hook插桩,以实现rasp的攻击检测功能,这里会重点分析。
private void initTransformer(Instrumentation inst) throws UnmodifiableClassException {
    transformer = new CustomClassTransformer(inst);
    transformer.retransform();
}

public CustomClassTransformer(Instrumentation inst) {
    this.inst = inst;
    inst.addTransformer(this, true);
    addAnnotationHook();
}

private void addAnnotationHook() {
    Set<Class> classesSet = AnnotationScanner.getClassWithAnnotation(SCAN_ANNOTATION_PACKAGE, HookAnnotation.class);
    for (Class clazz : classesSet) {
        try {
            Object object = clazz.newInstance();
            if (object instanceof AbstractClassHook) {
                addHook((AbstractClassHook) object, clazz.getName());
            }
        } catch (Exception e) {
            LogTool.error(ErrorType.HOOK_ERROR, "add hook failed: " + e.getMessage(), e);
        }
    }
}

addAnnotationHook()读取了com.baidu.openrasp.hook包中所有被@HookAnnotation注解的class,然后缓存到集合hooks中,如下图所示

这个hooks用来提供在后续类加载通过com.baidu.openrasp.transformer.CustomClassTransformer#transform的时候,对其进行匹配,判断是否需要hook

//com.baidu.openrasp.transformer.CustomClassTransformer
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                        ProtectionDomain domain, byte[] classfileBuffer) throws IllegalClassFormatException {
    if (loader != null) {
        DependencyFinder.addJarPath(domain);
    }
    if (loader != null && jspClassLoaderNames.contains(loader.getClass().getName())) {
        jspClassLoaderCache.put(className.replace("/", "."), new SoftReference<ClassLoader>(loader));
    }
    for (final AbstractClassHook hook : hooks) {
        if (hook.isClassMatched(className)) {
            CtClass ctClass = null;
            try {
                ClassPool classPool = new ClassPool();
                addLoader(classPool, loader);
                ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
                if (loader == null) {
                    hook.setLoadedByBootstrapLoader(true);
                }
                classfileBuffer = hook.transformClass(ctClass);
                if (classfileBuffer != null) {
                    checkNecessaryHookType(hook.getType());
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (ctClass != null) {
                    ctClass.detach();
                }
            }
        }
    }
    serverDetector.detectServer(className, loader, domain);
    return classfileBuffer;
}

//com.baidu.openrasp.hook.AbstractClassHook
public byte[] transformClass(CtClass ctClass) {
    try {
        hookMethod(ctClass);
        return ctClass.toBytecode();
    } catch (Throwable e) {
        if (Config.getConfig().isDebugEnabled()) {
            LOGGER.info("transform class " + ctClass.getName() + " failed", e);
        }
    }
    return null;
}

transform方法先根据hook.isClassMatched(String className)方法判断当前类名是否在hooks集合中,进而判断是否对加载的class进行hook。
接着调用的是hook类的transformClass(CtClass ctClass)->hookMethod(CtClass ctClass)方法进行了字节码的修改(hook),然后返回修改后的字节码并加载,最终实现了对class进行插桩。
这里依旧拿com.baidu.openrasp.hook.ssrf.HttpClientHook类举例说明transform()方法的流程
这里hook的类在commit-d437中由org/apache/http/client/methods/HttpRequestBase修改为org/apache/http/impl/client包下的CloseableHttpClientAutoRetryHttpClientDecompressingHttpClientAbstractHttpClient这四个方法。
所以当当前className等于其中之一时,进入if语句进行hook。
进入if语句,通过transformClass(CtClass ctClass)方法调用HttpClientHook#hookMethod,这个方法也在上面的commit中进行同步的修改

protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {
    CtClass[] interfaces = ctClass.getInterfaces();
    if (interfaces != null) {
        for (CtClass inter : interfaces) {
            // 兼容 http client 4.0 版本的 AbstractHttpClient
            if (inter.getName().equals("org.apache.http.client.HttpClient")) {
                LinkedList<CtBehavior> methods =
                        getMethod(ctClass, "execute", null, null);
                String afterSrc = getInvokeStaticSrc(HttpClientHook.class, "exitCheck",
                        "$1,$_", Object.class, Object.class);
                for (CtBehavior method : methods) {
                    if (method.getSignature().startsWith("(Lorg/apache/http/client/methods/HttpUriRequest")) {
                        String src = getInvokeStaticSrc(HttpClientHook.class,
                                "checkHttpUri", "$1", Object.class);
                        insertBefore(method, src);
                        insertAfter(method, afterSrc, true);
                    } else if (method.getSignature().startsWith("(Lorg/apache/http/HttpHost")) {
                        String src = getInvokeStaticSrc(HttpClientHook.class,
                                "checkHttpHost", "$1", Object.class);
                        insertBefore(method, src);
                        insertAfter(method, afterSrc, true);
                    }
                }
                break;
            }
        }
    }
}

public static void checkHttpUri(Object uriValue) {
    if (!isChecking.get() && uriValue != null) {
        isChecking.set(true);
        URI uri = (URI) Reflection.invokeMethod(uriValue, "getURI", new Class[]{});
        checkHttpUrl(getSsrfParamFromURI(uri));
    }
}

public static void checkHttpHost(Object host) {
    if (!isChecking.get() && host != null) {
        isChecking.set(true);
        checkHttpUrl(getSsrfParamFromHostValue(host));
    }
}

public static void exitCheck(Object uriValue, Object response) {
    try {
        if (isChecking.get() && response != null) {
            URI redirectUri = HttpClientRedirectHook.uriCache.get();
            if (redirectUri != null) {
                HashMap<String, Object> params = getSsrfParam(uriValue);
                if (params != null) {
                    HashMap<String, Object> redirectParams = getSsrfParamFromURI(redirectUri);
                    if (redirectParams != null) {
                        AbstractRedirectHook.checkHttpClientRedirect(params, redirectParams, response);
                    }
                }
            }
        }
    } finally {
        isChecking.set(false);
        HttpClientRedirectHook.uriCache.set(null);
    }
}

可以清晰的看到对于org.apache.http.client.HttpClient这个接口中参数包含org.apache.http.client.methods.HttpUriRequest类型的exitCheck()方法,会在其方法体前插入checkHttpUri()代码,并在最后插入exitCheck()代码进行收尾工作;
对于这个接口中参数包含org.apache.http.HttpHost类型的exitCheck()方法,会在其方法体前插入checkHttpHost()代码,并在最后插入exitCheck()代码进行收尾工作。
这两个hook方法都会调用checkHttpUrl()方法,对检测ssrf的js插件进行调用以检测攻击。
调用流程没变,这里直接搬一下三梦师傅的流程汇总(注释有部分增减):

1. com.baidu.openrasp.hook.ssrf.AbstractSSRFHook#checkHttpUrl

2. com.baidu.openrasp.HookHandler#doCheck

3. com.baidu.openrasp.HookHandler#doCheckWithoutRequest
在这里,做了一些云控注册成功判断和白名单的处理。
这里有两个注意点:
(1)当服务器的cpu使用率超过90%,禁用全部hook点。(这里能否DDOS,让CPU高负载,使用率超过90%,禁用全部hook点?)(2)当云控注册成功之前,不进入任何hook点

4. com.baidu.openrasp.HookHandler#doRealCheckWithoutRequest
在这里,做了一些参数的封装,以及失败日志、耗时日志等输出,并且在检测到攻击时(下一层返回),抛出异常

5. com.baidu.openrasp.plugin.checker.CheckerManager#check

6. com.baidu.openrasp.plugin.checker.AbstractChecker#check
在这里,对js或者其他类型的安全检测之后的结果,进行事件处理并返回结果

7. com.baidu.openrasp.plugin.checker.v8.V8AttackChecker#checkParam

8.com.baidu.openrasp.plugin.js.JS#Check
在这里,做了一些commonLRUCache的并发幂等处理

9.com.baidu.openrasp.v8.V8#Check(java.lang.String, byte[], int, com.baidu.openrasp.v8.Context, boolean, int)
因为在UpdatePlugin()中就读取plugins目录下的js文件并加载到V8引擎中,在InitFileWatcher()中初始化一个js plugin监视器,在js文件有所变动的时候,重新去加载所有插件,实现热更新的特性。所以这里就相当于要使用v8引擎调用之前写好的js进行check。

总结一下,OpenRASP工作流程为:(配置文件那些暂不描述)

  1. 准备阶段:
    • 将漏洞进行分类,然后加上@HookAnnotation注解,并重写isClassMatched()方法,然后将所有带有@HookAnnotation注解的类添加到hooks集合中。这样在执行到相应的底层类时能够根据hook.isClassMatched()的返回值判断是否需要hook。
    • 初始化v8引擎,并将js加载到其中,同时初始化一个js plugin监视器以实现热更新。
  2. 检测阶段:
    • 对于当前类属于准备阶段中设置的hooks集合中时,进行hook操作。具体的hook形式在hook.hookMethod()方法中实现,比如HttpClientHook#hookMethod()对包含不同参数的org.apache.http.client.HttpClient#execute方法添加不同的代码。
      • 添加不同的check代码后最终都会执行诸如doCheck、checkParam等方法,其中在v8引擎中调用js,实现对不同攻击的检测。

后续

后续会考虑相关绕过方式,比如到官网查找不常用的、危险的底层函数,绕过OpenRASP的hook;绕过OpenRASP设置的规则;之前也有师傅通过开启新线程,在新线程中进行命令执行的方式绕过OpenRASP,或是通过全局的Hook开关关闭rasp。

Reference

https://www.thoughtworks.com/zh-cn/insights/decoder/i/iast-rasp
https://rasp.baidu.com/doc/hacking/architect/main.html
https://turn1tup.github.io/2020/09/06/OpenRASP%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
https://threedr3am.github.io/2019/12/31/OpenRASP%E6%A0%B8%E5%BF%83%E6%BA%90%E7%A0%81%E6%B5%85%E6%9E%90/

本文作者:alt1r

本文转载于先知社区,如有侵权请联系删除

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享