Bezpečnější upload souborů pomocí php

Jednou z nejčastěji vyžadovaných funkcí snad v každé aplikaci je nahrávání souborů. U webů, kde nejde o peníze, na zabezpečení asi příliš nesejde, avšak tam, kde by případné napadení mělo nepříjemné důsledky, je nutné postupovat opatrně – vyplatí se být paranoidní.
Po nahrání souboru na server je vytvořena dočasná kopie a superglobální pole $_FILES. Problém s bezpečností může nastat například při manipulaci položek tohoto pole, což zkušený útočník zvládne. Proto se nedoporučuje spoléhat se při zpracování dat na toto pole, ale využít funkci getimagesize(), která jediná bezpečně potvrdí, že se jedná o obrazová data a ne o podstrčený zákeřný skript. Nastavení přístupových práv u složek je samozřejmostí a nebudu je zde rozebírat. Při napadení webu je to průšvih administrátora – ne programátora.

Podívejme se nejprve blíže na formulář pro odeslání souboru. Data budeme odesílat metodou POST. Při nahrávání souborů musí být přítomný také atribut enctype=“multipart/form-data“. Chcete-li omezit velikost nahraného souboru, lze přidat i skrytý element s názvem MAX_FILE_SIZE (velikost nastavte v bytech; více viz. POST method uploads). Některé prohlížeče s tím umí pracovat a upozorní na příliš velký soubor. Jinak samozřejmě velikost souboru ošetřujeme také v php.

index.php

Po stisknutí tlačítka Odeslat bude soubor předán skriptu upload.php. Na serveru se automaticky vytvoří dočasná kopie a globální pole $_FILES, které se skládá z pěti položek:

  1. $_FILES[„soubor“][„name“]   // originální název souboru
  2. $_FILES[„soubor“][„type“]   // MIME typ souboru
  3. $_FILES[„soubor“][„size“]    // velikost souboru
  4. $_FILES[„soubor“][„tmp_name“]   // umístění dočasného souboru na serveru
  5. $_FILES[„soubor“][„error“]   // chybový kód výsledku uploadu

Namísto $_FILES[„soubor“][„type“] můžete zjistit skutečný typ obrázku pomocí funkce getimagesize(). Tento postup je lepší nejen kvůli bezpečnosti, ale také pro funkčnější chod skriptu v případě, kdy MIME typ předává některá z nemocných verzí Internet Exploreru.

Zároveň se mohou hodit i údaje o velikosti obrázku v pixelech ($width, $height).
V době psaní článku bylo „uzákoněno“ 17 rozpoznatelných kódů typů obrazových souborů. Několik prvních ze seznamu zde uvádím:

1 IMAGETYPE_GIF
2 IMAGETYPE_JPEG
3 IMAGETYPE_PNG
4 IMAGETYPE_SWF
5 IMAGETYPE_PSD
6 IMAGETYPE_BMP

Zbývající kódy typů souborů naleznete na stránce http://www.php.net/manual/en/function.exif-imagetype.php .

Testovat, zda jde doopravdy o obrazový soubor můžete jednoduše i takto:

Je také dobré testovat, zda nebyla překročena maximální povolená velikost přednastavená serverem, nicméně pokud píšete aplikaci pro konkrétní server, stačí kontrolovat velikost souboru.

 

Po stisknutí tlačítka Odeslat je soubor odeslán skriptu upload.php:

upload.php

Podmínka if( !empty($_FILES[„soubor“]) ) otestuje, zda vůbec data dorazila.
Další podmínkou … if( $type == IMAGETYPE_JPEG || $type == IMAGETYPE_PNG ) … zjišťujeme, zda byl nahrán námi povolený typ souboru. Zároveň je tím zaručeno, že jde skutečně o obrazová data. Na webu objevíte většinou jen postupy, kde se typ souboru získává z pole $_FILES a sice z $_FILES[„soubor“][„type“]. Útočník by však mohl podstrčit php skript a pokud by nebylo na složce zakázáno vykonávání skriptů, mohl by ovládnout celý web.

V následujícím kroku pouze odstraníme diakritiku z názvu souboru (ukládat soubory včetně diakritiky samozřejmě lze, ale je to trochu pracnější na obsluhu) a pomocí cyklu while otestujeme, zda soubor náhodou již ve složce není. Pokud tam je, přidáme za název číslici. Nakonec slepíme dohromady název + číslici + tečku + příponu a předáme název funkci move_uploaded_file(), která přesune dočasný soubor $_FILES[‚soubor‘][‚tmp_name‘] do požadovaného umístění s požadovaným názvem souboru. To je vše. Zbytek kódu obsahuje pouze dodatečné testy, které pomáhají především při ladění.