【流量劫持】SSLStrip 的未來 —— HTTPS 前端劫持

前言

在之前介紹的流量劫持文章裏,曾提到一種『HTTPS 向下降級』的方案 —— 將頁面中的 HTTPS 超鏈接全都替換成 HTTP 版本,讓用戶始終以明文的形式進行通信。

看到這,也許大家都會想到一個經典的中間人攻擊工具 —— SSLStrip,通過它確實能實現這個效果。

不過今天講解的,則是完全不同的思路,一種更有效、更先進的解決方案 —— HTTPS 前端劫持。


後端的缺陷

在過去,流量劫持基本通過後端來實現,SSLStrip 就是個典型的例子。

類似其他中間人工具,純後端的實現只能操控最原始的流量數據,這嚴重阻礙了向更高層次的發展,面臨衆多難以解決的問題。

  • 動態元素怎麼辦?

  • 如何處理數據包分片?

  • 性能消耗能否降低?

  • ......

動態元素

在 Web 剛出現的年代裏,SSLStrip 這樣的工具還是大有用武之地的。那時的網頁都以靜態爲主,結構簡單層次清晰。在流量上進行替換,完全能夠勝任。

然而,如今的網頁日益複雜,腳本所佔比重越來越多。如果僅僅從流量上着手,顯然力不從心。

var protocol = 'https';
document.write('<a href="' + protocol + '://www.alipay.com/">Login</a>');

即使非常簡單的動態元素,後端也毫無招架之力。

分片處理

分塊傳輸的道理大家都明白。對於較大的數據,一口氣是無法傳完的。客戶端依次收到各個數據塊,最終才能合併成一個完整的網頁。

由於每次收到的都是殘缺的碎片,這給鏈接替換帶來很大的麻煩。加上不少頁面並非標準的 UTF-8 編碼,因此更是難上加難。

爲了能順利進行,中間人通常先收集數據,等到頁面接收完整,纔開始替換。

如果把數據比作水流,這個代理就像大壩一樣,攔截了源源不斷往下流的水,直到蓄滿了纔開始釋放。因此,下游的人們需忍受很久的乾旱,才能等到水源。

性能消耗

由於 HTML 兼容衆多歷史遺留規範,因此替換工作並非是件輕鬆事。

各種複雜的正則表達式,消耗着不少的 CPU 資源。儘管用戶最終點擊的只是其中一兩個鏈接,但中間人並不知道將會是哪個,因此仍需分析整個頁面。這不得不說是個悲哀。


前端的優勢

如果我們的中間人能打入到頁面的前端,那麼情況會不會有所改善呢?

分片處理

首先,要派一名間諜到頁面裏。這是非常容易辦到的:

不像超鏈接遍佈在頁面各處,腳本插入到頭部即可運行了。所以我們根本不用整個頁面的數據,只需改造下第一個 chunk 就可以,後續的數據仍然交給系統轉發。

因此,整個代理的時間幾乎不變!

動態元素

很好,我們輕易滲透到頁面裏。但接着又如何發起進攻?

既然到了前端裏,方法就相當多了。最簡單的,就是遍歷超鏈接元素,將 https 的都替換成 http 版本。

這個想法確實不錯,但仍停留在 SSLStrip 思維模式上。還是『替換』這條路,只是從後端搬到前端而已。

儘管這個方法能勝任大多場合,但仍然不是最完美的。我們並不知道動態元素何時會添加進來,因此需要開啓定時器不斷的掃描。這顯然是個很挫的辦法。

性能優化

事實上,超鏈接無論是誰產生的、何時添加進來的,只要不點擊,都是不起作用的。所以,我們只需關心何時去點擊就可以 —— 如果我們的程序,能在點擊產生的第一時間裏控制住現場,那麼之後的流程就可由我們決定了。

聽起來似乎很玄乎,不過在前端,這只是小菜一碟的事。點擊,不過個事件而已。既然是事件,我們用最基礎的事件捕獲機制,即可將其輕鬆拿下:

document.addEventListener('click', function(e) {
	// ...
}, true);

DOM-3-Event 是個非常有意義的事件模型。之前用它來實現『內聯 XSS 攔截』,如今同樣也可以用來劫持鏈接。

我們捕獲全局的點擊事件,如果發現有落在 https 超鏈接上,果斷將其......攔截?

如果真把它攔截了,那新頁面就不會出現了。當然你會說,可以自己 window.open 彈一個,反正點擊事件裏是可以彈窗的。

不過,請別忘了,並非所有的超鏈接都是彈窗,也有不少是直接跳轉的。你也會說可以修改 location 來實現。

但要識別是『彈窗』還是『跳轉』,並不簡單。除了超鏈接的 target 屬性,頁面裏的 <base> 元素也會有影響。當然,這些相信你都能處理好。

然而,現實未必都是那麼簡單的。有些超鏈接本身就綁定了 onclick 事件,甚至在其中 return false 或 preventDefault,屏蔽了默認行爲。如果我們不顧及這些,仍然模擬跳轉或彈窗,那就違背頁面的意願了。

事實上,有一個非常簡單的辦法:當我們的捕獲程序運行時,新頁面還遠沒出現,這時仍有機會修改超鏈接的 href。待事件冒泡完成、執行默認行爲時,瀏覽器纔讀取 href 屬性,作爲最終的結果。

因此,我們只需捕獲點擊事件,修改超鏈接地址就可以了。至於是跳轉、彈窗、還是被屏蔽,根本不用我們關心。

就那麼簡單。因爲我們是在用戶點下去之後才修改,所以瀏覽器狀態欄裏,顯示的仍是原先 https !

當然,點過一次之後,再把鼠標放到超鏈接上,狀態欄裏顯示的就是修改後的了。

爲了能繼續忽悠,我們在修改 href 之後的下個線程週期裏,把它改回來。因爲有了一定延時,新頁面並不受影響。

var url = link.href;                                // 保存原始地址
link.href = url.replace('https://', 'http://');	    // 暫時換成 http 的
setTimeout(function() {
    link.href = url;                                // 新頁面打開後,還原回來
}, 0);

這樣,頁面裏的超鏈接始終都是正常的 —— 只有用戶點下的瞬間,才臨時僞裝一下。


更多攔截

除了通過超鏈接,還有其他方式訪問頁面,我們應儘可能多的進行監控。例如:

  • 表單提交
  • window.open 彈窗
  • 框架頁面
  • .....

表單提交

表單提交和超鏈接非常類似,都具有事件,只是將 click 換成 submithref 換成 action 而已。

腳本彈窗

函數調用的最簡單了,只需一個小鉤子即可搞定:

var raw_open = window.open;
window.open = function(url) {
	// FIX: null, case insensitive
	arguments[0] = url.replace('https://', 'http://');
	raw_open.apply(this, arguments);
}

框架頁面

因爲我們把主頁面降級成 http 了,但裏面的框架地址仍是原先的。由於協議不同,這會產生跨域問題,導致頁面無法正常工作。

所以我們還要把頁面裏的框架,也都轉型成 http 版本,確保能和主頁面融爲一致。

但框架和之前的那些不同,因爲它是自動加載的,而且也沒有一個即將加載的事件。如果等到框架加載完了再去處理,說不定已經開始報跨域錯誤了。而且還會白白的浪費一次加載流量。

因此,我們必須讓框架一出現,就立即替換掉地址。

這在過去是個很棘手的問題,然而 HTML5 時代給我們帶來了新希望 —— MutationEvent。用它即可實時監控頁面元素,之前也嘗試過一些試驗

當然,即使 MutationEvent,偶爾也會有延時遺漏。爲了能徹底避免出現 https 框架頁,我們繼續使用 HTML5 帶來的一項新技術 —— Content Security Policy,由於它是瀏覽器原生支持的,因此實施的非常徹底。

在我們的代理返回頭中,加上如下 HTTP 頭部,即可完美攔截 https 框架頁了:

Content-Security-Policy: default-src * data: 'unsafe-inline' 'unsafe-eval'; frame-src http://*

解決了框架頁的問題,我們就能成功劫持支付寶登錄頁的賬號框 IFrame 了!


後端配合

通過前端的 XSS 腳本,我們輕易解決了過去各種棘手的問題。但挑戰並未就此結束,我們仍面臨着衆多難題。

如何告訴代理

儘管在前端上面,我們已經避開了各種進入 https 的途徑,讓請求以明文的形式交給代理。但代理又如何決定,這個請求用 https 還是 http 轉發呢?

傳統的後端劫持之所以能正確轉發,那是在替換超鏈接的時候,已經做下記錄。當出現記錄中的請求,就走 https 的轉發。

而我們的劫持在前端,並且只發生在點擊的一瞬間。即使馬上去告訴中間人,某個 URL 是 https 的,這時也來不及了。

告訴中間人是必須的。但我們可以用一個巧妙的方法,不必單獨發送消息 —— 我們只需在轉型後的 URL 裏,做個小記號就可以了。

當代理髮現請求的 URL 裏有這個記號,它自然就懂了,直接走 https!

由於把頁面從 https 降級到了 http,因此相關請求的referer也變成 http 版了。所以,中間人應儘量把 referer 也修正回來,避免被服務器察覺。

隱藏僞裝

不過,在 URL 里加標記的方法,也有很大的缺陷。

因爲頁面的 URL 會在地址欄裏顯示出來,所以用戶會看見我們的記號。當然,我們可以使用一些迷惑性的字符,例如 ?zh_cn?utf_8?from_baidu 等等,更好的欺騙用戶。

當然,如果你覺得還是不滿意,也有辦法讓這些礙眼標記儘快消失:

if url has symbol
	history.replaceState(..., clear_symbol(url) )

HTML5 爲我們提供了修改地址欄的能力,並且無需刷新。這些強悍的功能,如今都可以在前端利用起來了。

重定向劫持

當然,光靠前端的劫持,還是遠遠不夠的。現實中,還有另一種很常見的方式,那就是重定向到安全頁面。

仔細回想下,平時我們是怎樣進入想上的網站的。例如支付寶,除非你有收藏,否則就得自己敲入 www.alipay.com 或 www.zhifubao.com,當你回車進入時,瀏覽器又如何知道這是個 HTTPS 的網站呢?

顯然,第一個請求仍是普通的 HTTP 協議。當然,這個 HTTP 版的支付寶的確存在,它的唯一功能就將用戶重定向到 HTTPS 版本。

當我們的中間人一旦發現有重定向到 HTTPS 網站的,當然不希望用戶走這條不受自己控制的路。於是攔下這個重定向,然後以 HTTPS 的方式,獲取重定向後的內容,最後再以 HTTP 明文的方式,回覆給用戶。

因此在用戶看來,始終處於 HTTP 網站上。

不過,如今的 Web 裏增加一個新的安全標準:HTTP Strict Transport Security。如果客戶端收到這個頭部,之後一段時間內訪問該站點,就始終通過 HTTPS 的方式。

所以我們的中間人一旦發現有這個字段,就得果斷將其刪除。

當然,用戶直接敲網址的並不常見。大多都是搜索引擎,然後直接從第一個結果裏進來了。

比較悲劇的是,國內的搜索引擎幾乎都是 HTTP 的。在用戶訪問搜索頁面的時候,我們的 XSS 早已潛伏在其中了,因此從中點出來的任何一條結果,都是進不到官方的 HTTPS 裏的:)

除了搜索頁面,不少類似 hao123 之類的網址大全,大多也未開啓 HTTPS。因此從中導流的網站,都面臨着被中間人劫持的風險。


防範措施

介紹了攻擊方法,接着講解防禦措施。

腳本跳轉

事實上,無論是前端劫持還是後端過濾,仍有不少的網站無法成功。例如京東的登錄:

它是通過腳本跳轉到 HTTPS 地址的。而瀏覽器的 location 是個及其特殊的屬性,它可以被屏蔽,但無法被重寫。因此我們難以控制頁面的跳轉情況。

如果非要劫持京東頁面,我們只能使用白名單的方式,特殊對待該站點。但這樣就大幅增加了攻擊成本。

混淆明文

當然,不難發現京東的登錄腳本里,URL 是以最直白的明文出現的。所以我們利用 SSLStrip 的方式,對腳本里的 https:// 的文本進行替換,也能起到一定的作用,畢竟大多腳本都對此毫無防備。

但對於稍微複雜一點的腳本,例如通過字符串拼接而成的 URL,那麼就難以實施了。

所以在安全需要較高的場合,不妨把一些重要的地址進行簡單的處理,中間人就無法使用通用的方式來攻擊。而必須針對站點進行特殊對待,從而提高攻擊成本。

儘可能多的 HSTS

之前提到 HSTS 頭。只要這個字段出現過一次,瀏覽器在很長時間裏都會只用 HTTPS 訪問站點。因此,我們儘可能多的開啓 HSTS。

現實中的劫持並非都是 100% 成功的,上述提到,使用腳本跳轉很容易出現遺漏。所以,只要逮住用戶一次遺漏,HSTS 就可以讓之後的頁面降級徹底失效了。


攻擊演示

因爲是前端劫持,所以 Demo 有兩個文件:一個前端代碼,另一個後端腳本(NodeJS)。

相關源碼:https://github.com/EtherDream/https_hijack_demo

相比之前寫的流量劫持演示,這裏功能更爲專一,不再提供額外的劫持途徑(例如 DNS 等)。

想測試其實非常簡單,只需配置瀏覽器代理,即可模擬 HTTP 的劫持:

不嫌麻煩的話,也可以在 Linux 內核的系統上測試,轉發 80 到本機即可。原理都是一樣的。

我們隨便找一個 HTTP -> HTTPS 網站做測試。

得益於前端腳本的優勢,我們把鼠標放到登錄超鏈接上,狀態欄顯示的仍是原始 URL:

在我們點擊的瞬間,暗藏頁面中的 XSS 鉤子觸發了,成功把我們帶到中間人虛擬的 HTTP 登錄頁面裏。

當然,由於 URL 參數很多,地址欄裏的那個記號看不到了。

慶幸的是,淘寶的登錄頁面未進行地址判斷,被降級後的頁面仍然能登錄成功!

當然之前也說了,並非所有的頁面都能劫持成功。

如今越來越多的網站都已重視,因此前端的安全性檢測也隨之而生。僅僅通過一個工具,實現大規模通用化的劫持,未來會更加困難。

但先比傳統的純後端實現,前後結合的方案能夠帶來更大的發揮空間。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章