Konkrete Kritik an CVSS4
Marc Ruef
Im ersten Teil dieser Serie haben wir das Automatisieren von nmap mittels NSE-Skripten vorgestellt. Dabei haben wir kurz die Idee, das Konzept und die Möglichkeiten umrissen. Zusätzlich haben wir die grundlegende Struktur eines mit NSE implementierten Tests illustriert.
Im zweiten Teil wollen wir nun ein simples Skript schreiben. Dieses stützt sich in erster Linie auf den regulären Resultaten eines Portscans mit nmap ab. Wir bezeichnen ein solches Plugin als derivativ, da es eine Ableitung von grundlegenden Resultaten durchführt und damit keine eigenen Zugriffe über das Netzwerk umsetzen wird.
NSE-Skripte werden bei einer nmap-Installation standardmässig im Verzeichnis scripts
abgelegt. Dort finden sich sodann auch die Standardskripte der jeweiligen Distribution. Zum Beispiel werden einige Skripte für die Auswertung von FTP-Server mitgeliefert:
Ein NSE-Skript benutzt in der Regel die Dateierweiterung nse
. Neue Skripte können in dieser Art ebenfalls ins script
-Verzeichnis abgelegt werden. Oder sie können in einem Unterverzeichnis oder an anderer Stelle gespeichert werden. Ein Miteinbeziehen bei einem Script-Scan erfordert in diesem Fall die zusätzliche Angabe des jeweiligen Standorts.
Der Aufbau eines NSE-Skripts gestaltet sich immer gleich: Im Kopf einer NSE-Datei finden sich einige deskriptive Felder, die grundlegende Informationen zum Plugin bereitstellen: Im Feld description
wird beispielsweise eine Beschreibung des Tests festgehalten, in categories
findet eine Zuweisung der Kategorien statt (siehe Teil 1), in dependencies
werden Abhängigkeiten von anderen Skripten definiert, in author
wird der Autor spezifiziert und in license
die Lizenzbestimmungen festgehalten. Eine minimale Angabe gestaltet sich beispielsweise wie folgt:
description = [[ Dieses minimale Skript identifiziert Webdienste ]]author = “Marc Ruef” license = “(c) 2010 by scip AG” categories = {“default”, “safe”}
In der rule
-Sektion wird nun definiert, unter welchen Bedingungen das Skript ausgeführt wird. Hierbei kann entweder eine portrule
(für Tests von Ports) oder eine hostrule
(für Tests von Hosts) verwendet werden. Eine Ableitung von einem Portscan fokussiert sich in erster Linie auf die portrule
(das Prinzip der hostrule
werden wir später besprechen). Innerhalb von ihr wird vordefiniert, bei welchem Zustand eines Zielports eine Weiterverarbeitung des Skripts initiiert und damit schlussendlich die Schwachstelle gemeldet werden soll.
Eine portrule
kann unterschiedlich implementiert werden. Die einfachste und von vielen bevorzugte Methode besteht unter Zuhilfenahme der Library shortport und der entsprechenden Methoden. Nachfolgend wird zuerst die Library mittels require
inkludiert und danach die Methode port_or_service()
auf das Objekt shortport
angewendet. Die genannte Methode erwartet drei verschiedene Argumente, wobei diese jeweils als Table (in anderen Programmiersprachen wird dieser Datentyp Array genannt) übergeben werden müssen. Das erste Argument definiert die Portnummern, das zweite die Protokollnamen und das dritte das Transportprotokoll. Es scheint offensichtlich, dass in diesem Beispiel eine portrule
für Web-Dienste umgesetzt wird.
require “shortport”portrule = shortport.port_or_service({80, 443}, {“http”, “https”}, {“tcp”})
In einem weiteren Schritt kann nun in action
die Weiterverarbeitung – in der NSE-Dokumentation als Mechanism bezeichnet – spezifiziert werden. Diese Funktion, sie kann als Main-Funktion verstanden werden, wird dann ausgeführt, wenn portrule
gleich true
ist. Also dann, wenn ein Webserver auf dem Zielport vermutet (anhand der Portnummern) oder identifiziert (anhand der Protokollnamen) wird. In diesem Fall geben wir nur ganz allgemein mittels return
eine Zeichenkette zurück, die explizit darüber informiert, dass ein Webserver gefunden wurde und mit port.number
geben wir die entsprechende Portnummer an.
action = function(host, port) return "Webserver gefunden auf Port " .. port.number end
Es wird nun mit dem Schalter --script="scripts\labs\http-detection_simple.nse"
dieses einzelne Skript ausgeführt. Und wie zu sehen ist, wird als Detail des offenen Ports zusätzlich die explizite Ausgabe ausgewiesen:
C:\Dokumente und Einstellungen\maru>nmap -sS -sV —script=“scripts\labs\http-detection_simple.nse” www.scip.ch -p 80Starting Nmap 5.21 ( http://nmap.org ) at 2010-03-24 12:37 Westeuropõische Normalzeit NSE: Script Scanning completed. Nmap scan report for www.scip.ch (192.168.0.10) Host is up (0.0020s latency). rDNS record for 192.168.0.10: www.scip.ch PORT STATE SERVICE VERSION 80/tcp open http Apache httpd |_http-detection_simple: Webserver gefunden auf Port 80
Service detection performed. Please report any incorrect results at http://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 7.59 seconds
Dies war alles relativ simpel und erfüllt zugegebenermassen zur Standardausgabe von nmap keinen zusätzlichen Zweck. Sodann soll die Funktion erweitert werden, um ein Mehr an Informationen bereitstellen zu können. So wird mit common_http_ports
eine Table angelegt, in der die Standardports für Webserver abgelegt sind. Indem nun diese Table mittels einer for
-Schleife durchlaufen wird, kann nun explizit ermittelt werden, ob der angenbotene Webserver auf einem Standardport horcht oder nicht. Der Zustand dessen wird als String (Yes|No)
in der lokalen Variable common_port_found
abgelegt. Zum Schluss wird in der lokalen Variable output
das Resultat generiert und zurückgeliefert.
action = function(host, port) local common_http_ports = {80, 443} local common_port_found = “Nein” for i=1, #common_http_ports, 1 do if port.number == common_http_ports[i] then common_port_found = “Ja” break end end local output = “Webserver gefunden:\n” output = output .. “Port:\t\t” .. port.number .. “\n” output = output .. “Standard:\t” .. common_port_found .. “\n” return output end
Es lassen sich in einem weiteren Schritt zusätzliche eigene Funktionen definieren. Diese sind wie bei anderen funktionalen Programmiersprachen über das Schlüsselwort function
definiert, können aus anderen Subroutinen aufgerufen und ihre Rückgabewerte weiterverarbeitet werden. Dedizierte Prozeduren lassen sich ebenfalls in externe Dateien ablegen. Möchte man diese mittels require
einbinden, sieht nmap das Verzeichnis nselib
vor. In diesem finden sich verschiedene Standard-Bibliotheken, die den Umgang mit nmap-Scans erleichtern.
Wird ein Skript-Ausgeführt, das einen Syntaxfehler aufweist, wird die Ausführung dessen abgebrochen. Die durch den Lua-Interpreter ausgegebene Fehlermeldung wird ersichtlich, wenn nmap mit dem Debugging-Schalter -d
ausgeführt wurde. In der Standardbilbiothek nmap
wird mit den Methoden verbosity()
und debugging()
die Möglichkeit geboten, die Aktivierung von Verbose- und Debugging-Parametern zu erkennen und auf diese einzugehen. Vor allem, aber nicht nur, lohnt sich bei der Entwicklung von NSE-Skripten auf diese Möglichkeit sowie auf print_debug()
zurückzugreifen, um je nachdem interne Details der Verarbeitung anzeigen zu lassen.
Im dritten Teil werden wir die Entwicklung dieses NSE-Skripts vorantreiben. Dabei werden wir die Möglichkeiten der Version Detection von nmap ausschöpfen, um anhand der ermittelten Fingerprints komplexere Ableitungen umzusetzen. Mitunter werden wir Pattern-Matching und reguläre Ausdrücke miteinbeziehen, um verwundbare Versionen erkennen und dokumentieren zu können.
Unsere Spezialisten kontaktieren Sie gern!
Marc Ruef
Marc Ruef
Marc Ruef
Marc Ruef
Unsere Spezialisten kontaktieren Sie gern!