基于tomcat的内存 Webshell 无文件攻击技术
0x01 tomcat通用的获取request和response
首先我们看看一个普通http请求进来的时候,tomcat的部分执行栈:
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)按照kingkk师傅的方法,利用的点是在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter:
其中,通过反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true,并且对lastServicedRequest和lastServicedResponse这两个ThreadLocal进行初始化,之后,每次请求进来,就能通过这两个ThreadLocal获取到相应的request和response了。但是,也存在一点小限制,在其set之前,看:
先执行完所有的Filter了filter.doFilter(request, response, this)
因此,对于shiro的反序列化利用就没办法通过这种方式取到response回显了。
0x02 动态注册Filter
没错的,正如标题所说,通过动态注册一个Filter,并且把其放到最前面,这样,我们的Filter就能最先执行了,并且也成为了一个内存Webshell了。
要实现动态注册Filter,需要两个步骤。第一个步骤就是先达到能获取request和response,而第二个步骤是通过request或者response去动态注册Filter
步骤一
首先,我们创建一个继承AbstractTranslet(因为需要携带恶意字节码到服务端加载执行)的TomcatEchoInject类,在其静态代码块中反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true,并且对lastServicedRequest和lastServicedResponse这两个ThreadLocal进行初始化
接着,我们改造一下ysoserial中的Gadgets.createTemplatesImpl方法
可以看到,第二个传入的Class参数,我们并没有用到javassist,而是直接转字节数组,然后放到TemplatesImpl实例的_bytecodes字段中了。
最后,回到ysoserial中有调用Gadgets.createTemplatesImpl的payload类中来,我这边对每一个都做了拷贝修改,例如CommonsCollections11,我拷贝其修改后的类为CommonsCollections11ForTomcatEchoInject,在调用Gadgets.createTemplatesImpl(command[0];的地方,改成了final Object templates = Gadgets.createTemplatesImpl(null, TomcatEchoInject.class);
并且,对ysoserial的main入口做一点小修改,因为原来的代码规定必须要有payload的入参,而我们这里不需要了
ysoserial.GeneratePayload#main:
在ysoserial执行maven指令生成jar包
这样,我们就能使用这个新的payload(CommonsCollections11ForTomcatEchoInject)了
步骤二
在使用步骤一生成的序列化数据进行反序列化攻击后,我们就能通过下面这段代码获取到request和response对象了
接着,我们要做的就是动态注册Filter到tomcat中,参考《动态注册之Servlet+Filter+Listener》,可以看到,其中通过ServletContext对象(实际获取的是ApplicationContext,是ServletContext的实现,因为门面模式的使用,后面需要提取实际实现),实现了动态注册Filter
然而实际上并不管用,为什么呢?
因为this.context.getState()在运行时返回的state已经是LifecycleState.STARTED了,所以直接就抛异常了,filter根本就添加不进去。
不过问题不大,因为this.context.getState()获取的是ServletContext实现对象的context字段,从其中获取出state,那么,我们在其添加filter前,通过反射设置成LifecycleState.STARTING_PREP,在其顺利添加完成后,再把其恢复成LifecycleState.STARTE,这里必须要恢复,要不然会造成服务不可用。
其实上面的反射设置state值,也可以不做,因为我们看代码中,只是执行了this.context.addFilterDef(filterDef),我们完全也可以通过反射context这个字段自行添加filterDef。
在实际执行栈中,可以看到,实际filter的创建是在org.apache.catalina.core.StandardWrapperValve#invoke执行ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);的地方
跟进其实现方法,忽略不重要的代码:
可以看到,从context提取了FilterMap数组,并且遍历添加到filterChain,最终生效,但是这里有两个问题:
我们最早创建的filter被封装成FilterDef添加到了context的filterDefs中,但是filterMaps中并不存在
跟上述一样的问题,也不存在filterConfigs中(
context.findFilterConfig是从context的filterConfigs中获取)
这两个问题,也比较简单,第一个问题,其实在下面代码执行filterRegistration.addMappingForUrlPatterns的时候已经添加进去了
而第二个问题,既然没有,我们就反射加进去就行了,不过且先看看StandardContext,它有一个方法filterStart
没错,它遍历了filterDefs,一个个实例化成ApplicationFilterConfig添加到filterConfigs了。
这两个问题解决了,是不是就完成了呢,其实还没有,还差一个优化的地方,因为我们想要把filter放到最前面,在所有filter前执行,从而解决shiro漏洞的问题。
也简单,我们看回org.apache.catalina.core.ApplicationFilterFactory#createFilterChain的代码:
创建的顺序是根据filterMaps的顺序来的,那么我们就有必要去修改我们添加的filter顺序到第一位了,最后,整个第二步骤的代码如下:
和第一个步骤创建的TomcatEchoInject不一样,这里我们不但基础了AbstractTranslet,还实现了Filter创建一个我们自定义的内存Webshell
最后,我们也按照第一个步骤那样,创建一个ysoserial的CommonsCollections11类的拷贝,名叫CommonsCollections11ForTomcatShellInject,并把其Gadgets.createTemplatesImpl(command[0])的调用改成Gadgets.createTemplatesImpl(null, TomcatShellInject.class),这样,我们的Webshell payload就完成了。
通过执行maven打包
然后执行生成的jar
就生成了CommonsCollections11ForTomcatShellInject的payload了
0x03 测试
上一节中,我们生成了两个payload,接下来,我们启动一个具有commons-collections:commons-collections:3.2.1依赖的服务端,并且存在反序列化的接口。
然后我们把步骤一和步骤二生成的payload依次打过去


可以依次看到,两个步骤都返回500异常,相关信息证明已经执行反序列化成功了,接下来我们试试这个内存Webshell


最后更新于
这有帮助吗?