shiro代码层面追踪

文章目录

环境

环境搭建:https://blog.csdn.net/qq_44769520/article/details/123476443

漏洞分析

硬编码

shiro是对rememberMe这个cookie进⾏反序列化的时候出现了问题。

相应代码

java 复制代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.shiro.web.mgt;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.AbstractRememberMeManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.subject.WebSubjectContext;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CookieRememberMeManager extends AbstractRememberMeManager {
    private static final transient Logger log = LoggerFactory.getLogger(CookieRememberMeManager.class);
    public static final String DEFAULT_REMEMBER_ME_COOKIE_NAME = "rememberMe";
    private Cookie cookie;

    public CookieRememberMeManager() {
        Cookie cookie = new SimpleCookie("rememberMe");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(31536000);
        this.cookie = cookie;
    }

    public Cookie getCookie() {
        return this.cookie;
    }

    public void setCookie(Cookie cookie) {
        this.cookie = cookie;
    }

    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);
            }

        } else {
            HttpServletRequest request = WebUtils.getHttpRequest(subject);
            HttpServletResponse response = WebUtils.getHttpResponse(subject);
            String base64 = Base64.encodeToString(serialized);
            Cookie template = this.getCookie();
            Cookie cookie = new SimpleCookie(template);
            cookie.setValue(base64);
            cookie.saveTo(request, response);
        }
    }

    private boolean isIdentityRemoved(WebSubjectContext subjectContext) {
        ServletRequest request = subjectContext.resolveServletRequest();
        if (request == null) {
            return false;
        } else {
            Boolean removed = (Boolean)request.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY);
            return removed != null && removed;
        }
    }

    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;
        } else {
            WebSubjectContext wsc = (WebSubjectContext)subjectContext;
            if (this.isIdentityRemoved(wsc)) {
                return null;
            } else {
                HttpServletRequest request = WebUtils.getHttpRequest(wsc);
                HttpServletResponse response = WebUtils.getHttpResponse(wsc);
                String base64 = this.getCookie().readValue(request, response);
                if ("deleteMe".equals(base64)) {
                    return null;
                } else if (base64 != null) {
                    base64 = this.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 {
                    return null;
                }
            }
        }
    }

    private String ensurePadding(String base64) {
        int length = base64.length();
        if (length % 4 != 0) {
            StringBuilder sb = new StringBuilder(base64);

            for(int i = 0; i < length % 4; ++i) {
                sb.append('=');
            }

            base64 = sb.toString();
        }

        return base64;
    }

    protected void forgetIdentity(Subject subject) {
        if (WebUtils.isHttp(subject)) {
            HttpServletRequest request = WebUtils.getHttpRequest(subject);
            HttpServletResponse response = WebUtils.getHttpResponse(subject);
            this.forgetIdentity(request, response);
        }

    }

    public void forgetIdentity(SubjectContext subjectContext) {
        if (WebUtils.isHttp(subjectContext)) {
            HttpServletRequest request = WebUtils.getHttpRequest(subjectContext);
            HttpServletResponse response = WebUtils.getHttpResponse(subjectContext);
            this.forgetIdentity(request, response);
        }

    }

    private void forgetIdentity(HttpServletRequest request, HttpServletResponse response) {
        this.getCookie().removeFrom(request, response);
    }
}

可以看到获取了cookie

创建cookie对象,并设置HttpOnly

java 复制代码
public CookieRememberMeManager() {
        Cookie cookie = new SimpleCookie("rememberMe");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(31536000);
        this.cookie = cookie;
    }

将rememberMe反序列化为字节数组的方法。

java 复制代码
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;
    } else {
        WebSubjectContext wsc = (WebSubjectContext)subjectContext;
        if (this.isIdentityRemoved(wsc)) {
            return null;
        } else {
            HttpServletRequest request = WebUtils.getHttpRequest(wsc);
            HttpServletResponse response = WebUtils.getHttpResponse(wsc);
            String base64 = this.getCookie().readValue(request, response);
            if ("deleteMe".equals(base64)) {
                return null;
            } else if (base64 != null) {
                base64 = this.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 {
                return null;
            }
        }
    }
}

获取序列化信息,并将其字节数组反序列化为 PrincipalCollection 对象

java 复制代码
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;
}
 
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
    if (getCipherService() != null) {
        bytes = decrypt(bytes);
    }
    return deserialize(bytes);
}

接下来找到解密函数

java 复制代码
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;
}
 
public byte[] getDecryptionCipherKey() {
    return decryptionCipherKey;
}

加密算法已知,接下来寻找密钥是怎么获得的

对于代码审计⽽⾔,加密算法的安全性来⾃于密钥的机密性

追踪代码可知密钥是硬编码到代码中的

java 复制代码
public AbstractRememberMeManager() {
    this.serializer = new DefaultSerializer<PrincipalCollection>();
    this.cipherService = new AesCipherService();
    setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

知道加密方式,我们就可以构造任意密文了

反序列化

通用的反序列化方法,用于将字节数组反序列化为 Java 对象。

java 复制代码
public <T> 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 deserialize argument byte array.";
        throw new SerializationException(msg, e);
    }
}

Gadget构造

java反序列化 = 反序列化数据可控 + 利用链构造

⽽在java中,利⽤链往往是⼀些通⽤的底层⼯具类库,⽐如出名的CC链、CB链、

URLDNS链,很少有⼈⾃⼰再去挖掘新链,⼤家代审过程中哪怕遇到了反序列漏洞,基本也是

只要找到反序列化数据可控的点就好了,利⽤链⽤⽹上公开的就⾏。

shiro涉及的链会在后面再跟

相关推荐
W大小姐想让我裂开来16 分钟前
jenkins+1panel面板java运行环境自动化部署java项目
java·自动化·jenkins·1panel
罗念笙16 分钟前
Java 的 BigDecimal 是什么?
java·后端
kill bert21 分钟前
第30周Java分布式入门 ThreadLocal
java·分布式·wpf
小钊(求职中)36 分钟前
Lambda 和 Stream 从 0 到 1,从基础到实战
java·开发语言·后端·算法
老华带你飞37 分钟前
医院挂号预约小程序|基于微信小程序的医院挂号预约系统设计与实现(源码+数据库+文档)
java·数据库·微信小程序·小程序·毕业设计·springboot·医院挂号预约小程序
努力努力再努力wz1 小时前
【c++入门系列】:引用以及内联函数详解
java·运维·服务器·c语言·开发语言·c++
昂子的博客1 小时前
热门面试题第14天|Leetcode 513找树左下角的值 112 113 路径总和 105 106 从中序与后序遍历序列构造二叉树 (及其扩展形式)以一敌二
java·数据结构·算法·leetcode·职场和发展
代码续发1 小时前
为什么要将项目部署到外部tomcat
java·tomcat
凯酱1 小时前
timeGetTime()函数的用法
java
程序媛-徐师姐1 小时前
Java 基于微信小程序的美食推荐系统(附源码,文档)
java·微信小程序·美食