浅谈RASP

文章首发于先知社区:RASP简单实现

前言

前边的Javassit、Javaagent其实都是为了RASP做铺垫,由于接触较少,这里也只是浅浅的做个测试,了解下RASP原理。

RASP

​ RASP(Runtime application self-protection,应用程序运行时防护),其与WAF等传统安全防护措施的主要区别于其防护层级更加底层——在功能调用前或调用时能获取访问到当前方法的参数等信息,根据这些信息来判定是否安全。

Rasp 与 Waf 区别

优点

  • 误报率低:WAF 放置在 Web 应用程序外层,依赖于分析网络流量,拦截所有它认为可疑的输入而并不分析这些输入是如何被应用程序处理的。RASP 通过对应用程序上下文,会精确分析用户输入在应用程序里的行为,根据分析结果区分合法行为还是攻击行为,然后对攻击行为进行响应和处理。 RASP 不依赖于网络流量分析,大大减少了误报。
  • 保护全面性:WAF 在分析与过滤用户输入并检测有害行为方面比较有效,但是对应用程序的输出检查则毫无办法。RASP 不但能监控用户输入,也能监控应用程序组件的输出,这就使 RASP 具备了全面防护的能力。RASP 解决方案能够定位 WAF 通常无法检测到的严重问题——未处理的异常、会话劫持、权限提升和敏感数据披露等等。

缺点

  • 性能损耗:RASP 实时拦截、深入检测用户数据流,这是对精确度和误判率都有很大的帮助,但是对用户性能有一些影响,这些性能消耗也必然影响到用户的体验,这也是影响企业客户部署 RASP 的很大一方面原因。现在 RASP 的提供商在优化方面做了很大努力,大部分 RASP 对性能影响在 5% 左右。
  • 部署成本:RASP 是针对应用程序的,每个应用程序都必须有独立的探针,不能像防火墙一样只在入口放置一个设备就可以了,并且需要根据应用开发的技术不同使用不同的 RASP。比如 PHP 应用与 Java 应用需要不同的 RASP 产品,增加了部署成本。

双亲委派

简单解释下就是,如下三个类,在JVM加载某个类时,会先从BootstrapClassLoader进行加载,如果它没有则会从ExtClassLoader,还没有则到AppClassLoader,最终到自定义的类加载器

  • 启动类加载器(BootstrapClassLoader),由C++实现,没有父类。
  • 拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null
  • 系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader

而默认情况下premain,agentmain都是由AppClassLoader加载的,用一个实例看一下

Main.java

内容随便,这个只是后边会用到

public class Main {
    public static void main(String[] args) throws IOException {
        ProcessBuilder command = new ProcessBuilder().command("cmd", "/c", "chdir");
        Process process = command.start();
        InputStream inputStream = process.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        System.out.println(bufferedReader.readLine());
    }
}

ClassLoaderDemo

public class ClassLoaderDemo {
    public static void premain(String agentArgs, Instrumentation inst) throws IOException, UnmodifiableClassException {
        System.out.println(ClassLoader.getSystemClassLoader().toString());
    }
}

分别打成jar包后执行:

java -javaagent:agent.jar=Sentiment -jar Main.jar

可以看到当前使用的类加载器是AppClassLoader
在这里插入图片描述

而这就会引起一个问题

问题

这里直接引用的参考文章里师傅写的内容,但是好像有些不准确的地方,因此简单了解下就好

agentmain和main都是同一个appClassLoader加载的,并且我们写好的各种类都是AppClassLoader加载的,那BootstrapClassLoader和extClassLoader加载的类调用我们写好的代理方法,这些类加载器向上委派寻找类时,扩展类加载器和引导类加载器都没有加过,直接违背双亲委派原则!举个例子,因为我们可以在transform函数里面获取到类字节码,并加以修改,如果我们在系统类方法前面插了代理方法,由于这些系统类是被Bootstrap ClassLoader加载的,当BootstrapClassLoader检查这些代理方法是否被加载时,直接就报错了,因为代理类是appClassLoader加载的

要解决这个问题,我们就应该想办法把代理类通过BootstrapClassLoader进行加载,从百度的OpenRASP可以学到解决方案:

// localJarPath为代理jar包的绝对路径
inst.appendToBootstrapClassLoaderSearch(new JarFile(localJarPath))

通过appendToBootstrapClassLoaderSearch方法,可以把一个jar包放到Bootstrap ClassLoader的搜索路径,也就是说,当Bootstrap ClassLoader检查自身加载过的类,发现没有找到目标类时,会在指定的jar文件中搜索,从而避免前面提到的违背双亲委派问题。

RaspDemo

这里编写一个Hook ProcessBuilder执行cmd的简单例子,同时也遇到了上述提到的双亲委派问题,之后会提到。

Main.java

主程序不变

public class Main {
    public static void main(String[] args) throws IOException {
        ProcessBuilder command = new ProcessBuilder().command("cmd", "/c", "chdir");
        Process process = command.start();
        InputStream inputStream = process.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        System.out.println(bufferedReader.readLine());
    }
}

PreMainDemo

public class PreMainDemo {
    public static void premain(String agentArgs, Instrumentation inst) throws IOException, UnmodifiableClassException {
        Class[] classes = inst.getAllLoadedClasses();
        for (Class aClass : classes) {
            if (aClass.getName().equals("java.lang.ProcessBuilder") && inst.isModifiableClass(aClass)){
                inst.addTransformer(new TransformerDemo(),true);
                inst.retransformClasses(aClass);
            }
        }
    }
}

TransformerDemo

public class TransformerDemo implements ClassFileTransformer {

    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        byte[] bytes = null;
        if (className.equals("java/lang/ProcessBuilder")) {
            ClassPool cp = ClassPool.getDefault();
            CtClass ctClass = null;
            try {
                ctClass = cp.get("java.lang.ProcessBuilder");
                CtMethod[] methods = ctClass.getMethods();
                String source = "if ($0.command.get(0).equals(\"cmd\")){\n" +
                        "    System.out.println(\"Dangerous....\");\n" +
                        "    System.out.println($0);\n" +
                        "    return null;\n" +
                        "}";
                for (CtMethod method : methods) {
                    if (method.getName().equals("start")) {
                        method.insertBefore(source);
                        break;
                    }
                }
                bytes = ctClass.toBytecode();
            } catch (NotFoundException | CannotCompileException | IOException e) {
                e.printStackTrace();
            } finally {
                if (ctClass != null) {
                    ctClass.detach();
                }
            }
        }
        return bytes;
    }
}

双亲委派问题

上述PreMainDemo中,通过if判断,来找到JVM加载的ProcessBuilder类,进而触发transform对该类进行修改

if (aClass.getName().equals("java.lang.ProcessBuilder") && inst.isModifiableClass(aClass)){

但是当执行之后发现,JVM并没有加载ProcessBuilder类,那就无法通过if判断,触发transform

猜想

上述问题我上网找了一些资料,但各抒己见,所以说说我的看法。(仅个人观点,望师傅们指正!!!)

上边提到premain函数默认使用AppClassLoader进行加载的,并且我们在代码中也没有加载ProcessBuilder类,因此默认不会加载ProcessBuilder类。因为该类在rt.jar包中,而该包是由BootstrapClassLoader进行加载的

解决

既然没有加载ProcessBuilder类,那就可以在遍历JVM加载类之前,用ProcessBuilder processBuilder = new ProcessBuilder();对其进行加载即可。

public class PreMainDemo {
    public static void premain(String agentArgs, Instrumentation inst) throws IOException, UnmodifiableClassException, ClassNotFoundException, InstantiationException, IllegalAccessException {

        ProcessBuilder processBuilder = new ProcessBuilder();

        Class[] classes = inst.getAllLoadedClasses();
        for (Class aClass : classes) {
            if (aClass.getName().equals("java.lang.ProcessBuilder") && inst.isModifiableClass(aClass)){
                inst.addTransformer(new TransformerDemo(),true);
                inst.retransformClasses(aClass);
            }
        }
    }
}

这里其实就可以在复制一遍Main中的代码,因为这样的话即可看出在触发Transofrom前后执行cmd的结果

最终代码

public class PreMainDemo {
    public static void premain(String agentArgs, Instrumentation inst) throws IOException, UnmodifiableClassException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        ProcessBuilder command = new ProcessBuilder().command("cmd", "/c", "chdir");
        Process process = command.start();
        InputStream inputStream = process.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        System.out.println(bufferedReader.readLine());

        Class[] classes = inst.getAllLoadedClasses();
        for (Class aClass : classes) {
            if (aClass.getName().equals("java.lang.ProcessBuilder") && inst.isModifiableClass(aClass)){
                inst.addTransformer(new TransformerDemo(),true);
                inst.retransformClasses(aClass);
            }
        }
    }
}

接着将Main.java和 打包

Main.jar

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifestEntries>
                        <Main-Class>RASP.Main</Main-Class>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

agent.jar

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
            </plugin>

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Premain-Class>RASP.PreMainDemo</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

执行agent

java -javaagent:AgentMemory-1.0-SNAPSHOT-jar-with-dependencies.jar=Sentiment -jar AgentMemory-1.0-SNAPSHOT.jar

可以看到一开始执行了chdir输出了D:\java\AgentMemory\target,因为此时还没触发transform,但之后触发后,输出了Dangerous,并返回了null,所以这里在InputStream inputStream = process.getInputStream();爆了空指针异常,成功h在这里插入图片描述

后记

本篇主要是为了简单了解一下RASP,其中可能有很多错误,尤其是在双亲委派的部分,望师傅们指正。

参考

从零开始的Java RASP实现(二) - bitterz - 博客园 (cnblogs.com)

奇安信攻防社区-初识Rasp——Openrasp代码分析 (butian.net)

Sky’s 自留地 (03sec.com)

浅谈RASP-安全客 - 安全资讯平台 (anquanke.com)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Raspberry Pi是一种小型的单片微型计算机,它的应用范围非常广泛,由于其体积小、成本低,被广泛应用于物联网、家庭娱乐以及智能家居等领域。Java作为一种非常流行的编程语言,也逐渐被应用于Raspberry Pi的开发中。下面将详细介绍Raspberry Pi的Java实现。 首先,Raspberry Pi的Java实现主要包含两个方面。一方面是Java SE Embedded,即Java编程语言在嵌入式系统和嵌入式设备上的实现。Java SE Embedded可直接运行在Raspberry Pi上,使其能够使用Java进行编程和应用开发。另一方面是Java ME Embedded,即Java嵌入式系统用于小型设备的高性能实现。Java ME Embedded适用于嵌入式系统,可运行在Raspberry Pi上,使Raspberry Pi能够支持Java ME平台。 其次,Raspberry Pi的Java实现可以通过多种方式进行编程和应用程序开发。其中,Java SE Embedded可以采用Eclipse、NetBeans和IntelliJ IDEA等开发工具,而Java ME Embedded则可使用Java ME SDK进行开发。此外,Raspberry Pi的Java实现还支持JavaFX,这是一种可以在桌面、浏览器和移动设备上运行的Java用户界面框架,十分方便实用。 总之,Raspberry Pi的Java实现可以使Raspberry Pi拥有更加灵活和多样的应用,同时也为Java开发者提供了一个全新的开发平台。此外,由于Java编程语言的强大和普遍性,Raspberry Pi的Java实现具有较高的开发效率和可维护性。可以预计,在未来的智能家居和物联网应用中,Raspberry Pi的Java实现将会发挥其巨大的潜力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值