最近在学习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.Agentpremain 函数进行初始化操作(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/

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖