Author:tr1ple

主要分析以下四个部分:

1.openrasp agent

这里主要进行插桩的定义,其pom.xml中定义了能够当类重新load时重定义以及重新转换

这里定义了两种插桩方式对应之前安装时的独立web的jar的attach或者修改启动脚本添加rasp的jar的方式

其中init操作则需要将rasp.jar添加到Bootstrap路径中,因为后面修改字节码时将涉及到bootstraploader加载的一些类,正常情况下由rasp位于System class path根据类加载机制是拦截不到的bootstrapclassloader的类加载路径下的class,加入到Bootstrapclassloader的搜索路径下以后,才能拦截到

接着调用Moduleloader.load,通过选择mode(premain或者agentmain),action(install或者uninstall),该类主要进行加载和初始化引擎模块rasp-engine.jar

load方法将会根据选择的action来new一个moduloader,传入模式和inst

moduleLoader中将使用rasp引擎jar文件new一个ModuleContainer容器(static代码块主要完成获取rasp.jar路径以及设置moduleclassloader),然后启动该引擎容器,传入插桩方式mode和插桩实例inst

启动引擎函数:

根据加载的agent\java\engine下面的主类来启动rasp引擎

也就是rasp-engine.jar的manifest.mf里面所定义的EngineBoot类的start方法,模块名为rasp-engine,采用低版本的1.6.0_45打包可以兼容高版本

2.openrasp engine

主要的一些rasp具体的操作逻辑,包括hook操作

根据第一部分初始化的最后一个阶段调用rasp引擎模块的start方法,对应Engineboot类,所以直接定位到该类:

public class EngineBoot implements Module { //该类是实现Moudle接口的,因此可以调用start方法

    private CustomClassTransformer transformer; //定义类转换器

    @Override
public void start(String mode, Instrumentation inst) throws Exception {
System.out.println("\n\n" + //rasp打印标志
" ____ ____ ___ _____ ____ \n" +
" / __ \\____ ___ ____ / __ \\/ | / ___// __ \\\n" +
" / / / / __ \\/ _ \\/ __ \\/ /_/ / /| | \\__ \\/ /_/ /\n" +
"/ /_/ / /_/ / __/ / / / _, _/ ___ |___/ / ____/ \n" +
"\\____/ .___/\\___/_/ /_/_/ |_/_/ |_/____/_/ \n" +
" /_/ \n\n");
try {
Loader.load(); //加载v8引擎,用于解释js
} 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()) { //进行rasp引擎的初始化配置
return;
}
//缓存rasp的build信息
Agent.readVersion();
BuildRASPModel.initRaspInfo(Agent.projectVersion, Agent.buildTime, Agent.gitCommit);
// 初始化js插件系统
if (!JS.Initialize()) {
return;
}
CheckerManager.init(); //初始化所有类型的checker,包括js插件检测,java本地检测,服务器基线检测
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);
} @Override
public void release(String mode) {
CloudManager.stop();
CpuMonitorManager.release();
if (transformer != null) {
transformer.release();
}
JS.Dispose();
CheckerManager.release();
String message = "[OpenRASP] Engine Released [" + Agent.projectVersion + " (build: GitCommit="
+ Agent.gitCommit + " date=" + Agent.buildTime + ")]";
System.out.println(message);
} private void deleteTmpDir() {
try {
File file = new File(Config.baseDirectory + File.separator + "jar_tmp");
if (file.exists()) {
FileUtils.deleteDirectory(file);
}
} catch (Throwable t) {
Logger.getLogger(EngineBoot.class.getName()).warn("failed to delete jar_tmp directory: " + t.getMessage());
}
} /**
* 初始化配置
*
* @return 配置是否成功
*/
private boolean loadConfig() throws Exception {
LogConfig.ConfigFileAppender(); //初始化log4j的logger
//单机模式下动态添加获取删除syslog
if (!CloudUtils.checkCloudControlEnter()) {
LogConfig.syslogManager();
} else {
System.out.println("[OpenRASP] RASP ID: " + CloudCacheModel.getInstance().getRaspId());
}
return true;
} /**
* 初始化类字节码的转换器
*
* @param inst 用于管理字节码转换器
*/
private void initTransformer(Instrumentation inst) throws UnmodifiableClassException {
transformer = new CustomClassTransformer(inst);
transformer.retransform();
} }

v8的引擎的初始化,调用的为本地java代码的initalize方法

    public synchronized static boolean Initialize() {
try {
if (!V8.Initialize()) {
throw new Exception("[OpenRASP] Failed to initialize V8 worker threads");
}
V8.SetLogger(new com.baidu.openrasp.v8.Logger() { //设置v8的logger
@Override
public void log(String msg) {
PLUGIN_LOGGER.info(msg);
}
});
V8.SetStackGetter(new com.baidu.openrasp.v8.StackGetter() { //设置v8获取栈信息的getter方法,这里获得的栈信息,每一条信息包括类名、方法名和行号classname@methodname(linenumber)
@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();
if (!CloudUtils.checkCloudControlEnter()) {
UpdatePlugin(); //加载js插件到v8引擎中
InitFileWatcher(); //启动对js插件的文件监控,从而实现热部署,动态的增删js中的检测规则
}
return true;
} catch (Exception e) {
e.printStackTrace();
LOGGER.error(e);
return false;
}
}

updatePlugin:

其中涉及到rasp hook功能的开关,关于rasp绕过的一种方式就是通过反射关掉这个引擎

接着获取到js插件的目录plugins

默认就是official.js,检测各种攻击的逻辑就写在里面,用js写实现热部署,并加载到v8引擎中

InitFileWatcher:

这里利用jnotify对js插件目录进行监控,用的代码是openrasp二次开发过的https://github.com/baidu-security/openrasp-jnotify

public synchronized static void InitFileWatcher() throws Exception {
boolean oldValue = HookHandler.enableHook.getAndSet(false);
if (watchId != null) { //监听器id
FileScanMonitor.removeMonitor(watchId); //移除监听器
watchId = null;
}
watchId = FileScanMonitor.addMonitor(Config.getConfig().getScriptDirectory(), new FileScanListener() {
@Override
public void onFileCreate(File file) {
if (file.getName().endsWith(".js")) {
UpdatePlugin();
}
} @Override
public void onFileChange(File file) {
if (file.getName().endsWith(".js")) {
UpdatePlugin();
}
} @Override
public void onFileDelete(File file) {
if (file.getName().endsWith(".js")) {
UpdatePlugin();
}
}
});
HookHandler.enableHook.set(oldValue);
}

addMonitor将传入监听目录和事件回调接口,最后返回监听器id,其中mask定义了创建+删除+修改三种模式,对应回调函数则重写了OnfileCreate、OnfileChange、OnfileDelete三种方法,只要是后缀为js的文件被创建、删除或者修改了则调用UpdatePlugin方法重新读取plugins目录下的检测js逻辑并重新加载到v8引擎中

CheckerManager.init方法:

public class CheckerManager {

    private static EnumMap<Type, Checker> checkers = new EnumMap<Type, Checker>(Type.class);

    public synchronized static void init() throws Exception {
for (Type type : Type.values()) {
checkers.put(type, type.checker); //加载所有类型的检测放入checkers,type.checker就是某种检测对应的类
}
} public synchronized static void release() {
checkers = null;
} public static boolean check(Type type, CheckParameter parameter) {
return checkers.get(type).check(parameter); //调用检测类进行参数检测
} }

包括使用js插件进行检测的,对应的是类V8AttackChecker,就是调用V8引擎加载js进行检测

本地检测的两种攻击:

另外一些也是是本地的类检查的,一些服务器安全配置检查,数据库连接以及日志检查

接着CheckManager.init结束以后,此时将初始换插桩用的转换器

自定义classTransformer:

/*
* Copyright 2017-2020 Baidu Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package com.baidu.openrasp.transformer; import com.baidu.openrasp.ModuleLoader;
import com.baidu.openrasp.config.Config;
import com.baidu.openrasp.dependency.DependencyFinder;
import com.baidu.openrasp.detector.ServerDetectorManager;
import com.baidu.openrasp.hook.AbstractClassHook;
import com.baidu.openrasp.messaging.ErrorType;
import com.baidu.openrasp.messaging.LogTool;
import com.baidu.openrasp.tool.annotation.AnnotationScanner;
import com.baidu.openrasp.tool.annotation.HookAnnotation;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import org.apache.log4j.Logger; import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.ref.SoftReference;
import java.security.ProtectionDomain;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet; /**
* 自定义类字节码转换器,用于hook类的方法
*/
public class CustomClassTransformer implements ClassFileTransformer {
public static final Logger LOGGER = Logger.getLogger(CustomClassTransformer.class.getName());
private static final String SCAN_ANNOTATION_PACKAGE = "com.baidu.openrasp.hook"; //hook的类所在的包,hook的类都有对应的注解标注
private static HashSet<String> jspClassLoaderNames = new HashSet<String>(); //保存要用到的一些类加载器
private static ConcurrentSkipListSet<String> necessaryHookType = new ConcurrentSkipListSet<String>();
private static ConcurrentSkipListSet<String> dubboNecessaryHookType = new ConcurrentSkipListSet<String>(); //dubbo要hook的类型
public static ConcurrentHashMap<String, SoftReference<ClassLoader>> jspClassLoaderCache = new ConcurrentHashMap<String, SoftReference<ClassLoader>>(); private Instrumentation inst;
private HashSet<AbstractClassHook> hooks = new HashSet<AbstractClassHook>(); //各种攻击对应的hook类的实例
private ServerDetectorManager serverDetector = ServerDetectorManager.getInstance(); public static volatile boolean isNecessaryHookComplete = false; //volatile修饰,保证多线程下该共享变量的可见性,值更改后立即刷新到主存,工作线程才能够从内存中取到新的值
public static volatile boolean isDubboNecessaryHookComplete = false; //dubbo的hook static {
jspClassLoaderNames.add("org.apache.jasper.servlet.JasperLoader"); //类加载要用到的一些类加载器
jspClassLoaderNames.add("com.caucho.loader.DynamicClassLoader");
jspClassLoaderNames.add("com.ibm.ws.jsp.webcontainerext.JSPExtensionClassLoader");
jspClassLoaderNames.add("weblogic.servlet.jsp.JspClassLoader");
dubboNecessaryHookType.add("dubbo_preRequest");
dubboNecessaryHookType.add("dubboRequest");
} public CustomClassTransformer(Instrumentation inst) {
this.inst = inst;
inst.addTransformer(this, true);
addAnnotationHook(); //在这要操作所有带hook注解的类了,虽然看注解用上貌似效率慢一点,但是这里用起来感觉还是很方便
} public void release() {
inst.removeTransformer(this);
retransform();
} public void retransform() {
LinkedList<Class> retransformClasses = new LinkedList<Class>();
Class[] loadedClasses = inst.getAllLoadedClasses();
for (Class clazz : loadedClasses) {
if (isClassMatched(clazz.getName().replace(".", "/"))) {
if (inst.isModifiableClass(clazz) && !clazz.getName().startsWith("java.lang.invoke.LambdaForm")) {
try {
// hook已经加载的类,或者是回滚已经加载的类
inst.retransformClasses(clazz);
} catch (Throwable t) {
LogTool.error(ErrorType.HOOK_ERROR,
"failed to retransform class " + clazz.getName() + ": " + t.getMessage(), t);
}
}
}
}
} private void addHook(AbstractClassHook hook, String className) { //正常情况下将添加所有带注解的hook点
if (hook.isNecessary()) { //默认是false
necessaryHookType.add(hook.getType()); //每种hook类对应一个type,例如读文件、删除文件、xxe、ognl
}
String[] ignore = Config.getConfig().getIgnoreHooks(); //拿到不hook的类名,支持配置的
for (String s : ignore) {
if (hook.couldIgnore() && (s.equals("all") || s.equals(hook.getType()))) { //hook点可以忽略
LOGGER.info("ignore hook type " + hook.getType() + ", class " + className);
return;
}
}
hooks.add(hook);
} private void addAnnotationHook() {
Set<Class> classesSet = AnnotationScanner.getClassWithAnnotation(SCAN_ANNOTATION_PACKAGE, HookAnnotation.class); //取到所有带HookAnnotaion.class注解的类
for (Class clazz : classesSet) {
try {
Object object = clazz.newInstance(); //实例化每种攻击对应的hook类
if (object instanceof AbstractClassHook) {
addHook((AbstractClassHook) object, clazz.getName());
}
} catch (Exception e) {
LogTool.error(ErrorType.HOOK_ERROR, "add hook failed: " + e.getMessage(), e);
}
}
} /**
* 过滤需要hook的类,进行字节码更改
*
* @see ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])
*/
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain domain, byte[] classfileBuffer) throws IllegalClassFormatException { //transform也就是实际插桩生效的地方,loadclass到jvm中时触发
if (loader != null) {
DependencyFinder.addJarPath(domain);
//因为用到的class可能是某个jar包中的,因此这里根据当前保护域去找到当前load的class的绝对路径,若其存在,则将对应的jar包加到loadedJarPath中
}
if (loader != null && jspClassLoaderNames.contains(loader.getClass().getName())) { //如果当前的类加载器是jsp相关的类加载器
jspClassLoaderCache.put(className.replace("/", "."), new SoftReference<ClassLoader>(loader));
        //这里用softReference对jsp相关的classloader进行弱引用封装,SoftReference 所指向的对象,当没有强引用指向它时,会在内存中停留一段的时间,
后面jvm再根据内存情况(堆上情况)和SoftReference.get来决定要不要回收该对象,弱引用封装的对象通过get拿到对象的强引用再使用对象,这里是为了防止classloader内存泄露
}
for (final AbstractClassHook hook : hooks) { //对添加到hooks中的所有类别的hook点进行遍历
if (hook.isClassMatched(className)) { //此时要判断要hook的类名
CtClass ctClass = null;
try {
ClassPool classPool = new ClassPool(); //要用到javaassist技术改变字节码了
addLoader(classPool, loader); //初始化class文件的搜索路径
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;
} private void checkNecessaryHookType(String type) {
if (!isNecessaryHookComplete && necessaryHookType.contains(type)) {
necessaryHookType.remove(type);
if (necessaryHookType.isEmpty()) {
isNecessaryHookComplete = true;
}
} if (!isDubboNecessaryHookComplete && dubboNecessaryHookType.contains(type)) {
dubboNecessaryHookType.remove(type);
if (dubboNecessaryHookType.isEmpty()) {
isDubboNecessaryHookComplete = true;
}
}
} public boolean isClassMatched(String className) {
for (final AbstractClassHook hook : getHooks()) {
if (hook.isClassMatched(className)) {
return true;
}
}
return serverDetector.isClassMatched(className);
} private void addLoader(ClassPool classPool, ClassLoader loader) {
classPool.appendSystemPath(); //添加jvm启动时的一些搜索路径比如扩展类,rt.jar或者classpath下的类
classPool.appendClassPath(new ClassClassPath(ModuleLoader.class));
if (loader != null) {
classPool.appendClassPath(new LoaderClassPath(loader));
}
} public HashSet<AbstractClassHook> getHooks() {
return hooks;
} }

hook的相关类

判断是不是某个注解的hook类对应的要进行插桩的class

3.openrasp安装时的一些检测代码

其中App.java为安装rasp的主程序

根据nodetect选择安装模式:

nodetect模式下attach方法:

找到服务器对应的启动脚本并修改

不同系统支持的平台如下所示:

operateServer主要在这个阶段要完成的是:

1.根据不同的操作系统种类使用不同的工厂类,调用工厂类的getInstaller来根据nodetect参数判断目标程序是否是以springboot型的独立jar启动选择GenericInstaller模式安装(此时将定义不需要修改启动shell脚本去插入一下启动rasp的配置项,直接使用attach模式根据提供的pid进行attach)。若nodetect为false,则要探测一些服务器的标志文件去判断目标服务器种类拿到Installer的实例,后面则要根据不同服务器种类去修改相应的服务器的shell启动脚本添加加载rasp的配置项

2.拿到GenericInstaller或者Installer后调用其install方法进行rasp的安装,Installer的install调用中需要去找到服务器的启动脚本添加配置项

4.openrasp的攻击检测插件,检查攻击的源码

之前分析到rasp在初始化js插件时将会把plugins下的js文件加载到v8引擎中,来实现热部署,这部分检测逻辑代码太多啦,这里对于不同语言使用js来实现检测逻辑,从而实现通用检测,我只关心java相关的漏洞检查,除了下面列出的一些CVE,还包括java的一些通用漏洞的检测,这部分单独将进行研究。

Java openrasp学习记录(二)的更多相关文章

  1. Java openrasp学习记录(一)

    前言: 最近一直在做学校实验室安排的项目,太惨了,没多少时间学习新知识,不过rasp还是要挤挤时间学的,先从小例子的分析开始,了解rasp的基本设计思路,后面详细阅读openrasp的源码进行学习!欢 ...

  2. Material Calendar View 学习记录(二)

    Material Calendar View 学习记录(二) github link: material-calendarview; 在学习记录一中简单翻译了该开源项目的README.md文档.接下来 ...

  3. Spring Boot学习记录(二)--thymeleaf模板 - CSDN博客

    ==他的博客应该不错,没有细看 Spring Boot学习记录(二)--thymeleaf模板 - CSDN博客 http://blog.csdn.net/u012706811/article/det ...

  4. Java设计模式学习记录-迭代器模式

    前言 这次要介绍的是迭代器模式,也是一种行为模式.我现在觉得写博客有点应付了,前阵子一天一篇,感觉这样其实有点没理解透彻就写下来了,而且写完后自己也没有多看几遍,上次在面试的时候被问到java中的I/ ...

  5. Java设计模式学习记录-外观模式

    前言 这次要介绍的是外观模式(也称为门面模式),外观模式也属于结构型模式,其实外观模式还是非常好理解的,简单的来讲就是将多个复杂的业务封装成一个方法,在调用此方法时可以不必关系具体执行了哪些业务,而只 ...

  6. JavaScript学习记录二

    title: JavaScript学习记录二 toc: true date: 2018-09-13 10:14:53 --<JavaScript高级程序设计(第2版)>学习笔记 要多查阅M ...

  7. Java IO学习笔记二

    Java IO学习笔记二 流的概念 在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成. 程序中的输入输 ...

  8. 2019/3/4 java集合学习(二)

    java集合学习(二) 在学完ArrayList 和 LinkedList之后,基本已经掌握了最基本的java常用数据结构,但是为了提高程序的效率,还有很多种特点各异的数据结构等着我们去运用,类如可以 ...

  9. Java设计模式学习记录-模板方法模式

    前言 模板方法模式,定义一个操作中算法的骨架,而将一些步骤延迟到子类中.使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤. 模板方法模式 概念介绍 模板方法模式,其实是很好理解的,具体 ...

随机推荐

  1. Ripple 20:Treck TCP/IP协议漏洞技术分析

    本文由“合天智汇”公众号首发,作者:b1ngo Ripple 20:Treck TCP/IP协议漏洞技术分析 Ripple20是一系列影响数亿台设备的0day(19个),是JSOF研究实验室在Trec ...

  2. bzoj1787[Ahoi2008]Meet 紧急集合&bzoj1832[AHOI2008]聚会

    bzoj1787[Ahoi2008]Meet 紧急集合 bzoj1832[AHOI2008]聚会 题意: 给个树,每次给三个点,求与这三个点距离最小的点. 题解: 倍增求出两两之间的LCA后,比较容易 ...

  3. ant-design-vue中实现modal模态框的复用(添加,编辑展示同一个模态框)

    用两个button(添加,编辑)按钮展示同一个模态框,并不是什么大问题,问题在于解决这两个模态框得有自己的确定和取消方法 父页面完全接管子页面(利于子页面复用) 父页面代码: <template ...

  4. P1525 关押罪犯(洛谷)

    前几天没做题,神经有点错乱,感觉一片虚无.今天开始继续写博客. 题目描述 S 城现有两座监狱,一共关押着N名罪犯,编号分别为1-N.他们之间的关系自然也极不和谐.很多罪犯之间甚至积怨已久,如果客观条件 ...

  5. 数字孪生,数据驱动下的北京 CBD 智能楼宇三维可视化系统

    前言 楼宇作为建筑基础设施的主体,为人们提供着重要的生存空间.随着物联网.人工智能概念的兴起以及智慧城市如火如荼的开展,智能楼宇的重要性越发突显. 随着城市现代化建设的发展,建筑的智能化,特别是公用建 ...

  6. Centos7安装ftp服务

    本文介绍的ftp是可以使用匿名用户登录,且默认路径是根路径,私人使用非常方便,公开使用具有一定的风险,不安全. # .安装 yum install -y vsftpd # .配置 vim /etc/v ...

  7. 题解 SP2713 【GSS4 - Can you answer these queries IV】

    用计算器算一算,就可以发现\(10^{18}\)的数,被开方\(6\)次后就变为了\(1\). 所以我们可以直接暴力的进行区间修改,若这个数已经到达\(1\),则以后就不再修改(因为\(1\)开方后还 ...

  8. 字符编码笔记:ASCII,Unicode 和 UTF-8个人理解

    一.ASCII 码 我们知道,计算机内部,所有信息最终都是一个二进制值.每一个二进制位(bit)有0和1两种状态,因此八个二进制位(字节(Byte )是计算机信息技术用于计量存储容量的一种计量单位,作 ...

  9. 21天学通C++(C++程序的组成部分)

    C++程序被组织成类,而类由成员函数和成员变量组成. 本章学习: 1)C++程序的组成部分. 2)各部分如何协同工作. 3)函数及其用途. 4)基本输入输出操作. C++程序划分为两个部分,以#大头的 ...

  10. sql数据管理语句

    一.数据管理 1.增加数据 INSERT INTO student VALUES(1,'张三','男',20); -- 插入所有字段.一定依次按顺序插入 -- 注意不能少或多字段值 如只需要插入部分字 ...