Passwörter
Die Sicherheit eines Linux-Systems steht und fällt mit der
Qualität der Passwörter. Lassen sie sich leicht erraten oder
sind sie zu kurz, lassen sie sich mit nur geringem Aufwand
knacken.
Ein Beispiel für ein Crack-Programm ist john. Leichte
Passwörter werden in nur wenigen Sekunden erraten.
Mit diesem
Programm sollte von Zeit zu Zeit die Qualität der Passwörter
abgeklopft werden.
Funktionsweise des aktuellen Linux-Passwort-Systems
- Userinformation werden in /etc/passwd und Passwörter
in der Datei /etc/shadow gespeichert.
- Das Passwort ist 13 Zeichen lang, wobei die ersten beiden Zeichen
das sogenannte Salt bilden und die folgenden 11 Zeichen das
eigentliche verschlüsselte Passwort. Das Passwort wird Wie folgt
erzeugt:
- Aktueller Zeitpunkt dient als Seed
- Das Passwort wird auf 8 Zeichen abgeschnitten
- Die jeweils ersten 7 Bit aller Zeichen ergeben zusammen einen 56
Bit Key
- Mit diesem Key wird ein 64 Bit Block aus Nullen (00000000) mit
dem Seed als Startwert verschlüsselt
- Mit diesem Ergebnis als neuen Schlüssel wird der Vorgang 25
mal wiederholt.
- Dieses Ergebnis wird auf 11 Zeichen gekürzt und zusammen
mit dem Salt in der Datei /etc/shadow gespeichert
- Das Passwort kann durch diesen DES-Hash zwar leicht erzeugt, aber nur
extrem schwer rekonstruiert werden
- Im Falle einer Authentifizierung wird das aktuelle Passwort mit
diesem Hash verschlüsselt und anschließend mit dem
verschlüsselten Passwort verglichen. Fällt der Vergleich
positiv aus, so ist der Benutzer authentifiziert.
Pluggable Authentication Module (PAM)
Noch besser ist allerdings, das Standard-Passwort-System durch PAM zu
ersetzen. Dies ist bei SuSE-Linux ab der Version 6.2 integriert, benötigt
allerdings noch etwas Feintuning. Eine Nachrüstung ist schwierig,
da sämtliche Dienste, die Authentifizierung verwenden, neu
compiliert werden müssen.
Funktionsweise PAM
- Ein Benutzer möchte sich bei einem Dienst authentifizieren
- Der Dienst leitet die Anfrage an PAM weiter
- PAM authentifiziert den Benutzer, oder auch nicht
- PAM liefert die Freigabe / Ablehnung an den Dienst zurück
Konfiguration von PAM
PAM lässt sich auf zwei Arten konfigurieren, hier wird allerdings
nur die Verzeichnismethode beschrieben, da sie verbreiteter ist und
flexibler.
Das Konfiguration-Verzeichnis ist /etc/pam.d. Hier befindet
sich für jeden Dienst genau eine Konfigurationsdatei,
z.B. passwd.
Der Aufbau ist folgender:
module-type control-flag module-path arguments
module-type control-flag module-path arguments
othermodule-type control-flag module-path arguments
othermodule-type control-flag module-path arguments
module-type (vereinfacht)
- auth: Benutzerauthentifizierung. Stellt sicher, dass
der Benutzer derjenige ist, der er vorgibt zu sein
- account:Feinere
Account-Einstellungen. Z.B. root-Login nur an realer Konsole oder zu
bestimmten Zeiten
- session: Erledigt Dinge vor Einloggen und nach
Ausloggen.
- password: Ändert Passwörter
control-flag (vereinfacht)
- required: Dieses Modul muss zwingend erfolgreich
durchlaufen werden. Folgende Module desselben Typs müssen
verarbeitet werden.
- requisite: Im Fehlerfalle wird nach Abarbeiten
dieses Moduls in den aufrufenden Dienst zurückgekehrt,
andernfalls werden weitere Module desselben Typs ausgeführt.
- sufficient: Bei Erfolg und keinen verherigen
Fehlerfalle, wird in den Dienst zurückgekehrt. Sonst
wird das nächste Modul ausgeführt, Fehler ist aber bedeutungslos.
- optional: Modul fällt nur ins Gewicht, wenn
ansonsten kein anderes Modul positiv durchlaufen wird.
arguments (unvollständig)
- debug: Zusätzliche Informationen in Syslog schreiben
- use_first_pass: Benutze Password von vorherigem
Modul
- md5: sichereren MD5-Hash zum Verschlüsseln von
Passwörtern verwenden
- ...
Ob ein Programm PAM-Funktionalität besitzt. lässt sich auf
eine unschöne, aber einfache Art mit ldd appname
herausfinden. Taucht libpam.so in der Liste auf, so benutzt die
Applikation PAM zur Authentifizierung.
Leider ist PAM bei der Standardinstallation etwas zu einfach
konfiguriert und bietet eine Fülle an
Schlupflöchern. Folgende Anpassungen sollten durchgeführt
werden:
passwd
password required pam_cracklib.so minlen=8
password required pam_pwdb.so use_authtok md5
Hiermit wird die minimale Passwortlänge auf 8 Zeichen gesetzt und
der MD5-Hash aktiviert. Nun sind lange Passwörter kein Problem
mehr. Alle neu eingegebenen Passwörter werden md5-codiert. Mit
Hilfe des cracklib-Moduls lassen sich neue Passwörter sofort bei
Eingabe auf Unsicherheiten checken, wie z.B. Benutzername,
emanreztuneB, einfache lexikalische Wörter, Tastaturzeichenfolgen
wie QWERTZ, Ähnlichkeit dem alten Passwort,
Groß/Klein-Schreibung vorhanden, ...
other
auth required /lib/security/pam_warn.so
auth required /lib/security/pam_deny.so
account required /lib/security/pam_warn.so
account required /lib/security/pam_deny.so
password required /lib/security/pam_warn.so
password required /lib/security/pam_deny.so
session required /lib/security/pam_warn.so
session required /lib/security/pam_deny.so
Findet sich für eine Applikation keine passende Konfiguration,
wird other gewählt. Dieses gewährt im Normalfalle
jedem den Zutritt. Durch Änderung wie oben, scheitern diese mit
Vermerk im Syslog.
Vorsicht! Falsche Konfiguration kann das System total
unbrauchbar machen!
Buffer Overflow
Eine der beliebtesten Einbruchsmethoden ist der sogenannte Buffer
Overflow. Programme, die sich diese Buffer Overflows zu Nutze
machen, nennt man Exploits. Davon wiederum gibt es Varianten,
die nur lokal auf einem System arbeiten, allerdings kommen auch viele
Remote-Exploits in der freien Wildbahn vor.
Der Ablauf ist immer derselbe:
Ein unsauber programmierter Dienst benutzt folgenden
Unterprogrammaufruf:
void parse(char *arg) {
char param[1024];
int localdata;
strcpy(param,arg);
.../...
return;
}
Dieser grobe Schnitzer erlaubt es, dass der Inhalt von arg den
Stack nach oben hin vollschreiben kann. Hierbei wird die
Rücksprungadresse der aufrufenden Routine überschrieben
und eigener Exploit-Code kann auf dem Stack ausgeführt
werden. (Zeichnung)
Der Stack wird so manipuliert, dass er folgendes Aussehen hat:
bottom of DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF top of
memory 89ABCDEF0123456789AB CDEF 0123 4567 memory
buffer sfp ret *arg
<------ [JJSSSSSSSSSSSSSSCCss][ssss][0xD8][0x01]
^|^ ^| |
|||_____________||____________| (1)
(2) ||_____________||
|______________| (3)
top of bottom of
stack stack
In ret wird ein Zeiger auf die Funktion abgelegt,
welche parse ausführt.
Ein passendes Assembler-Beispiel könnte etwa so aussehen:
jmp 0x26 # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2b # 5 bytes
.string \"/bin/sh\" # 8 bytes
Dieses Assembler-Programm holt sich zuerst den Offset für den
Shell-String und fügt an dessen Ende eine "\0" an, gefolgt von
einem Zeiger auf diesen String, dieser wiederum gefolgt von einem
NULL-Zeiger. Anschließend wird die Funktion execve mit
den entsprechenden Parametern in ebx, ecx und edx
aufgerufen. Dieser Auruf startet die angegebene Shell.
Nach dem Aufruf von execve terminiert das Programm mit einem
Rückgabecode von 0.
Beispiel: qpopper2.2 auf zwergpinselaffe.fh-wedel.de