Stažení souboru po ověření práv
Některé soubory můžeme chtít zpřístupnit jen přihlášeným uživatelům. Samozřejmě se dá napsat skript, který dostane název stahovaného souboru v parametru a pošle ho uživateli. V tom případě ale musíme zároveň zajistit, aby byl soubor fyzicky uložen v adresáři nepřístupném z webu, jinak hrozí, že uživatel stahovací skript obejde a stáhne si soubor bez přihlášení.
Pokud je seznam oprávněných uživatelů omezený, dá se autorizace vyřešit na straně Apache. Pokud jsou oprávnění uživatelé uložení v databázi, dá se použít buď neoficiální modul nebo můžeme adresu, na které jsou soubory skutečně uloženy, přepsat na autorizační skript pomocí mod_rewrite. To se také hodí v případě, kdy soubory byly v minulosti k dispozici bez ověření a toto ověření chceme dodatečně doplnit.
<?php
// RewriteRule ^data/ data.php [L,QSA]
$filename = substr($_SERVER["REDIRECT_URL"], 1);
if (!overit_pristup() || ($filename && (strpos($filename, "..") !== false || $filename[0] == "/" || !is_file($filename)))) {
header('WWW-Authenticate: Basic realm="data"');
header('HTTP/1.0 401 Unauthorized');
} elseif ($filename) {
header("Content-Type: application/octet-stream");
header("Content-Length: " . filesize($filename));
readfile($filename);
exit;
}
?>
Diskuse
Zdravím a posílám nástin alternativního řešení:
1) Ukládat informace o souborech při uploadu do databáze (název, přípona, typ a cokoliv dalšího)
2) Soubory fyzicky ukládat pod unikátním hashovaným názvem bez přípony, přičemž takový název je samozřejmě odvozen z databázového záznamu a je v zásadě neuhádnutelný.
3) Soubory stahovat prostřednictvím skriptu s parametrem jako např. soubor.php?cislo=[cislo_souboru] který nejdříve ověří práva uživatele a teprve poté mu soubor vydá:
<?php
// kontrola cache kvůli problémům s HTTPS v IE
session_cache_limiter('public');
header("Cache-Control: public");
header("Pragma: public");
// vrácení souboru
header("Content-Description: File Transfer");
header("Content-Length: ". $ulozena_velikost);
header("Content-Disposition: Attachment; filename=". $ulozeny_nazev .".". $ulozena_pripona);
@readfile($hashovany_nazev);
// znak @ je před funkcí readfile proto, aby nemohl být
// kompromitován název souboru ani při chybě skriptu
?>
Podotýkám, že při použití výše uvedeného postupu se ani přihlášený uživatel nemůže dostat ke skutečnému názvu souboru či souborů.
4.7.2008 03:19:00
1) Ukládáním souborů s hashovanými názvy je zbytečné a o mnoho to nezvyšuje bezpečnost nad tím, když byste ukládal soubory s nějakými normálními (nejlépe původními) názvy - jen se Vám zvyšuje nepřehlednost. Sice si zase v PHP můžete vytvořit správce, který Vám tyto soubory ukazuje s původními názvy, ale tím příklad kouzlo jednoduchosti a geniality.
2) Pokud Vám příjde lepší volání stazeni_souboru.php?soubor=253256, tak potom jste asi nepochopil nejméně třetinu Jakubových článků.
Proč nevolat např.: /soubory/smlouva_o_pronajmu.doc a programově si ošetřit ověřování pěkně na pozadí??? Dobře zabezpečený server vaše soukromí ochrání.
4.7.2008 03:58:51
Je to také možné řešení, ale není tak elegantní.
Navíc co mají všichni pořád s tím hašováním? Pokud chci nějaký náhodný identifikátor, tak přeci použiji náhodné číslo nebo náhodný řetězec, ne?
4.7.2008 04:35:05
Díky za připomínky, byl to jen nástin alternativního řešení, nic víc nic míň :-) Robo
4.7.2008 04:50:20
ady:
Predpokladam ze HASH pouzivaji v pripade kdy kuprikladu chteji, aby se stejny input vzdy prevedl na stejny hashovaci retezec, zatimco nahodne cislo vam pro vicekrat poslany totozny input vrati pokazde jiny result.
Takze pocitam, ze chteji udelat neco jako /download.php?jmeno_mojeho_souboru a ten aby sahal nekam do zvenku nepristupne slozky na soubor "muj_hash"
4.7.2008 05:22:02
V tom případě může být soubor uložen rovnou pod původním názvem. Robo to zmiňoval jako možnost skrytí původního názvu, na což je to nevhodné.
6.7.2008 14:56:12
qwe:
Taky mi připadá zbytečné hashovat nebo jinak názvy. Stačí soubory uložit do adresáře, kam je "Deny from all" z webu.
4.7.2008 06:07:33
MichaL:
Delam to stejne, je to OK ze nemusim resit kolizi pri uploadu souboru stejneho nazvu.
URL souboru je pak treba domena.cz/soubor/{hash}/{puvodni-nazev}
4.7.2008 06:27:19
Tady se ale jedná o hash obsahu, nikoliv názvu.
6.7.2008 15:00:39
Oldis:
kolizi nazvu resi uploadovany_soubor.jpg, id_z_db_ukladany_soubor_na_fs.jpg, hash mi prijde zbytecny
28.8.2008 22:04:01
Petr Kozelek:
Prováděl bych ještě detekci na $_SERVER['HTTP_RANGE'] v případě, že by uživatel chtěl navázat na předchozí přerušené stahování...tak ať se mu posílá jen část souboru, kterou požaduje.
<?php
$size = filesize($filename);
if(isset($_SERVER['HTTP_RANGE']))
{
list($a, $range)=explode("=",$_SERVER['HTTP_RANGE']);
//if yes, download missing part
str_replace($range, "-", $range);
$size2=$size-1;
$new_length=$size2-$range;
header("HTTP/1.1 206 Partial Content");
header("Content-Length: $new_length");
header("Content-Range: bytes $range$size2/$size");
}
else
{
$size2=$size-1;
header("Content-Range: bytes 0-$size2/$size");
header("Content-Length: ".$size);
}
?>
Více na
http://www.phpbuilder.com/board/showthread.php?threadid=10318152.
4.7.2008 06:17:00
Zdravím,
pokud mám třeba gigovej soubor, který takto zpřístupňuji (přes readfile()), znamená to, že skript běží po celou dobu stahování? I po uběhnutí (defaultních) 30 vteřinách, po kterém proces "típne" samotné PHPko?
5.7.2008 15:38:05
Přesně tohle jsem řešil. Jen v tomto případě byl problém s memory_limit :)
Nakonec sem to vyřešil a blognul o tom :) Neřikám, že tam ještě není někde nějaká chyba nebo exploit, ale byl oto to nejlepší, co sem z toho dostal ;)
https://blog.tomasfejfar.cz/zabezpecene-stahovani-souboru/8.7.2008 17:13:46
readfile() samo o sobě moc paměti nezabírá, protože data posílá rovnou na výstup. Problém by mohl spočívat v zapnutém output bufferingu.
9.7.2008 09:56:23
Milan:
Dá se takto přesměrovaný soubor kontrolovat pomocí file_exists()?
30.8.2008 12:25:35
Lokálně samozřejmě ano, vzdáleně ne – je to stejné jako s normálními soubory.
1.9.2008 03:52:24
Zdravím.
Jak je v daném příkladu řešeno ošetření proti podvržení jména souboru pokusem o escapování pomocí pravidel pro tvorbu URL? a) dá se podvrhnout takové jméno souboru, které by dokázalo obejít kontrolu na ".." , "/" a b) co soubor, který obsahuje znaky, které byly url-encodovány?
Díky
14.3.2009 07:13:00
Kontroluje se ta hodnota, která se následně používá pro získání názvu souboru. Takže pokud by útočník např. znak / zakódoval, tak se bude hledat soubor s názvem zakódovaného lomítka. Funkce readfile() už žádné dekódování nedělá.
Názvy souborů obsahujících speciální znaky se musí náležitě zakódovat. Např. k souboru a% se přistoupí přes URL a%25.
17.3.2009 03:55:51
Michal:
Zdravím po delší době. Mám problém s hlavičkou:
header("Content-Disposition: Attachment; filename=xxx.xxx");
Když dám jako filename soubor s mezeru v názvu, tak se za posledním znakem před mezerou ořízne, např "Mateřská školka.txt" a bude tam jen "Mateřská". Diakritiku to bere v pořádku. Neví někdo proč ?
6.1.2010 19:30:44
Emil:
Musíš to dát do uvozovek, např:
header("Content-Disposition: Attachment; filename=\"xxx.xxx\"");
zpětná lomítka escapují php rětězec
15.4.2010 21:55:46
Vozka:
Ahoj, je možné nějak zjistit, zda byl soubor stažen komplet celý, že nebylo stahování z nějakého důvodu přerušeno?
U nějakých download managerů jsem to viděl, ale nikde se mi nedaří najít jak je to dělané. :-(
Předem díky
4.4.2011 11:11:21
Pokud server pošle hlavičku Content-Length, tak stačí její obsah porovnat s velikostí staženého souboru. Pokud se používá Transfer-Encoding: chunked, tak se to dá poznat při stahování podle posledního chunku (měl by být nulový).
5.4.2011 20:55:45