Icinga 2.8.2 behebt mehrere Sicherheitslücken

Manchmal stösst man bei der Arbeit als Site Reliability Engineer auf Situationen, die so nicht sein sollten. So war es auch Mitte Januar 2018, als ich für ein privates Projekt an einer Icinga2-Installation arbeitete. Ich versuchte über die Konsole eine interne Funktion von einem CheckCommand-Objekt aus der Icinga2-Skriptsprache aufzurufen:

get_check_command("dummy").execute(null, null, null, false)

Daraufhin stürzte der Icinga2-Daemon wegen der Dereferenzierung eines NULL-Zeigers ab. Nach kurzer Lektüre des Codes war klar, dass nur wenige per Skript erreichbare Funktionen ihre Parameter mit der nötigen Sorgfalt prüfen.

Mein Interesse war geweckt und so suchte ich nach einer Möglichkeit, diesen Absturz über die Icinga-API herbeizuführen. Ohne „console“-Berechtigung war dies jedoch nicht direkt möglich.

Allerdings fand ich dann eine Reihe von anderen Problemen, die allesamt übers Netzwerk von extern ausnutzbar sind und als „Denial of Service“ klassifiziert werden können. Beispielsweise wurden bei mehreren API-Endpunkten die Parameter ohne vorgängige Kontrolle dereferenziert. Folgender Code benötigt keine Authentifizierung und führt bei allen Icinga2-Versionen vor 2.8.2 zu einem Absturz:

msg=$(jq --null-input --compact-output --raw-output '{
  "id": "foo",
  "jsonrpc": "2.0",
  "method": "event::Heartbeat",
  "params": null
}') && \
{ sleep 1; echo "${#msg}:${msg},"; sleep 3; } | \
openssl s_client -connect 192.0.2.1:5665

Weitere Beispiele dieser Art sind in meinem technischen Bericht zu finden. Einige davon benötigen ein Client-Zertifikat, andere setzen eine bestimmte Konstellation der Master und Clients voraus.

Daneben fanden sich mehrere Szenarien, in denen auf dem Server unbegrenzt Ressourcen, vor allem Arbeitsspeicher, konsumiert werden. Mit diesem Python-Code beispielsweise wird eine grosse Anzahl TCP-Verbindungen geöffnet, die dann unbenutzt liegen bleiben und wovon jede im Icinga2-Daemon einen Thread mit dazugehörigem Stack belegt:

import resource
import socket
import time

resource.setrlimit(resource.RLIMIT_NOFILE, (131072, 131072))

conn = []

while True:
    try:
        conn.append(socket.create_connection(('192.0.2.1', 5665)))
    except BaseException as err:
        print(err)
        break

print(len(conn))

while True:
    time.sleep(1)

Je nach verfügbaren Ressourcen auf dem Server wird der Icinga2-Daemon schon nach kurzer Zeit vom OOM-Killer im Linux-Kernel beendet.

Beim Lesen des Codes stellte sich auch heraus, dass Passwörter mit std::string::operator== verglichen werden. Diese Funktion, wie auch strcmp, bricht den Vergleich ab, sobald ein Unterschied vorliegt. Ein Angreifer kann die Zeitunterschiede messen und damit eine Seitenkanalattacke durchführen.

Auf meiner privaten Webseite habe ich einen technischen Bericht mit weiteren Details zu den von mir gefundenen Sicherheitsproblemen in Icinga 2.8.1 und älter veröffentlicht. Version 2.8.2 von Icinga behebt diese und weitere. VSHN hat Icinga2 auf sämtlichen relevanten Kundensystemen bereits aktualisiert.