Schwachstelle im Mailserver von OpenBSD

Qualys hat eine Schwachstelle in „OpenSMTPD“ entdeckt, dem Mailserver von OpenBSD. Es handelt sich dabei um eine Out-of-Bounds-Read-Lücke, die im Dezember 2015 eingeführt wurde (Commit 80c6a60c, „when peer outputs a multi-line response …“). Sie ist aus der Ferne ausnutzbar und ermöglicht die Ausführung beliebiger Shell-Befehle: entweder, nach Mai 2018 (Commit a8e22235, „switch smtpd to new grammar“), als Root; oder als beliebiger Nicht-Root-Benutzer vor dem Mai 2018.

Da sich diese Schwachstelle im clientseitigen Code von OpenSMTPD befindet (der E-Mails an entfernte SMTP-Server zustellt),  gilt es zwei verschiedene Szenarien zu beachten:

Clientseitige Ausnutzung: Diese Schwachstelle ist in der Standardkonfiguration von OpenSMTPD (und damit OpenBSD) aus der Ferne ausnutzbar. OpenSMTPD lauscht zwar standardmäßig nur auf localhost, akzeptiert aber E-Mails von lokalen Benutzern und liefert sie an Remote-Server aus. Wenn ein solcher Remote-Server von einem Angreifer kontrolliert wird (sei es, weil der Server bösartig oder kompromittiert ist, oder aufgrund eines Man-in-the-Middle-, DNS- oder BGP-Angriffs – SMTP ist standardmäßig nicht TLS-verschlüsselt), dann kann der Angreifer auf der anfälligen OpenSMTPD-Installation beliebige Shell-Befehle ausführen.

Serverseitige Ausnutzung: Zunächst muss sich der Angreifer mit dem OpenSMTPD-Server verbinden (der externe E-Mails akzeptiert) und eine E-Mail senden, die einen Bounce erzeugt. Wenn sich OpenSMTPD dann wieder mit dem E-Mail-Server verbindet, um diese Bounce-Message zuzustellen, kann der Angreifer die clientseitige Schwachstelle in OpenSMTPD ausnutzen. Damit seine Shell-Befehle ausgeführt werden, muss der Angreifer schließlich (aktueller Status-Quo von Qualys) OpenSMTPD zum Absturz bringen und warten, bis das Programm neu gestartet wird (entweder manuell durch einen Administrator oder automatisch durch ein System-Update oder einen System-Neustart). Die Experten von Qualys haben einen einfachen Exploit für diese Schwachstelle entwickelt und erfolgreich gegen OpenBSD 6.6 (aktuelle Version), OpenBSD 5.9 (der erste anfällige Release), Debian 10 (stabil), Debian 11 (in der Testphase) sowie Fedora 31 getestet. Auf Wunsch von OpenBSD und um den OpenSMTPD-Nutzern die Möglichkeit zu geben, ihre Systeme zu patchen, hält Qualys Einzelheiten zur Ausnutzung und den Code bis Mittwoch, 26. Februar 2020 zurück.

Nachtrag: Wir haben unseren Exploit gegen die jüngsten Änderungen in OpenSMTPD 6.6.3p1 getestet, mit folgenden Resultaten: Wenn die „mbox“-Methode zur lokalen Auslieferung genutzt wird (in OpenBSD –current voreingestellt), ist die Ausführung beliebiger Befehle als Root weiterhin möglich. Andernfalls (wenn beispielsweise die „maildir“-Methode verwendet wird), ist die Ausführung beliebiger Befehle als jeder Nicht-Root-Benutzer möglich.

Analyse

SMTP-Clients verbinden sich mit SMTP-Servern und senden Befehle wie EHLO, MAIL FROM und RCPT TO. SMTP-Server liefern dann entweder einzeilige oder mehrzeilige Antworten zurück:  Die ersten Zeilen beginnen mit einem dreistelligen Code und einem Bindestrich (‚-‚), gefolgt von einem optionalen Text (zum Beispiel „250-ENHANCEDSTATUSCODES“); – die letzte Zeile beginnt mit dem gleichen dreistelligen Code, gefolgt von einem optionalen Leerzeichen (‚ ‚) und Text (zum Beispiel „250 HELP“). Im clientseitigen Code von OpenSMTPD werden diese mehrzeiligen Antworten von der Funktion mta_io() geparst:

—————————————————————————

1098 static void

1099 mta_io(struct io *io, int evt, void *arg)

1100 {

….

1133         case IO_DATAIN:

1134             nextline:

1135                 line = io_getline(s->io, &len);….

1146                 if ((error = parse_smtp_response(line, len, &msg, &cont))) {

————————————————————————— –

Die ersten Zeilen (when line[3] == ‚-‚) werden zu einem 2 KB replybuf verknüpft:

—————————————————————————

1177                 if (cont) {

1178                         if (s->replybuf[0] == ‚\0‘)

1179                                 (void)strlcat(s->replybuf, line, sizeof s->replybuf);

1180                         else {

1181                                 line = line + 4;

….

1187                                         (void)strlcat(s->replybuf, line, sizeof s->replybuf);

1188                         }

1189                         goto nextline;

1190                 }

—————————————————————————

Die letzte Zeile (when line[3] != ‚-‚) wird ebenfalls zu einem replybuf verknüpft:

—————————————————————————

1195                 if (s->replybuf[0] != ‚\0‘) {

1196                         p = line + 4;

….

1201                         if (strlcat(s->replybuf, p, sizeof s->replybuf) >= sizeof s->replybuf)

—————————————————————————

Das Problem: Wenn auf den dreistelligen Code der letzten Zeile nicht das optionale Leerzeichen und Text folgen, zeigt p (in Zeile 1196) auf das erste Zeichen *nach* dem Terminator ‚\0‘ der Zeile (der den Terminator ‚\n‘ in iobuf_getline() ersetzt hat), und dieser Out-of-Bounds-String wird zu einem replybuf (in Zeile 1201) verknüpft.

#Qualys