![ea5d33e699b345f390005a49411ff3ca.png](https://i-blog.csdnimg.cn/blog_migrate/2fedf83fcdf17b9ae955762b2d499082.jpeg)
本文首发于“合天智汇”公众号 作者:Fortheone
前言
最近学习java反序列化学到了weblogic部分,weblogic之前的两个反序列化漏洞不涉及T3协议之类的,只是涉及到了XMLDecoder反序列化导致漏洞,但是网上大部分的文章都只讲到了触发XMLDecoder部分就结束了,并没有讲为什么XMLDecoder会触发反序列化导致命令执行。于是带着好奇的我就跟着调了一下XMLDecoder的反序列化过程。
xml序列化
首先了解一下java中的XMLDecoder是什么。XMLDecoder就是jdk中一个用于处理xml数据的类,先看两个例子。
这里引用一下浅蓝表哥的(强推浅蓝表哥的博客https://b1ue.cn/
import java.beans.XMLEncoder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
/**
* @author 浅蓝
* @email blue@ixsec.org
* @since 2019/4/24 12:09
*/
public class Test {
public static void main(String[] args) throws IOException, InterruptedException {
HashMap<Object, Object> map = new HashMap<>();
map.put("123","aaaa");
map.put("321",new ArrayList<>());
XMLEncoder xmlEncoder = new XMLEncoder(System.out);
xmlEncoder.writeObject(map);
xmlEncoder.close();
}
}
![fc1afc8f585b2cf9ad51403e24d61a27.png](https://i-blog.csdnimg.cn/blog_migrate/d08d707fd8b9b478f8decceffd11729a.jpeg)
这样就把map对象变成了xml数据,再使用XMLDecoder解析一下。
/**
* @author 浅蓝
* @email blue@ixsec.org
* @since 2019/4/24 12:09
*/
public class Test {
public static void main(String[] args) throws IOException, InterruptedException {
String s = "<java version="1.8.0_131" class="java.beans.XMLDecoder">n" +
" <object class="java.util.HashMap">n" +
" <void method="put">n" +
" <string>123</string>n" +
" <string>aaaa</string>n" +
" </void>n" +
" <void method="put">n" +
" <string>321</string>n" +
" <object class="java.util.ArrayList"/>n" +
" </void>n" +
" </object>n" +
"</java>";
StringBufferInputStream stringBufferInputStream = new StringBufferInputStream(s);
XMLDecoder xmlDecoder = new XMLDecoder(stringBufferInputStream);
Object o = xmlDecoder.readObject();
System.out.println(o);
}
}
![efdd44c96a58721088763f3eb90d9c86.png](https://i-blog.csdnimg.cn/blog_migrate/a52d6cb7a5e176bffdf0b66de64180dc.png)
就可以把之前的xml数据反序列化回map对象,那么如果对xml数据进行修改,使其变成一个执行命令的数据。比如说:
<java version="1.7.0_80" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0"><string>calc</string></void>
</array>
<void method="start"></void>
</object>
</java>
然后对其反序列化即可执行命令弹出计算器。
![27c260d48743fd7fe000b8776ed93b96.png](https://i-blog.csdnimg.cn/blog_migrate/8b431d79c6bb41fd573b892e6b0236ae.jpeg)
现在我们知道了如果使用XMLDecoder去反序列化xml数据,数据中包含的命令会被执行。接下来就对其进行分析一下。
XMLDecoder反序列化漏洞成因
一、XML数据解析前的函数处理
![120a544ff1e15fb8ac658185eee85a83.png](https://i-blog.csdnimg.cn/blog_migrate/ca0804cc8387ea35b061dbe4f3389243.png)
在readObject处打上断点开始debug
![7c53455e1e38937f70266478e3072be7.png](https://i-blog.csdnimg.cn/blog_migrate/ad658ce15668acfb62c663546a16a1c2.jpeg)
进入了parsingComplete方法,跟进。
![0c124ca87e3f65e850e43b472f723a17.png](https://i-blog.csdnimg.cn/blog_migrate/44e3c4f908042579ea6527ae012cd5eb.jpeg)
其中使用XMLDecoder的handler属性DocumentHandler的parse方法,并且传入了我们输入的xml数据,跟进。
![5a324fb5938b342945763559a47e8329.png](https://i-blog.csdnimg.cn/blog_migrate/153ed8d48083d500e87e3460081f888b.jpeg)
这里调用了SAXParserImpl类的parse方法。
![80972d51b61258859142f94ec1e88a01.png](https://i-blog.csdnimg.cn/blog_migrate/667d178871653de0d6d94cd7405f5c1e.jpeg)
然后又进了xmlReader的parse方法。
![45660ae12f72487178619a089fb9cd8c.png](https://i-blog.csdnimg.cn/blog_migrate/9e010962ed8b20f15f67f7dffb4e6aa3.jpeg)
这里又调用了xmlReader父类AbstractSAXParser的parser方法。
![a475d4712b851ef44d82698aed4cdc73.png](https://i-blog.csdnimg.cn/blog_migrate/12eb593645563e6855d560ce4d7a0808.jpeg)
最后进入了XML11Configuration类的parse方法。
二、XML数据的处理
![38f02fb99341afb09c1dcaf522adc365.png](https://i-blog.csdnimg.cn/blog_migrate/3da3c9a3c32761b4415c3aa6ed6d9af5.jpeg)
在XML11Configuration中进行了很多解析XML之前的操作,我们不去仔细研究,看到处理XML数据的函数scanDocument。跟进查看
![2c47ddef1e7b5fb1368f6065061433a3.png](https://i-blog.csdnimg.cn/blog_migrate/eca4dd792e0864f60e714d5510c4f67f.jpeg)
这个函数通过迭代的方式对XML数据的标签进行解析,网上有些文章写道“解析至END_ELEMENT时跟进调试”,但是我看了一下我这里的END_ELEMENT。
![86be014fc3724007cfd8a62799369e2c.png](https://i-blog.csdnimg.cn/blog_migrate/ae22f80c5bfcef4784cfefd7566c2bdd.jpeg)
里面没有函数可以跟进啊,然后搜了一些其他的文章,是因为jdk版本的问题,处理的逻辑放在了next函数里。在do while循环里跳了大概十次,就开始解析了xml的标签。
![94faa3f63080272fcc7ffacf3a19a33b.png](https://i-blog.csdnimg.cn/blog_migrate/bc141fcedc796f9cc90e25f5fe5fea51.png)
跳到XMLDocumentScannerImpl中的next方法
![3726b3935897d455b483e8a003b30a4c.png](https://i-blog.csdnimg.cn/blog_migrate/09540e6f1ef6b77db3f25d0f0947e022.jpeg)
跳到XMLDocumentFragmentScannerImpl中的next方法,解析到endtag时会走到scanEndElement方法里。
然后就到了网上说的endElement方法里,跟进。
![89e41b3e9f14e50186307b4b4c1c6d54.png](https://i-blog.csdnimg.cn/blog_migrate/208744163d8a100600d93003707630d9.jpeg)
这一部分的解析可以参考下图:
![a47ca9a382eb53e1063572c9948f65a8.png](https://i-blog.csdnimg.cn/blog_migrate/711b19a5457dc8e9f94e19c8341eba50.jpeg)
也就是说解析时会按照标签一个一个解析。
![f5a9e53bd5b3919971de4a25c7c3d6f8.png](https://i-blog.csdnimg.cn/blog_migrate/3cabe5ed4ebd0ed8109b03384beb8c80.jpeg)
这里调用了DocumentHandler的endElement方法。接下来就是很重要的部分
![345832c382993b0acf23e122addcb1e1.png](https://i-blog.csdnimg.cn/blog_migrate/eac5c165283c6f31057fa879e88506d3.jpeg)
![9fde401335c4de595e0143f2a209c333.png](https://i-blog.csdnimg.cn/blog_migrate/88fa6eae632030cb5074364d259a968e.jpeg)
这里的handler是StringElementHandler,但是这个类没有重写endElement方法,所以调用的是父类ElementHandler的endElement方法,其中调用了getValueObject来获取标签中的value值,这里的标签是string标签,所以获取到的值是calc。
![ab871b28062ba509e0d99e53a350eff7.png](https://i-blog.csdnimg.cn/blog_migrate/87f7058d61fd036f7a1c445584a11abb.jpeg)
![50060ba0423e3d94f1f935f595d0d1e5.png](https://i-blog.csdnimg.cn/blog_migrate/9d0b0482120a60353d6c0f8900dc7569.png)
然后将其添加到其父类标签VoidElementHandler的Argument属性中。
![4139b256f26a9c5f24cffe869364095f.png](https://i-blog.csdnimg.cn/blog_migrate/5c265c7b33f91336c70cecb544bcfc1a.jpeg)
然后将handler指向其父类VoidElementHandler。
![b7d22a6c6052244122d614e8b03e1889.png](https://i-blog.csdnimg.cn/blog_migrate/af95c97dad4a8223d4b0e0dd7237a1b0.jpeg)
继续解析到void标签,此时的handler就是VoidElementHandler,接着调用getValueObject。但是因为没有重写该方法,所以调用父类NewElementHandler的getValueObject。
![7198040b33650fc4d8e7392423a8132e.png](https://i-blog.csdnimg.cn/blog_migrate/39621e6b5c67c2096177bc13f1afc63d.jpeg)
![e74230df22f3d7ace3611a6a9258b644.png](https://i-blog.csdnimg.cn/blog_migrate/32ec3287b6c1374c186f7c882c110e4e.jpeg)
继续跟进发现实现了反射调用invoke方法,也就是执行了set方法。接着再解析Array标签,按照上面的步骤解析,就完成了这一部分参数的解析。
<array class="java.lang.String"length="1">
<void index="0">
<string>calc</string>
</void>
</array>
![69fa8f36596d98b0a8a7e4fa7e75a891.png](https://i-blog.csdnimg.cn/blog_migrate/9ff756efc1f4d44a45afa1e523a7ca6c.jpeg)
那么再按照上面的步骤解析object标签,然后调用new 方法实例化 ProcessBuilder类。
![7ae766c3e54f56bff62bc5944336775e.png](https://i-blog.csdnimg.cn/blog_migrate/206b64fcc5a6ac38494e3c6404139d87.jpeg)
然后解析到void标签获取到start方法,然后通过调用start方法实现了命令执行,弹出计算器。
也就相当于最后拼接了 new java.lang.ProcessBuilder(new String[]{"calc"}).start();
![c388591216ba330a9ca3eeb89b45fbe1.png](https://i-blog.csdnimg.cn/blog_migrate/898ab6364390ee1d2434ed3d403792c6.jpeg)
文章有说的不对的地方请师傅们指点,刚开始学java,大佬们轻喷。。。
参考文章
https://b1ue.cn/archives/239.html
https://zhuanlan.zhihu.com/p/108754274
https://blog.csdn.net/SKI_12/article/details/85058040
相关实验
Java反序列漏洞
实验:Java反序列漏洞(合天网安实验室)
(本实验通过Apache Commons Collections 3为例,分析并复现JAVA反序列化漏洞。)