PHP: Mögliche Sicherheitslücke mit mod Rewrite und Query-String-Append

Rewrite mit QSA ermöglicht Umgehung Authorization Basic Passwortschutz

Issue, Problemstellung

Der Path von URI's wird auf Parameter abgebildet (Parametrisierung). Parameter werden in einer RewriteRule an einen Indexer angehängt (QSA). Zum Parsen der Parameter wird serverseitig PHP eingesetzt.

Kategorie: Unsichere Konstrukte.

Auswirkung: Umgehung Passwortschutz Authorization Basic. Möglichkeit der Umgehung auch weiterer auf den Path bezogener Mechanismen infolge Vortäuschen eines Path und Rekonstruktion eines anderen Path mit angehängten Parametern.

Problembeschreibung anhand eines Beispiels

Natürlich ist das folgende Beispiel konstruiert: Damit das Problem verständlich nachvollzogen werden kann. Es sei bereits hier notiert, dass Perl nicht die Lösung ist, jedoch gibt es wesentliche Unterschiede zwischen Perl und PHP, was das Parsen von Parametern betrifft, von daher der Vergleich.

Egal wie kompliziert eine RewriteRule in der Praxis ist, der Artikel mit dem Beispiel verdeutlicht das Prinzip eines unsicheren Konstrukts, wobei das Problem in dem Moment sofort auftritt, wenn der Path auf Parameter abgebildet, nur über die Parameter geroutet, das Parsen der Parameter mit PHP erfolgt und eine Authorization Basic gemacht werden soll. Dementsprechend gestaltet sich auch die Lösung, beschrieben am Ende des Artikels. Doch nun zur Sache:

Eine Eigenheit von PHP besteht darin, dass aus gleichnamigen HTTP-Parametern kein Array für die Werte gebildet wird wie das die HTTP-Spezifikation vorsieht, so sollte ein Array entstehen, wie folgt:

    # GET Parameter, Query-String
    page=user&page=admin

    # Perl
    # ['user', 'admin']

    # PHP
    $_GET['page'] hat nur einen Wert: 'admin'

Mit dem Parser CGI.pm, der gewöhnlich in Perl eingesezt wird, um GET- oder POST-Parameter auszulesen, erfolgt die Array-Bildung wie oben beschrieben, auf den Schlüssel page gäbe es also zwei dazugehörige Werte user und admin.

PHP jedoch bildet nicht das Array, sondern überschreibt die vorherigen bzw. alle vorherigen Werte, so haben wir mit obenstehendem QUERY-String lediglich den Wert admin in $GET_['page'].

Betrachten Sie nun untenstehende Serverkonfiguration in einer .htaccess-Datei:

    RewriteEngine On

    <Files ~ "admin.html">
        AuthUserFile d:/home/spoof/html/.htpasswd
        AuthName "Secret Realm"
        AuthType Basic
        order deny,allow
        require valid-user
        SetEnv Realm Secret
    </Files>

    RewriteRule ^(.*).html /index.php?page=$1 [QSA,L]

Ziele dieser Konfiguration sind:

Diese Konfiguration funktioniert wie gewünscht, index.php wird aufgerufen und liefert die Inhalte aus entsprechend des angeforderten URL. Der Passwortschutz für URL admin.html funktioniert ebenfalls.

URL-Manipulation: Passwortschutz kann umgangen werden

Mit Kenntnis der RewriteRule und dem Wissen, wie der Parser arbeitet, lässt sich das Passwort umgehen, der Angreifer ruft folgende URL's im Browser auf:

    http://example.com/user.html            # Seite user.html wird ausgeliefert
    http://example.com/user.html?page=admin # Seite admin.html wird ausgeliefert

Beim Aufruf der URL user.html wird, wie beabsichtigt, kein Passwort verlangt, die Seite wird über index.php ausgeliefert. Der angehängte Parameter page=admin veranlasst jedoch index.php dazu, nun auch die designierte Seite admin.html auszuliefern ohne dass der Passwortschutz mit .htaccess greift. Denn im PHP-Script kommt aufgrund der Überschreibung nur ein Parameter an und der hat den Wert admin.

Aber auch dann, wenn PHP dazu veranlasst wird, ein Array zu bilden, ist es direkt manipulierbar:

    RewriteRule ^(.*).html /index.php?page[]=$1 [QSA,L]               # /user.html?page[0]=admin
    RewriteRule ^(.*).html /index.php?page[secret]=$1 [QSA,L]         # /user.html?page[secret]=admin
    RewriteRule ^(.*).html /index.php?page[secret][strong]=$1 [QSA,L] # /user.html?page[secret][strong]=admin

Das heißt, dass sich am Verhalten des in PHP eingesetzten Parsers auch dann nichts ändert, wenn eine Array-Bildung angestrebt wird (Hinweis: page[secret][strong] ist als Parameter kein Array sondern ein String!). Egal wieviele scheinbare Indizies in der RewriteRule vorgegeben werden, sie werden stets überschrieben, wenn ein gleichnamiger Parameter angehängt wird. Das Array $_GET ist komplett manipulierbar, selbst wenn Sie einen Schlüssel sonstwotief verstecken und denken, mit der Abfrage $id = $_GET['strong']['secret']['index']['foo']['bar']['id'] sind Sie auf der sicheren Seite, mit dem Parameter strong[secret][index][foo][bar][id]=4712 kriegt jeder Angreifer jede id dahin, wo sie soll, vorausgesetzt, er kennt die id und den dazugehörigen Parameternamen.

Das Problem ist systematischer Natur

Sie werden sich nun fragen, welche Prüfungen in der index.php möglich sind, um das Problem aus der Welt zu schaffen. Hier ist die Anwort: Sie können prüfen, was Sie wollen, es wird das Problem nicht lösen. Selbst wenn Sie aus einem URL:

    http://example.com/123/de/index.html

einen Parameter machen, z.B. id=123 und im Script index.php wird aufgrund der id der Seiteninhalt aus irgendeiner beliebigen Datenquelle geladen (egal ob MySQL oder Dateisystem), Sie können allenfalls prüfen, ob unter der id=123 die Inhalte vorliegen. Die Manipulierbarkeit des Parameter ist mit:

    http://example.com/foo.html?id=12345

weiterhin gegeben, dann wird index.php eben die Seite mit der id=12345 laden. Und wenn Sie in der .htaccess bspw. den URL /shopmanager.html mit einem Passwort versehen, der Server weiß nichts davon, dass dem URL die id=12345 zugeordnet ist und wird gar nicht nach einem Passwort fragen: Der Besucher bekommt die Seiteninhalte für Seite mit id=12345, denn index.php weiß andererseits nicht, was Sie in der Serverkonfiguration beabsichtigen.

Das Problem: Ist die Parametrisierung von URLs verbunden mit einer Authorization Basic: Mit Query-String-Append (QSA) werden aus dem URL erzeugte Parameter an den Indexer angehängt. Der Indexer kennt nicht die Vereinbarungen in der Serverkonfiguration (AuthType Basic, bezieht sich auf den Path des URI). Und der Server kennt nicht die Parameter (id, page usw.), welche der Indexer zum Ausliefern von Inhalten braucht.

Es ist ein systematischer Fehler, der mit einer Parametrisierung von URLs begangen wird und als Fehler nicht einmal dann erkennbar, wenn darauf aufbauende Anwendungen infolge URL-Manipulation kompromittiert wurden.

Security through obscurity

Statistische Erhebungen belegen, dass Angreifer auch aus den eigenen Reihen kommen. Ein Insider braucht nur wenige Informationen um durch URL-Manipulationen einen Super-GAU zu verursachen: Von jedem beliebigen Ort aus. Es genügt die Kenntnis der fürs Routing relevanten Parameternamen, der dazugehörigen Werte und die Kenntnis der RewriteRule.

Infolge dessen, dass der Parser von PHP jeden einzelnen Parameter überschreibt, kann der Angreifer mit Parametern jeden Path rekonstruieren und einen anderen Path vortäuschen. Sämtliche Mechanismen, die sich auf den Path beziehen, wie z.B. Authorization Basic, können dadurch ausgehebelt werden.

Lösung und Empfehlung

Wenn Authorization Basic im Spiel ist, führt das Routen über die Parameter zu Inkonsistenzen und dies wiederum zu der hier beschriebenen Sicherheitslücke in Verbindung mit PHP. Nutzen Sie statt Parameter (QSA) den Path für das Routing über Mod-Rewrite, der Path (z.B. /foo.html ohne QUERY_STRING) eines URI ist eindeutig.

Konkret: Nutzen Sie für das Routing per RewriteRule den vollständigen Path eines URI und nicht nur einen Teil davon, wenn Sie gleichzeitig Authorization Basic einsetzen und bilden Sie den Path NICHT auf Parameter ab.

Beachten Sie, dass HTTP-Parameter grundsätzlich Strings sind, daran ändert auch eine Schreibweise page[foo][bar] nichts, das sieht zwar aus wie ein PHP-Array aber es ist und bleibt ein String der manipulierbar ist. Beachten Sie daher auch, dass PHP aus gleichnamigen Parametern:

page[foo][bar]=1&...&page[foo][bar]=2

kein Array ('page[foo][bar]' => 1, 'page[foo][bar]' => 2) bildet sondern vielmehr nur den Wert '2' in $_GET['page']['foo']['bar'] behält, weil sich gleichnamige Parameter aufgrund der Arbeitsweise des Parsers überschreiben.

Überprüfen Sie selbst Ihre Webanwendungen!

Aufbau URI (Uniform Ressource Identifier)

     http://example.com:8078/foo/bar.html?name=boo;vname=dog#nose
     \__/   \______________/\___________/ \________________/ \__/
       |           |             |                  |          |
    Scheme     Authority        Path          Query-String    Fragment

Routing über den Path

Betrachten Sie untenstehende Regel:

    RewriteRule !\.(css|jpg|jpeg|js|gif|ico|txt|pdf)$ /index.php [L]

Die Liste der von der Regel ausgenommenen Dateierweiterungen ist individuell und ggf. erweiterbar. Geroutet wird unabhängig von Parametern auf den Path bezogen und dieser geht als Kriterium für eine Authorization Basic dadurch nicht verloren. GET-Parameter sind, genauso wie POST-Parameter der jeweiligen Anwendung vorbehalten und spielen in der RewriteRule überhaupt keine Rolle. Für das Ausliefern parametergesteuerter Inhalte ist eine Parameter-Kontrollstruktur zuständig, welche in der Anwendung angesiedelt ist und nicht in der Serverkonfiguration.

Auf diese Art und Weise gibt es auch keine für alle Anwendungen reservierten Parameter und damit verbundene Abhängigkeiten, die bei komplexen Anwendungen schwer zu überschauen sind. Parameter sind und bleiben dadurch anwendungsspezifisch und effektiv nur für einen bestimmten URL. Das Routing über den Path benötigt eine Routing-Table mit dem Path als Primärschlüssel.


Anbieter: nmq​rstx-18­@yahoo.de, die Seite verwendet funktionsbedingt einen Session-Cookie und ist Bestandteil meines nach modernen Aspekten in Perl entwickelten Frameworks.