Home > po polsku, technicznie > Zabezpieczenie uploadu plików w PHP

Zabezpieczenie uploadu plików w PHP

Dziś w DI pojawił się news, w którym autor informuje o potencjalnym zagrożeniu ze strony plików GIF. Tak się skład, że nie tylko pliki GIF mogą okazać się niebezpieczne, a stworzenie takiego niebezpiecznego pliku wcale nie jest trudne.

Okazuje się bowiem, że przy odrobinie nieuwagi webmastera (a wiadomym jest, że wśród wszelkiej maści programistów PHP wielu jest nieuważnych…) można niebezpiecznym uczynić tak pliki GIF jak JPG.

Jak to działa?

Otóż pliki GIF i JPG pozwalają na wprowadzenie komentarza. Dodajmy zatem komentarz (w np. Gimpie) do pliku JPG lub GIF:

phpinfo();

Zapiszmy naszą grafikę jako plik o nazwie test.php. Gdy teraz sprawdzimy tak przygotowany plik np.: funkcją getimagesize(), to jako parametr mime dostaniemy odpowiednio albo image/gif albo image/jpeg.

Dlaczego tak się dzieje? Otóż parser PHP w każdym pliku przekazanym do niego przeszukuje binarną papkę pod kątem tagów <?php ?> – nie ma dla niego znaczenia z jakim plikiem ma do czynienia.

Jak się zabezpieczyć?

Najprostszym rozwiązaniem jest sprawdzenie rozszerzenia pliku. Niestety – nie jest to w 100% pewne, gdyż wystarczy, że niefrasobliwy użytkownik/administrator, doda do listy plików parsowanych przez parser PHP pliki *.jpg/*.gif (jak dla mnie to głupie, ale mozliwe) i już dziura gotowa.

Lepszym rozwiązaniem jest zapisywanie plików poniżej public_html i z pomocą mod_rewrite serwowanie ich użytkownikowi jakimś skryptem PHP. Przykładowy kod wyglądałby tak (zakładam, że użytkownik jako adresy do plików widzi /uploads/files/plik.rozszerzenie:

.htaccess
<ifModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^uploads/(.*) viewer.php?file=$1 [L]
</ifModule>
viewer.php
/**
 * Plik wspomagający upload plików
 */
$mimes	= array('gif'	=> 'image/gif',
		'jpg'	=> 'image/jpeg',
		'jpeg'	=> 'image/jpeg',
		'jpe'	=> 'image/jpeg',
		'pdf'	=> 'application/pdf',
		'png'	=> 'image/png',
		'swf'	=> 'application/x-shockwave-flash',
		'tif'	=> 'image/tiff',
		'tiff'	=> 'image/tiff');
list( $dir, $file ) = explode( '/', $_GET['file'] );
// sprawdzam folder i nazwę pliku
$ok	= ( $dir == 'files' || $dir == 'thumbs' || $dir == 'avg' ) && preg_match( '!^[a-z0-9_.-]{5,20}$!',$file );
// jak folder i plik OK, to - jeśli plik istnieje - posyłam go do usera
if( $ok && file_exists( $f = 'uploads/' . $dir . '/' . $file ) ) {
	// typ mime - jak to grafika/pfd/swf, to wysyłam z odp. typem, aby można było od razu plik wyświetlić
	$ext 	= substr( $file, strrpos( $file, '.' ) + 1 );
	if( array_key_exists( $ext, $mimes ) ) {
		header( "Content-type: " . $mimes[$ext] );
	} else {
		header( "Content-Type: application/download");
		header( "Content-Transfer-Encoding: binary");
		header( "Content-Disposition: attachment; filename=\"" . $file . "\"" );
	}
	readfile( $f );
	die();
} else {
	header( "HTTP/1.1 404 Not Found" );
}
die();

Jednak ten sposób ma znów inny minus – może zdrowo obciążyć serwer przy wielu wywołaniach. A może ktoś zna jeszcze jakiś inny, lepszy, sposób?

Wpis jest luźnym tłumaczeniem części poradnika bezpiecznego uploadowania plików znalezionego na Scanit.be

Tags: ,
  1. May 23rd, 2010 at 23:17 | #1

    Hmm, wystarczy przeszukać zawartość pliku pod kątem obecności kodu zaraz po uploadzie. ;) Konkretniej, szukamy bloku otwierającego skrypt.

  2. May 23rd, 2010 at 23:40 | #2

    Masz rację :-)

    Pozdrawiam

  3. pc
    May 24th, 2010 at 15:47 | #3

    A nie lepiej stworzyć .htaccess z
    RemoveHandler .cgi .shtm .shtml
    RemoveType .php .php3 .php4 .php5
    w tym katalogu?

  4. May 25th, 2010 at 11:59 | #4

    A nie jest przypadkiem tak, że niektóre instrukcje .htaccess można odgórnie wyłączyć? Wtedy byłby problem. Dodatkowo istnieje problem, jeśli aplikacja jest podatna na atak typu directory traversal – propozycja eRIZa wydaje się po prostu najbardziej kompletna.

    Pozdrawiam

  1. August 6th, 2009 at 10:33 | #1

Powered by WP Hashcash