OpenRASP agent source code analysis

Table of contents

foreword

Prepare

Source code analysis

1. manifest

 2. Agent analysis

3. Agent uninstallation logic 

Summarize


foreword

The author wrote a long time ago (231 messages) OpenRASP Java application self-protection use_fenglllle's blog-CSDN blog

In fact, many commercial rasp tools are based on the inspiration of OpenRASP. The main method is to inject bytecodes into specific methods through Javaagent technology for core Java classes and perform parameter verification. The core technology is Javaagent, so by analyzing the agent implementation principle of OpenRASP, you can understand the mainstream rasp implementation logic. Optimizing part of the implementation logic on OpenRASP can form a commercial product.

Prepare

Prepare Baidu's admin side, execute ./rasp-cloud -d

get after login

appid、appsecret

Configure the agent and the parameters used by the agent on the Java target machine

Pay attention to Baidu's own one-click injection code jar, the injected agent is before all other agents, and the agent is loaded sequentially.

Source code analysis

1. manifest

agent is the entry defined by mainfest, which can be seen inside the pom plug-in and java package

pom-plugin 

jar package

 2. Agent analysis

 Taking the jvm parameter method as an example, the dynamic injection is similar, and rasp is generally injected when the jvm parameter is started.

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 {
            //添加jar文件到jdk的跟路径下,优先加载
            JarFileHelper.addJarToBootstrap(inst);
            //读取一些agent的数据
            readVersion();
            //核心代码
            ModuleLoader.load(mode, action, inst);
        } catch (Throwable e) {
            System.err.println("[OpenRASP] Failed to initialize, will continue without security protection.");
            e.printStackTrace();
        }
    }

 ModuleLoader.load(mode, action, 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 {
                    //安装
                    instance = 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);
        }
    }

Take a look at the new ModuleLoader logic

    /**
     * 构造所有模块
     *
     * @param mode 启动模式
     * @param inst {@link java.lang.instrument.Instrumentation}
     */
    private ModuleLoader(String mode, Instrumentation inst) throws Throwable {
        // JBoss参数,实际上就是设置一些系统变量
        if (Module.START_MODE_NORMAL == mode) {
            setStartupOptionForJboss();
        }
        //构造对象,加载另外的jar engine.jar 初始化com.baidu.openrasp.EngineBoot
        engineContainer = new ModuleContainer(ENGINE_JAR);
        //核心逻辑
        engineContainer.start(mode, inst);
    }

new ModuleContainer(ENGINE_JAR);

    public ModuleContainer(String jarName) throws Throwable {
        try {
            //engine jar,openrasp有2个jar,单独加载,方便卸载,agent常用手段
            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");
            // com.baidu.openrasp.EngineBoot
            String moduleEnterClassName = attributes.getValue("Rasp-Module-Class");
            //用classloader加载jar
            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);
                    //com.baidu.openrasp.EngineBoot对象
                    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);
                    //初始化com.baidu.openrasp.EngineBoot
                    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;
        }
    }

engineContainer.start(mode, inst); 

    public void start(String mode, Instrumentation inst) throws Exception {
        //帅气的时刻,打印logo
        System.out.println("\n\n" +
                "   ____                   ____  ___   _____ ____ \n" +
                "  / __ \\____  ___  ____  / __ \\/   | / ___// __ \\\n" +
                " / / / / __ \\/ _ \\/ __ \\/ /_/ / /| | \\__ \\/ /_/ /\n" +
                "/ /_/ / /_/ /  __/ / / / _, _/ ___ |___/ / ____/ \n" +
                "\\____/ .___/\\___/_/ /_/_/ |_/_/  |_/____/_/      \n" +
                "    /_/                                          \n\n");
        try {
            //载入v8,调用c,实际上可以使用火狐引擎替代,百度的官方文档的图就是rhino
            Loader.load();
        } catch (Exception e) {
            System.out.println("[OpenRASP] Failed to load native library, please refer to https://rasp.baidu.com/doc/install/software.html#faq-v8-load for possible solutions.");
            e.printStackTrace();
            return;
        }
        if (!loadConfig()) {
            return;
        }
        //缓存rasp的build信息
        Agent.readVersion();
        BuildRASPModel.initRaspInfo(Agent.projectVersion, Agent.buildTime, Agent.gitCommit);
        // 初始化插件系统 调用v8,支持动态更新插件和js
        if (!JS.Initialize()) {
            return;
        }
        //核心代码,加入检查点,就是哪些类的哪些方法需要check
        CheckerManager.init();
        //字节码替换
        initTransformer(inst);
        if (CloudUtils.checkCloudControlEnter()) {
            CrashReporter.install(Config.getConfig().getCloudAddress() + "/v1/agent/crash/report",
                    Config.getConfig().getCloudAppId(), Config.getConfig().getCloudAppSecret(),
                    CloudCacheModel.getInstance().getRaspId());
        }
        deleteTmpDir();
        String message = "[OpenRASP] Engine Initialized [" + Agent.projectVersion + " (build: GitCommit="
                + Agent.gitCommit + " date=" + Agent.buildTime + ")]";
        System.out.println(message);
        Logger.getLogger(EngineBoot.class.getName()).info(message);
    }
loadConfig()
    private boolean loadConfig() throws Exception {
        //处理日志相关
        LogConfig.ConfigFileAppender();
        //单机模式下动态添加获取删除syslog
        if (!CloudUtils.checkCloudControlEnter()) {
            LogConfig.syslogManager();
        } else {
            System.out.println("[OpenRASP] RASP ID: " + CloudCacheModel.getInstance().getRaspId());
        }
        return true;
    }

    private void init() {
        this.configFileDir = baseDirectory + File.separator + CONFIG_DIR_NAME;
        String configFilePath = this.configFileDir + File.separator + CONFIG_FILE_NAME;
        try {
            //载入靶机的配置文件,里面配置了appid appsecret等
            loadConfigFromFile(new File(configFilePath), true);
            if (!getCloudSwitch()) {
                try {
                    FileScanMonitor.addMonitor(
                            baseDirectory, instance);
                } catch (JNotifyException e) {
                    throw new ConfigLoadException("add listener on " + baseDirectory + " failed because:" + e.getMessage());
                }
                //支持动态刷新文件
                addConfigFileMonitor();
            }
        } catch (FileNotFoundException e) {
            handleException("Could not find openrasp.yml, using default settings: " + e.getMessage(), e);
        } catch (JNotifyException e) {
            handleException("add listener on " + configFileDir + " failed because:" + e.getMessage(), e);
        } catch (Exception e) {
            handleException("cannot load properties file: " + e.getMessage(), e);
        }
        String configValidMsg = checkMajorConfig();
        if (configValidMsg != null) {
            LogTool.error(ErrorType.CONFIG_ERROR, configValidMsg);
            throw new ConfigLoadException(configValidMsg);
        }
    }

JS.Initialize(), JS engine initialization, js file monitoring 

    public synchronized static boolean Initialize() {
        try {
            //v8初始化检查
            if (!V8.Initialize()) {
                throw new Exception("[OpenRASP] Failed to initialize V8 worker threads");
            }
            //log
            V8.SetLogger(new com.baidu.openrasp.v8.Logger() {
                @Override
                public void log(String msg) {
                    pluginLog(msg);
                }
            });
            //
            V8.SetStackGetter(new com.baidu.openrasp.v8.StackGetter() {
                @Override
                public byte[] get() {
                    try {
                        ByteArrayOutputStream stack = new ByteArrayOutputStream();
                        JsonStream.serialize(StackTrace.getParamStackTraceArray(), stack);
                        stack.write(0);
                        return stack.getByteArray();
                    } catch (Exception e) {
                        return null;
                    }
                }
            });
            Context.setKeys();
            //是否云端控制,比如js更新,插件更新
            if (!CloudUtils.checkCloudControlEnter()) {
                //更新插件
                UpdatePlugin();
                //js文件监听器
                InitFileWatcher();
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error(e);
            return false;
        }
    }


    public synchronized static boolean UpdatePlugin(List<String[]> scripts) {
        boolean rst = V8.CreateSnapshot(pluginConfig, scripts.toArray(), BuildRASPModel.getRaspVersion());
        if (rst) {
            try {
                //执行js
                String jsonString = V8.ExecuteScript("JSON.stringify(RASP.algorithmConfig || {})",
                        "get-algorithm-config.js");
                Config.getConfig().setConfig(ConfigItem.ALGORITHM_CONFIG, jsonString, true);
            } catch (Exception e) {
                LogTool.error(ErrorType.PLUGIN_ERROR, e.getMessage(), e);
            }
            Config.commonLRUCache.clear();
        }
        return rst;
    }
CheckerManager.init();
    public synchronized static void init() throws Exception {
        for (Type type : Type.values()) {
            checkers.put(type, type.checker);
        }
    }

The checked class is written in the code, which means that the new class needs to be restarted, but generally the parameter update rule is added, and the frequency of new classes is relatively low

​​​​​​​​​​​​​​​​​​​​​

custom converter, retransform

    private void initTransformer(Instrumentation inst) throws UnmodifiableClassException {
        transformer = new CustomClassTransformer(inst);
        transformer.retransform();
    }

here is the key

    public CustomClassTransformer(Instrumentation inst) {
        this.inst = inst;
        inst.addTransformer(this, true);
        //定义了哪些类需要类替换
        addAnnotationHook();
    }

 addAnnotationHook();

    private void addAnnotationHook() {
        //com.baidu.openrasp.hook HookAnnotation注解的类
        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);
            }
        }
    }

addHook((AbstractClassHook) object, clazz.getName());//Actually cache the class that needs to be replaced, and can be used when restoring

The key is still the transform method

 

    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));
        }
        //关键点,使用javassist字节码增强
        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);
                    }
                    //字节码增强了,包括Tomcat,jdk等类
                    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;
    }

Defines a series of buried point class replacements

 

Take com.baidu.openrasp.hook.file.FileHook as an example

 Added a parameter check before calling the list method of File

    protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {
        String src = getInvokeStaticSrc(FileHook.class, "checkListFiles", "$0", File.class);
        insertBefore(ctClass, "list", "()[Ljava/lang/String;", src);
    }

 

The architecture diagram of openrasp does not correspond to the open source code. The v8 engine used in the open source code is Rhino, which is native Java code and does not require JNI.

3. Agent uninstallation logic 

release(mode)

    public static synchronized void release(String mode) {
        try {
            if (engineContainer != null) {
                System.out.println("[OpenRASP] Start to release OpenRASP");

                engineContainer.release(mode);
                engineContainer = null;
            } else {
                System.out.println("[OpenRASP] Engine is initialized, skipped");
            }
        } catch (Throwable throwable) {
            // ignore
        }
    }

 engineContainer.release(mode);

    public void release(String mode) {
        //任务线程停止
        CloudManager.stop();
        //CPU 监控线程停掉
        CpuMonitorManager.release();
        if (transformer != null) {
            transformer.release(); //类替换还原
        }
        JS.Dispose(); //JS检查还原
        CheckerManager.release(); //java 检查还原
        String message = "[OpenRASP] Engine Released [" + Agent.projectVersion + " (build: GitCommit="
                + Agent.gitCommit + " date=" + Agent.buildTime + ")]";
        System.out.println(message);
    }

    public synchronized static void Dispose() {
        if (watchId != null) {
            boolean oldValue = HookHandler.enableHook.getAndSet(false);
            FileScanMonitor.removeMonitor(watchId);
            watchId = null;
            HookHandler.enableHook.set(oldValue);
        }
    }

4. The process of carrying out the inspection 

Take Tomcat Xss as an example, the same is true for the rest, because the hook bytecode interception of Tomcat's request is defined, and the source code at startup has been analyzed

Because it is bytecode interception, the interception level can be defined and the request can be blocked

 

The specific method of use is defined at startup. For example, http requests have the risk of xss. The specific interception efficiency can greatly improve the performance of the application. For example, if there is no xss, for example, there is no front-end page. Configure xss interception

 

 Finally execute each checker

Checker inspection, and take FileHook code injection inspection as an example

This will also call the checker code, and then check the parameters

 

Part of the inspection means is JS inspection

 

 Actually the logic is very clear

Bytecode injection checkpoint

Execute the check, according to the logic, you can call the checker through the plug-in, or you can directly check the return

When executing the checker, parameters can be checked through the JS engine (the js file of the js engine can dynamically update the rules)

 

Summarize

The source code of OpenRASP has been analyzed. In fact, the code is not complicated, but the idea is very good. In the actual use process

To consider the performance impact,

Consider whether to block and intercept the impact on business

It is necessary to consider the use logic of each component, which can be customized and supports dynamic updates. After all, there is still a risk of bytecode replacement, so the data for inspection is generally updated, so the js engine is introduced and updated through js

In the end, we still have to consider the actual needs, such as the intranet environment, after all, the cost is very high.

Guess you like

Origin blog.csdn.net/fenglllle/article/details/128167659