前言
这篇文章是在上一篇Xstream反序列化SSRF研究(CVE-2020-26258)之后的文章,如果要阅读,必须要先看上一篇文章。
POC
1 | import com.thoughtworks.xstream.XStream; |
代码分析
和上一篇的ssrf相比较,原理是相同的,都是使用了HashMap的put方法作为触发点,所以我们直接看和ssrf的poc不同的地方即可。我们可以看到不同是在下面这个标签开始出现的。
1 | <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'> |
因此,现在dataHandler类的dataSource变量为com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource的实例。而该实例的contentType变量值为text/plain,后面的解析以此类推,总之万变不离其中还是实例化的类的一个过程。不过中间有一些不好理解的地方,要拿出来说。比如下面这段xml。
1 | <iter class='java.util.ArrayList$Itr'> |
这个iter变量的实例为一个java.util.ArrayList$Itr,然后其cursor=0,lastRet=-1,expectedModCount=1,这些都好理解,就是out-class这里不好理解。因为java.util.ArrayList$Itr这个类中没有outer-class这个变量,通过动态调试我发现out-class表示的是this$0。
his$0表示的是内部类获取外部类实例的一个引用,java.util.ArrayList$Itr的外部实例是java.util.ArrayList。
因此下一步的转换就是将下面的标签按照java.uti.ArrayList的转换器转换,java.uti.ArrayList的转换器为CollectionConverter
这个转换器会把内部的标签转换为一个个的数组元素,比如下图中官方示例的应该转换后就等同于array[0]=”apple”,array[1]=”banana”。在我们的示例中,java.uti.ArrayList这个实例中存放的第一个数组元素就是一个ProcessBuilder的实例。
到这里解析方面告一段落,下面要研究触发的问题,我们在java.lang.ProcessBuilder的start方法上打上断点,然后debug模式运行我们的poc。
我们先把目光放到Base64Data.get方法的第98行,这里是和上一篇中ssrf不同开始的地方。我们从下图可以看到,此时的is为java.io.SequenceInputStream。
接下来看java.io.SequenceInputStream.nextStream()方法,这里面调用了e.nextElement()。e的值在xml中被我们设置为了javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator这个实体类,因此我们会跳转到这个类的nextElement方法。
javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator的iterator参数为javax.imageio.spi.FilterIterator这个实体类,因此其调用next(),会调用javax.imageio.spi.FilterIterator的next方法。
然后调用内部的advance()
下图中的filter为javax.imageio.ImageIO$ContainsFilter这个实体类,而iter为java.util.ArrayList$Itr这个实体类。
那么,elt等于什么?elt为java.util.ArrayList$Itr的next()方法的返回值。cursor=0,最后返回的就是elementData[0],也就是ProcessBuilder这个实例。
然后跟入到filter.filter(elt),而method为java.lang.ProcessBuilder的start方法,elt为ProcessBuilder实体类,最后调用到java.lang.ProcessBuilder的start触发命令执行。
总结
挖掘手法很精妙,不像是慢慢看出来的,可能是使用了某种自动化挖掘的方式,以后水平高了可以学习一下。