干貨 | 滲透測試中端口復用實現方案總結
常見邊界拓撲
第一種情況
Inbound Stream ---> Firewall ---> Target Inbound Stream ---> Load Balance ---> Target
這其中無論負載均衡設備轉發或者防火墻的,均有可能存在帶來源IP轉發或者不帶來源IP轉發的情況,帶來源IP或者端口的方案我們在下文的常見端口復用實現機制中有所介紹,該如何在此種情況下實現端口復用,而不帶來源IP或者端口的情況目前網上討論得不多,下文也將就這類情況進行分析提出解決方案。
第二種情況
Inbound Stream ---> Reverse Proxy ---> Target
另外一種較為常見的場景就是類似于Nginx或者Squid之類的反向代理應用了,這種其中絕大部分所代理的協議均為HTTP,通常我們遇到這類場景,在滲透中都會使用一個基于腳本的正向HTTP Tunnel,在權限允許的情況下,本文也給出了針對此種場景的一個端口復用方案。
常見的端口復用實現機制
看完常見的邊界拓撲,我們來談一談流行的端口復用實現方案,筆者幾乎都動手編碼實現過如下的端口復用方案。
要實現端口復用,首先需要理解其本質,端口復用的本質實際上就是包的重定向(在特定時機下拿到特定的包并進行轉發),在我們熟悉了各類操作系統從內核層到用戶層實現的網絡協議棧后,就可以輕松的從中挑選出某個環節進行加工對包重定向。在內核層,比如針對 Windows 在不同的NT內核中均提供了不同的方案,在NT6以后提供了 WFP(Windows Filter Platform),針對 Linux 則一直都有 Netfilter;在用戶層,Windows 提供了LSP等機制。
故由此可以得出的端口復用方案可以從內核層或者用戶層入手。我們先來聊聊用戶層,比較通用的方式是Hook Socket的相關函數,而絕大部分Linux的Port Knock類型的Rootkit實現方式都是基于此,比如通過Hook Accept判斷來源端口是否為某個Magic Port來重定向包,或者通過Hook Recv函數,同時指定其Flag為MSG_PEEK即可判斷收到包是否包含特定特征,而不破壞原來的數據流,最終依條件進行包的重定向;再比如在IIS6的時代,數據包是通過IOCP放到用戶層進程w3svc來處理的,此時只需要Hook CreateFile有同樣可以拿到發來的數據包;同時針對指定用戶層進程的端口復用方式也可以通過程序本身提供的功能,比較流行WEB中間件如Apache、Nginx等均提供了Module的編程接口,在特定的回調函數或事件中即可拿到包進行判斷進而轉發,還有Socket的Setsockopt函數中關于SOCK_REUSE設定端口可重用的一種實現方式,這在以前也比較場景,可以重新Bind一個自己的工具監聽端口到設置了SOCK_REUSE標志的端口上,從而實現端口復用;而在內核層中,如Linux最方便的莫過于Netfilter了,在之前代表也寫了一篇文章介紹基于其實現“端口復用”的文章,而Windows則可以通過過濾驅動的方式來重定向包,但多數已有內核方案的用戶層接口并不友好,比如僅僅只能實現TCP三層的轉發,而無法實現四層的包鑒別,同時開發的成本也不低,作為一個RedTeamer應該有隨手拿起身邊的工具,為攻擊過程提供便利的能力,介于此有了這篇筆記,這或許不是最完美的解決方案,希望所提供的思路能為大家拋磚引玉。
廉價的端口復用方案
基于以上討論,便有了一個廉價的端口復用方案,能夠兼容常見邊界拓撲中的種種情況,主要的流程如下:
Inbound Stream ---> Kernel Packet Filter ---> Userland Packet Redirect ---> Proxy Server
我們以一個開放在IP為10.1.1.1的WEB服務為例(80端口),假設我們通過前期的滲透獲取了10.1.1.1的系統權限(Root或System),但由于只開放了80端口,我們通常的攻擊方案是選取對應腳本語言實現的HTTP Tunnel將此臺機器作為跳板攻擊內網,如常見邊界拓撲中所描述的情況一樣,10.1.1.1的上層可能是一個反向代理或者一個防火墻
當流量到達需要進行端口復用的機器上時,我們首先通過內核層所提供的包轉發機制將入站的數據包依據特定規則,將原本會到達80端口的流量,優先轉發到我們用戶態的程序進行(下文將以Haproxy為例),同時我們用戶態的程序會通過基于協議的識別、或者應用層協議包中的特定數據或者特征再進行一次分流,流程講變為如下圖所示:
+ ----> Proxy Server
|
+
Inbound Stream ---> Kernel Packet Filter ---> Userland Packet Redirect -|
+
|
+ ----> Web Server
Ps: Ascii流程圖 http://asciiflow.com/
最終,我們包含特定特征的流量會到達我們的Proxy Server,同時不影響正常業務的使用。下面就不同的操作系統和具體實現方案進一步展開。
For Linux
假設這里的通信接口為eth0,我們將所有目的端口為80的流量轉發到9999,通過Iptables進行轉發,這里必須得指定接口,以避免回環接口上的地址也進行轉發,造成 loop redirect。
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 9999
其他常見操作
iptables -t nat -L -n -v iptables-save iptables -L INPUT –line-numbers && iptables -D INPUT 4
For Windows
對于非系統服務,比如重定向Windows上的 Apache的 8080 端口到 1080 端口,我們可以使用 IpNat 進行轉發
netsh interface portproxy add v4tov4 listenport=8080 connectport=1080 connectaddress=10.1.1.1
其他常見操作
netsh int portproxy delete v4tov4 listenport=9999 netsh int portproxy show all
對于系統服務,針對IIS的80端口(Http.sys)SMB的445端口(Smb.sys),使用 IpNat 無法直接轉發。以SMB為例,首先我們需要通過 sc config smb start= demand將 SMB 這個內核驅動設定為禁止自動運行,然后添加一張本地回環網卡(這里將IP設定為 10.255.255.1),并且將躍點數修改為一個較大值,同時取消 Netbios 協議在此網卡上的注冊,為了保證原SMB服務的正常運行,我們需要在計劃任務中添加一個SMB的自啟動,sc start smb,最后通過添加轉發規則即可。
netsh interface portproxy add v4tov4 listenaddress=10.1.1.1 listenport=445 connectaddress=10.255.255.1 connectport=44445
- Q:是否必須添加回環網卡 A:是,并且需要進行配置以修改445監聽
- Q:是否必須重啟服務 A:實際上從 sc query 可以知道 smb是一個Kernel Driver,所以是無法通過sc restart方式來重啟這個驅動的
- Q:是否必須重啟計算機 A:必須重啟計算機以加載他,故為了使原有服務正常,必須再在登陸后重新啟動smb這個kernel driver
如果想要不進行重啟,那么只能通過加載驅動的方式來進行處理,并且需要自己編寫Ring3的部分代碼來通過驅動回調增加過濾的條件等,比較流行的方式有基于WFP實現的WIndiver以及基于NDIS的WinpkFilter,可以參考 https://github.com/BarbaTunnelCoder/BarbaTunnel/wiki/Choosing-FilterDriver-(WinDivert-vs-WinpkFilter) 的比較,由于本文旨是廉價版的端口復用,故這里不再贅述如何以驅動方式來進行包轉發了。
處理完內核層的包重定向后,接下來需要做的針對到達用戶層的包進行分流,這里我們不需要自己實現一個 Load Balance 而是使用比較成熟的方案 Haproxy 來進行TCP四層的包識別與轉發,對于
TCP/IP 三層
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
user haproxy
group haproxy
daemon
defaults
log global
mode tcp
option tcplog
option dontlognull
#maxconn 2000
timeout connect 24h
timeout client 24h
timeout server 24h
frontend main
mode tcp
bind *:9999
tcp-request inspect-delay 3s
#GET POS(T) PUT DEL(ETE) OPT(IONS) HEA(D) CON(NECT) TRA(CE)
acl is_http req.payload(0,3) -m bin 474554 504f53 505554 44454c 4f5054 484541 434f4e 545241
tcp-request content accept if is_http
tcp-request content accept
use_backend http if is_http
use_backend socks5
backend socks5
mode tcp
timeout server 1h
server ss 127.0.0.1:51025
backend http
mode tcp
server ngx 127.0.0.1:80
通過 req.payload 來取包中的前3字節,確認是否為 HTTP 中的 Verb,如果是則判斷為HTTP協議并進行轉發,如不是則判斷為是我們代理程序的協議轉發到 51025 中。
TCP/IP 四層
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
user haproxy
group haproxy
# daemon
defaults
log global
mode tcp
option tcplog
option dontlognull
#maxconn 2000
timeout connect 24h
timeout client 24h
timeout server 24h
frontend main
mode tcp
bind *:9999
option forwardfor except 127.0.0.1
option forwardfor header X-Real-IP
acl is-proxy-now urlp_reg(proxy) ^(http|https|socks5)$
# http-request set-var(req.proxy) urlp(proxy) if is-proxy-now
use_backend socks5 if is-proxy-now
use_backend http
backend socks5
mode tcp
timeout server 1h
server ss 127.0.0.1:51025
backend http
mode tcp
server ngx 127.0.0.1:80
這里通過 urlp_reg 注冊了一個變量 proxy,同時使用正則來判斷這個 proxy 的Key所有的值的范圍為 http\https\socks5,也就是說當訪問的url中帶了?proxy=socks5時候則進行轉發,此時我們的代理程序必須也能夠識別 HTTP 的數據包,這里的實現則變成了類似于HTTP Tunnel的一個實現了。而基于高級語言實現的代理程序,在絕大部分情況下的健壯性與效率基本優于基于腳本的HTTP Tunnel。
武器化
將如上過程武器化,只需要一個靜態編譯的 Haproxy 加上自己實現的 Proxy Server (代理程序)即可,通過一個 Loader 的腳本即可進行調用,甚至你可以完整的寫一套自己的方案,通過Go來實現也能使跨平臺變得方便,但本文中針對Windows這個方案并不算比較便捷,誠然可以通過 Powershell 來編寫 Loader,但由于需要重啟所以動靜還是比較大的,針對Windows后期壞牛會基于 Windivert 編寫 一個比較成熟的工具,敬請期待。
最后感謝肥欣哥提供的思路 ;)
總結和解決方案如下
PDF下載地址:
https://www.blackh4t.org/post/media/poorman-s-port-reuse-solution/Poorman_PortReuse.pdf



參考
http://woshub.com/port-forwarding-in-windows/
https://www.nikhef.nl/~janjust/CifsOverSSH/VistaLoopback.html
https://github.com/TalAloni/SMBLibrary#notes
https://blog.sleeplessbeastie.eu/2018/05/02/how-to-use-variable-to-choose-haproxy-backend/
https://davidhamann.de/2019/06/20/setting-up-portproxy-netsh/