Web渗透:Shiro550漏洞(CVE-2016-4437)

Apache Shiro 是一个强大且易于使用的Java安全框架,提供了身份验证(Authentication)、授权(Authorization)、会话管理(Session Management)和密码学支持等功能。Apache Shiro 550反序列化漏洞(CVE-2016-4437)是一个严重的Java反序列化漏洞,它允许攻击者通过特制的Java序列化对象在目标系统上执行任意代码。该漏洞影响了使用默认“rememberMe”功能的Apache Shiro。

shiro550(1.2.4版本)反序列化漏洞原理

Apache Shiro 提供了一种“rememberMe”功能,用于在用户关闭浏览器后仍然保持会话,当启用该功能时,Shiro 会将用户的会话信息序列化并存储在一个 cookie 中,以便在用户重新访问时反序列化并恢复会话;在这边攻击者可以通过修改“rememberMe” cookie,注入特制的恶意 Java 序列化对象;当 Shiro 反序列化这个恶意对象时,攻击者可以在目标系统上执行任意代码。

漏洞相关源代码片段

漏洞出现在 Shiro 的 RememberMeManager 组件中,特别是处理“rememberMe”cookie 的代码部分,以下是与漏洞相关的关键代码片段的简化示例,帮助我们理解漏洞的原理。

    public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
        //清理旧的身份验证信息c
        forgetIdentity(subject);
​
        //生成新的身份验证信息
        if (isRememberMe(token)) {  //如果有勾选remember me
            rememberIdentity(subject, token, info);//生成新的cookie中的RememberMe字段

在Shiro中,当用户成功登录时,可以通过onSuccessfulLogin方法来处理“记住我”功能的逻辑。这个方法会清理旧的身份验证信息,并根据需要生成新的身份验证信息。

forgetIdentity方法会删除存储在Cookie中的身份信息,以确保旧的身份信息不会被重新使用。isRememberMe(token):检查登录请求中是否包含“记住我”选项。如果用户在登录时选择了“记住我”,这个方法会返回truerememberIdentity(subject, token, info):如果用户选择了“记住我”,这个方法会生成并存储新的身份验证信息;接着我们再继续追踪rememberIdentity

   public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
        PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
        rememberIdentity(subject, principals);
    }

getIdentityToRemember(subject, authcInfo)方法用于从authcInfo(身份验证信息)中提取用户的身份信息。接着调用rememberIdentity(subject, principals)方法,使用提取到的身份信息来设置“记住我”功能,这边继续跟进rememberIdentity:

    protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
        byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
        rememberSerializedIdentity(subject, bytes);
    }
1.convertPrincipalsToBytes

这个函数中使用了convertPrincipalsToBytes(accountPrincipals)方法用于将PrincipalCollection(包含用户身份信息的对象)转换为字节数组;此时我们进入convertPrincipalsToBytes函数查看具体针对信息数据的操作是什么样的:

     protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
        byte[] bytes = serialize(principals);
        if (getCipherService() != null) {
            bytes = encrypt(bytes);
        }
        return bytes;
    }

可以看到再这个函数中首先使用了serialize(principals)方法将PrincipalCollection对象序列化为字节数组。接着进行一个获取密钥服务的操作,当CipherService不为空,则对序列化后的字节数组进行加密;接着我们进入encryp函数中查看具体的加密操作是如何进行的:

    protected byte[] encrypt(byte[] serialized) {
        byte[] value = serialized;
        CipherService cipherService = getCipherService();  
        if (cipherService != null) {    //如果cipherService不为空
            ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());   
            value = byteSource.getBytes();
        }
        return value;
    }

这里调用了getCipherService()方法获取配置的加密服务,并赋值给变量cipherService;进入getCipherService()方法后可以发现此处的加密为AES加密(除了指定加密的方式之外还指定了其他的一些加密细节)

获取到加密的信息后就是对序列化的数据进行加密了,在加密时,程序中使用了getEncryptionCipherKey()方法进行密钥获取:

此时可以看到默认的加密密钥实际上在shiro中是一串固定的base64解码后的字符串;其base64编码的值是“kPH+bIxk5D2deZiIxcaaaA==”

2.rememberSerializedIdentity

在清楚如何针对信息数据转化为字节数组后,接着再看一下rememberSerializedIdentity方法中具体做了哪些操作:

   protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
​
        if (!WebUtils.isHttp(subject)) {
            if (log.isDebugEnabled()) {
                String msg = "Subject argument is not an HTTP-aware instance.  This is required to obtain a servlet " +
                        "request and response in order to set the rememberMe cookie. Returning immediately and " +
                        "ignoring rememberMe operation.";
                log.debug(msg);
            }
            return;
        }
​
​
        HttpServletRequest request = WebUtils.getHttpRequest(subject);
        HttpServletResponse response = WebUtils.getHttpResponse(subject);
​
        //base 64 encode it and store as a cookie:
        String base64 = Base64.encodeToString(serialized);  //进行base64编码
​
        Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
        Cookie cookie = new SimpleCookie(template);
        cookie.setValue(base64);    //将base64编码的信息整合到cookie当中
        cookie.saveTo(request, response);
    }

在该方法中使用了Base64.encodeToString(serialized);对序列化数据进行Base64编码;并将Base64编码后的数据整合到Cookie中;至此我们就可以知道RememberMe的具体生成步骤:

用户信息-->序列化-->AES加密-->Base64编码-->Cookie信息

那么对应的解密过程就是:

Cookie信息-->Base64解码-->AES解密-->反序列化-->用户信息

漏洞利用

靶场则是使用vulhub进行搭建,进入对应的目录.../vulhub/shiro/CVE-2016-4437;使用docker命令进行启动

docker-compose up -d

查看具体的端口映射

docker ps -a

这个时候直接访问环境所在靶机IP地址的8080端口:

默认的账号密码为admin:vulhub;这个时候我们可以先输入任意Username和passwrod进行登录并抓包(Remember me选项必须勾选);抓到数据包后将数据包发送至BP的重放模块Repeater,进行发包,查看相应包内容:

发现相应包中包含有Set-Cookie: rememberMe=deleteMe字段就基本上可以断定这边使用了shiro框架;既然是shiro框架,我们就可以尝试进行安全测试:

安全工具(文章末尾附下载地址)

①Burpsuite ShiroPassiveScan插件扫描

这是一款基于BurpSuite的被动式shiro检测插件;该插件会对BurpSuite传进来的每个不同的域名+端口的流量进行一次shiro检测且key可在config.yml自定义,解决有些用户觉得key太少的问题。

安装方法:

添加进入插件即可;

这当我们正常去访问网站, 如果站点的某个请求出现了,那么该插件就会去尝试检测(需要挂BP);当检测到漏洞后则会在BP中进行显示:

这款插件也允许我们自行添加Key:打开./BurpShiroPassiveScan/resources/config.yml并找到application.shiroCipherKeyExtension.config.payloads,在后面添加新的Key即可。

②shiro_attack

使用此工具需要事先准备java环境,java10以上版本缺少javafx-sdk-18.0.2泛型,需要自己手动安装,最好使用jdk1.8版本(环境变量的设置这边就不说了),环境准备好后直接在工具目录打开cmd命令行,运行如下命令:

java -jar shiro_attack-4.7.0-SNAPSHOT-all.jar

能够爆破出Key则表示Shiro框架存在反序列化漏洞,在爆破出Key后可以直接选择检测当前利用链条,若利用链不能够利用则可以进行利用链爆破;

发现利用链可以使用后则尝试进行命令执行、内存马操作,成功GetShell。

【注意】若懒得去找jdk1.8以及这两个工具,关注风铃Sec回复[Shiro550]进行领取即可(文章底下有二维码)

  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值