Vermeiden von Sicherheitslöchern beim Entwickeln einer Applikation - Teil 1

ArticleCategory:

Software Development

AuthorImage:

[image of the authors]

TranslationInfo:

Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier

fr to en Georges Tarbouriech

en to de Guido Socher

AboutTheAuthor:

Christophe Blaess ist ein unabhängiger Raumfahrtingenieur. Er ist ein Linuxfan und arbeitet die meiste Zeit mit diesem System. Er koordiniert die Übersetzung der Man-pages die vom Linux Dokumentationsprojekt veröffentlicht werden.

Christophe Grenier ist Student im fünften Semester an der ESIEA, wo er auch als Systemadministrator arbeitet. Er hat eine Leidenschaft für Computersicherheit.

Frédéric Raynal benutzt Linux, weil er mit Software, die keine Patente enthält, arbeiten möchte. Er geht gerne ins Kino. Dancer in the Dark ist ein guter Film.

Abstract

Dies ist der erste Artikel in einer Serie von Artikeln über Sicherheitslöcher, die beim Entwickeln von Software entstehen können. Diese Artikel werden zeigen, wie man Sicherheitsprobleme vermeiden kann, indem man seine Programmiergewohnheiten ein wenig ändert.

ArticleIllustration

[article illustartion]

ArticleBody

Einführung

Im allgemeinen dauert es nicht länger als zwei Wochen, bis wieder ein Sicherheitsloch in einer größeren Anwendung auftaucht. Ein Sicherheitsloch, das es z.B dem auf dem Rechner eingeloggten Benutzern erlaubt, root zu werden. Trotz der hervorragenden Qualität der meisten dieser Applikationen scheint es immer sehr schwierig zu sein, sichere Programme zu schreiben. Ein sicheres Programm gibt einem Benutzer mit bösen oder kriminellen Absichten keine Möglichkeit, sich zu Systemteilen Zugang zu verschaffen, zu denen er keinen Zugang haben soll. Die Verfügbarkeit von Quellcode ist eine gute Sache und wird von vielen Programmierern sehr geschätzt, aber der kleinste Fehler in einem Programm wird damit sichtbar für jedermann. Diese Fehler werden oft zufällig gefunden und Leute, die nach solchen Fehlern suchen, haben nicht immer gute Absichten.

Für einen Systemadministrator besteht die tägliche Arbeit darin, Newsgruppen und Webseiten, auf denen Sicherheitslöcher veröffentlicht werden, zu studieren und für die entsprechenden Pakete einen Update einzuspielen. Der Programmierer kann durch die Studie dieser Informationen viel lernen, speziell wenn er selbst versucht, an seinem Rechner solche Sicherheitslöcher auszuprobieren. Egal wie schnell ein Sicherheitsproblem gefunden wird, ist es immer besser, Sicherheitsprobleme von vornherein zu vermeiden. Wir werden hier einige klassische Fehler besprechen, und zeigen Lösungen für diese Probleme. Wir werden keine Sicherheitsprobleme besprechen, die speziell in Netzwerken auftreten, da diese meist Konfigurationsfehler sind. Systemfehler, die DOS (Denial Of Service) Angriffe zulassen. Diese Probleme betreffen den Systemadministrator oder den Kernelentwickler, aber manchmal auch Applikationsprogrammierer. Speziell dann, wenn diese Programme auf Daten zugreifen, die nicht unbedingt vertrauenswürdig sind. Typische Programme in dieser Klasse sind pine, acroread, netscape, access,... Einige von diesen Programmen erlaubten es, Informationen auszuspionieren oder direkten Zugriff auf das System zu erhalten. Es ist eine Tatsache, daß sicheres Programmieren einfach jeden betrifft.

Diese Artikelserie zeigt Methoden, die benutzt werden können, um ein Unixsystem zu beschädigen. Wir hätten diese Probleme einfach auflisten können, aber wir bevorzugen offene und klare Erklärungen, damit alle die Mechanismen verstehen. Nach dieser Serie solltest du in der Lage sein, Sicherheitsfehler zu beheben und zu vermeiden. Für jedes Sicherheitsloch werden wir denselben Ansatz wählen. Wir erklären, wie sie funktionieren und dann zeigen wir, wie man sie vermeidet.

Dieser erste Artikel erklärt grundlegende Sicherheitsmechanismen. Wir zeigen, wie man Privilegien durch den Mißbrauch von Set-UID und Set-GID erhalten kann. Als nächstes analysieren wir die bekannte C Funktion system() und zeigen die Sicherheitslöcher, die es in dieser Funktion gibt.

Wir werden häufig die Problematik anhand kleiner C Programme aufzeigen, aber diese Problematiken lassen sich auf andere Sprachen übertragen: perl, java, shell scripts... Einige Probleme sind spezifisch für eine Sprache, aber das ist nicht immer so, wie wir anhand der Funktion system() sehen werden.

Privilegien

Auf einem Unixsytem sind die Benutzer nicht alle gleich. Sie haben nicht die gleichen Rechte und das gilt auch für Applikationen. Der Zugriff auf das Dateisystem und die Peripherie einer Maschine unterliegt einer strikten Identitätskontrolle. Einige Benutzer dürfen empfindliche Operationen ausführen, um das System in gutem Zustand zu halten, andere haben diese Rechte nicht. Eine Nummer, die als UID (User Identifier) bezeichnet wird, wird für diese Identitätskontrolle benutzt. Um die Sache einfacher zu machen, gibt es einen Namen, der dieser Nummer entspricht und die Assoziation zwischen Namen und Nummer erfolgt über die Datei /etc/passwd.

Der Benutzer root, mit der UID 0, kann auf alles im System zugreifen. Er kann nicht nur auf alle Dateien zugreifen, er kann auch die physikalische Konfiguration einer Maschine ändern. Er kann Partitionen mounten, Netzwerk Interfaces aktivieren, IP Adressen ändern oder Systemaufrufe wie mmlock() benutzen, um auf den physikalischen Speicher zuzugreifen. In zukünftigen Artikeln werden wir die Möglichkeiten studieren, die Posix.1e bietet, um die Privilegien von Programmen, die mit root Rechten laufen, zu limitieren. Für den Augenblick nehmen wir jedoch an, daß der Benutzer root alles kann.

Die Attacken, die wir besprechen, sind interne Attacken. Ein eingeloggter und authentifizierter Benutzer versucht, Privilegien zu erlangen, die er eigentlich nicht hat. Weiterhin gibt es Netzwerkangriffe, externe Angriffe, bei denen Leute versuchen, Verbindungen aufzubauen, die sie eigentlich nicht aufbauen dürfen. Wenn man die Privilegien eines anderen Benutzers erhalten hat, bedeutet das, daß alles unter seinem Namen, seiner UID, gemacht wird und nicht unter der ursprünglichen UID. Natürlich wird ein Angreifer versuchen, die ID von root zu erhalten, aber auch andere Benutzer sind von Interesse, weil man damit auf Informationen zugreifen kann, (news, mail, lp...) und man damit geschützte Daten (Briefe, persönliche Dateien, etc) erhalten kann. Außerdem werden sie benutzt, um illegale Aktivitäten gegenüber anderen zu verstecken.

Um die Privilegien, die für einen anderen Benutzer reserviert sind, zu nutzen, ohne als dieser Benutzer eingeloggt zu sein, muß man zumindest die Möglichkeit haben, mit einer Applikation zu kommunizieren, die unter der UID des Opfers läuft. Wenn eine Applikation --ein Prozeß-- unter Linux läuft, dann hat diese eine klar definierte Identität. Zunächst hat ein Programm ein Attribut namens RUID (Real UID), die der UID (Benutzer Identität) des Benutzers entspricht, der das Programm gestartet hat. Diese Daten werden vom Kernel verwaltet und können sich normalerweise nicht ändern. Es gibt noch ein weiteres Attribut, die EUID (Effective UID). Die EUID wird herangezogen, wenn der Kernel Zugriffsrechte regelt (beim Öffnen von Dateien, Benutzung spezieller Systemaufrufe ...).

Um eine Applikation mit einer Effective UID (bestimmte Privilegien), die anders als die Real UID (Benutzer, der das Programm startete) laufen zu lassen ist, muß ein spezielles Zugriffsrechte-Bit namens Set-UID (wird mit chmod gesetzt) gesetzt sein. Dieses Bit befindet sich in dem Datei Permission Attribut. Es hat den oktalen Wert 4000. Das Set-UID Bit wird als ein s dargestellt, wenn man sich die Zugriffsrechte mit dem Befehl ls anzeigen läßt:

>> ls -l /bin/su
-rwsr-xr-x  1 root  root  14124 Aug 18  1999 /bin/su
>> 
 
Der Befehl "find / -type f -perm +4000" zeigt alle Applikationen im Dateisystem, die das Set-UID Bit gesetzt haben. Wenn der Kernel ein Programm, das dieses Set-UID Bit gesetzt hat, startet, dann benutzt er als EUID die UID des Benutzers, dem die Datei gehört. Die RUID ändert sich nicht und entspricht weiterhin dem Benutzer, der das Programm startete. Der Befehl /bin/su benutzt z.B diese Eigenschaft. Jeder Benutzer kann den Befehl /bin/su starten, aber er läuft mit der UID des Eigentümers (root). Es braucht wohl nicht weiter betont zu werden, daß solch ein Programm sorgfältig programmiert werden muß.

Jeder Prozeß hat auch eine effektive Group ID, EGID, und eine real group ID , RGID. Das Set-GID (oktal 2000 ) regelt die Gruppenzugriffsrechte, wenn ein Programm gestartet wird. Eine merkwürdige Kombination entsteht, wenn das Set-GID Bit gesetzt ist, ohne daß die Datei ein execute Bit gesetzt hat. Dies ist eine Konvention, die nichts mit den Privilegien einer Applikation zu tun hat, sondern eine Datei, die mit der Funktion fcntl(fd, F_SETLK, lock) geblockt werden kann. Normalerweise benutzen Applikationen diese Set-GID bit nicht. Einige Spiele benutzen es z.B um Highscores systemweit zu speichern.

Typen von Angriffen und mögliche Ziele

Es gibt verschiedene Typen von Angriffen gegen ein System. Heute werden wir die Mechanismen studieren, mit dem ein Angreifer einen beliebigen Befehl aus einer Applikation heraus starten kann. Dieser Befehl ist normalerweise die Shell, die dann unter der UID der Applikation läuft. Eine zweite Art von Angriff ist ein buffer overflow. Dieser gibt dem Angreifer die Möglichkeit, beliebigen Maschinencode auszuführen. Ein dritter Typ eines Angriffs basiert auf race conditions. Es wird die Zeit genutzt, die zwischen dem Ausführen verschiedener Codestücke vergeht. In dieser Zeit wird irgendein Teil des Systems verändert (normalerweise eine Datei), während die Applikation denkt, es sei gleichgeblieben.

Die zwei ersten Arten von Angriffen versuchen, die Shell mit den Privilegien des Eigentümers eines Programmes auszuführen. Der dritte Type hingegen versucht, Zugriff auf geschützte Systemdateien zu erhalten. Auch der Lesezugriff auf bestimmte Dateien wie z.B /etc/shadow ist ein Sicherheitsrisiko.

Die Ziele eines Angriffs auf die Systemsicherheit sind meist Programme, die das Set-UID (oder Set-GID) Bit gesetzt haben. Das betrifft auch andere Applikationen, die nicht unter der UID ihrer Benutzer laufen. Die System daemons (Server Prozesse) repräsentieren einen großen Teil dieser Art von Programmen. Ein daemon wird im allgemeinen beim Booten gestartet und läuft im Hintergrund. lpd, z.B erlaubt es jedem Benutzer Dokumente an den Drucker zu schicken. sendmail empfängt und verschickt E-Mail, oder apmd, der das Bios nach dem Status der Batterie befragt (läuft meist auf Laptops). Einige daemons kommunizieren auch mit externen Benutzern über das Netzwerk (Ftp, Http, Telnet... ). Ein Prozeß namens inetd verwaltet Netzwerkverbindungen.

Zusammenfassend können wir feststellen, daß ein Programm angegriffen werden kann, sobald es mit einem Benutzer ungleich dem Benutzer der es gestartet hat, kommunizieren kann. Wenn es in der Natur des Designs eine Applikation liegt, so etwas zu tun, dann muß man sehr sorgfältig programmieren.

Privilegien ändern

Eine Applikation läuft normalerweise mit einer EUID ungleich der RUID, um dem Benutzer gezielten Zugriff auf Privilegien zu geben, die er normalerweise nicht hat (Dateizugriff, reservierte Systemaufrufe). Im allgemeinen wird das jedoch nur punktuell benötigt, zum Beispiel beim Öffnen einer Datei, ansonsten ist die Applikation in der Lage, mit den Rechten des Benutzers, der sie gestartet hat, auszukommen. Man kann die EUID temporär mit dem Befehl seteuid ändern:

  int seteuid (uid_t uid);
Eine Applikation kann immer den Wert der EUID so ändern, daß er der RUID entspricht. In diesem Fall wird die alte UID gespeichert in einem Feld namens SUID (Saved UID). Es ist möglich, die SUID zurückzuerhalten und als EUID zu benutzen. Natürlich kann ein Programm mit der EUID Null (root) sowohl die EUID als auch die RUID beliebig ändern (Das Programm /bin/su funktioniert so).

Um das Risiko eines Angriffs zu reduzieren wird vorgeschlagen, die EUID zu ändern und die RUID zu benutzen, wenn gerade keine speziellen Privilegien gebraucht werden. Wenn Privilegien gebraucht werden, schreibt man die Saved UID wieder in die EUID. Hier ist ein Beispiel:

  
  uid_t e_uid_initial;
  uid_t r_uid;
    
  int
  main (int argc, char * argv [])
  {
    /* Saves the different UIDs */
    e_uid_initial = geteuid ();
    r_uid = getuid ();

    /* limits access rights to the ones of the 
     * user launching the program */
    seteuid (r_uid);
    ...
    privileged_function ();
    ...
  }
  
  void
  privileged_function (void)
  {
    /* Gets initial privileges back */
    seteuid (e_uid_initial);
    ...
    /* Portion needing privileges */
    ...
    /* Back to the rights of the runner */
    seteuid (r_uid);
  }  
 

Diese Strategie ist besser als die oft gesehene andersherum arbeitende Strategie, bei der die EUID temporär zur RUID gesetzt wird, bevor man "riskante" Programmstücke ausführt. Diese Reduzierung der Privilegien ist jedoch nutzlos gegen einen Buffer overflow Angriff. Das werden wir im nächsten Artikel sehen. Bei einem Buffer overflow wird beliebiger Code ausgeführt und dieser kann die Anweisungen enthalten, um die EUID zu verändern. Trotzdem hilft dieses nur punktuelle Setzen der Privilegien gegen das beliebige Ausführen einiger Befehle und gegen die meisten Race Conditions gut.

Beliebige externe Befehle ausführen

Eine Applikation muß oft einen externen Befehl aufrufen. Ein bekanntes Beispiel ist der Befehl mail, um ein Mail zu verschicken ( einen Alarm zu melden oder einfach Statistiken zu schicken). Die einfachste Lösung, um das zu machen, ist die Library Funktion system() zu benutzen:

  int system (const char * command)

Die Gefahren der system() Funktion

Diese Funktion ist sehr gefährlich: Sie ruft die Shell auf, um einen Befehl, das in der Variable "command" spezifiziert wurde, auszuführen. Das Verhalten der Shell hängt dabei von den Vorlieben des Benutzers ab. Ein typisches Beispiel ist die PATH Umgebungsvariable. Laß uns annehmen, daß eine Applikation mail aufruft. Dieses Programm schickt z.B den Quellcode dem Benutzer, der es aufruft:

/* system1.c */

#include <stdio.h>
#include <stdlib.h>

int
main (void)
{
  if (system ("mail $USER < system1.c") != 0)
    perror ("system");
  return (0);
}
Nehmen wir weiterhin an, daß das Programm mit Set-UID root arbeitet:
>> cc system1.c -o system1
>> su
Password:
[root] chown root.root system1
[root] chmod +s system1
[root] exit
>> ls -l system1
-rwsrwsr-x  1 root  root  11831  Oct 16  17:25 system1
>>
 
Beim Ausführen dieses Programmes wird die Shell (/bin/sh) mit der Option -c aufgerufen und übergibt den String der als Befehl ausgeführt werden soll. Die Shell sucht dann die Verzeichnisse aus der Umgebungsvariable PATH ab, um eine ausführbare Datei names mail zu finden. Der Benutzer braucht bloß die Variable PATH zu ändern und der Befehl, den er ausführen möchte in mail umbenennen und schon kann er irgendetwas ausführen:
  >> export PATH=.
  >> ./system1
 
Damit wird z.B versucht, mail im augenblicklichen Verzeichnis zu finden. Jetzt schreiben wir einfach ein kleines Shell Script und nennen es mail. Das Skript wird dann mit der EUID des Dateieigentümers der Applikation ausgeführt. Hier ist ein Skript, das /bin/sh ausführt. Da stdin umgeleitet ist, müßen wir uns die Eingabe zurück vom Terminal holen. Unser Skript sieht damit so aus:
#! /bin/sh
# "mail" script running a shell
# getting its standard input back.
/bin/sh < /dev/tty
 
Hier ist das Ergebnis:
>> export PATH="."
>> ./system1
bash# /usr/bin/whoami
  root
bash# 
 

Der erste Lösungsansatz besteht natürlich darin, immer einen vollen Pfadnamen zu benutzen, z.B /bin/mail. Damit gibt es ein neues Problem. Die Applikation vertraut darauf, daß mail an einer bestimmten Stelle im System zu finden ist. Während /bin/mail im allgemeinen in jedem System zu finden ist, gibt es andere Programme wie z.B GhostScript, die in verschiedenen Distributionen unterschiedlich installiert sind. Desweiteren gibt es noch einen anderen Typ von Angriff bei einigen alten Shells, der auf er Umgebungsvariable IFS beruht. Die Shell benutzt sie, um die Trennzeichen zwischen Befehl und Argument zu finden. Im allgemeinen sind das Leerzeichen Tab und Return. Wenn der Benutzer / hinzugügt, dann wird der Befehl "/bin/mail" als "bin mail" interpretiert. Eine ausfürbare Datei namens bin kann jetzt im augenblicklichen Verzeichnis ausgeführt werden, wenn PATH entsprechend gesetzt ist.

Unter Linux ist die IFS Umgebungsvariable kein Problem mehr, da die bash sie mit Leerzeichen, Tab und Return beim Start vervollständigt. Das gleiche gilt für pdksh. Dennoch sollte man sich nicht darauf verlassen, denn Applikation werden oft auf andere Systeme portiert und diese Systeme können sich hier anders verhalten.

Einige andere Umgebungvariablen können unerwartete Probleme machen. Das Programm mail erlaubt es z.B dem Benutze,r ein anderes Programm auszuführen, während er eine Nachricht schreibt. Das geht mit der Escapesequenz "~!". Schreibt der Benutzer den String "~!command" am Anfang einer Zeile, dann wir dieser ausgeführt. Das Program /usr/bin/suidperl zum Schreiben von Set-UID perl Scripten rief /bin/mail auf, wenn es irgendein Problem hatte, um root eine Nachricht zu schicken. Da suidperl ein Set-UID root Programm ist, wird alles mit root Rechten ausgeführt. In der Nachricht an root steht der Name der fehlerhaften Datei. Jemand kann eine Datei erzeugen mit einem Dateinamen, der carriage return gefolgt von ~!command enthält. Wenn ein suidperl Programm über ein Problem stolpert, das mit dieser Datei zusammenhängt, dann wird /bin/mail aufgerufen und mit der Escapesequenz aus dem Dateinamen gefüttert.

Eigentlich sollte das kein Problem machen, da mail keine Escapesequenzen akzeptiert, wenn es nicht innerhalb eines Terminalfensters aufgerufen wird. Leider gibt es ein nicht dokumentiertes Feature (vermutlich vom Debuggen übrig geblieben), das es erlaubt, Escapesequenzen zu benutzen, sobald die Umgebungsvariable interactive gesetzt ist. Das Ergebnis? Ein leicht ausbeutbares Sicherheitsloch innerhalb einer Applikation, die eigentlich die Sicherheit eines Systems verbessern sollte. Der erste Fehler ist ein zwischen zwei Programmen geteilter Fehler. /bin/mail enthält ein nicht dokumentiertes Feature, das das Ausführen von beliebigem Code erlaubt. Das zweite Problem ist, selbst wenn die Entwickler des /usr/bin/suidperl dieses Feature nicht kannten, sollten sie nicht einfach alle Umgebungsvariablen übernehmen.

Linux ignoriert normalerwiese die Set-UID und Set-GID bits bei Skripten. Siehe /usr/src/linux/fs/binfmt_script.c und /usr/src/linux/fs/exec.c. Einige Tricks erlauben es, diesen Mechanismus zu umgehen. /usr/bin/suidperl benutzt solche Tricks.

Lösungen

Es ist nicht immer einfach einen Ersatz für die Funktion system() zu finden. Die erste Variante ist, Systemaufrufe wie execl() oder execle() zu benutzen. Das Verhalten ist dann jedoch völlig anders, da das externe Programm nicht mehr als Subroutine (Unterprogramm) aufgerufen wird. Das externe Programm ersetzt den augenblicklichen Prozess. Man muß den Prozess dublizieren und die Kommandozeilenargumente durchsuchen wie in dem folgenden Programm.


Aus
  if (system ("/bin/lpr -Plisting stats.txt") != 0) {
    perror ("Printing");
    return (-1);
  }
 
wird :
pid_t pid;
int   status;
  
if ((pid = fork()) < 0) {
  perror("fork");
  return (-1);
}
if (pid == 0) {
  /* child process */
  execl ("/bin/lpr", "lpr", "-Plisting", "stats.txt", NULL);
  perror ("execl");
  exit (-1);
}
/* father process */
waitpid (pid, & status, 0);
if ((! WIFEXITED (status)) || (WEXITSTATUS (status) != 0)) {
  perror ("Printing");
  return (-1);
}
 
Offensichtlich viel mehr Code. Unter einigen Umständen wird es sehr komplex. Zum Beispiel, falls man die Standardeingabe (stdin) umleiten möchte. Wie zum Beispiel hier:
system ("mail root < stat.txt");
 
Die Umleitung (<) wird von der Shell erledigt. Man kann das gleiche durch eine komplexe Verknüpfung von fork(), open(), dup2(), execl(), etc. erreichen. In diesem Fall ist es eine akzeptable Lösung, die system() Funktion zu benutzen, aber man muß vorher die gesamte Umgebung konfigurieren.

Umgebungsvariablen werden unter Linux in Form eines Zeigers auf ein Array abgespeichert: char ** environ. Dieses Array endet mit NULL. Die Strings haben die Form "NAME=value".

Wir fangen damit an, die gesamte Umgebung zu löschen. Wir benutzen die Gnu Erweiterung clearenv:

    int clearenv (void);
 
oder wir setzen den Pointer
    extern char ** environ;
 
einfach zu NULL. Als nächstes werden die Umgebungsvariablen, die wir brauchen, initialisiert:
    int setenv (const char * name, const char * value, int remove)
    int putenv(const char *string)
 
Vor einem system() aufruf z.B so:
    clearenv ();
    setenv ("PATH", "/bin:/usr/bin:/usr/local/bin", 1);
    setenv ("IFS", " \t\n", 1);
    system ("mail root < /tmp/msg.txt");
 
Falls nötig, kann man sich den Inhalt einiger nützlicher Umgebungsvariablen merken, bevor man die gesamte Umgebung löscht. Z.B. LANG, TERM, TZ, HOME. Der Inhalt, die Form und die Größe dieser Variablen muß genauestens geprüft werden. Es ist wichtig, daß man die gesamte Umgebung löscht, bevor man einige benötigte Variablen definiert. Das suidperl Sicherheitsproblem wäre nicht aufgetatucht, wenn die Umgebung korrekt gelöscht worden wäre.

Entsprechend geht man bei einer Netzwerkkonfiguration vor. Zunächst verwährt man jeder Maschiene die Verbindung. Als nächstes werden die benötigten Services aktiviert. Genauso geht man bei der Entwicklung einer Set-UID Applikation vor. Erst die gesamte Umgebung löschen und dann einzlne Umgebungsvariablen setzen.

Das Verifizieren der Parameter wird gemacht, indem man die erwarteten Werte mit den erlaubten Formaten vergleicht. Macht man es anders und überprüft nur auf mögliche Fehler in den Parametern, so könnte es sein, daß man einen Fall vergessen hat.

Was bei system() gefährlich ist, gilt natürlich auch für verwandte Funktionen wie popen(), oder Aufrufe wie execlp() und execvp(), die die PATH Variable berücksichtigen.

Indirektes Ausführen von Befehlen

Um die Ergonomie eines Programmes zu verbessern, ist es eine einfache Möglichkeit, ein Program konfigurierbar zu machen. Z.B. mit Hilfe von Makros. Um Variablen oder generische Muster handzuhaben, gibt es eine Funktion names wordexp(). Man muß mit ihr sehr vorsichtig sein, da sie einen String wie $(commande) als externen Befehl ausführt. Es ist genug, den String "$(/bin/sh)" einzugeben, um eine Set-UID Shell zu erhalten. Um so etwas zu vermeiden, hat wordexp() ein Attribut namens WRDE_NOCMD, das die Sequenz $( ) deaktiviert.

Beim Aufruf von externen Kommandos muß man vorsichtig sein, damit man kein Kommando erwischt, das eine Escapesequenz zum Öffnen einer Shell zuläßt. Wie z.B vi :!command. Es ist schwierig, alle aufzuführen. Einige Applikationen sind offensichtlich (Texteditoren, Dateimanger...) andere sind schwieriger zu entdecken, wie wir es bei /bin/mail gesehen haben.

Zusammenfassung

Dieser Artikel hat folgende Aspekte erläutert :

Im nächsten Artikel werden wir über Speicher sprechen. Wie er organisiert ist, Funktionsaufrufe .... und dann werden wir zu buffer overflows kommen und zeigen, wie man dieses Sicherheitsloch mit shellcode ausnutzen kann.