Archiv verlassen und diese Seite im Standarddesign anzeigen : Sicheres Upload-Skript?
Hallo,
für ein kleines Datenbankprojekt habe ich ein Uploadskript für Bilder im JPEG-Format geschrieben,
das ich hier zur "Sicherheitsbeurteilung" vorstellen möchte.
Vorgabe war, daß durch den Upload die Serversicherheit nicht gefährdet wird und wenn möglich durch
die Bilder auch am Client kein Schaden entstehen soll.
Die Bilder sollen durch ein ganz normales Formular mit einem Dateifeld das den Namen DateiName hat
hochgeladen werden. Als Bild ist nur JPEG mit maximal 50k vorgesehen.
Am Ende des Skriptes wenn alles OK ist "landen" die Bilder im Uploadverzeichnis und die Variable
$_POST['foto'] bekommt die URL für das Bild. Diese wird dann durch das folgende Skript
(hier nicht mit dabei) in die Datenbank eingetragen.
Das Upload (und Temp) Verzeichnis des Servers soll zusätzlich durch eine .htaccess geschützt werden.
Vorgabe: Es sollen nur die Bilder im Uploadverzeichnis in einem bestimmten Format (01234567890123.jpg)
gelesen werden können. (Wird so vom Skript erzeugt.)
Sicherheitshalber soll die Ausführung von Skripten und das Lesen von anderen Dateien in den
Bildverzeichnissen verboten werden.
Vielleicht sieht jemand sofort eine Sicherheitslücke die mir noch nicht bekannt ist.
(Rein technisch funktioniert es ja.)
Vielen Dank!
Klaus
upload.php:
<?php
// Einstellungen Anfang:
$UploadPfad='../sicher/bilder/'; // Dort werden die Bilder abgelegt
$UploadTempPfad='../sicher/temp/'; // Temp Zwischenablage der Bilder
$BasisBilderURL='http://www.example.local/sicher/bilder/'; // Über die URL sind die Bilder abrufbar
$MaxBildgroesseQuelle=51200; // In Byte
$MaxXQuelle=640;
$MaxYQuelle=480;
$MaxXZiel=90;
$MaxYZiel=90;
// :Einstellungen Ende
$_POST['foto']='';
if(isset($_FILES['DateiName']['name']))
{
if(!empty($_FILES['DateiName']['name']))
{
$BildName=time().rand(1000,9999).'.jpg';
$BildmitPfad=$UploadPfad.$BildName;
$BildmitTempPfad=$UploadTempPfad.rand(1000,9999).$ BildName;
if($_FILES['DateiName']['error'] == UPLOAD_ERR_OK)
{
if($_FILES['DateiName']['size'] <= $MaxBildgroesseQuelle)
{
if(move_uploaded_file($_FILES['DateiName']['tmp_name'],$BildmitTempPfad))
{
if(getimagesize($BildmitTempPfad) == TRUE)
{
$UploadBildInfo=getimagesize($BildmitTempPfad);
if(($UploadBildInfo[2] == 2) && ($UploadBildInfo[0] <= $MaxXQuelle) && ($UploadBildInfo[1] <= $MaxYQuelle))
{
$UploadBild=imagecreatefromjpeg($BildmitTempPfad);
$NeuesBild=imagecreatetruecolor($MaxXZiel, $MaxYZiel);
imagecopyresampled($NeuesBild, $UploadBild, 0, 0, 0, 0, $MaxXZiel, $MaxYZiel, $UploadBildInfo[0], $UploadBildInfo[1]);
imagejpeg($NeuesBild, $BildmitPfad);
imagedestroy($UploadBild);
imagedestroy($NeuesBild);
$_POST['foto']=$BasisBilderURL.$BildName;
}
}
unlink($BildmitTempPfad);
}
}
}
}
}
?>
.htaccess:
# .htaccess kommt nach ../sicher/
# und in jedes Unterverzeichnis
# mir den Rechten: r--r--r--
# Zusaetzlich eine leere index.html
ServerSignature off
CheckSpelling off
DirectoryIndex index.html
# Gilt fuer PHP als cgi
RemoveHandler .cgi .fcgi .shtm .shtml .pl
RemoveType .php .php3 .php4 .php5
Options -ExecCGI -Includes -Indexes -MultiViews
order deny,allow
deny from all
<Files ~ "\.(php|php3|php4|php5|pl|cgi|fcgi|phtml|shtml|py|a sp|htm|html)$">
deny from all
</Files>
<Files ~ "^[0-9]{14}\.jpg$">
allow from all
</Files>
phpBuddy
08.12.2009, 19:28
Hallo Klaus,
gibt von meiner Seite aus nichts zu meckern. MIME wird geprüft, Größe wird geprüft, Bild wird serverseitig umbenannt, alles bestens. :)
Mir ist nur nicht klar, wieso der Bildname in $_POST soll. Schadet nicht, ist aber ungewöhnlich, weil man so leichter verwirrt werden kann, wo POST herkommt, wenn es (noch) kein Formular dazu gibt. ;)
Das einzige was mir am Code nicht gefällt ist der Code selbst. Da wäre ordentliches Einrücken besser als einfach nur 'nen Leerzeichen. :D
Gruß Andreas
Hallo Andreas,
vielen Dank für den "SecurityCheck".
Das mit dem $_POST['foto'] hat den Hintergrund das man mit dem "Einfügen-Tool"
der NOF-DB-Tools nur übergebene Felder automatisch in die DB einfügen kann.
Das $_POST['foto'] wird natürlich so nicht vom Formular übergeben sondern
das Uploadskript kommt ganz an den Anfang der PHP Seite die NOF generiert und
das "Einfügen-Tool" nimmt dann die FotoURL so als würde sie vom Formular kommen.
Was ich aber nicht weiß ist ob man mal so einfach eine neue Postvariable mit
$_POST['foto']= anlegen darf. Funktionieren tut es, aber ist das so auch OK?
.. das mit dem Code .. meinst Du Tabs?
MfG
Klaus
phpBuddy
09.12.2009, 01:08
Hallo Klaus,
wie NOF arbeitet weiss ich nicht, da ich es nicht benutze. Daher meine Unwissenheit, wie das DB-Tool davon arbeitet.
Es spricht nichts dagegen manuell $_POST zu befüllen, da es auch nur ein Array ist. Es ist nur eben ungewöhnlich, da man ein Formular erwarten würde, wenn $_POST existiert. Und ja, beim Code meinte ich einrücken mit Tabs. Bei tieferen Verschachtelungen ist der Code so für die meisten besser lesbar und Tabs zum einrücken haben sich als Standard in allen Programmiersprachen in allen Ländern der Welt etabliert. :)
Wutzke888
21.03.2010, 21:17
Hallo Leute
Habe zuvor als Neuling etwas eingestellt, aber wollte zu diesem Thema hier noch mal einen drauflegen ;)
Wie im vorigen Beitrag erwähnt, baue ich eine Bilderdatenbank auf.
Dazu habe ich das Script von Thomas recht originell gefunden, aber es für meine Zwecke abgeändert. Demnach benutze ich auch NOF-Datenbankkomponenten für die Erstellung.
Ziel:
Ein User lädt ein Bild hoch.
Es wird in einem Ordner abgelegt und mit den von Thomas vorgeschlagenen Änderungen
im Dateinamen versehen.
Dann wird ein Vorschaubild erzeugt und in einen anderen Ordner abgelegt.
Danach werden die Eingaben und die Pfade zu den Bildern in einer Datenbank mittels POST der Variablen gespeichert. Gleiche Vorgehensweise wie beim SQL-Tutorial von Thomas.
Funst prächtig! Jedenfalls auf meinem Localhost.
Was nicht geht, sind größere Bilddateien auf meinem Server (STRATO Powerweb).
In der Anlage gern mal das Script, welches an den Anfang der Seite zum Speichern in
der Datenbank eingefügt ist (o_bilder_gespeichert.php).
Folgender Fehler tritt auf, sobald die hochgeladene Datei 2,5 MB übersteigt:
INTERNAL SERVER ERROR 500
In der error_log steht dann:
21.03.2010 15:44:56 m´MeineDomain.com [client 87.162.91.154] FATAL: emalloc(): Unable to allocate 13416 bytes, referer: http://www.MeineDOmain.de/GeAGAL/html/o_bilder_user.php
21.03.2010 15:44:56 MeineDomain.com [client 87.162.91.154] Premature end of script headers: o_bilder_gespeichert.php, referer: http://www.MeineDomain.de/GeAGAL/html/o_bilder_user.php
Fakten:
Ich habe ein memory_limit von 64 MB für mein PHP.
Ich habe den belegten Speicher durch das Script und die Aktionen darin während der Ausführung ausgelesen.
Es belegt bei einem 2,5MB großen Bild ca. 34 MB. Sobald ich aber 2,7MB hochlade,
dann kommt dieser blöde Fehler.
Meine Vermutung liegt in der Richtung, das das Script für einige Ausführungen zu lange braucht, um anschließend schon den nächsten Befehl abzuarbeiten.
Das Memorys kann es doch eigentlich nicht sein bei der Größe.
Eine eigene php.ini habe ich auch schon ins root gelegt, jedoch werden sämtliche
Angaben darin scheinbar ignoriert.
magic_quotes_gpc = on (sonst off)
memory_limit = 128M (sonst 64)
max_execution_time = 30 (sonst 0 )
max_input_time = 60) (sonst -1)
Was soll man da noch machen?
Ich hoffe hier sitzt ein Profi im Forum.
(der mich wahrscheinlich gleich anranzen wird, was das fürn Müll ist)
trotzdem mit Hoffnung
Grüße
Bernd S.
phpBuddy
22.03.2010, 06:02
Hallo,
einen Fehler in Verbindung mit emalloc hatte ich selbst noch nie, also kann ich dir dazu nichts konkretes sagen.
Offensichtlich scheint aber, dass dir der zur Verfügung stehende RAM nicht ausreicht. Jenachdem wie die Thumbs erzeugt werden, können mehr als eine Kopie im RAM abgelegt sein. Ebenso kann man den RAM Verbrauch auch nicht so einfach ermitteln, wie Du es scheinbar getan hast. Das Memory Limit bezieh sich ja nicht nur auf das eine Script, sondern auf alle Scripts, die gerade ausgeführt werden. Es kann schon gut sein, das Du gerade mit den 2.7 MB an die Grenze gestoßen bist.
Solche Werte, wie max_execution_time, memory_limit, usw. per PHP.ini ändern funktioniert in der Regel nicht, auch wenn der Provider eine eigene ini erlaubt, da diese Werte massiv das ganze Wirtssystem beeinträchtigen können. Solche "eigene PHP.ini" Versprechen seitens der Provider sind meistens nichts weiter als heiße Luft, da man eh nicht mehr ändern kann als vielleicht mygic_quotes, register_globals und wenn man Glück hat noch safe_mode.
Ich würde dir ja raten mal beim Support anzufragen, aber Anbieter wie Strato, 1&1, usw. sind da eigentlich immer sehr unkooperativ und schmettern solche Extrawürste ab.
Hallo,
Du kannst auch mal versuchen in der php.ini von XAMPP
memory_limit = 64M
oder
memory_limit = 32M
dann siehst Du ob es am Speicher liegt.
(Apache Neustart nicht vergessen.)
Hast Du den Speicher für das Skript mit
memory_get_peak_usage ermittelt?
Sind die Bilder von x/y gleich groß oder ist das mit
den 2,7 MB bei x/y viel größer?
Ein Bild von mir mit ca. 900k und 4000x3000 Pixel
benötigt schon 60M.
MfG
Klaus
Wutzke888
22.03.2010, 14:05
Hallo Klaus
Hallo phpBuddy
Ich hatte zwar mein Script attached, ist aber wohl mißlungen.
Ich hole das hier unten dann nach.
Die Sache ist die: Ich habe auf meinem XAMPP die selben Einstellungen wie auf dem
Webserver fürs PHP. Limit gleich, post- und script_time gleich usw. und dennoch geht
es da oben nicht. Ich kann auf meinem XAMPP sogar 24MB große Bilder bearbeiten lassen,
ohne das der je einen Fehler zeigt.
Also entweder es liegt an der Dauer der Bearbeitung bestimmter Anweisungen oder tatsächlich am Speicher.
Ob die php.ini auf dem Server erkannt wird ist unklar. Zumindest kann ich ja dort den Server nicht neu starten. Habe dort keine Verfügungsgewalt drüber.
Der Hinweis mit der Speicherbelegung bei verschiedenen Auflösungen ist aber trotzdem
bemerkenswert. Da die Funktionen zur php-Bildbearbeitung nur in TrueColor richtig
funktionieren, blasen sich die RAM-Bilder natürlich auf. Ein Foto von einer Kamera hat ja
meist nur 72 dpi und es gibt keine Probleme. Meine zukünftigen User werden aber ihre
Postkarten und Bilder scannen und zwar mit 300 oder 600 dpi. Dann würde mein RAM
natürlich abkacken.
Das hilft mir aber nicht weiter, also muß was am Script geändert werden, ohne solch großen Speicherplatz belegen zu müssen. Letztendlich brauche ich das hochgeladene Bild ja garnicht im Speicher, sondern nur für die Berechnung des Vorschaubildes.
Es wird zum Lagern ja eh auf die Disk geschrieben.
Könnte man denn nicht das Quellbild mit irgendetwas vorher komprimieren und dann
für das Vorschaubild - mit kleineren Abmaßen - weiter verarbeiten?
Ich packe nun das Script mal hier rein und hoffe ihr habt nen Nerv da mal reinzusehen.
Den verbrauchten Speicher habe ich in Zeile 106 mit "memory_get_peak_usage"
als am höchsten gemessen.
Grüße
Bernd S.
<?php
// Letzte Änderung: 17.03.2010
// Administrator-Einstellungen
$UploadMaxBildPfad='../../GeAGAL/GeAGAL_Dateien/bilder/'; // Dort werden die Originalbilder abgelegt
$UploadMiniBildPfad='../../GeAGAL/GeAGAL_Dateien/vorschaubilder/'; // Dort werden die Vorschaubilder abgelegt
// Über diese URL sind die Bilder später aus der MySQL-Datenbank abrufbar
$BasisBilderURL='http://localhost/GeAGAL/GeAGAL_Dateien/bilder/';
$BasisMiniBilderURL='http://localhost/GeAGAL/GeAGAL_Dateien/vorschaubilder/';
$MaxBildgroesseQuelle=15728640; // Maximale Bildgröße Quelle in Byte - hier 15 MB
$MaxXQuelle=6000; // Max X der Quelle, Größe in Pixeln
$MaxYQuelle=6000; // Max Y der Quelle, Größe in Pixeln
$MaxXZiel=200; // Max X des Zieles, Größe in Pixeln
$MaxYZiel=200; // Max Y des Zieles, Größe in Pixeln
// Administrator-Einstellungen Ende
$_POST['Bild']=''; // Leere BildURL anlegen, Datenbank-Feld heißt Bild
$_POST['MiniBild']=''; // Leere MiniBildURL anlegen, Datenbank-Feld heißt MiniBild
if(isset($_FILES['DateiName']['name'])) // prüfe ob überhaupt ein Datenfeld übergeben wurde
// Vorsicht: Hier wird der Upload auch sogleich
// beendet, wenn die durch den Server vorgegebene
// Größe von post_max_size überschritten wurde.
{
if(!empty($_FILES['DateiName']['name'])) // prüfen ob die übergebene Datei
// vom System angelegt wurde
{
$BildName=time().rand(1000,9999).'.jpg'; // ZielBild (Original) bekommt den Namen
// Timestamp+4stellige Zufallszahl+.jpg
$MaxBildmitPfad=$UploadMaxBildPfad.$BildName; // ZielBild_mit_Pfad ist UploadPfad+BildName
// z.B. ../bilder/12345678901234.jpg
$MiniBildmitPfad=$UploadMiniBildPfad.$BildName; // ZielMiniBild_mit_Pfad ist UploadPfad+BildName
if($_FILES['DateiName']['error'] == UPLOAD_ERR_OK) // Hier wird geprüft, ob der Upload OK war, d.h.
// auch, daß geprüft wird, ob die vom Server durch
// upload_max_filesize vorgegebene Größe
// überschritten wurde
{
if($_FILES['DateiName']['size'] <= $MaxBildgroesseQuelle) // Wenn keine Maxima vom Server überschritten
// wurden, wird hier geprüft ob die Dateigröße auch
// den Anforderungen (15 MB) entspricht
{
if(move_uploaded_file($_FILES['DateiName']['tmp_name'],$MaxBildmitPfad))
// Wenn soweit alles OK ist, wird die Datei,
// die durch den Browser/Server meist nach
// /tmp/ kopiert wurde in den Pfad für die
// Originalbilder verschoben.
{
if(getimagesize($MaxBildmitPfad) == TRUE) // Hier wird geprüft, ob Datei ein gültiges Bild ist.
// Die Funktion prüft aber nur den Header der Datei.
// Es könnte im Bild versteckt noch schädlicher Code
// enthalten sein.
{
$UploadBildInfo=getimagesize($MaxBildmitPfad); // weitere Infos zum hochgeladenen Bild werden geholt
// Prüfung auf Typ gleich JPEG und
// ob MaxX/Y der Quelle nicht zu groß sind.
if(($UploadBildInfo[2] == 2) && ($UploadBildInfo[0] <= $MaxXQuelle) && ($UploadBildInfo[1] <= $MaxYQuelle))
{
$UploadBild=imagecreatefromjpeg($MaxBildmitPfad); // Erzeuge JPEG Bild im Hauptspeicher von
// UploadDatei
$NeuesBild=imagecreatetruecolor($MaxXZiel, $MaxYZiel); // Erzeuge neues JPEG MiniBild im
// Hauptspeicher mit Max X/Y Ziel
if ($UploadBildInfo[0] >= $UploadBildInfo[1]) // Dehnungsfaktoren berechnen
{
$new_x = $MaxXZiel; // wenn Breitformat
$faktor = $UploadBildInfo[0] / $MaxXZiel;
$new_y = round($UploadBildInfo[1] / $faktor);
}
else {
$new_y = $MaxYZiel; // wenn Hochformat
$faktor = $UploadBildInfo[1] / $MaxYZiel;
$new_x = round($UploadBildInfo[0] / $faktor);
}
$dest_x = ( $MaxXZiel / 2 ) - ( $new_x / 2 ); // Zentrieren - Startpunkt x Ziel
$dest_y = ( $MaxYZiel / 2 ) - ( $new_y / 2 ); // Zentrieren - Startpunkt y Ziel
$src_x = $MaxXZiel - 2 * $dest_x; // Zentrieren - Endpunkt x Ziel
$src_y = $MaxYZiel - 2 * $dest_y; // Zentrieren - Endpunkt y Ziel
$bgcolor = imagecolorallocate($NeuesBild, 255, 255, 255); // Farbe Weiß ermitteln
ImageFill($NeuesBild, 0, 0, $bgcolor); // mit Farbe Weiß füllen, damit keine
// schwarzen Überstände bleiben
imagetruecolortopalette($UploadBild, false, 255); // Umwandlung in gleiche Farbpalette
// wie neu erzeugtes Bild
imagecopyresampled($NeuesBild, $UploadBild, $dest_x, $dest_y, 0, 0, $src_x, $src_y, $UploadBildInfo[0], $UploadBildInfo[1]);
// kopieren/schrumpfen des OriginalBildes in das MiniBild
// funktioniert leider nur mit TrueColor bei beiden
imagejpeg($NeuesBild, $MiniBildmitPfad); // Das neue MiniBild wird im ZielPfad für die
// Vorschaubilder gespeichert
imagedestroy($UploadBild); // Hauptspeicher freigeben, Originalbild
imagedestroy($NeuesBild); // Hauptspeicher freigeben, Minibild
$_POST['Bild']=$BasisBilderURL.$BildName; // Hier werden dann die Variablen mit der URL
$_POST['MiniBild']=$BasisMiniBilderURL.$BildName; // für NOF gesetzt um sie in die Datenbank
// zu speichern.
}
else ( unlink($MaxBildmitPfad) ); // Hier wird das hochgeladene Bild gelöscht
} // falls Prüfungen zur Dimension negativ
else (unlink($MaxBildmitPfad) ); // Hier wird das hochgeladene Bild gelöscht
// falls Prüfungen zum Typ negativ
}
}
}
}
}
?>
barbara0701
22.03.2010, 15:02
Mit dem Editor antworten, Text markieren, über dem Eingabefenster zweites Symbol von rechts: "Raute" anklicken , schon erschein der Code im Kästchen....
phpBuddy
22.03.2010, 15:25
Oder im Fall von PHP den PHP Button benutzen. ;)
Ansonsten heißt es phpBuddy, mit u nicht mit o. Buddy ist ein englischer Begriff und bedeutet soviel wie Kumpel, Kollege.
Vergleiche von RAM-Einstellungen zwischen lokalem Xampp und einem Livesystem sind nicht sonderlich aussagekräftig. Der zugesicherte Speicher steht ja nicht exklusiv für ein Script zur Verfügung, sondern wird zwischen allen Scripts aufgeteilt. Wenn also mehrere leute auf einer PHP Seite surfen, verbrauchen die natürlich auch RAM. Bildbearbeitung ist generell sehr speicherhungrig und belegt häufig ein Vielfaches der original Dateigröße. JPG ist ein komprimiertes Format - dekomprimiert auf dem Server erreicht es nicht selten den Faktor 10 bis 12. Bei der Thumbnailerstellung kann es sein, dass man mehr als eine Kopie der dekomprimierten Grafik im RAM hält und so stößt man schon mal sehr schnell an die Speichergrenze.
Wutzke888
22.03.2010, 16:13
Hallo phpBuddy ;-)
Der Zeigefinger war so groß, das ich ihn schon am Horizont sehen konnte.
Habe das natürlich berichtigt!
Kannste jetzt mal ins Script gucken, ob ich den Speicherbereich vielleicht doch
unnötig belegt habe? Vielleicht gibt es irgendwas, womit man nicht unbedingt
das volle Bild in den Speicher rammeln muß, da im Ergebnis sowieso nur maximal
200x200 pix im Ergebnis stecken.
Kann man auch so was ähnliches wie eine "Auslagerungsdatei" verwenden?
Grüße und Dank
Bernd S.
vBulletin® v3.8.6, Copyright ©2000-2012, Jelsoft Enterprises Ltd.