Java安全(3)shiro-550(CVE-2016-4437)反序列化审计+检测&攻击技巧

审计反向的过程之前,先记录一下检测 shiro 框架的方法,以及利用攻击方法

shiro检测

人工一点点瞧还是太 old school 了,攻防讲究一个效率为王

yakit插件(burp也同理的)

这里使用 yakit + foxyproxy

两个工具的安装就不多 bb 了,安装好之后给 foxyproxy 配置和 yakit 劫持监听的端口一样的端口然后切换过去

之后本地起一个 shiro ,用 IDEA 方便,然后浏览器挂上代理,yakit 劫持打开,被动检测模块里面勾选 shiro 插件(密钥就去搜然后安装一个)

之后访问本机的 shiro 服务,就可以测试一下 shiro 的检测了

检测到东西会在左上角这里,点开可以看到漏洞情报

如果抓不到访问自己 shiro 的包,就把浏览器访问的网址填成电脑内网 ip,不用 127.0.0.1

EHole

直接 -u 就有了,已经是专门指纹识别的工具了,直接用就行

shiro攻击

shiro-attack

一款针对 shiro-550 的攻击工具

https://github.com/SummerSec/ShiroAttack2

下载下来是个 jar 文件,直接 java -jar 就能启动了

使用也不难,给目标就能从爆破 key 到爆破利用链一口气梭哈

如下图,key 和链子都齐活了以后,可以直接命令执行等操作了

审计流程(反序列化)

读取 cookie – base64 解码 -AES 解密 – 反序列化

base64解码

之前正向的流程用到的接口 rememberSerializedIdentity

获取 cookie 的自然就是 getRememberedSerializedIdentity

protected abstract byte[] getRememberedSerializedIdentity(SubjectContext subjectContext);

确实还是个接口,直接跟进(选择 cookie 开头)如下

protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {

    if (!WebUtils.isHttp(subjectContext)) {
        if (log.isDebugEnabled()) {
            String msg = "SubjectContext argument is not an HTTP-aware instance.  This is required to obtain a " +
                    "servlet request and response in order to retrieve the rememberMe cookie. Returning " +
                    "immediately and ignoring rememberMe operation.";
            log.debug(msg);
        }
        return null;
    }

    WebSubjectContext wsc = (WebSubjectContext) subjectContext;
    if (isIdentityRemoved(wsc)) {
        return null;
    }

    HttpServletRequest request = WebUtils.getHttpRequest(wsc);
    HttpServletResponse response = WebUtils.getHttpResponse(wsc);

    String base64 = getCookie().readValue(request, response);
    // Browsers do not always remove cookies immediately (SHIRO-183)
    // ignore cookies that are scheduled for removal
    if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null;

    if (base64 != null) {
        base64 = ensurePadding(base64);
        if (log.isTraceEnabled()) {
            log.trace("Acquired Base64 encoded identity [" + base64 + "]");
        }
        byte[] decoded = Base64.decode(base64);
        if (log.isTraceEnabled()) {
            log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
        }
        return decoded;
    } else {
        //no cookie set - new site visitor?
        return null;
    }
}

重点还是后半段,读取 http 请求包 ,并且调用 getCookie().readValue() 去获取 base64 值

下面一行是对 cookie 值进行判断,如果已经是被删除的数据,就返回空,判定 rememberme 凭据无效

再下面就是进入一个 if 判断,如果 base64 不为空进入

byte[] decoded = Base64.decode(base64);

进行解码,后面可以看到返回了 decoded

那就去找一下这个东西给返回的 decoded 哪个地方会拿来调用

查看 usages,找到如下

public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
    PrincipalCollection principals = null;
    try {
        byte[] bytes = getRememberedSerializedIdentity(subjectContext);
        //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
        if (bytes != null && bytes.length > 0) {
            principals = convertBytesToPrincipals(bytes, subjectContext);
        }
    } catch (RuntimeException re) {
        principals = onRememberedPrincipalFailure(re, subjectContext);
    }

    return principals;
}

看到这一行:

byte[] bytes = getRememberedSerializedIdentity(subjectContext);

调用刚刚 base64 解码的功能然后返回值 decoded 赋给 bytes

之后进入 if 判断,如果 bytes 不是空的

principals = convertBytesToPrincipals(bytes, subjectContext);

根据命名推测是 AES 解密并且反序列化的功能,直接跟进一下看看干了啥

protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
    if (getCipherService() != null) {
        bytes = decrypt(bytes);
    }
    return deserialize(bytes);
}

看里面这两个命名就知道是解密和反序列化了

先解密,然后结果传参给反序列化并且返回

AES解密

跟进 decrypt

protected byte[] decrypt(byte[] encrypted) {
    byte[] serialized = encrypted;
    CipherService cipherService = getCipherService();
    if (cipherService != null) {
        ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
        serialized = byteSource.getBytes();
    }
    return serialized;
}

解密最后返回 serialized ,也对应了解密后是返回到了序列化后的状态

解密过程没啥可分析的,转回去看反序列化,跟进

protected PrincipalCollection deserialize(byte[] serializedIdentity) {
    return getSerializer().deserialize(serializedIdentity);
}

再跟进

T deserialize(byte[] serialized) throws SerializationException;

是个接口,查询一下,跟进 default 开头的

public T deserialize(byte[] serialized) throws SerializationException {
    if (serialized == null) {
        String msg = "argument cannot be null.";
        throw new IllegalArgumentException(msg);
    }
    ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
    BufferedInputStream bis = new BufferedInputStream(bais);
    try {
        ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
        @SuppressWarnings({"unchecked"})
        T deserialized = (T) ois.readObject();
        ois.close();
        return deserialized;
    } catch (Exception e) {
        String msg = "Unable to deserialze argument byte array.";
        throw new SerializationException(msg, e);
    }
}

调用了字节输入流,和序列化的对应,之后就是正常的反序列化操作了,利用对象输入流反序列化

 T deserialized = (T) ois.readObject();

java 反序列化漏洞最关键的点 readObject 就在这里了

小结

正向获取信息生成 cookie 和反向 获取 cookie 生成数据的流程审计了一遍,主要的安全隐患点(犯错误的点)有两个

  • AES 的 key 硬编码,跟个弱口令似的(复杂但是很轻易就可知的密钥也是弱口令)
  • 序列化和反序列化过程中缺失安全校验
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇