Konkrete Kritik an CVSS4
Marc Ruef
In Teil 1 haben wir das grundlegende Konzept von NSE-Skripting vorgestellt und in Teil 2 ein simples Plugin zur Ableitung von Portzuständen entwickelt. Im dritten Teil soll die Entwicklung vorangetrieben werden. So werden wir ein komplexeres Skript umsetzen, welches die Möglichkeiten des Application Fingerprinting resultierend aus dem Aufruf mit dem Schalter -sV
umfänglich ausschöpfen wird.
Wird nmap mit dem Schalter -sV
aufgerufen, wird das sogenannte Service and Version Fingerprinting umgesetzt. Als erstes wird bei einem offenen Ports mittels Application Mapping versucht zu ermitteln, was für ein Transportprotokoll angeboten wird. Es ist schliesslich wichtig für weiterführende Zugriffe zu wissen, ob ein Webserver mit HTTP oder ein Mailserver mit SMTP eingesetzt wird. Im gleichen bzw. im zweiten Schritt wird versucht mittels Application Fingerprinting das eingesetzte Produkt (Name und Versionsnummer) zu erkennen. Eine solche Identifikation wird erforderlich, um produktspezifische Auswertungen und Angriffe voranzutreiben.
Wird ein Scan mit aktivierter Version Detection durchgeführt, kann innerhalb von NSE auf die damit zusammengetragenen Informationen zurückgegriffen werden. Diese sind in port.version.*
abgelegt und können einzeln angesteuert werden. Folgende Tabelle verdeutlicht, dass sich ein analysierter SSH-Dienst umfassend auswerten lässt:
Variable | Inhalt | Beispiel |
---|---|---|
port.version.name | Name des Anwendungsprotokolls | ssh |
port.version.product | Name des Produkts | OpenSSH |
port.version.version | Versionsnummer des Produkts | 4.7 |
port.version.extrainfo | Zusätzliche Informationen | (protocol 1.99) |
Im zweiten Teil haben wir die shortport
-Library genutzt, um eine möglichst simple Portrule zu definieren. Doch es gibt Situationen, nämlich wenn komplexe Ausdrücke verwendet werden sollen, in denen eine manuelle Portrule umgesetzt werden muss. Durch eine entsprechende Funktion soll in portrule
ein wahrer Wert abgelegt werden. Liefert portrule
entweder nil
, false
oder eine leere Zeichenkette zurück, dann wird action
nicht ausgeführt. In allen anderen Fällen schon. Es liegt nun also an uns, anhand des komplexen Ausdrucks die effektive Skript-Ausführung einzuleiten.
Nachfolgende Funktion versucht zu erkennen, ob in port.service
als erkanntes Anwendungsprotokoll smtp
definiert wurde. Zusätzlich wird in port.version.product
überprüft, ob die Zeichenkette sendmail
im Rahmen der Version Detection ermittelt wurde. Dadurch kann eindeutig identifiziert werden, ob auf dem Zielport eine Sendmail-Implementierung vorhanden ist. Dies geschieht durch die uns schon bekannte Funktion string.match()
, welche mit Pattern und regulären Ausdrücken umgehen kann.
portrule = function(host, port) if port.service == "smtp" and (port.version.product ~= nil and string.match(port.version.product, "Sendmail")) then return true else return false end end
Obschon diese Determinierung relativ simpel erscheint, können mit ihr gewisse Komplikationen einhergehen. Doch bevor darauf eingegangen werden soll, soll das grundlegende Prinzip der Version Detection besprochen werden. Nmap nutzt die Datei nmap-service-probes
, um unterschiedliche Anfragen an einen Zielport zu schicken. Anhand eines regulären Ausdrucks wird die Rückantwort untersucht, um die gegebene Implementierung zu erkennen. Nachfolgende Zeilen werden beispielsweise genutzt, um Sendmail auf einem SMTP-Port zu erkennen:
match smtp m|^220[\s-](\S+) E?SMTP Sendmail (\d[^; ]+)| p/Sendmail/ h/$1/ v/$2/ o/Unix/
match smtp m|^220[\s-](\S+) E?SMTP Sendmail AIX([\d.]+)/(\d[^; ]+)| p/Sendmail/ h/$1/ v/$3/ i/AIX $2/ o/AIX/
match smtp m|^220[\s-](\S+) E?SMTP Sendmail AIX([\d.]+)/UCB (\d[^; ]+);| p/Sendmail/ h/$1/ v/$3/ i/AIX $2/ o/AIX/
match smtp m|^220[\s-](\S+) E?SMTP Sendmail \(#\)Sendmail version (\d[^; ]+) - Revision ([\d.]+) | p/Sendmail/ h/$1/ v/$2 rev $3/ o/HP-UX/
match smtp m|^220[\s-](\S+) E?SMTP Sendmail
\(#\)Sendmail version (\d[^; ]+) - Revision ([\d.]+):: HP-UX([\d.]+)| p/Sendmail/ h/$1/ v/$2 rev $3/ o/HP-UX $4/
Wie im zuvor gezeigten Code-Beispiel kann nun direkt in port.version.product nach dem Produktnamen Sendmail
gesucht werden. Eine solche Prüfung kann jedoch versehentlich fehlschlagen, wenn die zur Identifikation eingesetzte Schreibweise nicht berücksichtigt wird. Ein typisches Beispiel der inkonsistenten Schreibweise ist der Produktenamen VMware, der von vielen Leuten als Vmware (das M ist klein) geschrieben wird. In letztgenannten Fall würde eine Prüfung mit if port.version.product == "VMware"
fehlschlagen (gleiches Problem ist zum Beisipel auch bei JetDirect
zu beobachten). Aus diesem Grund kann es wichtig sein, dass die zuvor eingeführte Prüfung normalisiert wird, indem die Gross-/Kleinschreibung vereinheitlicht wird. Durch die Funktion string.lower()
kann eine Zeichenkette komplett kleingeschrieben werden. Dadurch kann dann eine Prüfung gegenüber der durchgängig kleingeschriebenen Schreibweise sendmail
– also case-insensitive – stattfinden:
string.match(string.lower(port.version.product), "sendmail"))
Eine zusätzliche Schwierigkeit der Identifikation kann sein, dass der Produktenamen nicht eindeutig ausfällt. Zum Beispiel dann, wenn ein Hersteller den Namen einer Produkteserie anpasst oder verschiedene Produkte in einer Produktpalette zusammenfasst. Ein typisches Beispiel ist ISS RealSecure. Die kommerzielle Lösung bietet verschiedene Intrusion Detection-Komponenten an. Nachfolgend werden die Identifikationsmuster von nmap abgedruckt:
match iss-realsecure m|^\0\0\0.\x08\x01\x03\x01\0.\x02\0\0..\0\0.\0\0\0..\0\0\x80\x04..\0.\0\xa0|s p/ISS RealSecure IDS/ o/Windows/ match iss-realsecure m|^\0\0\0.\x08\x01\x04\x01\0..\0\0..\0\0.\0\0\0..\0\0\x80\x04..\0.\0\xa0\0\0|s p/ISS RealSecure IDS ServerSensor/ v/6.0 - 7.0/ o/Windows/
Es ist zu sehen, dass die Identifikation einer RealSecure-Installation als solche durchaus möglich ist. Schliesslich benutzt die Version Detection stets die Zeichenkette ISS RealSecure IDS
in der Ausgabe. Von da unterscheiden sich jedoch die jeweiligen Implementationen und ihre Nennungen. Im ersten Fall wird generisch der Hinweis mit ISS RealSecure IDS
auf Windows
dargestellt. Im zweiten Fall wird jedoch zusätzlich die Versionsnummer mit IDS ServerSensor ServerSensor v6.0 - 7.0
, ebenfalls auf Windows
, eingeschränkt.
In letztgenanntem Fall findet also eine struktere Eingrenzung statt. Diese soll nun, soll denn eine RealSecure-Installation im Allgemeinen erkannt werden, wieder abstrahiert werden. Zu diesem Zweck kann innerhalb von string.match()
mit regulären Ausdrücken gearbeitet werden. Mit dem Zeichen ^
wird angegeben, dass die nachfolgende Zeichenkette zu Beginn gefunden werden muss. Da diese Zeichenkette mit beiden Identifikationen übereinstimmt, lässt sich damit die generische Identifikation des Produkts umsetzen.
string.match(port.version.product, "^ISS RealSecure")
Die in den vorangehenden Teilen dieser Dokumentationsserie sowie dem an dieser Stelle diskutierten Thema erlauben nun das Umsetzen von mehrstufigen Skripten. Ein Skript kann als mehrstufig verstanden werden, wenn es eine zusätzliche Applikationslogik enthält, die eine Identifikation einer Gegebenheit bzw. Schwachstelle auf unterschiedlichen Ebenen durchführen kann.
Die nun gezeigte Erweiterung bietet eine dreistufige Lösung, um einen SMTP-Mailserver als solchen zu identifizieren. Als erstes wird versucht anhand der Service Detection in port.version.product
die Zeichenkette Sendmail
zu finden. Ist dies der Fall, liefert die Portrule die Zeichenkette Application Fingerprinting
zurück. Die Genauigkeit der Identifikation ist damit sehr hoch. Kann sie jedoch nicht umgesetzt werden, wird als zweites versucht den Zielport mittels port.service
als smtp
zu identifizieren. Ist dies erfolgreich, wird die Zeichenkette Application Mapping
zurückgeliefert. Und versagt auch dieser Test, wird als dritte und letzte Möglichkeit versucht den Zielport anhand seiner Nummer in port.number
mit 25
als Standardport zu ermitteln. In diesem Fall wird die Zeichenkette Portscan
zurückgegeben. Kann keine der drei Identifikationsebenen einen Erfolg verbuchen, schickt die Funktion den Wert false
zurück.
portrule = function(host, port) if string.match(port.version.product, "Sendmail") then return "Application Fingerprinting" elseif port.service "smtp" then return "Application Mapping" elseif port.number 25 then return "Portscan" else return false end end
Die weiterführende Ausführung des Skripts kann sodann von den unterschiedlichen Rückgabewerten, den dabei zugrundeliegenden Identifikationsmethoden und der damit einhergehenden Genauigkeit abhängig gemacht werden. Zum Beispiel liesse sich in der Skript-Ausgabe ein Wert für Accuracy bzw. Confidence ausgeben. Der Benutzer des Skripts kann anhand dessen ableiten, wie genau und zuverlässig der Test funktioniert hat. Im weitesten Sinn versucht zum Beispiel der kommerzielle Vulnerability Scanner Qualys mit einer zweidimensionalen Risikoangabe das gleiche Ziel zu erreichen (PRACTICE
bezeichnet potentielle Schwachstellen und VULN
identifiziert ausgenutzte Schwachstellen).
Im vierten Teil werden wir erstmalig besprechen, wie sich eigene Netzwerkzugriffe realisieren lassen. Damit muss sich nicht mehr nur von den standardmässig durch nmap zusammengetragenen Informationen (Portstatus und Version Detection) abhängig gemacht werden. In ergänzender und alternativer Weise können effektive Anfragen an das Zielsystem geschickt und die Rückantworten ausgewertet werden. Damit wird sich ein voll funktionstüchtiger Vulnerability Scanner realisieren lassen.
Unsere Spezialisten kontaktieren Sie gern!
Marc Ruef
Marc Ruef
Marc Ruef
Marc Ruef
Unsere Spezialisten kontaktieren Sie gern!