前言
最近看新的Xstream反序列化链时看到的东西。
ServerTableEntry+GetterSetterReflection
来自CVE-2021-21345,ServerTableEntry类的verify函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public int verify() { try {
if (debug) System.out.println("Server being verified w/" + activationCmd);
process = Runtime.getRuntime().exec(activationCmd); int result = process.waitFor(); if (debug) printDebug( "verify", "returns " + ServerMain.printResult( result ) ) ; return result ; } catch (Exception e) { if (debug) printDebug( "verify", "returns unknown error because of exception " + e ) ; return ServerMain.UNKNOWN_ERROR; } }
|
可以看到这里有一个非常明显的命令执行点,问题在于这个函数在Java内部似乎没有什么调用的地方。
GetterSetterReflection里面有个刚好适用的任意函数调用,这个类继承了Accessor类,而且其get函数如下:
1 2 3 4 5 6 7 8 9
| public ValueT get(BeanT bean) throws AccessorException { try { return this.getter.invoke(bean); } catch (IllegalAccessException var3) { throw new IllegalAccessError(var3.getMessage()); } catch (InvocationTargetException var4) { throw this.handleInvocationTargetException(var4); } }
|
虽然有一个类型不可控的参数,但是用于调用无参函数还是很好使的,比如上面的verify。
然后是ClassBeanInfoImpl类的serializeURIs函数会调用Accessor类们的get函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void serializeURIs(BeanT bean, XMLSerializer target) throws SAXException { try { int var5; if (!this.retainPropertyInfo) { ... } else { ... }
if (this.inheritedAttWildcard != null) { Map<QName, String> map = (Map)this.inheritedAttWildcard.get(bean); target.attWildcardAsURIs(map, (String)null); } } catch (AccessorException var8) { target.reportError((String)null, var8); }
}
|
再往上是XMLSerializer类的childAsXsiType函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public final void childAsXsiType(Object child, String fieldName, JaxBeanInfo expected, boolean nillable) throws SAXException, IOException, XMLStreamException { if (child == null) { this.handleMissingObjectError(fieldName); } else { child = this.pushObject(child, fieldName); if (child == null) { this.endNamespaceDecls((Object)null); this.endAttributes(); return; }
...
actual.serializeURIs(child, this); ... }
}
|
MarshallerImpl类的write函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| protected final <T> void write(Name rootTagName, JaxBeanInfo<T> bi, T obj, XmlOutput out, Runnable postInitAction) throws JAXBException { try { try { this.prewrite(out, true, postInitAction); this.serializer.startElement(rootTagName, (Object)null); if (bi.jaxbType != Void.class && bi.jaxbType != Void.TYPE) { if (obj == null) { this.serializer.writeXsiNilTrue(); } else { this.serializer.childAsXsiType(obj, "root", bi, false); } } ... } ... } finally { this.cleanUp(); }
}
|
BridgeImpl类的marshal函数:
1 2 3 4 5 6 7 8 9
| public void marshal(Marshaller _m, T t, OutputStream output, NamespaceContext nsContext) throws JAXBException { MarshallerImpl m = (MarshallerImpl)_m; Runnable pia = null; if (nsContext != null) { pia = new StAXPostInitAction(nsContext, m.serializer); }
m.write(this.tagName, this.bi, t, m.createWriter(output), pia); }
|
BridgeImpl父类Bridge类的marshal函数:
1 2 3 4 5 6 7
| public void marshal(T object, OutputStream output, NamespaceContext nsContext, AttachmentMarshaller am) throws JAXBException { Marshaller m = (Marshaller)this.context.marshallerPool.take(); m.setAttachmentMarshaller(am); this.marshal(m, object, output, nsContext); m.setAttachmentMarshaller((AttachmentMarshaller)null); this.context.marshallerPool.recycle(m); }
|
BridgeWrapper类的marshal函数:
1 2 3
| public void marshal(T object, OutputStream output, NamespaceContext nsContext, AttachmentMarshaller am) throws JAXBException { this.bridge.marshal(object, output, nsContext, am); }
|
JAXBAttachment类的writeTO函数、asInputStream函数和getInputStream函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public void writeTo(OutputStream os) throws IOException { try { this.bridge.marshal(this.jaxbObject, os, (NamespaceContext)null, (AttachmentMarshaller)null); } catch (JAXBException var3) { throw new WebServiceException(var3); } }
public InputStream asInputStream() { ByteArrayBuffer bab = new ByteArrayBuffer();
try { this.writeTo((OutputStream)bab); } catch (IOException var3) { throw new WebServiceException(var3); }
return bab.newInputStream(); }
public InputStream getInputStream() throws IOException { return this.asInputStream(); }
|
XMLMultiPart类的getMessage函数和getAttachments函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private Message getMessage() { if (this.delegate == null) { MimeMultipartParser mpp; try { mpp = new MimeMultipartParser(this.dataSource.getInputStream(), this.dataSource.getContentType(), this.feature); } ... }
return this.delegate; }
@NotNull public AttachmentSet getAttachments() { return this.getMessage().getAttachments(); }
|
MessageWrapper类的getAttachments函数:
1 2 3
| public AttachmentSet getAttachments() { return this.delegate.getAttachments(); }
|
ResponseContext类的get函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public Object get(Object key) { if (this.packet.supports(key)) { return this.packet.get(key); } else if (this.packet.getHandlerScopePropertyNames(true).contains(key)) { return null; } else { Object value = this.packet.invocationProperties.get(key); if (!key.equals("javax.xml.ws.binding.attachments.inbound")) { return value; } else { Map<String, DataHandler> atts = (Map)value; if (atts == null) { atts = new HashMap(); }
AttachmentSet attSet = this.packet.getMessage().getAttachments(); ... } } }
|
这个类还是个Map,其get函数的调用还是比较好找的。利用链找到的是IndexOrderComparator类的compare函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public int compare(Object var1, Object var2) { return !this.order ? -compareIndices(this.indexMap, var1, var2, FALLBACK_INDEX) : compareIndices(this.indexMap, var1, var2, FALLBACK_INDEX); }
protected static int compareIndices(Map var0, Object var1, Object var2, Integer var3) { Integer var4 = (Integer)var0.get(var1); Integer var5 = (Integer)var0.get(var2); if (var4 == null) { var4 = var3; }
if (var5 == null) { var5 = var3; }
return var4.compareTo(var5); }
|
最后接上PriorityQueue类即可,通过Xsteam的原生反序列化触发comparator的compare函数即可。
ClassLoader
来自CVE-2021-21350,使用的是BCEL包下的ClassLoader,不过根据参考文章,好像高版本中已经删去了这个类,需要切换到8u251版本以下,这里就懒得换了,简单看下就好,其loadClass函数可以从字节码中加载类,会判断类名是否由BCEL开头,然后进行解码,解码代码如下:
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
| private static class JavaWriter extends FilterWriter { public JavaWriter(Writer out) { super(out); }
public void write(int b) throws IOException { if(isJavaIdentifierPart((char)b) && (b != ESCAPE_CHAR)) { out.write(b); } else { out.write(ESCAPE_CHAR);
if(b >= 0 && b < FREE_CHARS) { out.write(CHAR_MAP[b]); } else { char[] tmp = Integer.toHexString(b).toCharArray();
if(tmp.length == 1) { out.write('0'); out.write(tmp[0]); } else { out.write(tmp[0]); out.write(tmp[1]); } } } }
public void write(char[] cbuf, int off, int len) throws IOException { for(int i=0; i < len; i++) write(cbuf[off + i]); }
public void write(String str, int off, int len) throws IOException { write(str.toCharArray(), off, len); } }
|
总结一下就是十六进制编码,然后再将斜杠替换成$符号,听说还会加一层GZip压缩。
最后,加载类会执行其静态代码,也就可以进行任意代码执行。
参考文章
https://paper.seebug.org/1543