Mit PHP ist so ziemlich alles möglich, wenn es um das dynamisieren einer Webseite geht. So z.B. auch, dass man Benutzer-Dateien auf dem Server übertragen lässt.
Doch man sollte beim Entwickeln eines solchen Scriptes stets bedenken, dass der Mensch "böses" im Sinn hat.
So könnte ein Angreifer bei einem simplen Dateiupload, ohne irgendeine Validierung, auch selbst PHP-Scripte uploaden, wenn dieser nun auch den Pfad zum Script kennt, kann er so ziemlich alles an der Webseite verändern, wozu dieser gerade Lust hat.
Natürlich spielen dabei auch die Einstellungen auf dem Server eine Rolle.
Schauen wir uns hierzu mal solch ein Script an:
<php
if(is_uploaded_file($_FILES['clientdatei']['tmp_name'])
and ($_FILES['clientdatei']['error'] == 0))
{
move_uploaded_file($_FILES['clientdatei']['tmp_name'],"/upload/".$_FILES['clientdatei']['name']);
echo "Datenübertragung erfolgreich!";
echo "<a href=\"/upload/".$_FILES['clientdatei']['name']."\">Datei anzeigen</a>";
}else{
echo "<h1>Datenübertragung misslungen</h1>";
echo "Die Datei konnte nicht erfolgreich hochgeladen werden!
switch($_FILES['clientdatei']['error'])
{
case 1:
echo "<b>FEHLER #1:</b> Hochgeladene Datei zu groß!";
echo "<script language='JavaScript'>alert(\"FEHLER #1: Hochgeladene Datei zu groß!\");</script>";
break;
case 2:
echo "<b>FEHLER #2:</b> Hochgeladene Datei zu groß!";
echo "<script language='JavaScript'>alert(\"FEHLER #2: Hochgeladene Datei zu groß!\");</script>" ;
break;
case 3:
echo "<b>FEHLER #3:</b> Datei nur teilweise hochgeladen!";
echo "<script language='JavaScript'>alert(\"FEHLER #3: Datei nur teilweise hochgeladen!\");</script>";
break;
case 4:
echo "<b>FEHLER #4:</b> Es wurde keine Datei hochgeladen!";
echo "<script language='JavaScript'>alert(\"FEHLER #4: Es wurde keine Datei hochgeladen!\");</script>";
break;
default:
echo "Unbekannter Fehler während der Übertragung!";
echo "<script language='JavaScript'>alert(\"Unbekannter Fehler während der Übertragung!\");</script>";
break;
}
}
?>
Bevor ich hier auf die Sicherheitsaspekte eingehe, möchte ich zunächst einmal die grundlegende Funktion des Formulars und des Scriptes erläutern.
Dabei betrachten wir zuerst einmal das HTML-Formular:
<form...
...
<form/>
Formulare werden in HTML dazu verwendet um für Anwender Suchabfragen, individuelle Interaktionen zu bieten und natürlich Daten upzuloaden. Dabei können die Daten über HTTP GET- oder HTTP POST-Anfragen übertragen werden.
Doch was ist der Unterschied?
Bei GET-Anfragen wird hierzu das Zeichen "?" verwendet um den "Anfrage-Teil" zu kennzeichnen. Dieser taucht auch in der URI (auch bekannt unter dem veralteten Begriff URL) auf.
Hingegen bei der Übertragung von Daten über POST-Anfragen werden die Informationen im Body Teil von HTTP hinterlegt und tauchen daher nicht in der URI auf.
Weitere Informationen über Formulare und den einzelnen Funktionen/Variablen etc. sind hier zu finden.
action="phpscript.php"
Damit wird der Pfad zum PHP-Script angegeben, welches für die weitere Bearbeitung der Daten zuständig ist. Befindet sich das Script im gleichen Verzeichnis, genügt der Name des Scriptes.
Es ist natürlich auch möglich beides in einer Datei zu verwenden, dann würde der Aufruf des Scriptes wie folgt aussehen: action="<?php echo $PHP_SELF;" ?>
enctype="multipart/form-data">
Der enctype ist sehr wichtig, denn ohne ihn funktioniert kein File-Upload. Dieser legt die Umformatierung von den Daten fest bzw. wie das Formular mit den Daten umzugehen hat.
<input type="file" name="clientdatei">
Mit diesem Input-Feld wird der "Dateisystem-Auswahldialog" angezeigt. Damit ist es dem Anwender möglich bei sich lokal Daten zum Upload auszuwählen.
Wichtig ist dabei der Name, der später für die Verarbeitung im Script eine zentrale Rolle spielt. Dieser ist hier im Beispiel clientdatei, denn es ist der Name, mit dem man später im Script die Datei des Anwenders anspricht.
Nun schauen wir uns die Funktion des PHP-Scriptes an. Die hochgeladene/n Datei/en steht bzw. stehen im Array $HTTP_POST_FILE bzw. $_FILES zur Verfügung.
Da auch gleichzeitige Uploads möglich sind, muss als erster Index im Array der Name der vom Anweder übertragenden Daten angegeben werden. Also hier clientdatei.
Als zweiten Index ist einer der Namen einzugeben welche in nachfolgender Tabelle enthalten sind:
Index
Bedeutung
name
Ursprünglicher Name der Datei auf dem Rechner des Anwenders
size
Die Größe der übertragenden Datei in Bytes.
tmp_name
Der temporäre Dateiname beim Speichern der Datei auf dem Server.
type
Falls der Browser diesen zur Verfügung stellt, kann hiermit der Mime-Typ determiniert werden.
error
Enthält den Fehlercode, falls bei der Übertragung der Datei ein Fehler auftrat.
Daraus wird schnell ersichtlich, wie man sich z.B. den ursprünglichen Namen der Datei ausgeben lassen könnte: echo $_FILES['clientdatei']['name'];
Da die Datei nun temporär auf dem Server liegt, sollte man diese temporäre Datei in ein dafür vorgeschriebenes Verzeichnis legen.
Eine Funktion die dafür gerade zu prädestiniert ist, ist move_uploaded_file.
Als ersten Parameter erwartet die Funktion den Dateinamen und als zweiten Parameter das Zielverzeichnis und den Dateinamen. Wird die Operation erfolgreich ausgeführt, liefert die Funktion TRUE, ansonsten wird FALSE zurück geliefert.
Eine weitere wichtige Funktion ist is_uploaded_file. Mit dieser kann man ganz einfach testen ob die Übertragung der Datei erfolgreich verlief.
Dabei gibt man als Parameter die Datei an, bei der überprüft werden soll, ob es sich um die richtige handelt und beim Rückgabewert TRUE, ist dem so.
bool is_uploaded_file(string filename)
Jetzt gilt es noch zu klären, welche Bedeutungen die einzelnen Fehlercodes haben können, wenn man
echo $_FILES['clientdatei']['error'];
sich diese Ausgeben lässt. Hier soll folgende Tabelle Auskunft geben:
Wert
Bedeutung
0
Kein Fehler. Übertragung erfolgreich.
1
Die hochgeladene Datei ist größer, als es in der php.ini festgelegt wurde.
2
Die hochgeladene Datei ist größer, als es im Browser mit MAX_FILE_SIZE bestimmt wurde
3
Die Datei konnte nur teilweise hochgeladen werden.
4
Die Datei wurde nicht hochgeladen.
Somit bedeutet eine Überprüfung wie $_FILES['clientdatei']['error'] == 0, dass nur wenn kein Fehler in der Übertragung statt gefunden hatte, im Script weiter gemacht wird, hier z.B. mit der Verschiebung der Datei in ein bestimmtes Verzeichnis.
Jetzt wo wir wissen wie das ganze funktioniert, stellt sich die Frage, wo es hier demn Lücken gibt.
Doch wie ich schon zu Beginn erwähnte könnte allein der Upload eines PHP-Scriptes zum kompromittieren einer Webseite ausreichen. Weiterhin ist es jedem gestattet so große Dateien hochzuladen wie man öchte, sofern diese nicht den Grenzwert der php.ini überschreiten.
Somit könnte ein Angreifer jetzt versuchen einen Denial of Service auf dem Upload durchzuführen, in dem dieser den ganzen Speicherplatz mit groß verzerrt. Dabei kann ein einfaches PHP-Script ihm die ganze Arbeit abnehmen.
Um diese Lücken zu schließen, macht man sich ganz einfach zu nutze, das jede Datei einen MIME-Typen besitzt. So überprüft man beim Upload auf bestimmte MIME-Typen. Gleichzeitig überprüft man noch die Dateigröße der Datei und gibt auch wenn nicht unbedingt notwendig, keine Auskunft darüber wo sich die Datei befindet.
Weiterhin sollte die Datei noch umbenannt werden, damit diese nicht ungewollt vom Browser interpretiert wird.
Das ganze könnte dann in etwa so aussehen:
if(filesize($tmpfilename) >5120000 or $_FILES['clientdatei']['size']>5120000){
echo "<script language='JavaScript'>alert(\"FEHLER #6: Hochgeladene Datei zu groß!\");</script>";
}else if(is_uploaded_file($_FILES['clientdatei']['tmp_name'])
and ($_FILES['clientdatei']['error']==0) and $start == 'true')
{
if($_FILES['clientdatei']['type']=="application/msword" or $_FILES['clientdatei']['type']=="application/pdf"
or $_FILES['clientdatei']['type']=="text/plain" or $_FILES['clientdatei']['type']=="application/mspowerpoint" or $_FILES['clientdatei']['type']=="image/jpeg" or $_FILES['clientdatei']['type']=="image/png")
{
$file = fopen($stamp.".txt","w");
if($file)
{
fputs($file,$filename."\n");
fclose($file);
}
move_uploaded_file($_FILES['clientdatei']['tmp_name'],"upload/".$stamp);
echo "<h1>Datenübertragung erfolgreich</h1>";
echo "Die Datei wurde erfolgreich hochgeladen!<br>\n";
}else
{
echo "<b>FEHLER #5:</b> Hochgeladene Datei entspricht nicht den Vorgaben: Falscher Dateityp!";
echo "<script language='JavaScript'>alert(\"FEHLER #5: Hochgeladene Datei entspricht nicht den Vorgaben: Falscher Dateityp!\");</script>";
}
}
else
{
echo "<h1>Datenübertragung misslungen</h1>";
echo "Die Datei konnte nicht erfolgreich hochgeladen werden!<br>\n";
switch($_FILES['clientdatei']['error'])
{
case 1:
echo "<b>FEHLER #1:</b> Hochgeladene Datei zu groß!";
echo "<script language='JavaScript'>alert(\"FEHLER #1: Hochgeladene Datei zu groß!\");</script>";
break;
case 2:
echo "<b>FEHLER #2:</b> Hochgeladene Datei zu groß!";
echo "<script language='JavaScript'>alert(\"FEHLER #2: Hochgeladene Datei zu groß!\");</script>";
break;
case 3:
echo "<b>FEHLER #3:</b> Datei nur teilweise hochgeladen!";
echo "<script language='JavaScript'>alert(\"FEHLER #3: Datei nur teilweise hochgeladen!\");</script>";
break;
case 4:
echo "<b>FEHLER #4:</b> Es wurde keine Datei hochgeladen!";
echo "<script language='JavaScript'>alert(\"FEHLER #4: Es wurde keine Datei hochgeladen!\");</script>";
break;
default:
echo "Unbekannter Fehler während der Übertragung!";
echo "<script language='JavaScript'>alert(\"Unbekannter Fehler während der Übertragung!\");</script>";
break;
}
}
?>
Das sind alles natürlich nur ein paar Möglichkeiten um einen Datei-Upload zu schützen. Dennoch hat es kein Anspruch auf Vollständigkeit und soll nur ein paar Anregungen liefern.
Bei weiteren Fragen, Kritik oder Anregungen zum Artikel stehe ich natürlich jederzeit zur Verfügung.
Einfach eine E-Mail an duddits-[at]-remoteshell-security.com. Ich versuche diese dann so schnell als möglich zu beantworten.