本文將教你如何用瀏覽器自帶工具分析web應用的客戶端源代碼。這聽上去有點怪,因爲瀏覽器並不是做這活最好的選擇。但在你開始用burpsuite抓包和到處注入alert(1)來測試XSS之前,好好研究下你的目標總是好的。
本文主要面向初級漏洞賞金獵人或者對HTML、Js代碼分析缺乏經驗的人,但我同樣希望老鳥們能在這裏有所受益。
近期我的一條介紹小技巧的推文在社區引來不少關注,於是我決定寫本文:
這條推文只是小技巧的冰山一角,我想與其發一堆容易錯過的類似推文,不如發一篇彙總型的博文,希望對你們有用。那就開始吧!
工具集
每個現代瀏覽器都有一組內建工具集。要打開它們,你只需敲Ctrl+Shift+I、CMD+Option+I(macOS系統)、F12或在瀏覽器右邊的菜單欄中找到。本文中我將使用最新版的Chromium,你們也可以使用Firefox、Safari、Chrome、Edge等,隨你便啦,但我覺得Chrome的開發者工具是最強大的(該工具集集成在Chrome、Chromium、Brave、Opera和其他基於Chromium的瀏覽器中)。
另一樣需要安裝的工具是IDE(集成開發環境)或者任意帶HTML、Js語法高亮的代碼編輯器。隨你喜好,我個人推薦Visual Studio Code,VSCode也是我日常工作中唯一使用的IDE。你可以在這裏下載到https://code.visualstudio.com/。
NodeJS也值得安裝(並熟練使用它——英特網上有好幾千的資源包),在這裏下載https://nodejs.org/en/。
Python解釋器也是必裝神器(如果你在用*nix系統,它已將預裝了。如果是windows用戶,還是要自己裝一下)。會寫Python是一個寶貴的技藝,我也建議沒寫過一行代碼的人去學習一下。
在終端下用NodeJS測試JavaScript代碼是很方便的(瀏覽器也能辦到,我們隨後討論這一方法的優缺點)。Python是個好東西,你可以用它開發工具、寫PoC和利用代碼,下文將展示一些自寫工具。如果你更熟悉其他解釋型語言(Ruby、PHP、Perl、Bash等),你也可以使用它們。這類語言的優勢在於無需編譯、類似於命令行、跨平臺、大量內建庫和第三方模塊。
分析HTML源代碼
先回到我先前發的那條推特,你也許注意到了,截圖裏的網站看上去空白一片,但你若查看它的源代碼,就會發現大量代碼(沒辦法,我不能提供帶網站URL的截圖,因爲這是個私人衆測項目)。爲什麼瀏覽器上看不見這些元素?
重點是頁面中的部分HTML標籤沒有渲染任何內容。最常見的是<html>
,<head>
,<body>
,<style>
或<script>
。CSS同樣能隱藏元素(例如,設置它們的寬、高屬性爲0或顯示爲none),例如:
<html>
<head>
<title>Move along, nothing to see here!</title>
<style>
/* note to myself: add CSS from Bob's repo: https://verysecurecompany.com/__internal__/repo/bob/specs.git */
* {
font-size:16px;
color: #c0c0c0;
}
</style>
</head>
<body>
<iframe src="https://verysecurecompany.com/__internal__/loginframe.html" style="width:0;height:0" frameborder="0" id="you-cant-see-me"></iframe>
<script>
// a hidden feature
console.log('Diagnostic message: username is admin and password is password :)');
</script>
</body>
</html>
如果你在瀏覽器中打開這個html頁面,它不會渲染任何內容,你也不會看到任何內容。但當你查看源代碼,就會發現一些有意思的東西:
裏面有多出信息泄露:訪問內部資源的URL、隱藏的包含登錄表單的iframe、在控制檯輸出的憑據信息。這些信息不會顯示在頁面中,當然,別指望在每個網站裏都發現這些信息,但帶註解的Js代碼很常見,有時它們會暴露一些app的服務端仍可用的API。
僅僅使用“View Source”功能無法揭露所有的內容,因爲它只展示HTML文檔。更有趣的內容通常由<iframe>
,<script>
等標籤揭示,你可以在Chrome開發者工具的“Source”標籤欄中看到它們。
左側的目錄樹最底端的(index)節點就是你在“View Source”中看到的HTML主文檔,其他資源保存在文件夾和文件樹中。如果你點擊這些文件,就會在右側看到它們的內容。截圖中是jquery.min.js文件的內容,它是所有Js文件的通用壓縮版(從性能上說這麼做很不錯)。但如果你點擊底部的“{}”小按鈕,工具集會展開這些代碼來方便閱讀。
有些網站使用sourcemap特性(source map用來映射壓縮版函數、變量、對象名稱到它們在源代碼中的真實名稱及位置,更多詳情)。sourcemap使用對象有意義的名稱來增加格式化代碼的可讀性,而不是Js壓縮軟件產生的標識符。
另一個有用特性是全局搜索。假設你已注意到一些有趣的函數並想知道是否存在於源碼中其他地方。也許它是個eval()函數參數來自url(這很可能造成Js代碼執行)。爲了對“Source”標籤中文件做全部搜索,你可以用CTRL+Shift+F快捷鍵(MacOS中:CMD+Option+F)。下例中,我試着在AppMeasurement.js中查找getAccount()的所有引用。如你所見,該函數在同一文件中調用了一次。但若在其他文件中有出現搜索關鍵詞,就會被列出:
有時你會發現搜索結果是一條非常長的字串(尤其是在壓縮過的Js文件中)。點擊字串,工具集就會打開它,點擊“{}”按鈕就會在右側演示擴展版的Js版本(有時會有幾千行),搜索結果也會出現。
另一個查看源代碼的標籤叫“Elements”。“Sources”標籤的(index)文件(或“View Source”功能)與它看似相同,實則差別不小。前者展示的來自服務器的HTML文檔,後者展示的當前DOM樹,所有的節點有Js代碼添加。爲了明白這一區別,我先介紹些原理,然後展示個小示例。
DOM(文檔對象模型)代表真個網頁的HTML節點,它由單個根節點(<html>
)和兩個主要子節點(<head>
、<body>
)構成。所有其他節點都是二者的子節點,如:<title>
、<meta>
是<head>
的子節點,<div>
、<p>
、<img>
等是<body>
的子節點。
當你在瀏覽器打開一條URL,首先會加載HTML文件,代碼會由瀏覽器引擎執行。當瀏覽器發現<script>
、<style>
標籤(或其他帶有src屬性的標籤,如image、video),它會停止解析並加載src所指文件。如果該文件是可執行的Js,它就會被執行。如果是樣式表,CSS規則就會被CSS解析器解析應用。整個過程如下圖示:
但“Elements”和“Sources”標籤又不同在哪裏呢?考慮一下情形,Js添加元素到DOM中:
<html>
<head>
<title>Dynamic P Application</title>
<style>
* {
font-size:18px;
font-weight:bold;
color: #2e2e2e;
}
</style>
</head>
<body>
<div id="container">
</div>
<script>
const el = document.getElementById('container')
const dynamic_paragraph = document.createElement('p')
const dp_content = document.createTextNode('Hello from dynamically added <P>aragraph!')
dynamic_paragraph.appendChild(dp_content)
el.appendChild(dynamic_paragraph)
</script>
</body>
</html>
當你打開這一頁面並查看源碼,你會看到如下的內容:
這是Js添加DOM新元素最簡單的示例,當使用“Elements”標籤時會看到:
當你比較“Elements”版本和“View source”版本的區別,你會輕易看到區別:前者有個可見的<p>
標籤,作爲<div id="container">
的子節點。之前的“Source”中並無該節點,因爲它並不是源碼的一部分。
如果你用AngularJs,React,Vue.js,Ember.js等處理過單網頁應用。你會在“Elements”中看到大量動態生成的內容。這些內容各有不同,但在表單、動態分頁排序列表、搜索項等大量元素中,很容易發現DOM型XSS漏洞,用戶輸入也常在模板表達式中解析(如:AngularJS的{{}})。
原因在於這類應用經常用GET、POST請求,或Cookies、瀏覽器Storage的內容來構建網頁。當然,它們自己會生成大量內容。這裏也容易發現一些薄弱點。
在查看JavaScript之前值得一說的是:一定要讀一讀HTML註解,你會發現不少沒有渲染進頁面的元素,裏面常有意外之喜。
分析cookies和瀏覽器Storage
DevTools也可以用來檢查網站保存在客戶端的信息。有幾處可被web應用利用。最常見的就是cookies——它是一段以名稱定義的數據(你可以認爲它的結構是鍵值對)並在服務器和瀏覽器的HTTP交互中交換。
瀏覽器Storage是另一處你能發現有用信息的地方。有兩種Storage:本地Storage和會話Storage,區別在於會話Storage在關閉應用時(關掉瀏覽器標籤頁或瀏覽器時)會註銷掉。本地Storage將留存很長時間,除非被手動清理(數據沒有過期一說)。
想看到所有的存儲信息,你只需使用工具集的“Application”功能:
使用“Application”功能不僅能看到內容,還能修改、刪除和添加自己的鍵值對,這能引起意想不到的響應甚至發現漏洞。這也是檢查水平越權最容易的辦法,你可以替換會話token或更改cookie中保存身份信息的值來僞裝成其他用戶(這只是個例子,現在的web應用大多用多種方法認證用戶,在多數情況下,更改一個cookie的值不足以劫持其他用戶的會話):
這裏還有另一處可以查看與web應用關聯的Js源碼的地方:“Service Workers”。在網上可以找到該功能更多的介紹https://developers.google.com/web/fundamentals/primers/service-workers/。該功能比較新,沒有很多的web應用使用它。但它們的有些信息還是能揭示應用的工作流程,特別是下線後的行爲。
分析JavaScript
現在讓我們轉到整個Web應用程序實際運行的這一部分代碼(HTML和CSS只負責可視部分,因爲它們不能包含任何業務邏輯。除了CSS表達式這類例外,它們能夠運行JavaScript代碼,因此可能產生XSS,特別是將用戶的輸入數據投入使用——僅有HTML和CSS做不了太多事。
有幾種方法可以執行JavaScript代碼分析。讓我們先堅持使用瀏覽器。我們已經發布了Sources選項卡以及如何使用“{}”功能來讀取未經編輯的源代碼。但是你可以用DevTools做更多的事情,其中最好的就是JavaScript調試器。
使用開發者工具調試器
如果您不熟悉調試的知識,通常它可以在代碼的特定特定行時暫停執行。這允許您查看實際的變量值、實際執行的函數以及此函數的調用方式(這可以通過查看堆棧獲得——調試器能夠顯示函數調用的確切順序,如函數a()被調用通過函數b(),而函數b()被另一個函數c()調用)。此外,調試器允許你逐步運行代碼(一次只有一條指令),這樣可以跟蹤程序及其狀態的每次變化。最後一點,調試器允許“動態”修改程序,這意味着您可以看到修改變量值、程序邏輯時會發生什麼。利用好調試器是每個程序員應該具備的最重要的技能之一。
從bug賞金獵人角度看,調試可以讓您更好地瞭解應用程序的工作原理,並直接在可以注入的位置測試攻擊代碼。您可以輕易地分離程序中易受攻擊的部分,並專注於使用調試器的特性對其進行測試。
例如,假設您發現了有漏洞的重定向功能,但每次您嘗試查看確切的內容時,此函數都會被執行並且瀏覽器會重定向到外部資源,並且您無法從上一頁看到JavaScript,因爲現在看到的是重定向後頁面的源代碼。在重定向功能開始時設置斷點可以阻止重定向,現在您可以閱讀函數源代碼以瞭解它的工作原理,查查能否注入,編碼功能寫的對不對,等等。
以上都是理論,是時候進行一些練習了。
以下是重定向功能的示例實現:
<html>
<head>
<title>Redirection</title>
</head>
<body>
</div>
<script>
// imagine that read url from GET parameter routine goes here...
// but we just hardcode it for now :)
const url = 'https://hackerone.com'
function redirect() {
// I will redirect you! Now!
if (url) {
location.href = url
} else {
location.href = 'https://company.com/__internal__/supersecretadminpanel'
}
}
setTimeout( redirect, 10000 )
</script>
</body>
</html>
當您在瀏覽器中打開此網站時,它會在10秒後將您重定向到HackerOne網站,您將無法再看到原網站的源代碼,因此無法看到具體過程。
在瀏覽器中打開“開發人員工具”並切換到“Source”選項卡,然後打開上面的示例HTML文件。現在你有10秒鐘在第16行設置斷點( if (url) { )
。要執行此操作,只需單擊左側欄上的16行號。10秒鐘後,瀏覽器將調用redirect()並在設置斷點的行停止執行:
藍色那行是程序即將執行的實際行(它尚未執行!這一點非常重要)。在左側,您可以看到調試器面板 —— 它顯示您現在在哪裏(調用堆棧),如果您展開腳本節點,將看到在當前執行域中定義的所有變量的值(在我們的示例中它只是url
)。
現在,您可以不着急閱讀和理解源代碼了。就像之前所說的,我們將被重定向到HackerOne網站,但前提是第16行中的條件將返回true。
讓我們修改url
的值。將把它改爲在JavaScript中返回false
以使條件失敗的東西(它可以是空字符串,0,布爾值false
或任何錯誤表達式)。這裏選擇輸入false
( 要想修改變量的值,只需單擊它並輸入自己的值):
現在來看看有什麼變化。爲了更進一步,看一下調試器面板頂部的圖標:
第一個圖標將繼續運行程序,第二個圖標允許逐步執行代碼(我們將在一段時間內使用此代碼)。下一個圖標允許您跳轉到特定行中調用的函數(如果沒有設置斷點,調試器自己不會進入函數,它只是執行有函數調用那行並移動到下一行),然後有一個圖標從當前執行函數中“跳出”並返回到調用它的位置。
現在,單擊第二個圖標(確保已將url
變量的值更改爲false),您將注意到要執行的下一行是19行(這是由第16行中的錯誤條件結果引起的):
如果您按下調試器工具欄上的第一個圖標(“播放”圖標),您會注意到這次應用程序將嘗試重定向到company.com中的某個內部URL。
這種情況url
參數很可能存在漏洞,因此您現在可以嘗試查找開放重定向、反射性 XSS,或者你懷疑url
的值會被存儲在服務器端的某個位置從而深入挖掘(您可以通過進一步研究程序邏輯來確定它是否屬實)。
使用Snippets執行JavaScript
有時,您可能只想執行應用程序代碼的特定片段。當你想做些工作來找到有趣的點時,通常會很難並且耗時很多。在這種情況下,您可以使用Snippets並僅運行您想要的代碼。但請記住,這並不是萬能的,例如當您嘗試運行具有某些依賴性的代碼的一小段時,例如從其他部分傳遞的變量,或者當前代碼片段需要使用其他文件中定義的函數時。
但是,假設您已經確定了一個函數,該函數用以檢查值是否正確,並且您只想關注其邏輯。
在“Source”選項卡中,您可以找到名爲Snippets的面板:
當您單擊它時,您將看到一個片段列表(如果您已經創建了任何片段)以及創建新片段的選項。單擊此選項,在中間面板中,您將找到一個帶語法高亮的簡單代碼編輯器。您放在那裏的每個JavaScript代碼都可以運行,結果會立即打印在控制檯中,只要您運行代碼段就會出現在下面,您可以使用此面板左下角的“播放”圖標,在macOS按CMD+Enter,其他系統輸入CTRL+其他按鍵):
您可以根據需要修改代碼並運行它,但正如我在本文的開頭部分所提到的,這裏有一些缺點。
Snippets運行在選項卡所加載頁面的上下文中,您可以在其中打開DevTools並創建和運行您的代碼段。 此外,每次運行代碼段時,都會使用相同的上下文運行,也就是之前定義的所有變量都沒有變化,並保留着它們的最後一個值。
這裏爲什麼是重點?
考慮這個例子:
在JavaScript中,當使用const
關鍵字定義常量時,必須對它賦值初始化,並且以後不能更改其值。如你所見,代碼段按預期運行,但如果您現在想更改SOME_CONST並重新運行代碼段的值,則會爆出語法錯誤:
此錯誤是因爲在此上下文中,SOME_CONST已被初始化。DevTools“認爲”你還在同一個執行環境中執行代碼,你有沒有改過代碼不重要。
因此,如果您用調試器暫停程序(應用程序代碼定義的所有變量、對象和函數現在都將存在於執行上下文中)並嘗試在同一選項卡中創建代碼段,如果你使用現有標識符——你會覆蓋原始Web應用程序代碼的變量值;如果您想初始化無法重新初始化的標識符(如常量)時,會被報錯。爲了能夠重新運行代碼片段,首先需要在瀏覽器中重新加載頁面以提供全新的空執行空間(瀏覽不緩存重新加載後Web應用程序的狀態,因爲重新加載會引起整個資源的加載、構建DOM樹等)。
爲避免此類問題,您可以使用NodeJS環境運行代碼,而不是使用Snippets(但它仍然是非常方便的工具,因爲您只需使用DevTools打開新選項卡並在其中創建代碼段)。
要執行此操作,只需將要運行的代碼放在新的JavaScript文件中,然後在終端中運行NodeJS(確保已經安裝有它):
如圖示,我已運行了三次代碼,每次都更改SOME_CONST的值。如您所見,沒有錯誤,每次執行都成功並打印出正確的結果。
這種現象是由NodeJS的特性引起的—— 每次用它運行JavaScript代碼時,它都會創建新的執行上下文,因此不可能在相同的執行上下文中運行相同的代碼兩次。
源頭和執行池(原文:Sources and execution sinks)
當您查看JavaScript代碼時,首先應該主要關注兩方面內容。
第一個是源頭,這個術語描述了用戶提供的輸入映射到應用程序代碼的每個點。這可以是通過URL中GET傳遞的參數,應用程序讀取的cookie或業務邏輯中使用的本地Storage的內容。
第二個稱爲執行池。此術語描述的所有會將JavaScript語法元素或HTML API功能作爲傳遞的參數當做代碼去執行的地方。這裏一個明顯的例子是JavaScript函數eval(code_to_evaluate)
,它以代碼的方式去評估(執行)傳入參數。另一個例子是setTimeout(function_to_execute,timeout_in_miliseconds)
,第一個參數傳遞函數要執行的函數,第二個參數傳遞時間,在時間到期後開始執行函數。
在Web應用程序中發現漏洞的過程就是尋找源頭和執行池之間的聯繫,這裏指真正處理源頭的過程。在我介紹如何使用調試器的示例中,作爲參數(源頭)傳遞的url
直接在location.href
(執行池)中使用。這裏的另一個例子,是一個會評估用戶在HTML表單輸入字段中填入的數據的 函數(JavaScript可以使用DOM API讀取它,例如document.getElementById('input_id').value
並將其值賦給變量 ——這就是此時的源頭)然後將此值傳遞給另一個元素innerHTML()
函數,在瀏覽器窗口中更新實際DOM(那將是一個執行池)。
@LiveOverflow在他的YouTube頻道上提供了關於此主題的精彩視頻。我建議現在停止閱讀這篇文章,然後去看這個視頻,熟悉這個概念(長度約爲8分鐘)
由於其業務邏輯的不同,Web應用程序中存在許多的源頭和執行池(比如說表單字段、URL參數、cookie、瀏覽器存儲、WebSockets等)。但最重要的是它們真正被用在執行池裏。有很多這樣的執行池,包括像href
或hash
這樣的位置屬性,window.open()
,document.write()
或者像innerHTML
或appendChild
這樣的DOM方法。它們都可用於執行任意代碼,重定向或進行其他類型的注入。
爲了便於識別這樣的代碼模式,我寫了一個工具,nodestructor。它只是檢查JavaScript文件(單個文件或目錄參數中所有的JavaScript文件)並根據模式匹配廣爲人知的執行池(或源頭)。別指望nodestructor標識的每一行代碼都能被立即或輕鬆地利用—— 所有這些都取決於源頭到執行池之間的過程(數據清洗,編碼,解析,將數據轉換爲對象,字符串操作等)。此工具的主要目的是提供一種在大型代碼庫中查找此類模式的好用且快速的方法。
讓我向您展示一個快速使用示例。首先,我們需要一個JavaScript文件來檢查。我要檢查的文件是之前看過的GM.com網站上的AppMeasurement.js
。我從瀏覽器中複製了這個文件(首先將它擴展)並粘貼到代碼編輯器中並在本地保存在/tmp
文件夾中。
在終端中,我在AppMeasurement.js
文件上運行nodestructor
(使用-H選項包括搜索各種HTML5 API模式):
您可能會注意到,該工具確定了幾個可能的執行池。其中大多數是誤報,但爲了演示目的,讓我們看看第二項,它寫的是用location.hostname.toLowerCase()
函數結果對domain
變量初始化。
最好能在全文中跟蹤此變量出現的地方以判斷是否會在後面的執行池中被用到。你可以使用Visual Studio Code內置的功能,例如查找所有引用或直接搜索字符串domain
。
雖然前段時間我剛開始使用自己的工具來做這種事—— 對JavaScript文件進行簡單的靜態代碼分析,但這裏我想多講一點。它目前還沒有個好聽的名字:),現在仍然處於開發的早期階段(說實話,它更像是PoC,而不是實際能用的工具…),但是爲了給你展示個大概,我將針對domain變量運行這個工具(這是迄今爲止唯一可用的選項,但正如我所提到的,這只是這個工具的十分早期的功能,所以如果要輸入的文件名是被硬編碼的進去的:P)
如您所見,工具找到了變量定義的位置以及何時何地被使用。我希望這個工具能做更復雜的分析,比如在不同作用域內查找變量的用法(例如,它作爲參數傳遞給某個函數,或者它是否真正被用作某個執行池的參數)。
總結
Web瀏覽器是一個非常強大的工具。有時,它是您閱讀源代碼並完全理解應用程序工作流程,識別應用漏洞,進行一些測試或只是查看其工作原理以及學習新內容所需的唯一工具。
我希望我的帖子能幫助您瞭解如何使用開發人員工具這一非常強大的功能。您可以在此網址找到有關充分發揮其全部功能的資源。
https://developers.google.com/web/tools/chrome-devtools/
如果您有任何問題或只是吐槽我的帖子很糟糕:D—— 請不要猶豫,在Twitter上與我聯繫
謝謝你的閱讀,我希望你能找到很多很棒的bug ?
黑得愉快!