最近被動掃描器的話題如火如荼,好多公司都在做自己的被動掃描器。而獲取質量高的流量是被動掃描器起作用的關鍵。筆者主要開發了兩個被動掃描器的插件,r-forwarder 以及 r-forwarder-burp,兩個插件的代碼都在 Github 上開源。兩個插件分別為 Chrom 插件以及 Burp 插件,本文也從筆者開發這兩個插件的經驗來聊一聊被動掃描器中插件的開發。
Chrome 插件Chrome 插件是向 Chrome 瀏覽器添加或修改功能的瀏覽器拓展程序。一般通過 JavaScript, HTML 以及 CSS 就可以編寫 Chrome 插件了。市面上有很多非常優秀的 Chrome 插件擁有非常多的用戶。Chrome 插件的編寫也比較簡單,基本上你熟悉一點前端知識,然後熟悉一下 Chrome 插件的 API,你就可以編寫 Chrome 插件。Chrome 插件的安裝,如果你沒有發布在 Chrome 商店的話(因為網絡原因,可能沒辦法直接從商店下載),可以通過開發者模式安裝 Chrome 插件。或者你也可以註冊 Chrome 插件的開發者帳號(只需要 5 美元,就可以發布 20 個插件)。
簡單地介紹了一下 Chrome 插件的開發,咱們主要還是聊一下關於 Chrome 插件關於被動掃描器的方面的內容。對於 Chrome 插件,主要是通過插件的能力去獲取經過瀏覽器的流量,並將流量轉發給後端來進行處理。Chrome 插件關於網絡流量的處理地 API 主要有兩個:chrome.devtools.network 以及 chrome.webRequest。但是前者使用的時候需要打開 Chrome 開發者工具,這個有一點不太方面,所以選擇了後者,這也是對於被動流量獲取一種常見的方式。
Chrome 插件中的 webrequest API 是以相應的事件驅動的,其中請求的生命周期圖如下,主要有7個事件。只需要監聽關鍵事件進行處理就可以滿足被動掃描器獲取流量的需求了。
其實這些事件不難理解,基本通過事件的名稱就可以知道事件的含義了,主要就是請求發送前,發送請求頭之前,發送請求頭等等事件。對於不同的事件,可以獲取的流量數據也是不盡相同的。首先,考慮一下,對於被動掃描器來說,哪些流量數據是比較關心的。被動掃描器主要是通過收集業務的正常流量來進行測試,提高測試的效率,並能取得比主動掃描器更好的效果。那麼一般來說,被動掃描器最關心的就是請求的 URL 以及請求頭了,如果是 POST 請求,還需要請求體。對於掃描器來說,響應頭和響應體則沒那麼重要,其實可以通過響應狀態過濾一下,一般只需要能夠正常響應的請求頭以及請求體即可。
對於被動掃描器上述的需求,chrome.webrequest 中的 onBeforeRequest 以及 onSendHeaders 這兩個事件可以滿足需求。通過前者,可以獲取請求體。通過後者則可以獲取請求頭。不過在使用 onSendHeaders 的時候,有好幾點需要注意:
兼容問題從 Chrome 79 開始,必須要在 optextraInfoSpec 中指定 extraHeaders 才可以獲取 Origin 請求頭。從 Chrome 72 開始,必須要在 optextraInfoSpec 中指定 extraHeaders 才可以獲取 以下請求頭:
Accept-Language
Accept-Encoding
Referer
Cookie
毫無疑問,這些請求頭都是有價值的。為了獲取這些請求頭,你必須在 opt_extraInfoSpec 中指定 extraHeaders 才可以獲取相應的請求頭。同時,注意做兼容性檢查,因為之前的版本的是不需要指定的,如果你在之前版本的瀏覽器也指定了屬性,就會產生報錯。
const headers = version >= 72 ? ["requestHeaders", "extraHeaders"] : ["requestHeaders"];chrome.webRequest.onSendHeaders.addListener(beforeSendHeaderHandler, requestFilters, headers)requestBody 的格式問題可以通過 onBeforeRequest 事件來獲取 POST 請求中的請求體。但有一點注意,chrome.webrequest 中把請求體進行了解析,所以你獲取的不是原生的請求體。請求體位於 requestBody 中的 fromData,而 formData 其實是有兩種形式,一種是鍵值對形式的字典,這種一般對於請求體類型為 multipart/form-data 或者 application/x-www-form-urlencoded 而言,一般即為 a=xxx&b=xxx&c=xxx 這種形式;另外一種則是原生的字節,這個官方的 API 文檔沒有直接提到,你需要自己手工解析數據。
const postbody = decodeURIComponent(String.fromCharCode.apply(null, new Uint8Array(details.requestBody.raw[0].bytes)));使用 RequestFilter 去過濾請求如果你希望在事件中可以過濾特定的請求地址或者請求的資源類型,那麼就可能需要使用到 RequestFilter 了。RequestFilter 裡面有4個屬性,比較重要的屬性就是 urls 以及 types,通過這兩個屬性就可以過濾特定的請求 URL 以及資源類型。
但是注意一點是,RequestFilter 是在註冊事件的時候配置的參數的,不可以後續直接修改。不過有一種方法是先移除監聽事件,再添加新的事件。
if (!chrome.webRequest.onSendHeaders.hasListener(beforeSendHeaderHandler)) { chrome.webRequest.onSendHeaders.addListener( beforeSendHeaderHandler, requestFilters, headers )}Burp 插件篇Burp 是滲透測試中不可缺少的工具之一,而 Burp 插件也讓測試者如虎添翼,達到事半功倍的效果。同時,開發 Burp 插件也是為了彌補一些系統無法在 Chrome 中使用的場景來進一步地補充。
Burp 插件開發的資料網上不是特別的豐富,之前也寫過一篇文章「如何寫一個 Burp 插件」。其實開發 Burp 插件比較簡單,只要遵守基本的規範,然後學習一下 API 的使用,基本就可以完成 Burp 插件的開發了。反倒是如果希望在 Burp 插件中開發 GUI 有點困難,因為使用 J**A 來寫 GUI 比較麻煩,畢竟不能像 C# 那樣,妥妥拽拽就搞定了,不過這也不是本文的重點。
其實在 Burp 中的 Extender 標籤頁中的 APIs 就可以看到提供的 API 接口。基本上每個函數都有參數說明的注釋,不過其實學習 Burp 插件的最好的方法就是拿一個現成的插件代碼看一下,就可以很好地理解這些 API 的作用了。
在這,以我開發的 Burp 插件 r-forwarder-burp 為例,使用 JA 開發。在開發 Burp 插件需要注意幾點。必須定義一個 BurpExtender 類,並且必須實現 IBurpExtender,如果還需要其他 API 可以實現多個其它接口,JA 中的類是可以實現多個接口的。另外還需要重寫父類中的 registerExtenderCallbacks 方法。同樣,針對被動掃描器的需求,在 Burp 插件中我們最主要涉及的接口是 IHttpListener 接口。這個主要涉及到 HTTP
public interface IHttpListener{ /** * This method is invoked when an HTTP request is about to be issued, and * when an HTTP response has been received. * * @param toolFlag A flag indicating the Burp tool that issued the request. * Burp tool flags are defined in the * <code>IBurpExtenderCallbacks</code> interface. * @param messageIsRequest Flags whether the method is being invoked for a * request or response. * @param messageInfo Details of the request / response to be processed. * Extensions can call the setter methods on this object to update the * current message and so modify Burp's behavior. */ void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo);}在 processHttpMessage 方法中,主要涉及到以上3個參數。toolFlag 主要指的是和請求相關的 Burp 工具,比如 Proxy 以及 Repeater。可以在 IBurpExtenderCallbacks 接口中看到相應的定義。
messageIsRequest 則表示是請求還是響應,而我們只關心請求部分。通過解析 messageInfo 則可以獲取請求頭以及請求體數據。
public Map<String, String> getHeaders(IHttpRequestResponse messageInfo) { Map<String, String> headers = new HashMap<>(); IRequestInfo analyzeRequest = helpers.analyzeRequest(messageInfo); List<String> h = analyzeRequest.getHeaders(); for (String h1: h) { if (h1.startsWith("GET") || h1.startsWith("POST")) { continue; } else { String[] header = h1.split(":", 2); headers.put(header[0], header[1].trim()); } } return headers;}
private String getBody(IHttpRequestResponse messageInfo) { IRequestInfo requestInfo = helpers.analyzeRequest(messageInfo); int bodyOffset = requestInfo.getBodyOffset(); byte[] byteRequest = messageInfo.getRequest(); byte[] byteBody = Arrays.copyOfRange(byteRequest, bodyOffset, byteRequest.length); return new String(byteBody);}上面是簡單開發的內容方面的介紹,其它方面可以直接看原始碼了解更多,尤其是 GUI 開發的部分。另外想說明的一點就是如何打 jar 包。通過 maven-assembly-plugin 插件可以很方便地打包,只需要配置如下,然後通過 mvn package 即可進行打包。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration></plugin>另外注意如果使用了外部依賴的時候,需要配置 jar-with-dependencies,這樣在打包的時候就可以把依賴的 jar 包一併打進去。最後,成品的 jar 包安裝之後就可以使用了。
其實,我認為在 Burp 插件開發過程中最重要的部分就是調試了。通過調試可以快速提高開發效率。以 IDE IDEA 為例,只需要以下幾步就可以進行插件開發地調試:
1.配置 debug 配置項,點擊 IDE 右上角就可以新增配置項。
在終端中通過上述的配置項啟動 burp 插件。
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar burpsuite_community_v2.1.0.jar2.在 Burp 中通過上面的方式安裝打包好的插件。
3.在 IDE 中相應的代碼打上斷點,並打開 debug 就可以進行調試了。
總結以上就是在開發被動掃描器 Chrome 插件以及 Burp 插件遇到的一些坑,在這裡和大家分享一下。其實被動掃描器開發,最重要的還是一些細節方面的考慮,可以將插件的功能做到更完美。
*本文原創作者:madneal@平安銀行應用安全團隊,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載