Backslash Powered Scanning
摘要
現有的web掃描器通過在某個參數上利用大量特定payload進行測試或者尋找特徵來找尋服務端的注入漏洞--幾乎像一個殺軟。在本文中,作將分享另一種掃描方式的概念和開發過程,這種掃描方式能夠發現和確認已知與未知類型的注入漏洞。這種方式從經典的手工檢測進化而來,擁有手工測試的眾多優勢,例如WAF避規,極小的網路痕迹和針對過濾的靈活輸入。與以往相同,這種方式依然會設法利用一些對於經驗豐富的手工測試者來說十分熟悉的漏洞。作者將通過展示幾千個網站的樣例來分享一些有趣的發現和教訓,並放出一個特定的掃描工具。最後,作者將展示這款工具如何能被繼續開發,讓你有更多的研究方向。介紹
web應用程序掃描器被廣泛認為是只能識別』low-hanging fruit』(注釋:容易摘的果子,形容唾手可得)-- 很明顯任何人都能找到的漏洞,這通常是一個公正的判斷;與手工測試相比,自動掃描器依賴於大量特定技術的payload,缺乏應變性意味著即使是最先進的掃描器也不能像人類一樣識別某些明顯的漏洞。在某種意義上,這種比較也是不公平的--掃描器越來越擅長發現客戶端的問題,例如跨站點腳本(XSS),甚至能通過動態與靜態分析識別出基於DOM的XSS。然而,黑盒掃描器對服務端的運作缺乏了解,因此他們通常難以檢測出服務端的注入漏洞,例如SQL注入,代碼注入,命令注入。在這篇文章中,作者將拆分出掃描器對服務端注入漏洞檢測的三個核心盲點,接著展示如何實現一個從手工檢測進化而來的掃描方式,作者將開發一個開源掃描器能夠檢測遠高於』low-hanging fruit』的漏洞。特別是,作者將展示這個掃描器能夠在發現漏洞類之前,檢測出服務端的模板注入(SSTI)漏洞。
這個掃描器作為一個Burpsuite的插件能夠從BApp store中獲取到,源代碼也可以從Github中獲取到:backslash-powered-scanner,distribute-damage
掃描器的三個薄弱之處
- 盲點一:Rare Technology通過安全防範掃描儀。舉一個例子,來看下模板注入(SSTI),當應用程序不做防範,將用戶輸入嵌入到模板中時,即會出現漏洞。根據使用的模板引擎,這可能會被利用,導致任意代碼執行甚至完全控制伺服器。為了使掃描器能檢測到這種漏洞,需要使用到每一種模板引擎的payload硬編碼。如果你的應用程序使用的是一個流行模板引擎例如FreeMarker或者Jinja,那沒有什麼問題。但是你的掃描器能夠支持以下多少個模板引擎:
Amber, Apache Velocity, action4JAVA, http://ASP.NET (Microsoft), http://ASP.NET (Mono), AutoGen, Beard, Blade, Blitz, Casper, CheetahTemplate, Chip Template Engine, Chunk Templates, CL-EMB, CodeCharge Studio, ColdFusion, Cottle, csharptemplates, CTPP, dbPager, Dermis, Django, DTL::Fast (port of Django templates), Djolt-objc, Dwoo, Dylan Server Pages, ECT, eRuby, FigDice, FreeMarker, Genshi (templating language), Go templates, Google-ctemplate, Grantlee Template System, GvTags, H2o, HAH, Haml, Hamlets, Handlebars, Hyperkit PHP/XML Template Engine, Histone template Engine, HTML-TEMPLATE, HTTL, Jade, JavaServer Pages, jin-template, Jinja, Jinja2, JScore, Kalahari, Kid (templating language), Liquid, Lofn, Lucee, Mako, Mars-Templater, MiniTemplator, mTemplate, Mustache, nTPL, Open Power Template, Obyx, Pebble, Outline, pHAML, PHP, PURE Unobtrusive Rendering Engine, pyratemp, QueryTemplates, RainTPL, Razor, Rythm, Scalate, Scurvy, Simphple, Smarty, StampTE, StringTemplate, SUIT Framework, Template Attribute Language, Twital, Template Blocks, Template Toolkit, Thymeleaf, TinyButStrong, Tonic, Toupl, Twig, Twirl, uBook Template, vlibTemplate, WebMacro, ZeniTPL, BabaJS, Rage, PlannerFw, Fenom
這個列表只包含了一些在維基百科上眾所周知的模板引擎。Michael Stepankin最近發現一個在 paypal 上的遠程代碼執行漏洞(remote code execution vulnerability in Paypal),起因於Dust.js 產生的 SSTI,這個來自 LinkedIn 的模板引擎明顯不在上面的列表之中。匱乏的掃描器適用範圍同樣適用於使用無數資料庫語言的用戶,更不用說那些超出理解範圍的扭曲框架的代碼注入。
此外,使掃描器對後端技術進行假設,這意味著改變任何一個服務端組件的改變都會破壞對其餘漏洞的檢測。例如,在 SELinux 下運行一個 Webapp 能夠組織對本地文件包含和外部實體包含漏洞的檢測,這些檢測通常通過讀取 /etc/passwd來驗證,而 SELinux 將會阻止這些行為。
最終,掃描器對於使用小眾技術的應用程序進行檢測時,性能嚴重下降。
盲點二:Variants and Filters
利用一個眾所周知的語言來思考一個經典的漏洞:在 PHP 雙引號中的 blind code injection。掃描器可以通過發送一個 time-delay 的 payload 輕易檢測出這個問題:".sleep(10)."
到目前還好。但是如果應用程序碰巧過濾了括弧,代碼將會出錯,但是我們仍然可以利用:
".`sleep 10`."
如果應用程序的 WAF 檢測 payload 是否存在 』sleep』 這個關鍵詞,我們幾乎可以肯定會再次出錯。如果應用程序能正常化輸出,那麼我們仍然可以通過利用 Cyrillic 的』e』字元,希望能變成正常的』e』從而執行:
".sl%D0%B5ep(10)."
如果應用程序過濾了雙引號,我們會再一次獲得錯誤的結果,但是我們仍然能輕易的執行我們的代碼:
{${sleep(10)}}
這三個例子中,我在滲透測試中遇到過兩個,在別人的 Writeup 中看到了第三個。
掃描器的設計使得他們很容易因為意想不到的過濾和變體而檢測失敗。掃描器當然可以發送上面展示的變體 payload,但是這些僅僅是一個漏洞的眾多變體中的三個。發送足夠的 payload覆蓋每一種的漏洞的每一個變體,在現在的網路速度上基本上是難以置信的 --因為有數百萬個 payload。掃描器僅限於發送』best-effort』(注釋:儘力而為)的 payload,這意味著即使是使用雙引號代替單引號封裝 SQL 語句也會導致掃描器的檢測失敗。
- 盲點三:Buried Vulnerabilities下面這個請求是 Ebay 的一個端點上的HTTP請求,以前被用作 PHP 的代碼注入,那麼掃描器該從何處載入 payload?
GET /search/?q=david HTTP/1.1Host: sea.ebay.com.sgUser-Agent: Mozilla/5.0 etc Firefox/49.0Accept: text/htmlAccept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: http://sea.ebay.com.sg/Cookie: session=pZGFjciI6IjAkLCJlx2V4cCI6MTA4Connection: close
很明顯的載入地點是參數』q』,但是並沒有起作用,同樣也不是 Referer,User-Agent 或者是 Session cookie。一個經驗豐富的測試者可能會嘗試在不曾出現過的 headers 中載入 payload,例如 Origin,X-Forwarded-For 或者是 X-Forwarded Host。這些 headers 沒有一個起作用的,而掃描器掃描到這個地步時,已經發送了大量的 payload 且都沒有成功,David Vieira-Kurz 發現有可能可以通過傳入第二個參數在此端點形成代碼注入,通過創建一個服務端的惡意數組
GET /search/?q=david&q[1]=sec{${phpinfo()}}
他之所以嘗試這種攻擊方式時因為參數 q 會引起一個具有拼寫檢查,關鍵字過濾的搜索功能,這提供了一個線索表明了服務端會發生一些有趣的事情。在這裡,我們再次遇到一個漏洞,即只有掃描器對每個端點可能發送的有效載荷數量沒有限制(或者可以說是檢測到的拼寫檢查器),才能檢測到。這是一個比較極端的例子,但是其他很少有用的 header 比如 Accept-Language 處的漏洞也很可能被遺漏。
另一種掃描方式
在這一點上你該知道如何讓應用程序或多或少的做到防掃描;只需要使用不出名的 web 語言編寫代碼,將數據與非標準語法的 NoSQL 變體存儲在一起,在上面分層覆蓋 WAF。
那麼手工測試者時如何避開這些盲點的?最根本的區別是他們對於無用、有趣、可疑或者有效輸入的概念。David Vieira-Kurz 的觀察指出一個具有拼寫檢查功能的輸入點會直接導致他的廣泛審計,可能這會在你的正常輸入中浪費時間。
我們可以從中學到,相比起掃描漏洞,我們需要的是掃描那些有趣的行為。接著,我們可以進一步調查以確認產生這些有趣行為的極小部分的輸入。這種識別漏洞的迭代方法在漏洞確實存在時非常的靈活和高效。不會產生任何有趣行為的輸入被快速的忽略掉,為持續調查更有希望的輸入節省時間,形成一個部署探針-掃描比對-結果研究的循環:
可疑的輸入轉換
用於探測可疑行為的初始檢測應該儘可能的簡單和通用。例如如下用於利用 FreeMarker SSTI的 payload:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ex("id")}
這可以很輕易的回滾成一個更為通用的 payload 用以檢測大多數利用了流行語法的模板引擎:
${7*7} (expect 49)
如何擴大用於檢測通用代碼的覆蓋範圍?作者嘗試了如下方法:
7*7 (expect 49)
但其僅能夠適用於數字輸入。為了檢測字元串的注入,可以這樣做:
x41 (expect A)
然而很多語言,特別是包括 SQL,並不支持 Hex 轉義。那下面這個探針則可以做到更一步的通用,基本支持所有的語言:
\ (expect )
在這一點上作者首先探測出可疑的輸入變換。接著進入到掃描階段的開發過程,在大量的應用程序中嘗試這種 payload 看看會發生什麼。如果探針是有效的且試驗台足夠大(稍後講解),那麼將會得到一組適當大小的結果,從而手動調查並從中發現有趣有用的東西。
這種情況下,第一步去理解應用程序的行為是為了尋找出其他的輸入變換,例如 x41=>A。通過對比應用程序對已知的不良字元和其他字元的處理方式,可以獲得一些微妙的線索,知道在服務端哪些字元具有特殊的意義。例如,以zz為基線,可以很輕易的發現異常:
zz => zz " => " $ => $ { => { x41 => x41
上面這組測試說明了「{」這個字元具有特殊的意義。通過多次重複和完善探測的過程,可以循環成一個『實施』階段,自動化實行。下面這張掃描器截圖展示一個易受到 Markdown 注入的頁面的輸出情況:
這是一個沒有什麼薄弱點的頁面,只不過是在輸入上調用了 stripslashes() (注釋:用於刪除反斜杠)
這種自動化跟進意味著我們將一目了然的知道端點的可利用性。潛在的進一步優化是對具體轉換的指紋的識別與分類。
值得注意的是即使這種技術有能力檢測出大量的脆弱點,在大部分輸入上,它只會發送一次單獨的請求。這種靈活性和效率性的組合是迭代掃描的核心。
如果你意識到(或者能構建)目標是明顯存在問題的,則可以驗證掃描器的誤報率。作者發現掃描器無法識別 JSON 響應中的漏洞,因為雖然服務端會將\解碼成,但是當其被嵌入 JSON字元串的時候會重新從轉義成\。通過在適當的地方進行 JSON 解碼響應很容易能解決這個問題。
不幸的是,還有一個更為嚴重的弱點。這種掃描方式依賴於用戶的輸入被處理後展示出來。例如,如果一個應用程序將用戶的輸入拼接一個 SQL 的 SELECT 語句中,但是並不會顯示該查詢,這個漏洞將會被完全忽視掉。這是一個最根本的缺陷,依賴於可疑的輸入轉換來檢測漏洞。
Probe-Pair Fuzzing
- 核心邏輯我們可以通過分析整個響應內容和判斷我們的輸入是否造成了明顯的變化來避免依賴於輸入轉換。在最基本的情況下,這與經典的 webapp fuzzer 非常的相似(在應用程序中提供輸入來查看是否崩潰),並且很多滲透測試者都很熟悉適用 Burp Intruder 和 fuzzlists 來實現部分自動化。他們並不會局限於只是查看自動掃描結果的狀態碼和檢查錯誤信息,而是將細微到某個單獨的單詞或者空白行的消失都當作是一種變化。
就像手工測試者一樣,我們可以利用探針來獲取更多的信息。首先,我們通過發送包含隨機字母數字字元的探針來識別出應用程序正常的響應。這將會被當作是一個基準響應。如果攜帶』的探針獲得的響應始終與基準響應不同,那麼就可以斷定』這個字元對於應用程序來說有著特殊的意義。但這並不表明就是一個漏洞,或許只是因為應用程序拒絕攜帶』的輸入。再次的,我們可以利用 backslashes 來擺脫這個困境。如果應用程序對於攜帶』的探針的響應與隨機字母數字的探針響應相同,我們便可以斷定對於』的異常響應是因為未能逃逸。下面的圖可能更有意義。笑臉和哭臉分別代表了 』interseting』 和 』boring』 :
這種技術並不僅局限於識別字元串的注入。我們還可以通過替換探針來識別其他各種環境的注入。每一種額外的探針都只需要幾行代碼,所以我們已經在使用不少的代碼了:" vs " // single-quoted string" vs "" // single-quoted string (alternative escaping)" vs " // double-quoted string7/0 vs 7/1 // number${{ vs $}} // interpolation/**/ vs /*/ // raw code,99 vs ,1 // order-bysprintz vs sprintf // function name
我們還可以通過很多探針的共同使用迭代搜集一個潛在漏洞的信息。當遇到一個字元串注入時,Backslash Powered Scanner 將首先識別正在使用的引號,接著是字元的連接方式,識別可能的函數調用,最後發送一組特定語言的函數嘗試來識別後端語言。下面的一個截圖展示了一個指出了有 Javascript 注入漏洞的應用程序的掃描輸出。注意,每個階段獲取的信息都被用於下個階段。
- 類型的變化應用程序以兩種不同方式之一來處理修改後的輸入。大多數輸入容易受到服務端注入的影響,特別是那些來源於於自由文本格式的輸入,例如評論,只有當觸及語法錯誤時服務端才會顯示不同的響應:
/post_comment?text=baseComment 200 OK/post_comment?text=randomtext 200 OK/post_comment?text=random"text 500 Oops/post_comment?text=random"text 200 OK
其他的輸入上,任何與預期輸入不同都會引起一個錯誤:
/profile?user=bob 200 OK/profile?user=randomtext 500 Oops/profile?user=random"text 500 Oops/profile?user=random"text 500 Oops/profile?user=bo"||"b 200 OK/profile?user=bo"|z"b 500 Oops
後一種情況很難處理。為了找到這樣響應的漏洞,我們需要跳過引號識別階段並且猜測字元的連接方式以找出漏洞的證明,這使掃描器效率更低。由於無法將隨機文本放入探針中,我們被限制在一定數量的唯一探針,使得可靠的指紋識別響應變得更加困難。在寫這篇文章時,掃描器還不會處理這種情況。
這種限制不適用於檢測數字注入的輸入 -- 給定了一個基數,使用簡單的算術有無數的方式能夠表達相同的數目。我選擇x/1和x/0,因為除以零的好處時會在某些情況下拋出異常。
識別有意義的響應差異
這種技術的核心問題是識別應用程序對兩個不同探針的響應是否一致。只是一個簡單的字元串的比較在整個應用程序中是沒有什麼作用的,因為應用程序本身就是動態的。響應中充滿了動態生成的一次性令牌,時間戳,緩存以及對輸入的響應。作者三年前遇到這個問題時,他意識到響應是由靜態的內容和動態的 』fuzzy points』 共同組成獨。因此,作者嘗試用一組響應來生成一個正則表達式,將靜態的內容與通配符拼接到一起。為了簡潔起見,作者只提及了這種方法的一些小問題。最開始,計算密集度,作者使用的最長公共子序列的實現是 O(n2);處理響應花費的時間與響應長度的平方成正比。正則表達式通常十分複雜,因此掃描到錯誤的應用程序會導致掃描器停止工作。它也無法處理和正則完全不同的應用程序響應,也無法處理順序改變的響應。甚至響應中的時間戳也引起了困難,因為有些時間戳每10,60或者100秒才會產生改變。最後,調試起來非常困難,因為響應內容與500多行的正則表達式不匹配的原因很難確定。這些問題聽起來好像都可以解決,但是作者嘗試解決他們這也是為什麼掃描器代碼在兩年前沒有放出來。
相反的,Backslash Powered Scanner 使用更簡單的方法計算每個響應的屬性值,並注意到響應中的哪些屬性沒有發生變化。其中包括狀態碼,content type,HTML 結構,行數,單詞數,輸入的處理數量以及各種關鍵字的出現頻率。
Hunting Findings
- 分散式掃描系統為了保證掃描器在掃網站的時候不會出現發包過快導致 IP 被屏蔽的問題,以及掃描時超出授權的測試範圍,我需要限制它每3秒向一個網站發送一個請求,但 Burpsuite 只支持線程調節,為此,我又寫了一個擴展,用於實現分流,也就是前面說的功能,它可以在不同的主機上交叉掃描,提供整體掃描速度,又不被屏蔽。還有就是可以只掃描不常用的參數等。
- 樣本結果為了說明掃描器所提供的發現的漏洞類型以及如何闡述他們,作者將檢查一組選定的掃描結果。這或許會幫助我們理解為什麼 Backslash Powered Scanner 相比起像一個漏掃,更像是一個技術理解有限的助理。
- Mysql injection 以下結果來源於一個User-Agent頭存在SQL注入的站點:
掃描器識別出輸入是』interesting』,並且通過注入一個只存在於 Mysql 中的函數正確的識別出了漏洞。』Content:5357 VS 5263』這樣的一行是用來表示掃描器對比了兩個結果。在這個例子里,兩個響應中單詞的數目是不同的。當這麼多數量的證據顯現出來時,那麼問題就不太可能是假的。Basic fuzz (z`z"z" vs `z"z"\)Content: 5357 vs 5263String - apostrophe (zz"z vs z\"z)Content: 5357 vs 5263Concatenation: "|| (z||"z(z"z vs z(z"||"z)Content: 5357 vs 5263Basic function injection ("||abf(1)||" vs "||abs(1)||")Content: 5281 vs 5263MySQL injection ("||power(unix_timestanp(),0)||" vs "||power(unix_timestamp(),0)||")Content: 5281 vs 5263
- 存在過濾的 code injection下面這個例子來源於一個已經經過多次測試的網站,這將充分展現掃描器的能力:
String - doublequoted (zz" vs ") error: 1 vs 0 Content: 9 vs 1 Tags: 3 vs 0 Concatenation: ". (z."z(z"z vs z(z"."z) error: 1 vs 0 Content: 9 vs 1 Tags: 3 vs 0 Interpolation - dollar (z${{z vs }}$z) error: 1 vs 0 Content: 9 vs 1 Tags: 3 vs 0
這是一個容易受到 PHP 代碼注入的點,但是應用程序過濾了括弧。因為括弧被過濾,所以掃描器就無法注入一個函數,但是可以一點一點的手工測試嘗試執行任意的shell命令。
作者認為這樣的問題被之前的滲透測試人員忽略可能是因為注入點在文件路徑處,這不是一個手工測試者會花費大量時間測試代碼執行的地方。但為什麼應用程序會在一個路徑上調用 eval(),這就不得而知了。
以前存在過的漏洞
以下的發現表明當前狀態的 http://sea.ebay.com 站點上的輸入是很容易受到 PHP 代碼執行攻擊的。可以清晰的看到應用程序對攜帶「{」的輸入所產生的完全不同的響應。- 正則表達式注入掃描器經常會報告很多正則表達式注入,這是一個低危漏洞,它可以干擾應用程序的運行,嚴重時可能會導致拒絕服務。但有一個例外的是,當伺服器上的 PHP 版本低於5.4.7時,利用null byte to specify the "e" flag,這個注入將會升級為任意代碼執行。這個技術被用來攻擊過phpmyadmin,作者證實了掃描器能找到這種漏洞,通常的報告如下:
Diffing scanner:Backslash ( vs \)Transformation Scanner: => Truncated 1 => Truncated $ => >nbsp;$ => $