Kirjautuminen

Haku

Tehtävät

Keskustelu: Nettisivujen teko: Tiedoston lataus PHP-skriptillä

Sivun loppuun

askomyyrä [14.08.2009 00:14:59]

#

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!

Grez [14.08.2009 00:31:16]

#

Vaihdat tälle riville haluamasi nimen:

header("Content-Disposition: attachment; filename=\"".$fileName."\"");

askomyyrä [14.08.2009 22:52:20]

#

Juu, näinhän se tosiaankin toimii.

Kiitoksia.

Contraband [15.08.2009 01:10:23]

#

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.

askomyyrä [15.08.2009 02:16:25]

#

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
}

OILgame [15.08.2009 02:34:02]

#

Vaihtaisin rivin 2 vaan tälläiseksi.

<?php
$fileName = str_replace("/", "", $_GET['download']);
?>

askomyyrä [15.08.2009 02:38:53]

#

Ok,

Kiitti.

Eli toi meinaa juuri sitä, ettei kansioissa pysty menemään taaksepäin? ..eli toi korvaa kansioiden / -merkit tyhjällä merkillä..

Contraband [15.08.2009 13:28:29]

#

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.

punppis [15.08.2009 13:48:11]

#

Kaikista helpoin ratkaisu olisi tehdä tietokanta, jossa nuo tiedostonimet on tallessa ja sitten ladata niitä id-numeron perusteella.

askomyyrä [22.08.2009 16:25:07]

#

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/24301-php-kirjautuminen-rekisteröityminen-mysql-llä

..ja sivun alku koostuu aina include("ylaosa.php"); ja sivun alaosa taas tuosta include("alaosa.php"); -liitteistä.

map_ [22.08.2009 23:30:38]

#

Jaa yläosa-HTML:n tulostus ja muut alkutoimenpiteet erillisiin tiedostoihin.

askomyyrä [23.08.2009 01:25:24]

#

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.

Grez [23.08.2009 09:31:51]

#

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ö}


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta