Autor | Zpráva | ||
---|---|---|---|
Kuba5 Profil |
#1 · Zasláno: 16. 8. 2014, 14:21:04
Zdravím, dělám malý skoro soukromý image hosting (jen screeny ze hry) a potřebuji se zeptat zkušenějších na bezpečnost. Potřeboval bych zkontrolovat zdroják, protože mám na VPS vyplý safe mod, takže se v PHP dá dostat kamkoliv a je to dost nebezpečné.
Tady je zdroják (adresy jsem přepsal): <?php $koncovky = array('jpg', 'jpeg'); $chyba = ""; if(isset($_POST['nahrat'])){ if (!$_FILES || $_FILES["obrazek"]["error"] == UPLOAD_ERR_INI_SIZE) { echo("<center><b><span style='color:blue'>Maximální velikost je " . ini_get('upload_max_filesize') . ".</span></b></center>\n"); } elseif ($_FILES["obrazek"]["error"] == UPLOAD_ERR_NO_FILE) { echo("<center><b><span style='color:red'>Nevybrali jste soubor, který chcete nahrát.</span></b></center>\n"); } elseif ($_FILES["obrazek"]["error"]) { echo("<center><b><span style='color:blue'>Soubor se nepodařilo nahrát, kontaktujte prosím správce serveru.</span></b></center>\n"); } elseif (!in_array(strtolower(pathinfo($_FILES["obrazek"]["name"], PATHINFO_EXTENSION)), $koncovky)) { echo("<center><b><span style='color:blue'>Koncovka souboru musí být jedna z: " . implode(", ", $koncovky) . ".</span></b></center>\n"); } elseif (!($imagesize = getimagesize($_FILES["obrazek"]["tmp_name"])) || $imagesize[2] > 3) { echo("<center><b><span style='color:blue'>Typ obrázku musí být JPG.</span></b></center>\n"); } else { $uploaddir = "http://neco.eu/upload/images/"; $name_ciste = $_FILES["obrazek"]["name"]; $name = mt_rand(11111, 99999) . $_FILES["obrazek"]["name"]; move_uploaded_file($_FILES["obrazek"]["tmp_name"], "upload/$name"); echo "<center><img src='$uploaddir$name' /></center>"; echo("<center></br>Obrázek " .$name_ciste. " byl nahrán. </br>Tento text vlož do příspěvku:</br>test<br><br><br></center>"); } } ?> <html> <center><form method="post" enctype="multipart/form-data"> <input type="file" name="obrazek" multiple="multiple" /> <br><br><input type="submit" name="nahrat" value="Nahrát" /> </form> <? /*SCRIPT PRO ZJISTENI VELIKOSTI SLOZKY*/ $dirname='upload'; if ($handle = opendir($dirname)) { $cv=0; while (false !== ($file = readdir($handle))) { if ($file != "." && $file != "..") { $file="$dirname/$file"; $velikost=filesize ( $file )/1048576; $cv+=$velikost; } } closedir($handle); } echo "<br><br>Velikost nahraných obrázků: ".round($cv,2)." MB"; /*--------------------------------------*/ ?> </center> </html> Je tento kód bezpečný? Nejdou uploadnout jiné formáty než .jpg, takže by neměl jít nahrát žádnej script, ale i tak mám strach, protože jak jsem už psal, vše běží na soukromém VPS, takže musím být opatrný. Díky |
||
ProbablyYes Profil |
Zdravím,
Jestli nechceš, aby někdo nahrál potenciálně nebezpečný soubor - jednoduše přehazuj koncovku na vlastní - např. txt. |
||
Kuba5 Profil |
#3 · Zasláno: 16. 8. 2014, 15:04:59
ProbablyYes:
Ok, ale u fotek nic nehrozí ne? Jinak díky. |
||
Virtus Profil |
Zdravím,
Čistě teoreticky, než přehodit koncovku na vlastní je lepší zkontrolovat hlavičky souboru, protože přehození koncovky nemusí pomoct, v momentě kdy si totiž vyžádáž soubor, nezávisle na koncovce, s ním systém pracuje podle hlaviček jaké si soubor nese, takže přesto, že potenciální útočník soubor po nahrátí nebude schopen spustit (protože jej přejmenuješ), spustíš si jej sám až si jej od systému vyžádáš. Pro kontrolu hlaviček jpg sem našel, že se dá v PHP použít funkce http://php.net/manual/en/function.exif-read-data.php, ale zkušenosti s ní nemám. |
||
Jan Tvrdík Profil |
#5 · Zasláno: 16. 8. 2014, 15:22:57
Virtus:
„Čistě teoreticky, než přehodit koncovku na vlastní je lepší zkontrolovat hlavičky souboru“ Nesmysl, radíš hodně nebezpečné věci. Bezpečnost uploadu stojí téměř vždy primárně na kontrole koncovky, protože podle ní se webový server obvykle rozhoduje, jak soubor zpracuje. Validní obrázek může zároveň obsahovat PHP kód. |
||
Virtus Profil |
Jan Tvrdík:
Ano proto tam je to: Čistě teoreticky. Možná sem v tomhle směru lechce paranoidní, protože přesně tímto způsobem se mi na server dostali zaškodníci, i když to teda bylo přes nahrané video, od tý doby ratši kontroluju hlavičky a mám svatý klid. A zhruba tak jednou za půl roku narazím na serveru nato, že to někdo takto zkouší, nic méně jedná se o stránky, kde je návštěvnost +- 1M unikátních přástupů za den. Takže s tebou tady souhlasím, že zde je něco takového zbytečné a nebezpečné. Omlouvám se tedy, pokud sem uved někoho v omil a za zbytečné informace ;) |
||
Kuba5 Profil |
#7 · Zasláno: 16. 8. 2014, 16:11:59
Díky za rady, u obrázků to asi zatím nechám být.
Ale potřeboval bych poradit, jak zakázat obnovení stránky (F5) po uploadu, dá se tak snadno "spamovat". Nějaké rady? Třeba timer je dobré řešení, ale potřeboval bych i zakázat to obnovení. |
||
Virtus Profil |
#8 · Zasláno: 16. 8. 2014, 16:23:37
Dle mého názoru je nejlepší použít php header();. Odešlu obrázek, zpracuju obrázek, a pomocí PHP po zpracování reloadnu na nějakou stránku, v tvém případě by to mohla být třeba stránka s nahraným obrázkem.
header("Location: http://www.example.com/"); exit; |
||
Jan Tvrdík Profil |
Kuba5:
Po každém úspěšném POST požadavku by mělo následovat přesměrování. Virtus: „Možná sem v tomhle směru lechce paranoidní“ Na tom není nic paranoidního, to je normální blbost. Obsah souboru klidně zkontrolovat můžeš, ale kontrolu koncovky (za předpokladu normálního prostředí) vynechat prostě nelze. To pomýlím skutečnost, že podle mě, je i zbytek toho příspěvku (ta vysvětlující část) nesmysl, ale třeba je jenom nesmyslně napsaný nebo já nechápavý. |
||
Kuba5 Profil |
#10 · Zasláno: 16. 8. 2014, 18:44:01
Přesměrování je nejlepší řešení, díky.
Přes $_GET budu obsah proměnné $name přenášet na novou stránku ($name je název souboru i s ID). |
||
Chro Profil |
Kuba5 [#1]:
Co se týče bezpečnosti uploadu obrázků, je dobré detekovat, zda soubor obsahuje EXIF informace. Pokud ano, tyto informace ze souboru extrahovat a uložit zvlášť (pokud je potřebuješ) a fotografii převzorkovat pomocí GD knihovny - tím se EXIF odstraní. Pokud ne, soubor zkontrolovat na přítomnost řetězců jako <script, <?php a <?[mezera] a v případě nálezu fotku převzorkovat v GD funkcemi imagecreatefrom... a imagejpeg, imagepng, imagegif. Varovnou hlášku o možné přítomnosti škodlivého kódu v souboru bych nezobrazoval, běžný uživatel moc nepochopí o co jde, útočníka bych ponechal v blaženém pocitu vítězství, a soubor bych nenápadně zkontroloval a případně ošetřil. EDIT: V případě provozování imagehostingu zaměřeného nejen na screeny z her, je odstraňování EXIFu z fotografií priorita č. 1. Uživatel může např. nahrát fotku z domova a neuvědomit si, že obsahuje GPS souřadnice jeho bydliště. |
||
Jan Tvrdík Profil |
#12 · Zasláno: 17. 8. 2014, 11:06:33
Chro:
„fotografii převzorkovat pomocí GD knihovny“ To je dobrý postup. „soubor zkontrolovat na přítomnost řetězců jako <script, <?php a <?[mezera]“ To je blbost, proč by obrázek takové sekvence bytů nemohl obsahovat? |
||
Str4wberry Profil |
#13 · Zasláno: 17. 8. 2014, 11:14:41
Je třeba si uvědomit, že není nebezpečné, když se nebezpečný kód dostane na server — nebezpečné je, až když takový kód jde spustit, čemuž v drtivé většině případů zabrání ta koncovka.
|
||
Chro Profil |
#14 · Zasláno: 17. 8. 2014, 16:41:41
Jan Tvrdík [#12]:
Útočník může např. vložit škodlivý PHP kód začínající "<?php" do jpg obrázku třeba do položky Title v EXIFu a nahrát na web přes formulář. Obrázek i tak bude stále nepoškozený a zobrazitelný. Nebo do gifu, byť tím obrázek možná poškodí, ale kdo si toho všimne? Když se pak dostane k nastavení serveru a povolí vykonávání PHP v souborech s příponou jpg nebo gif, vznikne problém. |
||
Kuba5 Profil |
#15 · Zasláno: 17. 8. 2014, 16:47:14
No vzhledem k tomu, že EXIF informace nepotřebuju a jsou mi k ničemu, chtěl bych je pomocí GD rovnou v obrázku mazat a obrázek pak hned uploadnout.. může mi někdo poradit s kódem? Nikdy jsem se s GD knihovnou nesetkal a už jsem několik let s PHP nepracoval, takže mám teď často problémy. Potřebuji odstranění EXIF dostat do tohoto kódu:
<?php $koncovky = array('jpg', 'jpeg'); $chyba = ""; if(isset($_POST['nahrat'])){ if (!$_FILES || $_FILES["obrazek"]["error"] == UPLOAD_ERR_INI_SIZE) { echo("<center><b><span style='color:blue'>Maximální velikost je " . ini_get('upload_max_filesize') . ".</span></b></center>\n"); } elseif ($_FILES["obrazek"]["error"] == UPLOAD_ERR_NO_FILE) { echo("<center><b><span style='color:red'>Nevybrali jste soubor, který chcete nahrát.</span></b></center>\n"); } elseif ($_FILES["obrazek"]["error"]) { echo("<center><b><span style='color:blue'>Soubor se nepodařilo nahrát, kontaktujte prosím správce serveru.</span></b></center>\n"); } elseif (!in_array(strtolower(pathinfo($_FILES["obrazek"]["name"], PATHINFO_EXTENSION)), $koncovky)) { echo("<center><b><span style='color:blue'>Koncovka souboru musí být jedna z: " . implode(", ", $koncovky) . ".</span></b></center>\n"); } elseif (!($imagesize = getimagesize($_FILES["obrazek"]["tmp_name"])) || $imagesize[2] > 3) { echo("<center><b><span style='color:blue'>Typ obrázku musí být JPG.</span></b></center>\n"); } else { $uploaddir = "http://example.com/test/images/"; $name_ciste = $_FILES["obrazek"]["name"]; $name = mt_rand(11111, 99999) . $_FILES["obrazek"]["name"]; move_uploaded_file($_FILES["obrazek"]["tmp_name"], "images/$name"); header("Location: http://www.example.com/test/image.php?id=".$name.""); exit; } } ?> Děkuji |
||
Chro Profil |
#16 · Zasláno: 17. 8. 2014, 17:05:16
Před header s přesměrováním vlož:
if (exif_read_data("images/$name", 0, TRUE) !== FALSE) { $img = imagecreatefromjpeg("images/$name"); imagejpeg($img, "images/$name", 85); //85 je kvalita obrázku, smysl má zadávat cca 75-90 } |
||
Jan Tvrdík Profil |
Chro:
„Když se pak dostane k nastavení serveru…“ …tak budeš mít mnohem, mnohem větší problémy, než nějaký upload obrázků. Ke úpravě konfigurace serveru obvykle potřebuješ root práva. A pokud má útočník root práva, tak to je prostě game over =) |
||
Kuba5 Profil |
#18 · Zasláno: 17. 8. 2014, 17:35:23
Díky moc. Takže se normálně nahraje obrázek s případným EXIF info, přesune se i s EXIF do složky images (viz move_uploaded_file) a až poté script (ten, který jsi napsal) zkontroluje, zda se v obrázku nachází EXIF kód a pokud ano, tak vytvoří nový obrázek se stejným názvem (akorát už bez EXIF) a přepíše ten původní s EXIF?
Pak můžu normálně zobrazovat obrázek s původním názvem? Ještě pro jistotu přidám zdroják pro kontrolu.. <?php $koncovky = array('jpg', 'jpeg'); $chyba = ""; if(isset($_POST['nahrat'])){ if (!$_FILES || $_FILES["obrazek"]["error"] == UPLOAD_ERR_INI_SIZE) { echo("<center><b><span style='color:blue'>Maximální velikost je " . ini_get('upload_max_filesize') . ".</span></b></center>\n"); } elseif ($_FILES["obrazek"]["error"] == UPLOAD_ERR_NO_FILE) { echo("<center><b><span style='color:red'>Nevybrali jste soubor, který chcete nahrát.</span></b></center>\n"); } elseif ($_FILES["obrazek"]["error"]) { echo("<center><b><span style='color:blue'>Soubor se nepodařilo nahrát, kontaktujte prosím správce serveru.</span></b></center>\n"); } elseif (!in_array(strtolower(pathinfo($_FILES["obrazek"]["name"], PATHINFO_EXTENSION)), $koncovky)) { echo("<center><b><span style='color:blue'>Koncovka souboru musí být jedna z: " . implode(", ", $koncovky) . ".</span></b></center>\n"); } elseif (!($imagesize = getimagesize($_FILES["obrazek"]["tmp_name"])) || $imagesize[2] > 3) { echo("<center><b><span style='color:blue'>Typ obrázku musí být JPG.</span></b></center>\n"); } else { $uploaddir = "http://example.com/test/images/"; $name_ciste = $_FILES["obrazek"]["name"]; $name = mt_rand(11111, 99999) . $_FILES["obrazek"]["name"]; move_uploaded_file($_FILES["obrazek"]["tmp_name"], "images/$name"); if (exif_read_data("images/$name", 0, TRUE) !== FALSE) { $img = imagecreatefromjpeg("images/$name"); imagejpeg($img, "images/$name", 85); } header("Location: http://www.example.com/test/image.php?id=".$name.""); exit; } } ?> Stránka, kde se zobrazí uploadnutý obrázek i s odkazem vypadá asi takto: <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <body> <?php if(isset($_GET["id"])) { $uploaddir = "test/images/"; $name = $_GET["id"]; echo "<center><img src='$uploaddir$name' /></center>"; echo("<center></br>Obrázek " .$name. " byl nahrán. </br>Tento text vlož do příspěvku:</br>test<br><br><br></center>"); } ?> </body> </html> Nicméně po uploadu mám tento warning: Warning: Cannot modify header information - headers already sent by (output started at /var/www/upload/test/index.php:7) in /var/www/upload/test/index.php on line 35 Fotka se nahraje, ale bohužel se logicky neprovede přesměrování. |
||
Jan Tvrdík Profil |
#19 · Zasláno: 17. 8. 2014, 17:44:57
|
||
Kuba5 Profil |
Ok, vyřešeno. Myslel jsem, že bude muset jít ven echo, ale problém byl HTML kód před <?php.
Ještě bych se chtěl zeptat, jak je to teda s EXIF.. (víc horní příspěvek se zdrojákama) Konkrétně, co provede ten script od "Chro". Díky |
||
Dusann Profil |
Chro:
„imagejpeg($img, "images/$name", 85); //85 je kvalita obrázku, smysl má zadávat cca 75-90“ GD nie je na znovu-ukladanie JPEG ideálne, pretože nepoznáš hodnotu kompresie z pôvodného obrázku. Skončíš teda buď s obrázkom ktorého veľkosť súboru je väčšia a bez pridanej (ak nie horšej) kvality, alebo zbytočne degraduješ kvalitu pôvodného obrázku viac ako je nutné. Musel by si binárnou cestou extrahovať z metadát údaj o JPEG kompresii pôvodného JPG a použiť ho pri znovu-uložení aby si dosiahol minimálnu stratu kvality obrázku + zachoval približne pôvodnú veľkosť súboru. Pre znovu-uloženie JPEG je preto lepšie použiť Imagick v ktorom vieš jednoducho získať hodnotu JPEG kompresie z načítaného image resource. Inak najideálnejšie by samozrejme bolo stripovať ASCII metadáta z obrázku a nie kvôli tomu znovu-ukladať obrázok druhý krát, keďže vzniká zbytočná rekompresia. Nepoznám však žiadne riešenie pre PHP ktoré by stripovalo textové metadáta z obrázku bez toho, aby došlo k znovu-uloženiu. Jedine ak by bol nejaký expert ktorý by bol schopný vytvoriť vlastnú funkciu, ktorá by toto riešila binárnou úpravou súboru. Každopádne, pokiaľ sa pri znovu-uložení JPEG použije rovnaká hodnota kompresie, dôjde k minimálnej strate kvality, ktorú na prvý pohľad snáď ani nie je možné rozoznať (pokiaľ použitý JPEG enkoder pracuje podobne kvalitne ako enkoder pri prvom uložení ), takže riešenie jedného znovu-uloženia JPEG obrázku je myslím OK. Znovu-ukladanie som v Imagicku testoval a nespozoroval som pri rôznych obrázkoch žiadne badateľné degradácie ak som použil rovnakú hodnotu kompresie ako mal obrázok pôvodne. |
||
Kuba5 Profil |
#22 · Zasláno: 17. 8. 2014, 19:41:54 · Upravil/a: Kuba5
No nejde mi až tak o kvalitu, budou tam asi jen screeny z jedné hry a ta nejspíš žádné EXIF informace nemá, takže se nemá co znovu uložit.
Mně jde hlavně o bezpečnost, už jsem uvažoval, že ten uploader hodím na nějaký free webhosting, ale zase nechci riskovat smazání poskytovatelem, i když screeny bych mohl celkem jednoduše přes cron zálohovat právě na to VPS, akorát bych musel znemožnit spuštění scriptu, kdyby se přeci jen nějaký v nějakém obrázku našel. Jak už jsem říkal, uploader běží na VPS, kde je zablokovaný safe mode a při hacknutí se útočník okamžitě dostane k rootu a to je můj konec. |
||
Jan Tvrdík Profil |
#23 · Zasláno: 17. 8. 2014, 20:44:14
Kuba5:
„akorát bych musel znemožnit spuštění scriptu, kdyby se přeci jen nějaký v nějakém obrázku našel.“ Ještě jednou, kdyby se to ztratilo v těch bludech, pokud zkontroluješ příponu souboru (což tebou uvedený skript dělá dobře), tak je ten skript bezpečný. PHP kód v obrázcích se správnou příponou nepředstavuje pro server bezpečnostní riziko. „kde je zablokovaný safe mode“ safe_mode je blbost, od PHP 5.4 je úplně zrušený. Jediné, co dává trochu smysl, je open_basedir. |
||
Časová prodleva: 11 let
|
0