Rasp小结

2022/08/22

前言

关于RASP,目前国内的产品有灵蜥,安数云,云锁,OpenRasp等,最知名得应该就是百度的OpenRasp。现已开源,可以学习

前置知识

RASP(Runtime application self-protection)是一种新型应用安全保护技术,它将保护程序像疫苗一样注入到应用程序中,应用程序融为一体,能实时检测和阻断安全攻击,使应用程序具备自我保护能力,当应用程序遭受到实际攻击伤害,就可以自动对其进行防御,而不需要进行人工干预

RASP具有语言相关性,因此各种语言都有其RASP技术实现方式

Java

Java是通过Java Agent方式进行实现,也就是Instrumentation

Java Instrumentation | gk0d’s blog

PHP

PHP是通过开发第php扩展库来进行实现。以下是两个开源扩展

[laruence/taint: Taint is a PHP extension, used for detecting XSS codes (github.com)(https://github.com/fate0/xmark)

https://github.com/laruence/taint

other

RASP技术其实主要就是对编程语言的危险底层函数进行hook,毕竟在怎么编码转换以及调用,最后肯定会去执行最底层的某个方法然后对系统进行调用。由此可以反推出其hook点,然后使用不同的编程语言中不同的技术对其进行实现。

开发环境配置

以下代码均上传至Github中:Java_Rasp_Demo (g

首先我们在IDEA中新建一个maven项目:Rasp_Demo

image-20230307182310062

当前的目录结构如下:

image-20230307190836793

删除src目录,然后右键新建Module,依旧是Maven,这里我命名为Agent

image-20230307190817179

右键新建Module,依旧是Maven,这里我命名为Test,最终如下:

image-20230307192915416

其中Agent目录为我们要实现java agent的主要代码,Test目录则是测试代码。

Agent

调试配置

Agent模块下pom.xml配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>Rasp_Demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Agent</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>5.1</version>
        </dependency>

    </dependencies>

    <build>
        <finalName>agent</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <archive>
                        <manifestFile>src/main/resources/MANIFEST.MF</manifestFile>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <artifactSet>
                                <includes>
                                    <include>commons-io:commons-io:jar:*</include>
                                    <include>org.ow2.asm:asm:jar:*</include>
                                </includes>
                            </artifactSet>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.21.0</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

在resources目录下创建MANIFEST.MF文件,文件内容如下

这个文件在打jar包时要用,内容需要自行修改,不了解的建议直接下载我的项目进行调试

Manifest-Version: 1.0
Premain-Class: org.example.Agent.Agent
Can-Retransform-Classes: true
Can-Redefine-Classes: true
Can-Set-Native-Method-Prefix: true

idea中右上部分找到Add Configurations,然后点击此按钮(下面步骤是为了Maven自动打包)

image-20230307213848927

image-20230307204543221

选择Maven,修改两个地方

image-20230307204802438

image-20230307213910752

代码实现

现在目录结构如下:

image-20230308171957706

org.example下新建入口类Agent实现premain方法

package org.example;

import java.lang.instrument.Instrumentation;
/**
 * @author gk0d
 */
public class Agent {

    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new AgentTransform());
    }
}

Premain方法只做一件事情,就是设置一个ClassFileTransform,用来获取和操作字节码。

接下来创建AgentTransform类,该类需要实现ClassFileTransformer的方法

package org.example;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

/**
 * @author gk0d
 */
public class AgentTransform implements ClassFileTransformer {

    /**
     * @param loader
     * @param className
     * @param classBeingRedefined
     * @param protectionDomain
     * @param classfileBuffer
     * @return
     * @throws IllegalClassFormatException
     */
    @Override
    public byte[] transform(ClassLoader loader, String className,
                            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {

        System.out.println("Load class:" + className);
        return classfileBuffer;
    }


}

点击右上角的agent[clean,intall]进行build。

image-20230308172343790

可以看见打包成功,agent.jar位置为D:\vul\Rasp_Demo\Agent\target\agent.jar


此时我们需要将Test模块搭建为一个Web项目,方便调试。右键Test,添加框架支持

image-20230308173901528

Web应用程序就OK

image-20230308173945455

Test模块pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>Rasp_Demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Test</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
    <!--加入servlet依赖(servletjar-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    <!--jsp的依赖(jsp相关的jar加进来)-->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.2.1</version>
        <scope>provided</scope>
    </dependency>
    </dependencies>


</project>

编写一个servlet程序

package org.example;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class Servlet extends HttpServlet {
    //由于get或者post只是请求实现的不同方式,可以相互调用,业务逻辑是一样的

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        PrintWriter writer = resp.getWriter();//响应流
        writer.print("Hello,Servlet!");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

编写一个servlet的映射

子项目的web.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--注册Servlet-->
    <servlet>
        <servlet-name>hello</servlet-name>
        <!--java文件夹下的目录-->
        <servlet-class>org.example.HelloServlet</servlet-class>
    </servlet>
    <!--Servlet的请求路径-->
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

</web-app>

接下来启动个Tomcat进行调试

首先对Test子项目,添加web

image-20230308175324746

image-20230308175528660

接下来部署Tomcat

image-20230308175732577

启动成功

image-20230308175922542

这时我们设置以下Tomcat

image-20230308180322232

虚拟机选项处填写以下内容,把我们上面的jar包设置进去

-Dfile.encoding=UTF-8
-noverify
-Xbootclasspath/p:D:\vul\Rasp_Demo\Agent\target\agent.jar
-javaagent:D:\vul\Rasp_Demo\Agent\target\agent.jar

image-20230308180503866

启动tomcat,就可以看到我们在AgentTransform中写的打印包名已经生效了,如下图:

image-20230308180804179

Hook实现

然后我们在Agent模块下新建一个TestClassVisitor类,需要继承ClassVisitor类并且实现Opcodes类,代码如下:

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;


/**
 * @author gk0d
 */
public class TestClassVisitor extends ClassVisitor implements Opcodes {

    public TestClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);

        System.out.println(name + "方法的描述符是:" + desc);
        return mv;
    }
}

接下来进Hook

ProcessBuilder(命令执行)类进行hook用户执行的命令,使用transform对类名进行过滤

回到AgentTransform中,对transform方法的内容进行修改,transform方法代码如下:

import org.objectweb.asm.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

/**
 * @author gk0d
 */
public class AgentTransform implements ClassFileTransformer {

    /**
     * @param loader
     * @param className
     * @param classBeingRedefined
     * @param protectionDomain
     * @param classfileBuffer
     * @return
     * @throws IllegalClassFormatException
     */
    @Override
    public byte[] transform(ClassLoader loader, String className,
                            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {

        className = className.replace("/", ".");

        try {
            if (className.contains("ProcessBuilder")) {
                System.out.println("Load class: " + className);

                ClassReader classReader  = new ClassReader(classfileBuffer);
                ClassWriter  classWriter  = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
                ClassVisitor classVisitor = new TestClassVisitor(classWriter);

                classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);

                classfileBuffer = classWriter.toByteArray();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classfileBuffer;
    }

}

首先判断类名是否包含ProcessBuilder,如果包含则使用ClassReader对字节码进行读取,然后新建一个ClassWriter进行对ClassReader读取的字节码进行拼接,然后在新建一个我们自定义的ClassVisitor对类的触发事件进行hook,在然后调用classReaderaccept方法,最后给classfileBuffer重新赋值修改后的字节码。

接下来进行测试,在tomcat中新建一个jsp,用来调用命令执行,代码如下:

<%@ page import="java.io.InputStream" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<pre>
<%
    Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));
    InputStream in = process.getInputStream();
    int a = 0;
    byte[] b = new byte[1024];

    while ((a = in.read(b)) != -1) {
        out.println(new String(b, 0, a));
    }

    in.close();
%>
</pre>

image-20230308181445656

对上面更改后的Agent重新进行build,

在重新build的时候有以下问题

image-20230308204737747

解决方案:

image-20230308204837054

进行访问,可以看到成功执行命令

image-20230308205137157

命令台打印如下:

image-20230308205052115

拿到用户所执行的命令

Agent模块下新建一个名为ProcessBuilderHook的类,然后在类中新建一个名字为start的静态方法,完整代码如下:

import java.util.Arrays;
import java.util.List;

/**
 * @author gk0d
 */
public class ProcessBuilderHook {

    public static void start(List<String> commands) {
        String[] commandArr = commands.toArray(new String[commands.size()]);
        System.out.println(Arrays.toString(commandArr));
    }
}

这个类的作用就是打印我们Hook到的方法

打开TestClassVisitor,对visitMethod方法进行更改。具体代码如下:

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.AdviceAdapter;


/**
 * @author gk0d
 */
public class TestClassVisitor extends ClassVisitor implements Opcodes {

    public TestClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);

        if ("start".equals(name) && "()Ljava/lang/Process;".equals(desc)) {
            System.out.println(name + "--------" + desc);

            return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {
                @Override
                public void visitCode() {

                    mv.visitVarInsn(ALOAD, 0);
                    mv.visitFieldInsn(GETFIELD, "java/lang/ProcessBuilder", "command", "Ljava/util/List;");
                    mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/agent/ProcessBuilderHook", "start", "(Ljava/util/List;)Z", false);
                    Label l1 = new Label();
                    mv.visitLabel(l1);
                    super.visitCode();

                }
            };
        }
        return mv;
    }
}

首先判断传入进来的方法名是否为start以及方法描述符是否为()Ljava/lang/Process;,如果是的话就新建一个AdviceAdapter方法,并且复写visitCode方法,对其字节码进行修改,

mv.visitVarInsn(ALOAD, 0);

拿到栈顶上的this

mv.visitFieldInsn(GETFIELD, "java/lang/ProcessBuilder", "command", "Ljava/util/List;");

拿到this里面的command

mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/agent/ProcessBuilderHook", "start", "(Ljava/util/List;)V", false);

然后调用我们上面新建的ProcessBuilderHook类中的start方法,将上面拿到的this.command压入我们方法。

ProcessBuilderHook类的作用就是让这部分进行调用,然后转移就可以转入到我们的逻辑代码了。也就是打印我们的命令。

我们再次编译一下,然后启动tomcat,访问cmd.jsp看看.

image-20230308220525190

image-20230308220641212

这样就拿到了我们输入的命令。

小结

还很粗糙,后边继续写