PHP Ordnerübergreiffendes Includen mit $_GET verbieten

Spartan-117

Cadet 3rd Year
Registriert
Nov. 2010
Beiträge
41
Hallo an alle und willkommen zu meinem ersten Thread, zu dem mir leider kein passenderer Name eingefallen ist. :)

Ich habe eine PHP-Webseite, in der die verschiedenen Seiten in die Index.php includet und dort angezeigt werden.
Das Ganze läuft ganz einfach über eine Variable in der URL ab.
PHP:
index.php?s=irgendeineseite

Nun habe ich mir mal Gedanken darüber gemacht, dass wenn man nun anstelle eines Namens(z. B. register) einen relativen Pfad zu einer bestimmten Datei angibt, dass diese Datei auch angezeigt wird.
PHP:
index.php?s=../admin/passwords

Natürlich gibt es auf meiner Webseite keine obengenannte Datei, ich wollte nur das Ausmass demonstrieren/verherrlichen. :evillol:

Nun meine Frage an euch:
Wie unterbinde ich dieses ordnerübergreiffende Includen?

Am besten nur ein Denkanstoss, wegen dem Lerneffekt. :p (Fertiger Code ist aber auch erlaubt. ;))

Ich habe es schon mit der Function realpath() versucht, jedoch hilft das auch nicht gegen diese "../"-Dinger. :D

Natürlich kann ich diese "../"-Dinger mit str_replace() entfernen, ob es jedoch nicht einen einfacheren Weg gibt? Wenn nicht, was für Zeichen wie ".." und "/" gibt es noch, mit dem man unerwünschte Dateien includen kann?

Ich bedanke mich schonmal für alle Beiträge. :)

Spartan-117

P.S. Ich entschuldige mich schonmal dafür, dass in meinen Sätzen an gewissen Stellen kein "ß" vorhanden ist, aber da ich Schweizer bin und nicht immer ALT-0223 drücken will, muss in meinen Beiträgen wohl darauf verzichtet werden. :D
 
Zuletzt bearbeitet:
Du kannst natürlich einfach alle doppelten Punkte, Slashes und Backslashes aus dem String entfernen.

Ich würde allerdings sicherheitshalber soweit gehen, alle Dateien aus deinem include-Verzeichnis auszulesen (natürlich abzüglich der Einträge ".", ".." und ggf. ".htaccess") und auf eine Standardseite verweisen, wenn der String keinem Wert aus dieser Liste entspricht.

Was spricht gegen die altbewähre Praxis eines assoziativen Arrays, wo du als Parameter nicht den tatsächlichen Dateinamen des Includes angibst, sondern lediglich den passenden Schlüssel aus einem Array, das als Werte alle diese Dateien enthält? So muss man seine Datei- und Ordnerstruktur nicht ohne Not offenlegen.
 
Spartan-117 schrieb:
Nun meine Frage an euch:
Wie unterbinde ich dieses ordnerübergreiffende Includen?

Indem du erst gar nicht solche gefährlichen Abhängigkeiten wie include($_GET['xyz']) kreierst.

pcw hat bereits einen guten Hinweis gegeben. Mit einer switch-Anweisung kann dies ziemlich bequem umgesetzt werden. Alternativ sind auch XML-Konfigurationsdateien denkbar.
 
pcw schrieb:
Du kannst natürlich einfach alle doppelten Punkte, Slashes und Backslashes aus dem String entfernen.

Ich würde allerdings sicherheitshalber soweit gehen, alle Dateien aus deinem include-Verzeichnis auszulesen (natürlich abzüglich der Einträge ".", ".." und ggf. ".htaccess") und auf eine Standardseite verweisen, wenn der String keinem Wert aus dieser Liste entspricht.

Was spricht gegen die altbewähre Praxis eines assoziativen Arrays, wo du als Parameter nicht den tatsächlichen Dateinamen des Includes angibst, sondern lediglich den passenden Schlüssel aus einem Array, das als Werte alle diese Dateien enthält? So muss man seine Datei- und Ordnerstruktur nicht ohne Not offenlegen.
Also, meinst du das mit dem Array etwa so:
PHP:
<?PHP
$Sites = array();
$handle = opendir('pages');
while($site = readdir($handle)) {
	if($site != '..' && $site != '.' && $site != '.htaccess' && $site != '_notes') {
		if(strpos($site, '.') === false) {
			$key = $site.'/';
			$site = $site.'/';
		}
		else {
			$key = substr($site, 0, strpos($site, '.'));
		}
		$Sites[$key] = $site;
	}
}
if(isset($Sites[$_GET['s']]) && !empty($Sites[$_GET['s']])) {
	include('./pages/'.$Sites[$_GET['s']]);
}
else {
	// Fehlermeldung und ggf. Standardseite
}
?>
Tatsächlich ist jetzt kein ordnerübergreiffendes Includen mehr möglich, und das sogar dynamisch. :D

Trainmaster schrieb:
Indem du erst gar nicht solche gefährlichen Abhängigkeiten wie include($_GET['xyz']) kreierst.

pcw hat bereits einen guten Hinweis gegeben. Mit einer switch-Anweisung kann dies ziemlich bequem umgesetzt werden. Alternativ sind auch XML-Konfigurationsdateien denkbar.
Eine switch-Anweisung würde doch in etwa auf das Selbe kommen wie das o.g Verfahren mit einem Array.

Vielen Dank an euch beide für die Hilfe. :)

€: Verbesserte Version:
PHP:
<?PHP
$Sites = array();
$handle = opendir('pages');
while($site = readdir($handle)) {
	if($site != '..' && $site != '.' && $site != '.htaccess' && $site != '_notes' && is_dir('./pages/'.$site)===false) {
		$key = substr($site, 0, strpos($site, '.'));
		$Sites[$key] = $site;
	}
}
if(isset($_GET['s']) && !empty($_GET['s'])) {
	if(isset($Sites[$_GET['s']]) && !empty($Sites[$_GET['s']])) {
		include('./pages/'.$Sites[$_GET['s']]);
	}
	else {
		// Fehlermeldung und ggf. Standardseite
	}
}
else {
	include('./pages/'.$Sites['home']);
}
?>
 
Zuletzt bearbeitet: (Verbesserte Version)
Hi,

- performanter da kein FS Zugriff
- sicherer da keine includes

PHP:
// Diese Files sind verboten
$denied = array("htaccess","notes");

// Wenn nicht in Array und in ./pages vorhanden und  $_GET['s'] ist Typ Datei
if(!in_array($_GET['s'],$denied) AND is_file("./pages/".$_GET['s'])) {

     // Dann File zum Browser streamen, script beenden.
     // ACHTUNG: nur wenn es sich nicht um Serverscripts handelt
     // wenn Serverscript dann include verwenden.
     echo file_get_contents("./pages/".$_GET['s']);
     exit();

} else {
// Datei in array oder nicht vorhanden oder $_GET['s'] ist keine Datei
DIE("Access Denied!");
}
 
Zuletzt bearbeitet:
Zwei Möglichkeiten sind eigentlich am gebräuchlichsten.

1) Du gibst ne Whitelist an (also alle Werte für s die zulässig sind), z.B. in nem Array
2) Du filterst generisch das Argument s. z.B. nur [a-z0-9]+ erlauben.
 
Yuri_Orlov schrieb:
Hi,

- performanter da kein FS Zugriff
- sicherer da keine includes

PHP:
// Diese Files sind verboten
$denied = array("htaccess","notes");

// Wenn nicht in Array und in ./pages vorhanden und  $_GET['s'] ist Typ Datei
if(!in_array($_GET['s'],$denied) AND is_file("./pages/".$_GET['s'])) {

     // Dann File zum Browser streamen, script beenden.
     // ACHTUNG: nur wenn es sich nicht um Serverscripts handelt
     // wenn Serverscript dann include verwenden.
     echo file_get_contents("./pages/".$_GET['s']);
     exit();

} else {
// Datei in array oder nicht vorhanden oder $_GET['s'] ist keine Datei
DIE("Access Denied!");
}
Naja, ich find die andere Methode besser, da in einer Whitelist alles andere verboten wird. Und file_get_contents() kann ich auch nicht benutzen, da ich Serverscripts in "./pages/" habe.

IceMatrix schrieb:
Zwei Möglichkeiten sind eigentlich am gebräuchlichsten.

1) Du gibst ne Whitelist an (also alle Werte für s die zulässig sind), z.B. in nem Array
2) Du filterst generisch das Argument s. z.B. nur [a-z0-9]+ erlauben.
Die erste Methode gefällt mir besser, da man dann kein Risiko eingeht, weil nur bestimme Wörter zugelassen werden, die man auch definiert.
 
Whitelists können halt wieder problematisch werden, wenn man dynamisch Seiten generiert.

Wenn du mehr zu dem Thema erfahren möchtest, solltest du dich mal zum Stichwort "(Prevent) Directory Traversal" etwas umsehen. Solltest du dazu übergehen, Zeichenketten zu ersetzen (zB ../), bedenke bitte, dass diese Filter durch verschiedene Codierungen (zB urlencode) leicht umgangen werden können.


so long
 
@Yuri_Orlov

wenn du
PHP:
is_file("./pages/".$_GET['s'])
und
PHP:
file_get_contents("./pages/".$_GET['s']);
vorschlägst, nehm ich
PHP:
$_GET['s']='../../../../verzeichnis/datei.dat';

dann kommen wir auf
PHP:
file_get_contents("./pages/../../../../verzeichnis/datei.dat".$_GET['s']);
und ich seh, was ich nicht sehen soll.
 
Zurück
Oben