Nmap NSE Hacking, Teil 6: Application Fingerprinting selber implementieren

Nmap NSE Hacking, Teil 6

Application Fingerprinting selber implementieren

Marc Ruef
by Marc Ruef
time to read: 11 minutes

In den letzten Teilen dieser Schriftenreihe haben wir die erweiterte Funktionalitäten von NSE/Lua kennengelernt. Besonders die beiden durch nmap mitgelieferte Bibliotheken zum Umgang mit Sockets und HTTP-Kommunikationen sind in den Mittelpunkt des Interesses gerückt. Durch sie lassen sich eigene Netzwerkzugriffe durchsetzen und damit die volle Funktionalität eines Vulnerability Scanners erreichen.

In diesem Teil wollen wir unser Verständnis für die Bibliothek http – die Grundlagen derer gelten als Voraussetzung für das Verständnis dieses Teils – vertiefen. Wir werden eine spezielle Form der Version Detection implementieren. Und zwar werden wir den HTTP-Header der Rückantworten eines Webservers analysieren, um anhand dieses HTTP-Fingerprinting die gegebene Implementierung ableiten zu können. Damit wird die Grundfunktionalität geschaffen, die wir in der angekündigten NSE-Portierung von httprecon erreichen wollen.

Mit httprecon wurde 2007 ein Projekt ins Leben gerufen, das sich um die Verbesserung von HTTP-Fingerprinting bemüht. Eine erster Implementierung einer automatisierten Software wurde für Windows umgesetzt. Hierbei kommen eine Reihe von Anfragen zum Tragen, bei denen die HTTP-Header der Rückantworten ausgewertet werden. Je höher die Fingerprint-Matches ausfallen, desto eher kann eine entsprechende Implementierung identifiziert werden. Nachfolgend ein typischer Scan mit httprecon für Windows:

httprecon für Windows

Die gesamte Funktionalität von httprecon in diesem Artikel nachzubilden, würde den Rahmen dessen bei weitem sprengen. Stattdessen soll einfach das Grundprinzip einer entsprechenden Implementierung vorgetragen werden.

Die nmap NSE-Portierung von httprecon kann auf der offiziellen Projektseite heruntergeladen werden.

Das Grundprinzip von httprecon basiert darauf, die einzelnen Eigenschaften des HTTP-Headers zu extrahieren und diese Fingerprints mit den in der Datenbank gespeicherten zu vergleichen. Die Datenbank wird durch kommagetrennte Dateien (CSV) bereitgestellt. Der Statustext für nicht existente Seiten (404 Not Found) wird beispielsweise wie folgt vorgelegt:

Apache;Not Found
Compaq HTTP Server;Ok
Microsoft IIS;Object Not Found
Netscape Enterprise Server;Not found
Oracle Application Server;Not Found

Hier sind zwei grundsätzliche Unterschiede in den jeweiligen Zeichenketten zu erkennen:

  1. Es werden unterschiedliche Zeichenketten verwendet:
    • Apache, Netscape und Oracle benutzen Not Found
    • Compaq benutzt Ok
    • Microsoft benutzt Object Not Found
  2. Die Gross-/Kleinschreibung fällt bei gleichem Text Not Found unterschiedlich aus:
    • Apache und Oracle schreiben Found gross
    • Netscape schreibt found klein

Wird nun der HTTP-Header eines Webservers so dissektiert, dass der Statuscode extrahiert werden kann, lässt er sich anhand dieser Merkmale vergleichen. Je nach zu beobachtenden Charakteristika kann damit der Webserver, in einigen Fällen gar bis auf die Version genau, ausgemacht werden. Die Kombination des Vergleichs unterschiedlicher Zugriffe und ihrer Merkmale erhöht die Genauigkeit dieser Determinierung.

Zu diesem Zweck muss als erstes eien Reaktion des Webservers provoziert werden. Dies kann durch unterschiedliche Mechanismen (z.B. verschiedene HTTP-Methoden, Ressourcen, Protokoll-Versionen und Header-Informationen) erfolgen. Nachfolgend wird dies mit der in der Funktion send_http_request() getan. Diese lässt neben der Definition des Zielsystems und -ports ebenfalls die zu nutzende HTTP-Methode sowie Ressource zu. Hierbei wird eine übliche GET-Anfrage für das Standarddokument durchgesetzt. Die Rückantwort wird dann weiterführend durch die Funktion identify_fingerprint(), welche sich an der Fingerprint-Datenbank im Verzeichnis scripts/httprecon/get_existing/ orientiert, analysiert.

response = send_http_request(host, port, "GET", "/")
if type(response) == "table" then
   identify_fingerprint(response, "scripts/httprecon/get_existing/")
end

Wie zu sehen ist, wird sodann die Rückantwort unterschiedlichen Tests unterzogen. Hauptsächlich wird dabei der Wert einer Header-Zeile verglichen. Ein typisches Beispiel ist der Banner, welcher in der Server-Zeile bereitgestellt wird. Zusätzlich werden aber ebenfalls Schreibweisen (Gross-/Kleinschreibung), Interpunktion (Komma, Komma+Abstand) sowie Reihenfolgen (z.B. Header-Order und Vary-Order) berücksichtigt.

function identify_fingerprint(res, db)
   find_match_in_db(db.."accept-range.fdb", get_header_value(get_header_line(res.rawheader, "Accept-Ranges", false)), 1)
   find_match_in_db(db.."banner.fdb", get_header_value(get_header_line(res.rawheader, "Server", false)), 3)
   find_match_in_db(db.."cache-control.fdb", get_header_value(get_header_line(res.rawheader, "Cache-Control", false)), 2)
   find_match_in_db(db.."connection.fdb", get_header_value(get_header_line(res.rawheader, "Connection", false)), 2)
   find_match_in_db(db.."content-type.fdb", get_header_value(get_header_line(res.rawheader, "Content-Type", false)), 1)
   find_match_in_db(db.."etag-legth.fdb", string.format("%s", string.len(get_header_value(get_header_line(res.rawheader, "ETag", false)))), 3)
   find_match_in_db(db.."etag-quotes.fdb", get_quotes(get_header_value(get_header_line(res.rawheader, "ETag", false))), 2)
   find_match_in_db(db.."header-capitalafterdash.fdb", string.format("%s", capital_after_dash(analyze_header_order(res.rawheader))), 2)
   find_match_in_db(db.."header-order.fdb", analyze_header_order(res.rawheader), 5)
   find_match_in_db(db.."header-space.fdb", string.format("%s", header_space(res.rawheader)), 2)
   find_match_in_db(db.."htaccess-realm.fdb", get_realm(get_header_line(res.rawheader, "WWW-Authenticate", false)), 3)
   find_match_in_db(db.."pragma.fdb", get_header_value(get_header_line(res.rawheader, "Pragma", false)), 2)
   find_match_in_db(db.."protocol-name.fdb", get_protocol_name(res['status-line']), 1)
   find_match_in_db(db.."protocol-version.fdb", get_protocol_version(res['status-line']), 2)
   find_match_in_db(db.."statuscode.fdb", get_status_code(res.status), 4)
   find_match_in_db(db.."statustext.fdb", get_status_text(res['status-line']), 4)
   find_match_in_db(db.."vary-capitalize.fdb", string.format("%s", has_capital(get_header_line(res.rawheader, "Vary", false))), 2)
   find_match_in_db(db.."vary-delimiter.fdb", vary_delimiter(get_header_line(res.rawheader, "Vary", false)), 2)
   find_match_in_db(db.."vary-order.fdb", get_header_value(get_header_line(res.rawheader, "Vary", false)), 3)
   find_match_in_db(db.."x-powered-by.fdb", get_header_value(get_header_line(res.rawheader, "X-Powered-By", false)), 3)
end

Sodann ist die Funktion find_match_in_db() für die Identifikation der Matches in der Datenbank zuständig. Diese erwartet den Inhalt einer Fingerprint-Datei (z.B. statustext.fdb) und den zu findenen Fingerabdruck (z.B. Object Not Found):

function find_match_in_db(databasefile, fingerprint)
   local database      = read_from_file(databasefile) — Fingerprint database
   local delimiterpos                                 — Position of delimiter
   local name                                         — Name of implementation
   local pattern                                      — Pattern of fingerprint
   local arraypos                                     — Position in array

for i=1, #database, 1 do delimiterpos = string.find(database[i], “;”) if type(delimiterpos) == “number” then name = string.sub(database[i], 1, delimiterpos – 1) pattern = string.sub(database[i], delimiterpos + 1) if type(pattern) "string" and pattern ~= "" and type(name) “string” and name ~= “” then if fingerprint == pattern then arraypos = in_array(result, name) if type(arraypos) == “number” then result[arraypos] = { matchname = name, count = result[arraypos].count + 1 } else result1 = { matchname = name, count = 1 } end end end end end return true end

Ein Aufruf von function find_match_in_db("statustext.fdb", "Object Not Found") schreibt in die öffentliche Table result sämtliche Systeme, die für diesen Fingerabdruck bekannt sind. Wie wir zuvor gesehen haben, sollte dies Microsoft IIS betreffen.

Mit dem Zugriff auf result[1].name kann sodann der erste Treffer dieses Zugriffs gefunden werden. Wird die Table zuvor entsprechend sortiert (vorzugsweise durch .count), kann damit der beste Treffer mit den meisten Übereinstimmungen ausgemacht werden. Durch das iterieren dieser Table liessen sich dann die 10 besten Hits ausmachen. Die Ausgabe des Skripts für einen erfolgreichen Test eines Webservers sähe entsprechend so aus (hier wurde ein Microsoft IIS 6.0 identifiziert):

PORT   STATE SERVICE REASON
80/tcp open  http    syn-ack
| httprecon:     Implementation     Hits
| 1   Microsoft IIS 6.0  38
| 2   Apache 2.0.46      35
| 3   Apache 2.0.54      34
| 4   Apache 2.2.2       34
| 5   Apache 2.2.8       33
| 6   AOLserver 3.4.2    34
| 7   Apache 1.3.33      33
| 8   Apache 1.3.34      33
| 9   Apache 2.2.3       33
|_10  Zeus 4.3           33

Die offizielle nmap NSE-Portierung von httprecon enthält einige weitere Funktionalitäten. Zum Beispiel erhalten die einzelnen Hits individuelle Werte (Score). Durch diese Gewichtung können genauere und solidere Resultate generiert werden.

Im letzten Teil dieser Artikelserie werden wir abschliessend das Umsetzen von hostbasierten Skripten anschauen. Dabei werden wir mitunter die Möglichkeit schaffen, iterativ die bestehenden Resultate von nmap zu prüfen und zu modifizieren.

About the Author

Marc Ruef

Marc Ruef has been working in information security since the late 1990s. He is well-known for his many publications and books. The last one called The Art of Penetration Testing is discussing security testing in detail. He is a lecturer at several faculties, like ETH, HWZ, HSLU and IKF. (ORCID 0000-0002-1328-6357)

Links

You want more than a simple security test with Nessus und Nmap?

Our experts will get in contact with you!

×
Specific Criticism of CVSS4

Specific Criticism of CVSS4

Marc Ruef

scip Cybersecurity Forecast

scip Cybersecurity Forecast

Marc Ruef

Voice Authentication

Voice Authentication

Marc Ruef

Bug Bounty

Bug Bounty

Marc Ruef

You want more?

Further articles available here

You need support in such a project?

Our experts will get in contact with you!

You want more?

Further articles available here