RASP 技术通常内置在一个应用程序或应用程序运行时环境中,能够控制应用程序的执行,并检测漏洞以防止实时攻击,当应用程序开始运行时,RASP 可以通过分析应用程序的行为和该行为的上下文,保护其不受恶意输入或行为的影响。RASP 通过使应用程序持续检测自身的行为,可以立即识别和缓解攻击,且无需人工干预。在Java应用程序中,通常使用Agent来进行实现。
一 前置知识
Agent
agent分为preagent和agentmain,其主要差别为一个是在应用程序启动前进行加载,一个是在启动中也可以进行加载,agentmain的使用更加广泛,本文也主要使用agentmain(了解过agent内存马对这块内容会比较熟悉)
创建一个agent,新建一个类,实现agentmain方法,方法中body可以打印一句话
项目中添加一个工件
修改MANIFEST.MF文件,主要添加Agent-Class,指定为我们的类
正常构建工件,即可得到jar包
想要将这个agent注入到别的程序中,我们还需要一个注入器(需要tools.jar的依赖,可手动导入)
代码:
apache.catalina.startup表示我现在注入的是tomcat,这里牵扯到VirtualMachine这个类的使用,可以看我之前agent内存马的文章
import com.sun.tools.attach.*; import java.io.IOException; import java.util.List; public class Main { public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor virtualMachineDescriptor : list) { System.out.println(virtualMachineDescriptor.displayName()); if (virtualMachineDescriptor.displayName().contains("apache.catalina.startup")) { VirtualMachine attach = VirtualMachine.attach(virtualMachineDescriptor); attach.loadAgent("路径\\agent01.jar"); } } } }
当注入成功,会直接执行agentmain中自定义的代码
Runtime.getRuntime().exec()本质调用
这里直接看一下,可以发现其实调用的还是java.lang.ProcessImpl的start方法和java.lang.Process
贴一个命令执行流程的图
二 RASP实现
命令执行hook
继续修改agentmain代码,这里涉及 Instrumentation 和javassist(修改字节码)类,
为什么不直接hook Runtime.getRuntime.exec的原因是冰蝎执行命令这里我看到不会通过Runtime方法(如果真实常见应该是全部hook掉)
版本1,只能加载一次,之后不会再触发,通过instrumentation.addTransformer向 JVM 注册一个ClassFileTransformer实例
import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import java.util.Arrays; public class AgentDemo implements ClassFileTransformer { private static final String TARGET_CLASS_NAME = "java.lang.ProcessImpl"; public static void agentmain(String agentArgs, Instrumentation instrumentation) { instrumentation.addTransformer(new AgentDemo(), true); }
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
System.out.println(className.replace('/', '.'));
if (!TARGET_CLASS_NAME.equals(className.replace('/', '.'))) {
return classfileBuffer; // 不是目标类,直接返回
}
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.getCtClass(TARGET_CLASS_NAME);
CtMethod execMethod = ctClass.getDeclaredMethod("start");
CtMethod[] methods = ctClass.getDeclaredMethods();
String insertBefore = "System.out.println(\"ProcessImpl start method is called\");";
execMethod.insertBefore(insertBefore);
return ctClass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
return classfileBuffer; // 发生异常,返回原始字节码
}
}
版本2
每次遇到目标hook类都会重加载,通过instrumentation.retransformClasses实现
import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.security.ProtectionDomain; import java.util.Arrays; public class AgentDemo implements ClassFileTransformer { private static final String TARGET_CLASS_NAME = "java.lang.ProcessImpl"; public static void agentmain(String agentArgs, Instrumentation instrumentation) throws UnmodifiableClassException { // 获取所有已加载的类 Class[] allLoadedClasses = instrumentation.getAllLoadedClasses(); for (Class<?> clazz : allLoadedClasses) { if (clazz.getName().equals(TARGET_CLASS_NAME)) { // 对已加载的类应用变换 instrumentation.retransformClasses(clazz); } } // 添加变换器以应用于之后加载的类 instrumentation.addTransformer(new AgentDemo(), true); } @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { System.out.println(className.replace('/', '.')); if (!TARGET_CLASS_NAME.equals(className.replace('/', '.'))) { return classfileBuffer; // 不是目标类,直接返回 } try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.getCtClass(TARGET_CLASS_NAME); CtMethod execMethod = ctClass.getDeclaredMethod("start"); CtMethod[] methods = ctClass.getDeclaredMethods(); String insertBefore = "System.out.println(\"ProcessImpl start method is called\");"; execMethod.insertBefore(insertBefore); return ctClass.toBytecode(); } catch (Exception e) { e.printStackTrace(); } return classfileBuffer; // 发生异常,返回原始字节码 } }
好的,现在启动一个tomcat,注射器加载一下agent,可以看到当执行命令前被检测到,先执行了我们通过javassist写的内容
这就是RASP的基本原理,我们可以自定义检测Hook的函数,如果是恶意操作直接关闭该访问
文件读写Hook
其实只是更换一个hook的类
其实java中对文件操作类有特别多java.io.File、java.io.FileInputStream、java.io.FileOutputStream,还有很多,以java.io.File为例:
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 java.util.Arrays; import javassist.*; public class AgentDemo implements ClassFileTransformer { private static final String TARGET_CLASS_NAME = "java.io.File"; public static void agentmain(String agentArgs, Instrumentation instrumentation) throws UnmodifiableClassException { // 获取所有已加载的类 Class[] allLoadedClasses = instrumentation.getAllLoadedClasses(); for (Class<?> clazz : allLoadedClasses) { if (clazz.getName().equals(TARGET_CLASS_NAME)) { // 对已加载的类应用变换 instrumentation.retransformClasses(clazz); } } // 添加变换器以应用于之后加载的类 instrumentation.addTransformer(new AgentDemo(), true); } @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if (className.equals(TARGET_CLASS_NAME.replace('.', '/'))) { try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.getCtClass(TARGET_CLASS_NAME); // 获取 createNewFile 方法 CtMethod createNewFileMethod = ctClass.getDeclaredMethod("createNewFile"); // 在 createNewFile 方法前后插入代码 String beforeCode = "System.out.println(\"Before createNewFile: \";)"; createNewFileMethod.insertBefore(beforeCode); String afterCode = "System.out.println(\"After createNewFile: \");"; createNewFileMethod.insertAfter(afterCode); return ctClass.toBytecode(); } catch (Exception e) { e.printStackTrace(); } } return classfileBuffer; // 发生异常或不是目标类,返回原始字节码 } }
更多的不在展示,大致思路就是hook不同的类,可以实现的效果
- 文件系统防御(目录遍历、文件读、写、重命名、移动等)
- SQL查询防御
- XML实体注入防御
- 恶意表达式执行防御(Ognl、SpEL、MVEL2等)
- 恶意WebShell请求拦截
- 恶意文件上传
- 本地命令执行
- 反序列化攻击(Java、XML、Json)
- SSRF攻击
三 绕过思路
现在绕过思路基本就是两个
- 寻找没有被限制的类或者函数来绕过(类似绕过黑名单)
这个其实就是上面File读写的问题,尽量覆盖所有的或者hook更加深入底层的东西
- 利用更底层的技术进行绕过,比如上面图中直接hook c代码(难度较大)
- JNI 绕过 RASP(通过编译so和dll文件的方式)
- 创建新线程的方式(如果有黑名单无法绕过,但是可以绕过堆栈)