存货文,积了好久,懒得发233,今天发下。
所谓回显。根据各师傅的文章所说。现有的大多数是用defineClass 或 URLClassLoader。加载恶意类并执行。恶意类中进行例如报错、写数据、开RMI通信、等这些操作。就回显了。
不,其实,涉及到内存马加载,都要用这。
反序列化回显,一般都是依靠最终的代码执行点,执行到defineClass或URLClassLoader。,RCE嘛,咱想怎么操作就怎么操作。
RMI方式回显
RMI Bind Echo时要注意:类型转换时,只能转换成接口类型。不能转换成类类型:
1 | public class Client { |
但是,我们传给defineClass
来还原类时,一次只能传一个类。没法将本地自定义接口传上去。
虽说这个报错是客户端的报错。照理说我们仿写一下发送逻辑,就可以规避这些问题?
不。不可以。RMI服务端在调用方法时,是根据接口定义的方法,来生成MethodHash的。普通的Remote接口中没有定义方法,所以最终的MethodHashMap是空的。没法成功调用方法。
所以,我们需要在服务端上找一个继承了Remote的接口,并且该接口能接受参数(命令传参),存在返回值(命令回显)。
在原生JDk中,找到如下接口:
1 | RMIConnection |
虽说大部分接口的返回值不是String。传入的参数也不是String。但只要参数对象中,存在String、byte[]这两种类型的属性。就可以存放命令执行的回显。
或者,目标没有RMI。我们可以自创一个RMI Server。只需要修改下恶意类,改成LocateRegistry.createRegistry()
就好了。这里不赘述了。
用CC2的话。一直反射Invoke,也是可的。只是麻烦点。这里就不写啦哈哈哈。
Tomcat回显
主要的方法:
- 报错回显
- 拿response对象
- Linux下拿
/proc/self/fd/i
。控制文件描述符作回显
SpringMVC中用Tomcat回显,有些小问题。因为Controller逻辑中设置了response后,SpringMvc会再处理一边
解决思路:在Controller中就调用Response#finishResponse
。强制返回。
但不管怎么样。在SpringMVC中强行设置response。会留下一个报错日志。不太优雅哈哈哈
日志信息
1 | java.lang.IllegalStateException: getWriter() has already been called for this response |
修改程序逻辑,初始化静态变量
起一个Tomcat,在Controller打断点。然后回看调用栈。
寻找思路:找一个存放了response
的Static变量,这样程序全局都可以使用到他。
定位到 ApplicationFilterChain#internalDoFilter()
1 | final class ApplicationFilterChain implements FilterChain, CometFilterChain { |
缺点:使用Shiro这种另外走自定义Filter的。如果没继续 chain.doFilter(request, response);
的操作,就会调用到if (ApplicationDispatcher.WRAP_SAME_OBJECT)
语句块。
操作:
通过反射修改ApplicationDispatcher.WRAP_SAME_OBJECT
为true
,即可让Tomcat将request和response存入静态变量lastServicedRequest
。
这里有个小坑:ApplicationDispatcher.WRAP_SAME_OBJECT
是final
类型的,我们需要先通过反射,将其final
属性抹掉,才能正常通过反射设置值。关于抹掉final
属性的文在这 反射修改final属性
全局存储
直接用的话,获取路径为:
1 | Thread.currentThread().getContextClassLoader() -> ParallelWebappClassLoader -> StandardRoot -> StandardContext -> ApplicationContext -> StandardService -> Connector -> Http11NioProtocol -> AbstractProtocol$ConnectionHandler -> RequestGroupInfo -> RequestInfo -> Request -> Response |
从Thread.currentThread().getContextClassLoader()
到ApplicationContext
的阶段,都是线程类加载器处理的。所以我们不需要关心。真正需要关心的是StandardService
之后的,是如何获取的。
原作者的这篇文也把他寻找的思路写出来了。讲的挺清楚了。下文都是我跟着调时记的笔记
思路:找到存放response
的类。跟调用栈,看最终存储到哪。最后用线程上下文加载器来拿到这个类
找到一个存放response的类:Http11Processor
1 | Http11Processor.Response - 原因:final,不会被修改 |
开Java Web项目,断点打在控制器。看调用栈。创建Http11Processor
的操作就在AbstractProtocol#process()
此时的思路是:寻找哪里存放了Http11Processor
。
注意:找存储变量的操作。不一定在调用栈前,也不一定在调用栈后。前后我们都应该去翻一翻,看一看。
最佳方式是:找到变量最初的创建点,然后顺着看。如果变量是启动时创建的,那就看做参数传入函数时,有无对其进行操作。跟的太深不要紧,做好笔记就好了。
Http11Processor的存储
AbstractProtocol#process
1 | if (processor == null) { |
[!]
作用:
将
processor
里的RequestInfo request.reqProcessorMX
塞入**RequestGroupInfo AbstractProtocol$ConnectionHandler.global
**RequestInfo request.reqProcessorMX
存在org.apache.coyote.Request req
字段。里头有response
既然Http11Processor
被存储到AbstractProtocol$ConnectionHandler.global
中,接下来就找哪里存放了AbstractProtocol$ConnectionHandler
。这是一个内部类。一般都会被外面的类的属性保存。
Tomcat中,AbstractProtocol
存放内部类ConnectionHandler
的属性是handler
所以接下来的思路是,找AbstractProtocol
的存储点。不过需要注意的是,这是一个抽象类。所以寻找存储点时,应该寻找的是其子类。
继承关系如下
1 | AbstractProtocol (org.apache.coyote) |
这里需要提一下,由于又很多类实例是在Tomcat启动时,就已经初始化好了,所以我们不需要挠破脑子想变量哪里被创建,哪里被赋值。我们目的是找存储点。找到即可,不然会越陷越深。
Http11AprProtocol的存储
跟着调用栈,技巧是只看Debug栏中的类属性。如果属性中有存放我们想找的类的实例,就去深入探究下大概的赋值链。
AbstractProcessorLight#process 下一个调用点是Http11Processor#service。这是processor里的方法。
Http11Processor
有趣的属性如下
1 | protocol = {org.apache.coyote.http11.Http11NioProtocol@3161} |
找到了有趣属性,就往调用点前/后去找,有无队这些属性进行保存等操作
在*CoyoteAdapter#service()*中。connector.protocolHandler
存放了上文的protocol。最后Tomcat启动时会将connector
放入StandardContext
。Tomcat8+可以通过currentThread获取到StandardContext
ps:
AbstractProcessor.response 也是response
记个没用东西
Tomcat写文件。不知道路径的情况下,可以使用org.apache.coyote.http11.Http11Protocol.log
下的logger.manager.classLoaderLoggers
。这里有很多classLoader。还会记录路径
Tomcat7 - 9。都可用
1 | org.apache.coyote.http11.Http11Protocol.log = {org.apache.juli.logging.DirectJDKLog} |
那么,我们也可以通过写文件的方式,写入到 ?????
Adpter是启动时就已经设置好了的
* Tomcat7 另一个静态变量
李三师傅提出来的。原文:tomcat不出网回显连续剧第六集
这个相对于全局存储来说,还是比较好理解的哈哈哈。毕竟一个反射全解决了。
Tomcat对processor
进行register()
的操作和Tomcat8不太一样。具体代码在Http11Protocol#createProcessor()
。其中有调用 *Http11Protocol#register()*的操作
在*Http11Protocol#register()*中。有下面这么一行。将rp
丢进了registerComponent()
。
1 | Registry.getRegistry(null, null).registerComponent(rp, rpName, null); |
很明显的看到Registry.getRegistry(null, null)
是一个静态调用。跟进去可以发现。其返回的就是Registry.registry
静态属性。
那,如果接下来的registerComponent()
逻辑中有将rp
存入Registry.registry
或其引用的类中,那么就可以通过Registry.getRegistry(null, null)
静态调用,在Tomcat任意位置取到response
了。下面我们要找的是在何处存储了rp
。
跟进registerComponent()
,可以找到将RequestInfo rp
设置到静态变量的代码。调用栈如下:
寻找存储点的思路:高亮显示要被的变量rp
,发现哪个方法有传入该变量,跟进分析
1 | addMoiToTb:278, Repository |
最终在*Repository#addMoiToTb()*进行了存储操作。存入的是一个map
。
1 | private void addMoiToTb(final DynamicMBean object, |
变量内容如下,我们需要用循环+判断,获取RequestGroupInfo
类型的元素:
1 | moiTb = {java.util.HashMap@2764} size = 37 |
获取RequestGroupInfo
的存储链如下
1 | org.apache.tomcat.util.modeler.Registry.registry |
代码如下
1 | /* |
ps:回显不太稳定,有一小部分时间会没有回显。估计和Tomcat的多线程有关。多刷新几次又出来了
这里可以注意下。最终response的输出是调用doWrite(ByteBuffer chunk)
的。其中有这么一段
ChunkedOutputFilter#doWrite()
1 | int result = chunk.remaining(); |
跟进chunk.remaining();
,能看到这样的光景:
Buffer#remaining()
1 | public final int remaining() { |
我们在进行ByteBuffer.put()
以后,需要让字节指针归位。不然Buffer#remaining()
返回0
适配多版本的思路是:不同版本,有些方法参数不一样,包名不一样。我们可以通过反射来“相对动态”地调用。并加入try-catch。如果方法不存在,就说明当前Tomcat是其他版本
* ClassLoader拿Context
作为一个技巧,Tomcat中Servlet
的ClassLoader
是ParallelWebappClassLoader
。所以只要拿到Servlet.class.getClassLoader
,就能拿到ParallelWebappClassLoader
。在ParallelWebappClassLoader
中可以通过ParallelWebappClassLoader.resources.context
拿到Context
,这样,内存马啊回显啊都能搞定了。
Thread.currentThread().getContextClassLoader()
可获得当前线程的ClassLoader()
,如果该代码写在Servlet
中或是由Servlet
触发的话,ClassLoader
是ParallelWebappClassLoader
通过ParallelWebappClassLoader.resources.context
拿到的Context
是StandardContext
的实例。对于回显来说,从StandardContext
拿到真正的Request
的路径如下:
1 | context.service.connectors.0.protocolHandler.handler.global.processors |
* getServletContext()
在Servlet下,可以通过直接调用getServletContext()
,获取ServletContext
。ServletContext
的类型是ApplicationContextFacade
。该类型可以通过context.context
获取StandardContext
。从StandardContext
获取到Request
的路径上文有说。
* NioEndpoint
木头师傅提到的,感觉是很通用的方法。由于上文”ClassLoader拿Context”和”getServletContext()”无法在Tocmat7下使用(Tomcat7结构不同)。木头师傅找到了NioEndpoint
类,该类的抽象父类AbstractEndpoint
中存放着handler
。NioEndpoint
是socket NIO poller
线程的主模块。可以通过遍历线程获取到 NioEndpoint$Poller
,再获取其父类NioEndpoint
。直接得到handler.global.processors
的路径以获取Request。
大概的代码如下,懒得测了,回显马也不常用了233,,,
1 | Field threadsFields = threadGroup.getClass().getDeclaredField("threads"); |
Mapper
翻Tomcat处理路由这一块逻辑时看到的,虚拟主机的Map类MappedHost
是一个静态类,其成员contextList
和object
中存在Context
。具体的反射拿还没试,应该是可以的。走的还是前文”ClassLoader拿Context”的老路
Linux下Tomcat回显 - 不通用
思路:找socket描述符,没法确定是哪一个就依次write()
,总会找到能write()
的那个。
1 | //have no request & response args. Only execute command by fixed |
效果

并发太多,容易烂掉
