JavaScript >> Javascript-Tutorial >  >> Tags >> npm

Injizieren von Backdoors in NPM-Pakete

❗️❗️❗️ Ich rate niemandem, Open-Source-Pakete tatsächlich durch Hintertüren zu öffnen, es ist eigentlich das Gegenteil, lasst uns eine Welt zu einem besseren Ort machen.

In diesem Artikel möchte ich Schritte reproduzieren, die in der Forschung im Jahr 2019 beschrieben wurden, und sehen, ob es immer noch ein Problem gibt – Warum npm-Sperrdateien ein Sicherheits-Blindspot für das Einschleusen bösartiger Module sein können.

Kurz gesagt, wenn Sie Abhängigkeiten installieren, schaut Ihr Paketmanager zuerst in Sperrdateien wie garn.lock. Dort kann es den Paketnamen, die genaue Paketversion, einen Link zu den Quellen und Integritätsprüfungen finden, die dabei helfen, festzustellen, ob das Paket unterwegs nicht beschädigt oder verändert wurde.

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==

Das Problem ist, dass jemand diese Sperrdatei aktualisieren und einen neuen Link setzen kann, der auf eine Backdoor-Paketversion zeigt . Lassen Sie uns versuchen, diesen Angriff zu wiederholen und sehen, wie schwer er ist.

Paket installieren

Als Beispiel werden wir versuchen, das is-number-Paket zu ändern. An diesem Paket ist nichts Besonderes, es ist nur klein, also wird es einfach sein, es zu modifizieren.

Lassen Sie es uns installieren und prüfen, ob es überhaupt funktioniert.

yarn add is-number

index.js

const isNumber = require("is-number");

console.log(isNumber(1));
➜  malicious-lockfile git:(master) ✗ node index.js
true

Garn.Schloss

# 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==

Im Moment ist alles legal.

Paket kopieren

Wie Sie vielleicht wissen oder zuvor in der Sperrdatei bemerkt haben, werden Pakete als tgz-Dateien bereitgestellt. Es ist nicht schwer, selbst einen zu erstellen, verwenden Sie einfach den integrierten npm-Befehl 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

Ausgabe:

➜  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

Das war's im Grunde, Sie brauchen nur diese Schritte, um ein Paket zu replizieren.

Quellen ändern

Die aktuelle index.js-Version ist super einfach.

/*!
 * 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;
};

Machen wir nichts Schlimmes, sondern geben einfach Hello world 🌎 aus

/*!
 * 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;
};

Jetzt packen wir es einfach wieder, aber wir müssen die Integritätsnummer drucken, die wir später brauchen werden, wir können es mit --json tun Möglichkeit.

➜  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": []
  }
]

Liefern Sie dieses Paket

Für dieses Experiment werden wir es nicht einmal in npm oder anderswo veröffentlichen, wir können diese Datei einfach lokal mit einem http-Server bereitstellen. Auf diese Datei kann lokal über http://127.0.0.1:8080/is-number-7.0.0.tgz zugegriffen werden .

Änderung der Sperrdatei

Der letzte Vorbereitungsschritt besteht darin, die Sperrdatei zu ändern, es wird nicht schwer sein, da wir die Shasum- und Integritätsnummer aus dem vorherigen Schritt kennen.

Garn.Sperre vorher:

# 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==

Garn.Sperre nach:

# 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==

Prüfen Sie, ob es funktioniert

Wir müssen zuerst node_modules bereinigen, außerdem müssen wir den Garn-Cache leeren, da sonst die offizielle Version installiert wird, die zuvor zwischengespeichert wurde (als wir sie zum ersten Mal installiert haben).

➜  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.

Wie wir in der ausführlichen Version sehen können, haben wir die lokale Paketversion abgerufen, also lassen Sie es uns ausführen.

➜  malicious-lockfile git:(master) ✗ node index.js
Hello world 🌎
true

Warum ist das wichtig?

Jemand mag jetzt schon denken:"Warum sollte mich das interessieren? Du hast die lokale Abhängigkeit aktualisiert und dich selbst gehackt, gute Arbeit, Bruder 🤣".

Das Problem ist, dass es nicht so einfach ist. Wenn wir uns ansehen, wie Sperrdateiaktualisierungen normalerweise in Open Source aussehen, werden wir feststellen, dass sie in den meisten Fällen vor einem Prüfer verborgen sind.

Übrigens, seien Sie mal ehrlich, wie oft haben Sie sich persönlich schon mehr als 500 Änderungen in der Sperrdatei angesehen?

Es wird also keine leichte Aufgabe sein, eine URL-Änderung in diesem Blob von Änderungen zu erkennen. Was, wenn wir sogar is-nomber hochladen zum npm? package.json wird immer noch sagen, dass wir normales is-number verwenden , aber wir werden is-nomber installieren 🤷 Viel Glück beim Erkennen eines nicht übereinstimmenden Buchstabens in über 700 geänderten Zeilen.

Selbst wenn NPM falsch geschriebene Pakete wie is-nomber entfernt , können wir immer noch yranpkg.com registrieren und den genauen Pfad zum Paket dort nachahmen. Viel Glück beim Erkennen der URL-Änderung um einen Buchstaben in über 700 geänderten Zeilen.

Schlussbemerkungen

Seien Sie besonders vorsichtig bei Fremden, die Abhängigkeiten in Ihrem Open-Source-Projekt aktualisieren. Es mag wie das erste Open-Source-Engagement eines Studenten aussehen, aber es kann auch ein Versuch sein, alles von einem erfahrenen schwarzen Hut hinter die Tür zu bekommen. Vielleicht sollten Sie das Aktualisieren von Sperrdateien und das Installieren neuer Pakete sogar nur bewährten Mitwirkenden erlauben, aber es ist keine 💯 Prozent bewährte Lösung (lesen Sie dies).

Ein zusätzlicher Ansatz könnte darin bestehen, lockfile-lint zu verwenden, aber Sie sollten sich nicht ausschließlich auf dieses Skript verlassen, da es andere Ökosysteme als npm gibt und diese möglicherweise ähnliche Probleme haben.

upd:Dieses Problem ist nicht nur Garn-spezifisch, es gibt offene Probleme/Diskussionen in pnpm, Garn1 &Garn2 und npm.

Verwandte Artikel

Eine weitere verwandte Lektüre wäre Ein Post-Mortem der bösartigen Event-Stream-Hintertür

Wenn Ihnen diese Artikel gefallen haben, werfen Sie einen Blick auf diese beiden:

  • 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

Ich hoffe, du hattest Spaß 👋

Übrigens, lass uns hier und auf Twitter Freunde sein 👋