环境搭建

git clone https://github.com/apache/shiro.git shiro-rootcd shiro-root
git checkout -f shiro-root-1.4.0

参考之前SHIRO-550的搭一下就行

漏洞分析

首先是SHIRO-550,触发点是cookie中的RememberMe,漏洞触发流程:

  1. base64 解码
  2. 使用 AES 解密
  3. 反序列化解密后的字符串

shiro-1.25以前,AES密钥是硬编码到源码中的,因此可以更改RememberMe的值进行反序列化RCE

而1.2.5之后,shiro采用了随机密钥,也就引出了SHIRO-721,通过padding oracle attack的方式得到,

根据p0师傅之前的文章,在shiro中,当我们更改padding值时,padding正确但反序列化错误则会爆deserialize error;padding错误爆padding error,
具体处理代码如下:

padding正确但爆反序列化error
try {
    ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
    @SuppressWarnings({"unchecked"})
    T deserialized = (T) ois.readObject();
    ois.close();
    return deserialized;
} catch (Exception e) {
    String msg = "Unable to deserialize argument byte array.";
    throw new SerializationException(msg, e);
}

padding错误爆padding error
try {
    return cipher.doFinal(bytes);
} catch (Exception e) {
    String msg = "Unable to execute 'doFinal' with cipher instance [" + cipher + "].";
    throw new CryptoException(msg, e);
}

而shiro中如果解密rememberMe的过程中有错误,统一的处理方式都是调用removeFrom,最终返回deleteMe

这就很矛盾,没办法构造出padding oracle需要的bool条件。

解决这个问题用到了java反序列化中的一个小trick,java中的ObjectOutputStream是一个Stream,他会按格式以队列方式读下去,后面拼接无关内容,不会影响反序列化。通过这种方式,在抓到的rememberMe之后加新的iv和value,就既能反序列化成功,又能验证padding是否正确了,从而满足了padding oracle所需要的bool条件。具体的padding oracle过程和CBC bit flipping就不详细写了,参考之前大佬们的文章即可。

除此之外,shiro的接口在验证登陆时有authc和user两种权限,authc是认证过,user是登录过,如果开启了rememberMe功能的话,user可以通过的,而authc通过不了。因此rememberMe只在有user权限的接口有用。

综上,该洞的利用条件如下:

  1. 可以登录
  2. 找到可以用rememberMe的接口
  3. 可以padding oracle

本地测试

网上的poc很多,这里直接找一个跑一下本地的shir-simpleweb环境就行,URLDNS很容易验证。

实战挖掘

漏洞刚出时找到了一个RuoYi CMS,使用了shiro 1.4.1。
本地照文档搭起来,看一下接口:

public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe)
{
    UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
    Subject subject = SecurityUtils.getSubject();
    try
    {
        subject.login(token);
        return success();
    }
    ...
}

这里用到了rememberMe,下面找一下哪些接口有使用的权限。

// 所有请求需要认证
filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

shiroConfig里很清楚,所有接口都有user权限,大概稳了。
exp打一发,收到了URLDNS,稳了

参考

https://p0sec.net/index.php/archives/126/
https://superxiaoxiong.github.io/2019/11/26/shiro-padding-oracle-attack%E5%88%86%E6%9E%90/

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖