前言

最近看新的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压缩。

最后,加载类会执行其静态代码,也就可以进行任意代码执行。


参考文章

https://paper.seebug.org/1543


Web Java

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

CodeQL入门
chrome浏览器漏洞强化3-CVE-2020-6418