前言
最近看新的Xstream反序列化链时看到的东西。
ServerTableEntry+GetterSetterReflection
来自CVE-2021-21345,ServerTableEntry类的verify函数如下:
/**
* Verify whether the server definition is valid.
*/
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函数如下:
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函数:
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函数:
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函数:
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函数:
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函数:
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函数:
public void marshal(T object, OutputStream output, NamespaceContext nsContext, AttachmentMarshaller am) throws JAXBException {
this.bridge.marshal(object, output, nsContext, am);
}
JAXBAttachment类的writeTO函数、asInputStream函数和getInputStream函数:
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函数:
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函数:
public AttachmentSet getAttachments() {
return this.delegate.getAttachments();
}
ResponseContext类的get函数:
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函数:
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开头,然后进行解码,解码代码如下:
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); // Escape character
// Special escape
if(b >= 0 && b < FREE_CHARS) {
out.write(CHAR_MAP[b]);
} else { // Normal escape
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压缩。
最后,加载类会执行其静态代码,也就可以进行任意代码执行。