Moi,
Käytän seuraavanlaista php skriptiä, jolla käyttäjä voi ladata tiedostoja palvelimeltani. Tämä on ollut joku ihan valmis esimerkki copy-paste tyyliin jostain..
<?php $fileName = $_GET['download']; $file_extension = $_GET['ext']; $fileDir = "../tiedostot"; $fileName = ''.$fileName.'.'.$file_extension.''; $fileString=$fileDir.'/'.$fileName; // Muunnetaan tiedostonimet IE:lle kelvollisiksi if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) { $fileName = preg_replace('/\./', '%2e', $fileName, substr_count($fileName, '.') - 1); } if(!$fdl=@fopen($fileString,'r')) { } else { header("Cache-Control: "); // Jätetään tyhjäksi IE-ongelmien välttämiseksi header("Pragma: "); // Jätetään tyhjäksi IE-ongelmien välttämiseksi header("Content-type: Application/octet-stream"); header("Content-Disposition: attachment; filename=\"".$fileName."\""); header("Content-Transfer-Encoding: binary"); header("Content-length:".(string)(filesize($fileString))); sleep(1); $fdl=fopen($fileString, 'rb'); fpassthru($fdl); fclose($fdl); } ?>
Hienosti siis toimii, mutta nyt haluaisinkin niin, että serveriltä ladattava tiedosto vaihtaisikin nimeä siinä loppukäyttäjän selaimen "Save File" -ikkunassa.. eli tiedosto tulisi eri nimisenä loppukäyttäjälle, kuin se tiedosto serverin päässä oikeasti on.. onko tämä miten mahdollista?
Mod. lisäsi kooditagit!
Vaihdat tälle riville haluamasi nimen:
header("Content-Disposition: attachment; filename=\"".$fileName."\"");
Juu, näinhän se tosiaankin toimii.
Kiitoksia.
Askomyyrän skripti toimii hieman liiankin hyvin: asiansa osaava käyttäjä voi nimittäin ladata sen avulla palvelimelta minkä tahansa tiedoston, johon web-palvelimella on lukuoikeus.
skripti.php?download=../../../../home/askomyyra/paivakirja&ext=txt skripti.php?download=.&ext=/../../../../etc/passwd
Korjauksena kannattaisi rajata niiden merkkien joukkoa, joita sallitaan käytettävän tiedoston nimessä ja päätteessä. Tähän voi käyttää esimerkiksi säännöllisiä lausekkeita, joista Antti on kirjoittanut mainion oppaan.
Aivan, hyvä pointti taas tietoturvasta..
..meniskö se sitten näin, jos oletan että skriptillä voisi hakea vain kuva tiedostoja:
$fileName = $_GET['download']; $file_extension = $_GET['ext']; if (preg_match("/$file_extension/", "gif") || preg_match("/$file_extension/", "jpg")) { // ja sitten tähän väliin koko entinen koodi }
Vaihtaisin rivin 2 vaan tälläiseksi.
<?php $fileName = str_replace("/", "", $_GET['download']); ?>
Ok,
Kiitti.
Eli toi meinaa juuri sitä, ettei kansioissa pysty menemään taaksepäin? ..eli toi korvaa kansioiden / -merkit tyhjällä merkillä..
Pelkkä OILgamen ehdotus ei riitä korjaukseksi, sillä hyökkäystä ei ole pakko tehdä download-parametrin kautta. Lisäksi huolimaton download-muuttujan tarkistus aiheuttaa skriptiin erään toisenkin haavoittuvuuden. Myös periaatetasolla ei ole suositeltavaa ruvata "siivoamaan" selvästi väärää syötettä vaarattomaksi. Sen sijaan skriptin on syytä kieltäytyä käsittelemästä epäkelpoa syötettä.
Mainitsin jo aiemmassa viestissäni esimerkin, joka toimisi OILgamen ehdotuksesta huolimatta:
skripti.php?download=.&ext=/../../../../etc/passwd
Tiedostonnimi ja pääte yhdistetään skriptissäsi seuraavalla rivillä:
$fileName = ''.$fileName.'.'.$file_extension.'';
Kun itse tiedoston nimi olisi pelkkä piste ("."), pääte olisi ("/../../../../etc/passwd") ja skripti yhdistää nämä laittamalla pisteen noiden väliin, saadan lopputuloksena polku "../../../../../etc/passwd"
Esimerkkikorjaus voisi olla seuraavankaltainen. En ole testannut esimerkkiä, mutta idea tullee selväksi.
<?php if(!preg_match("/^[a-z0-9\\-_ ]+$/i", $fileName) || !preg_match("/^(gif|jpg)$/i", $file_extension)){ die("Malformed filename or extension, aborting..."); }?>
Koodinpätkä tarkistaa, onko tiedostopääte gif tai jpg (kirjainkoolla ei väliä). Lisäksi tarkistetaan, että tiedostonnimi voi sisältää vain kirjaimia a-z, numeroita, viivoja (-), alaviivoja (_) tai välilyöntejä. Lauseketta voi toki laajentaa, jos tiedostonnimien halutaan tukevan esim. ääkkösiä. Vainoharhaisimmat voisivat jättää virheilmoituksen kokonaan tulostamatta, jotta hyökkääjä saa mahdollisimman vähän tietoa skriptin toiminnasta.
Kaikista helpoin ratkaisu olisi tehdä tietokanta, jossa nuo tiedostonimet on tallessa ja sitten ladata niitä id-numeron perusteella.
Kiitoksia vastauksista. Parantelin hiukan tuota tietoturvaa noiden kommenttien perusteella.
..vielä kysyisin sellaista, että kun minulla on sivulla aina session tarkistus, niin miten voisin lisätä tähän lataamis skriptiin myös saman session tarkastuksen? Jos meinaan lisään sen ihan tavallisella tavalla, niin nuo headerin mime tiedot varmaankin jotenkin sekoaa(?) sillä sen jälkeen tulee vain roskaa ruutuun kun kokeilen ladata samalla skriptillä tiedostoja.
Eli käytän tätä valmista esimerkkiä sessionissa sivuillani:
https://www.ohjelmointiputka.net/koodivinkit/
..ja sivun alku koostuu aina include("ylaosa.php"); ja sivun alaosa taas tuosta include("alaosa.php"); -liitteistä.
Jaa yläosa-HTML:n tulostus ja muut alkutoimenpiteet erillisiin tiedostoihin.
Joo, näin mä olen ymmärtänytkin, että noi headerit ovat todella tarkkoja siitä, ettei niiden edessä ole mitään ylimääräistä koodia.
..mut, mä olen yrittänyt tätä nyt jakaa aika monella eri tavalla, mutta jos mä include:lla yhdistän sitten noi erilliset tiedostot, niin eikö se ole lopulta kuitenkin sama asia, kuin että ne ois samassa pötkössäkin?
En ainakaan toimimaan saanut.
askomyyrä kirjoitti:
Joo, näin mä olen ymmärtänytkin, että noi headerit ovat todella tarkkoja siitä, ettei niiden edessä ole mitään ylimääräistä koodia.
Ei niitä kiinnosta tippaakaan, onko niiden edellä koodia. Kunhan et aloita sivun lähettämistä käyttäjälle ennen niitä. Eli sivun headerit tietenkin lähetetään ennen varsinaista sivua, ja jos yrität muokata niitä vasta sen jälkeen kun ne on jo käyttäjälle lähetetty, niin olet auttamattomasti liian myöhässä.
askomyyrä kirjoitti:
jos mä include:lla yhdistän sitten noi erilliset tiedostot, niin eikö se ole lopulta kuitenkin sama asia, kuin että ne ois samassa pötkössäkin?
Kyllä periaatteessa on. Eli siis sama lopputulos tulisi jos yhdistäisit tiedostot yhdeksi korvaamalla:
include("tiedosto.php");
===>
?>{tiedosto.php sisältö}<?php
tai jos tiedostossa ei ole ?> lopetusta ===>
?>{tiedosto.php sisältö}
Aihe on jo aika vanha, joten et voi enää vastata siihen.