什么是 RASP
RASP,全称为 Runtime application self-protection,将保护程序像疫苗一样注入到应用程序中,应用程序融为一体,能实时检测和阻断安全攻击,使应用程序具备自我保护能力,当应用程序遭受到实际攻击伤害,就可以自动对其进行防御,而不需要进行人工干预
在攻防中,大部分都是基于流量规则的 waf 和态感,waf 误报率高,也容易被绕过,而且需要人工干预,而RASP技术防御是根据请求上下文进行拦截的,和 WAF 对比非常明显,我们用一个 sql 的攻击来举例
攻击者对http://xxx.com/index.do?id=1进行测试,一般情况会通过一些 sql 语句的拼接来验证是否有注入,这时会对 url 进行大量发包,如下:
http://xxx.com/index.do?id=1' and 1=2–
但是由于程序内部的过滤,实际执行的 sql 语句可能是这样:
select id,name,age from home where id=’1 ' and 1=2–’
对于 WAF 来说,只要你的流量包命中 WAF 规则,就会告警,当他监测到流量包中有 id=1’ and 1=2–这样的 sql 拼接语句时就会告警,这样误报率会大大增加
但是 RASP 技术可以做到在程序底层,在这个 sql 语句传到数据库之前就进行拦截,如果是恶意请求就拦截或净化参数,如果不是就正常放行,不影响程序本身的功能
这只是一个简单的例子,现在的 WAF 也有净化参数规则,WAF 规则也不是只要 sql 语句有拼接就拦截
各语言 RASP 实现方法
RASP技术其实主要就是对编程语言的危险底层函数进行hook,毕竟在怎么编码转换以及调用,最后肯定会去执行最底层的某个方法然后对系统进行调用。由此可以反推出其hook点,然后使用不同的编程语言中不同的技术对其进行实现。
Java 是通过 javaagnet 机制实现该功能
php 通过开发 php 扩展库实现
.NET 通过 IHostingStartup(承载启动)实现,.NET 我不是很懂,懂得可以看这个文档https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/host/platform-specific-configuration?view=aspnetcore-2.2
Java Agent 实现 RASP
下面主要研究一下 java 下 RASP 的实现,在此之前要先学习一下 java agent
https://www.yuque.com/u49948530/smwxig/gx61p5zdsgxin6o6
这里仿照上面简单 Java Agent,只需将 hook 的类变成关键漏洞触发的类,即可实现简单的 RASP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package com.ale;
import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException;
public class MyCustomAgent { /** * jvm 参数形式启动,运行此方法 * * @param agentArgs * @param inst */ public static void premain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException { System.out.println("premain"); customLogic(inst); }
/** * 动态 attach 方式启动,运行此方法 * * @param agentArgs * @param inst */ public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException { System.out.println("agentmain"); customLogic(inst); }
/** * 打印所有已加载的类名称 * 修改字节码 * * @param inst */ private static void customLogic(Instrumentation inst) throws UnmodifiableClassException { inst.addTransformer(new MyTransformer(), true); Class[] classes = inst.getAllLoadedClasses(); for (Class cls : classes) { System.out.println(cls.getName()); }
boolean retransformClassesSupported = inst.isRetransformClassesSupported(); System.out.println("isRetransformClassesSupported: " + retransformClassesSupported); boolean modifiableClass = inst.isModifiableClass(Runtime.class); System.out.println("isModifiableClass: " + modifiableClass); // 只有确认可重变形后才调用 if (retransformClassesSupported && modifiableClass) { // 主动重加载以触发 transform inst.retransformClasses(java.lang.Runtime.class); } else { System.out.println("Runtime is NOT modifiable!"); }
} }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| package com.ale;
import javassist.*; import javassist.bytecode.AccessFlag;
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain;
public class MyTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("正在加载类:" + className); if (!"java/lang/Runtime".equals(className)) { return classfileBuffer; } try {
ClassPool pool = ClassPool.getDefault(); // bootstrap 类加载器需手动插入 rt.jar 路径 if (loader == null) { pool.appendClassPath(new LoaderClassPath(ClassLoader.getSystemClassLoader())); } CtClass ctRuntime = pool.get("java.lang.Runtime");
// 遍历所有 exec 方法重载 for (CtMethod m : ctRuntime.getDeclaredMethods()) { if (m.getName().equals("exec") && (m.getModifiers() & AccessFlag.PUBLIC) != 0) { wrapExec(m); } } byte[] byteCode = ctRuntime.toBytecode(); ctRuntime.detach(); System.out.println("[SimpleRaspAgent] java.lang.Runtime patched successfully."); return byteCode;
} catch (Exception e) { e.printStackTrace(); } return classfileBuffer; }
/** * 给 exec 方法增加简单的安全检查逻辑 */ private void wrapExec(CtMethod method) throws CannotCompileException, NotFoundException { CtClass[] params = method.getParameterTypes(); String paramExpr;
if (params.length == 0) { paramExpr = "\"\""; // 空参数时直接拼入空串 } else { StringBuilder sb = new StringBuilder(); for (int i = 0; i < params.length; i++) { sb.append("$").append(i + 1); if (i < params.length - 1) sb.append(" + \", \" + "); } paramExpr = sb.toString(); }
String src = "{" + " String _cmd = \"" + method.getName() + "(\" + " + paramExpr + " + \")\";" // 始终合法 + " System.out.println(\"[RASP] Attempt to exec: \" + _cmd);" + " if(_cmd.contains(\"calc\")){" + " System.out.println(\"[RASP] Blocked dangerous command: \" + _cmd);" + " throw new RuntimeException(\"Blocked by RASP\");" + " }" + "}";
method.insertBefore(src); } }
|
1 2 3 4 5 6 7 8
| Manifest-Version: 1.0 Created-By: ale Agent-Class: com.ale.MyCustomAgent Can-Redefine-Classes: true Can-Retransform-Classes: true Premain-Class: com.ale.MyCustomAgent
|
同样有两种启动方法,第一种不再赘述
第二种是通过动态附加 Agent 来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.ale;
import com.sun.tools.attach.*; import java.io.IOException; import java.util.List;
public class AttachAgent { public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { // 调用 VirtualMachine.list() 获取正在运行的 JVM 列表 List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : list) {
// 遍历每一个正在运行的 JVM ,如果 JVM 名称为 RunJvm 则连接该 JVM 并加载特定 Agent if (vmd.displayName().equals("com.ale.runtimeTest")) { // 连接指定 JVM VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id()); // 加载 Agent virtualMachine.loadAgent("C:\\Users\\miaoj\\Documents\\Java安全代码实验\\RaspDemo\\target\\RASP-Demo.jar"); // 断开 JVM 连接 virtualMachine.detach(); }
} } }
|
只要你能获取到 pid ,你可以在任何地方运行上述代码来动态 attach
RASP 绕过
从上面的学习,我们得知RASP主要是通过转换字节码来达到目的,那么如果设置的检测方法存在更底层的方法或者同层级的不同方法能达到相同效果,那么就能完成绕过,但是这里比较难以举例,要根据实际情况来分析