給木馬帶雙眼睛
前言
利用內存馬達到命令執行/文件下載等操作已經是老生常談的,在現在的紅藍對抗中,很多時候拿到權限,但卻在內網無法伸展,這種情況已經越來越常見了。
近月,內存馬的技術也產生新的變化,如利用websocket進行通信,Executor內存馬進行socket通信。可以看到內存馬開始尋找新的載體。本文介紹利用Poller(Executor的上層類)內存馬實現全流量監控,這樣,攻擊方可以實時監控經過系統的每一個請求,或者增加了釣魚等信息利用的便利。先貼出項目https://github.com/Kyo-w/trojan-eye
原理

這里借用深藍師傅的圖片,Poller作為TCP連接最前置的處理類,此類能夠直接處理socket上完整的原始數據。我嘗試過Acceptor,但是由于Acceptor只是做監聽8080端口把請求的socket連接轉發給Poller來處理,所以不太可能實現自定義的Acceptor來完成內存馬注入。至于實現為什么不采用Executor,我從以下幾點考慮:
- Poller能夠決定是否提交給web業務處理,Executor是要經過web業務處理,Poller能靈活地決定web業務流程
- Executor的流程是要經過Poller的,所以意味著性能上要比Poller多幾道判斷與注冊
- Poller存在的問題,Executor也存在,所以復雜度基本差不多
但是Executor比Poller有個非常大的優勢:Executor魯棒性比Poller好。原因很簡單,Poller是全局唯一的線程(tomcat8、9/springboot),如果線程處理邏輯的時候出現不可預測的異常,線程就會終止,一旦線程終止,整個tomcat的服務直接崩潰,因為缺少了接收與創建任務的執行線程。這也就是說注入Poller的內存馬一定是不能出任何bug的,一旦出了,整個服務直接崩潰。在起初做試驗時,經常因為木馬報錯,導致整個服務不能訪問,只能重啟服務解決,所以風險很高!反向,Executor是一個任務類,創建后就執行一個線程任務,如果這次業務異常,最多這次的請求無法正常執行罷了。那究竟怎么寫一個Poller內存馬呢?其實很簡單:繼承Poller+重寫processKey方法。(可參考https://github.com/Kyo-w/trojan-eye/blob/master/theory/README.md)
@Override
protected void processKey(SelectionKey sk, NioEndpoint.NioSocketWrapper attachment) {
//自定義:業務處理之前
beforeHandler()
super.processKey(sk, attachment);
//自定義:業務處理之后
afterHandler()
}
上面的代碼中,super.processKey(sk, attachment)就是會創建SocketProcessor,然后執行web業務邏輯。所以如果你需要在web業務處理之前做些什么,你就在super.processKey(sk, attachment)之前寫自己的業務。如果不想執行web業務,直接return即可。如果你需要在web業務處理之后做些什么,就在super.processKey(sk, attachment)之后添加。但是需要注意的是:super.processKey(sk,attachment)是一個“啟動任務線程的函數”。在調用完super.processKey(sk,attachment)之后,程序并不會等待業務的響應結果,而是直接結束processKey函數(底層就是后臺開線程執行web業務,所以是一個多線程的環境)。就是這個原因,導致我們無法完成這樣一個功能:等待web業務執行結束,然后查看web業務的響應結果。因為兩個線程之間如果沒有通信等機制,我們無法預測線程之間的執行順序的。這個問題讓我受阻了挺長的時間,如果我們只能拿到每一個流量的請求而拿不到響應,顯得有點無意義了。所以經過一番思考,我終于找到新的突破點:buffer緩存的利用。
buffer緩存+websocket實現實時流量獲取
如果要實現讀取業務所有的流量,我們要解決兩個問題:
- 每次請求的request/response怎么獲取
- 每次請求的請求/響應數據應該傳輸到哪里
針對第一個問題,其實很好解決。

每次請求到來,socket都會有一個bufHandler,而這個bufHandler存儲的是上一個請求和響應。但是在高版本中,request的buffer并不記錄數據。所以,這里只能通過深藍師傅文章的unread函數去將本次請求的數據裝載到緩存中。
ByteBuffer allocate = ByteBuffer.allocate(8192);
try {
read = attachment.read(false, allocate);
} catch (IOException e) {}
ByteBuffer allocate1 = ByteBuffer.allocate(read);
// 有多少數據就放多少數據
allocate1.put(allocate.array(), 0, read);
allocate1.position(0);
attachment.unRead(allocate1);
allocate.clear();
需要注意的是,unRead的數據大小不能隨便設置,因為本人之前在測試中遇到了一個巨大的坑(https://github.com/Kyo-w/trojan-eye/blob/master/theory/websocktbug.md中描述了具體的原因)。現在每次請求時,請求的buffer都會攜帶上次請求的記錄。這樣我們相當于得到了所有的request/response,只是相比之下,我們永遠得到的永遠都是上一次的記錄。最后在得到數據的時候,請求的數據如何進行傳輸?總需要一個標記好的連接進行傳輸,并且這個連接不能是客戶端的短連接,原因如下:
- 如果只是通過短連接,然后每次請求去讀取buffer,顯然我們獲取的流量都是零散的,流量是不實時的
- 短連接會造成大量的HTTP請求,容易出現流量異常問題
- 由于發送大量的HTTP請求,我們可能獲取的是自己的請求流量
在https://github.com/Kyo-w/trojan-eye/blob/master/theory/buffersocket.md中詳細的介紹了利用Keep-Alive持久一個毒化socket,然后進行數據傳輸。但是這個存在一個問題,遇到NGINX反向代理時,默認的NGINX并不支持一個請求的長連接,NGINX默認是端口輪詢訪問的,阻礙了socket毒化的過程。但是如果設置了對應的配置,那也是可以保證這種技術的可行性。
upstream BACKEND {
server 127.0.0.1:8080 weight=1 max_fails=2 fail_timeout=30s;
keepalive 3000;
}
map $http_upgrade $connection_upgrade{
default upgrade;
'' close;
}
既然我們控制的是socket,理論我們是可以構造傳輸層上的各種協議,但是還是不得不面對現實的情況:我們的業務依舊面對的是NGINX之類的反向代理,而NGINX默認支持的協議并不支持各種高層的應用層協議,換句話說,我們只能盡可能用業務中NGINX代理最常見的協議進行通信,但是原生的HTTP已經無法做到我們的需求了。Keep-Alive雖然可以,但是也會面對幾個問題:
- 連接超時。這就需要客戶端實時發送心跳以維持socket的通信。
- 我們需要響應適合的響應體,由于控制整個連接過程,所以導致響應體都是由我們自己封裝返回客戶端,而自己封裝的響應,很容易導致出現統一的特征(涉及到響應的模板)。
當然在NGINX 1.9.0版本以后,已經支持對TCP協議代理的支持
stream{
upstream test{
#這里代理socket,其端口是3307
server 127.0.0.1:3307 weight=1;
#server x.x.x.x:1111 weight=1;
}
server {
#監聽3306端口
listen 3306;
proxy_pass test;
}
}
但是既然是做工具的,那必然要盡可能降低環境的需求。所以必須尋找其他更常見的協議。幸好在前段時間已經有師傅找到了新的內存馬——websocket。看到websocket,讓我頓時有了靈感,websocket本身就是會常見于一些web應用,這勢必然增加了工具的適用范圍,并且websocket在HTTP代理的日志中只能看到一條記錄(由于websocket在做完一次協議升級后,就不再使用HTTP進行傳輸了)。所以立刻展開深入的研究,以下是整個內存馬通信的核心。

下面是NGINX配置websocket的配置(供學習參考)
server {
listen 80;
#域名
server_name localhost;
location /sell {
proxy_pass http://127.0.0.1:8080/; // 代理轉發地址
proxy_http_version 1.1;
proxy_read_timeout 3600s; // 超時設置
// 啟用支持websocket連接
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
從圖像中可以看到,每次代理的流量都會經過Poller,Poller會根據存在的websocket通信列表,把上一次請求的所有數據,發送給每一個websocket監聽器。現在,整個基本骨架已經基本實現了,剩下的就只剩敲代碼了。最后希望這些研究能在攻防起到作用吧!