Sécurité en forçant le téléchargement de .png

Eléphanteau du PHP | 21 Messages

04 avr. 2011, 13:48

Bonjour,
J'ai cherché pas mal de temps comment forcer le téléchargement d'un fichier .png
J'ai trouvé finalement, mais je me demande si ce code est sécuritaire ?

Ma page telecharger.php :
<?php
$filepath = "./dossier/";
$filename = $_GET["filename"];
header("Content-Type: application/force-download" );
header("Content-Transfer-Encoding: image/png\n" );
header("Content-disposition: attachment; filename=$filename" );
header("Content-Length: ".filesize($filepath. $filename));
header("Pragma: no-cache" );
header("Cache-Control: must-revalidate, post-check=0, pre-check=0, public" );
header("Expires: 0" );
readfile($filepath. $filename );
?>
Mon lien :
<?php
$imapng = "$urlphoto/$fichiersansex.png";
$ima = "$fichiersansex.png";
if( file_exists( $_SERVER{'DOCUMENT_ROOT'} . "$imapng"))  {
echo '<a href="telecharger.php?filename='.$ima.'" title="Haute qualit&eacute; 300 dpi"><img src="hq.png" width="19" height="11" alt"" /></a>';

} 
?>
Merci !
Modifié en dernier par Doubrovski le 05 avr. 2011, 10:44, modifié 2 fois.

Eléphanteau du PHP | 21 Messages

04 avr. 2011, 16:16

J'ai édité mon message, car j'ai trouvé une solution (j'avais oublié d'ajouter un point devant l'adresse du dossier comme ./dossier/)
Je me demande toujours si ce code ne représente pas une faille de sécurité pour mon site ?

ViPHP
xTG
ViPHP | 7331 Messages

04 avr. 2011, 18:08

Ton premier code est non sécurisant car tu ne vérifies pas l'existence du fichier passé en paramètre.
Il faut vérifier qu'il peut être télécharger (dans un espace web accessible et autorisé).

Eléphanteau du PHP | 21 Messages

05 avr. 2011, 01:15

Merci.
J'ai même modifié un peu mon lien pour ne pas avoir une page de téléchargement par dossier, et donc c'est encore moins sécurisant j'imagine.
Je fais passer une autre variable dans le lien pour faire passer le nom du dossier.

Un exemple pour vérifier l'existence du fichier ? Je vais essayer.

EDIT :
Ça irait comme ça ?
<?php
$filepath = $_GET["filepath"];
$filename = $_GET["filename"];
if (file_exists($filepath. $filename ))
{
header("Content-Type: application/force-download" );
header("Content-Transfer-Encoding: image/png\n" );
header("Content-disposition: attachment; filename=$filename" );
header("Content-Length: ".filesize($filepath. $filename));
header("Pragma: no-cache" );
header("Cache-Control: must-revalidate, post-check=0, pre-check=0, public" );
header("Expires: 0" );
readfile($filepath. $filename );
}
else
{
echo = "erreur";
}
?>
Lien :
<?php
$tele = $_SERVER['SCRIPT_NAME'];
$teleco = substr("$tele", 0, -4);
$telecopo = ".$teleco/";
$imapng = "$urlphoto/$fichiersansex.png";
$ima = "$fichiersansex.png";
if( file_exists( $_SERVER{'DOCUMENT_ROOT'} . "$imapng"))  {
echo '<a href="telecharger.php?filename='.$ima.'&filepath='.$telecopo.'" title="Haute qualit&eacute; 300 dpi"><img src="hq.png" width="19" height="11" alt"" /></a>';

} 
?>
-->> Ca ne marche pas... j'arrive à télécharger mon index.php en trafiquant l'url.

ViPHP
AB
ViPHP | 5818 Messages

05 avr. 2011, 04:13

Merci.
J'ai même modifié un peu mon lien pour ne pas avoir une page de téléchargement par dossier, et donc c'est encore moins sécurisant j'imagine.
Je fais passer une autre variable dans le lien pour faire passer le nom du dossier.

Un exemple pour vérifier l'existence du fichier ? Je vais essayer.

EDIT :
Ça irait comme ça ?
<?php
$filepath = $_GET["filepath"];
$filename = $_GET["filename"];
if (file_exists($filepath. $filename ))
{
header("Content-Type: application/force-download" );
header("Content-Transfer-Encoding: image/png\n" );
header("Content-disposition: attachment; filename=$filename" );
header("Content-Length: ".filesize($filepath. $filename));
header("Pragma: no-cache" );
header("Cache-Control: must-revalidate, post-check=0, pre-check=0, public" );
header("Expires: 0" );
readfile($filepath. $filename );
}
else
{
echo = "erreur";
}
?>
Lien :
<?php
$tele = $_SERVER['SCRIPT_NAME'];
$teleco = substr("$tele", 0, -4);
$telecopo = ".$teleco/";
$imapng = "$urlphoto/$fichiersansex.png";
$ima = "$fichiersansex.png";
if( file_exists( $_SERVER{'DOCUMENT_ROOT'} . "$imapng"))  {
echo '<a href="telecharger.php?filename='.$ima.'&filepath='.$telecopo.'" title="Haute qualit&eacute; 300 dpi"><img src="hq.png" width="19" height="11" alt"" /></a>';

} 
?>
-->> Ca ne marche pas... j'arrive à télécharger mon index.php en trafiquant l'url.
Ah ben oui la vérification faut la faire juste avant le code de chargement, dans le lien ça sert à rien.
Tester si un fichier existe ne suffit pas (index.php existe comme tu as pu le constater).
Il faut vérifier que ton fichier se trouve dans une liste de fichiers autorisés et pareil pour le dossier si tu transmet le nom du dossier.

Te plante pas dans tes vérif sinon c'est une faille de sécurité majeure.

Eléphanteau du PHP | 21 Messages

05 avr. 2011, 10:38

Oui, mais dans le lien la vérification à une autre fonction : afficher ou non l'icone de download (si une image .png existe dans le dossier). Elle n'est pas liée à la sécurité.
Mais ok, c'est bien ce que je pensais, c'est dangereux...

J'ai trouvé un autre script plus sécurisant qui peut m'aider à comprendre, mais qui a pour moi deux problèmes :
-Il ne fonctionne pas du internet explorer
-il ne permet pas de choisir le dossier par variable --> et j'ai peur de mettre un simple $filepath = $_GET["filepath"];

Voici ce script :
<?php

/** path du répertoir contenant les fichiers (à éditer) **/
define('DOWNLOAD_PATH', dirname(__FILE__).DIRECTORY_SEPARATOR.'photos-arbres'.DIRECTORY_SEPARATOR);

/** on récupère le nom du fichier demandé **/
if(isset($_GET['file']))
$file = $_GET['file'];
elseif(isset($_POST['file']))
$file = $_POST['file'];
else
$file = '';

/** tant qu'a faire évitons les attaques par directory transversal **/
$file = str_replace(array('../','..\\'),'',$file);

/** la variable est elle vide, le fichier existe ? **/
if(empty($file))
exit('Please select a file for download !');
elseif(!is_file(DOWNLOAD_PATH.$file))
exit('Requested file not found !');

function getMimeType($file)
{
$mimes = array(
'hqx' => 'application/mac-binhex40',
'doc' => 'application/msword',
'dot' => 'application/msword',
'bin' => 'application/octet-stream',
'lha' => 'application/octet-stream',
'lzh' => 'application/octet-stream',
'exe' => 'application/octet-stream',
'class' => 'application/octet-stream',
'so' => 'application/octet-stream',
'dll' => 'application/octet-stream',
'pdf' => 'application/pdf',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
'smi' => 'application/smil',
'smil' => 'application/smil',
'wbxml' => 'application/vnd.wap.wbxml',
'wmlc' => 'application/vnd.wap.wmlc',
'wmlsc' => 'application/vnd.wap.wmlscriptc',
'xla' => 'application/vnd.ms-excel',
'xls' => 'application/vnd.ms-excel',
'xlt' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'csh' => 'application/x-csh',
'dcr' => 'application/x-director',
'dir' => 'application/x-director',
'dxr' => 'application/x-director',
'spl' => 'application/x-futuresplash',
'gtar' => 'application/x-gtar',
'php' => 'application/x-httpd-php',
'php3' => 'application/x-httpd-php',
'php5' => 'application/x-httpd-php',
'phtml' => 'application/x-httpd-php',
'js' => 'application/x-javascript',
'sh' => 'application/x-sh',
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'tar' => 'application/x-tar',
'tcl' => 'application/x-tcl',
'xhtml' => 'application/xhtml+xml',
'xht' => 'application/xhtml+xml',
'xhtml' => 'application/xml',
'ent' => 'application/xml-external-parsed-entity',
'dtd' => 'application/xml-dtd',
'mod' => 'application/xml-dtd',
'gz' => 'application/x-gzip',
'zip' => 'application/zip',
'au' => 'audio/basic',
'snd' => 'audio/basic',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'kar' => 'audio/midi',
'mp1' => 'audio/mpeg',
'mp2' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'aif' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'm3u' => 'audio/x-mpegurl',
'ram' => 'audio/x-pn-realaudio',
'rm' => 'audio/x-pn-realaudio',
'rpm' => 'audio/x-pn-realaudio-plugin',
'ra' => 'audio/x-realaudio',
'wav' => 'audio/x-wav',
'bmp' => 'image/bmp',
'gif' => 'image/gif',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'jpe' => 'image/jpeg',
'png' => 'image/png',
'tiff' => 'image/tiff',
'tif' => 'image/tif',
'wbmp' => 'image/vnd.wap.wbmp',
'pnm' => 'image/x-portable-anymap',
'pbm' => 'image/x-portable-bitmap',
'pgm' => 'image/x-portable-graymap',
'ppm' => 'image/x-portable-pixmap',
'xbm' => 'image/x-xbitmap',
'xpm' => 'image/x-xpixmap',
'ics' => 'text/calendar',
'ifb' => 'text/calendar',
'css' => 'text/css',
'html' => 'text/html',
'htm' => 'text/html',
'asc' => 'text/plain',
'txt' => 'text/plain',
'rtf' => 'text/rtf',
'sgml' => 'text/x-sgml',
'sgm' => 'text/x-sgml',
'tsv' => 'text/tab-seperated-values',
'wml' => 'text/vnd.wap.wml',
'wmls' => 'text/vnd.wap.wmlscript',
'xsl' => 'text/xml',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
'avi' => 'video/x-msvideo',
);

if(false === ($pos = strrpos($file,'.')))
return isset($mimes[$file]) ? $mimes[$file] : 'application/force-download';

$ext = substr($file,$pos+1);

return isset($mimes[$ext]) ? $mimes[$ext] : 'application/force-download';
}

/**** on désactive les erreurs ****/
error_reporting(0);
/**** on désactive la compression en sortie ****/
ini_set('zlib.output_compression', 0);
$now = time();

/*** Gestion du cache ***/
header('Pragma: public');
header('Last-Modified: '.gmdate("D, d M Y H:i:s").' GMT');
header('Cache-Control: must-revalidate, pre-check=0, post-check=0, max-age=0');

/**** Informations sur la réponse HTTP elle-même ****/
header('Date: '.gmdate("D, d M Y H:i:s", $now).' GMT');
header('Expires: '.gmdate("D, d M Y H:i:s", $now+1).' GMT');
header('Last-Modified: '.gmdate("D, d M Y H:i:s", $now).' GMT');

/**** Informations sur le contenu à envoyer ****/
header('Content-Tranfer-Encoding: none');
header('Content-Length: '.filesize(DOWNLOAD_PATH.$file));
header('Content-Type: '.getMimeType($file).'; name="'.$file.'"');
header('Content-Disposition: attachement; filename="'.$file.'"');

/**** pour finir lecture du fichier ****/
readfile(DOWNLOAD_PATH.$file);
exit();

?>

ViPHP
AB
ViPHP | 5818 Messages

05 avr. 2011, 16:43

ça à l'air correct à première vue.

Mais bon cela te permet apparemment de télécharger n'importe quel fichier contenu dans un répertoire déterminé. Si tu n'as besoin que de certains types de fichiers (images par exemples), limites les possibilités de téléchargement à ceux là (genre tu vérifies si l'extension du fichier est une extension que tu autorise au téléchargement).
...et j'ai peur de mettre un simple $filepath = $_GET["filepath"];
c'est une bonne réaction d'avoir peur, l'inverse serait plutôt inconscient.

Mais bon tu peux faire passer ton dossier dans l'url à condition de vérifier s'il fait partie d'une liste de dossiers autorisés (comme déjà dit).

Par exemple
$dos_autorise = array('photos-arbres','photo-chats', 'photos-chiens');

if (!in_array($_GET['dossier'],$dos_autorise)) exit('dossier non autorisé pour le téléchargemnt');

Eléphanteau du PHP | 21 Messages

06 avr. 2011, 13:52

Merci, j'ai mélangé un peu mon script initial avec celui trouvé ci dessus, et comme tu m'as conseillé j'ai limité l'extension des fichiers téléchargeables à .png
Il ne me reste qu'a autoriser ou non les dossiers, mais sur quelle page je devrais mettre cette ligne ?
$dos_autorise = array('photos-arbres','photo-chats', 'photos-chiens');
Si je l'insère sur la page telecharger.php avant la condition qui vérifie le dossier et avant l'envoi du téléchargement, ça ne pose pas problème ? (simple doute, mais je pense que non : sinon la variable ne serait pas transmise à la page telecharger.php)

Voilà le code actuel :
<?php

/** on récupère le nom du fichier demandé **/
if(isset($_GET['filename']))
$filename = $_GET['filename'];
elseif(isset($_POST['filename']))
$filename = $_POST['filename'];
else
$filename = '';

/** on récupère le nom du dossier demandé **/
if(isset($_GET['filepath']))
$filepath = $_GET['filepath'];
elseif(isset($_POST['filepath']))
$filepath = $_POST['filepath'];
else
$filepath = '';

/** tant qu'a faire évitons les attaques par directory transversal **/
$filename = str_replace(array('../','..\\'),'',$filename);

/** tant qu'a faire évitons les attaques par directory transversal **/
$filepath = str_replace(array('../','..\\'),'',$filepath);

/** la variable est elle vide, le fichier existe ? **/
if(empty($filename))
exit('Aucun fichier s&eacute;lectionn&eacute; !');
elseif(!is_file($filepath.$filename))
exit('Fichier introuvable !');

/** la variable est elle vide, le fichier existe ? **/
if(empty($filepath))
exit('Aucun dossier s&eacute;lectionn&eacute; !');
elseif(!is_dir($filepath))
exit('Dossier introuvable !');

$extensions_ok = array ( ".png");
if (in_array(strtolower(substr($filename, -4)),$extensions_ok))
   {
header("Content-Type: application/force-download" );
header("Content-Transfer-Encoding: image/png\n" );
header("Content-disposition: attachment; filename=$filename" );
header("Content-Length: ".filesize($filepath. $filename));
header("Pragma: no-cache" );
header("Cache-Control: must-revalidate, post-check=0, pre-check=0, public" );
header("Expires: 0" );
readfile($filepath. $filename );
   }
?>

ViPHP
AB
ViPHP | 5818 Messages

06 avr. 2011, 18:25

Il ne me reste qu'a autoriser ou non les dossiers, mais sur quelle page je devrais mettre cette ligne ?
$dos_autorise = array('photos-arbres','photo-chats', 'photos-chiens');
Si je l'insère sur la page telecharger.php avant la condition qui vérifie le dossier et avant l'envoi du téléchargement, ça ne pose pas problème ? (simple doute, mais je pense que non : sinon la variable ne serait pas transmise à la page telecharger.php)
Bien entendu qu'il faut mettre ça dans telecharger.php, justement pour que ta liste soit indépendante de toute variable transmise.

Eléphanteau du PHP | 21 Messages

06 avr. 2011, 18:58

Merci de votre aide, je pense que le code est assez sécurisé pour le moment !
Vérif du dossier + vérif de l'extension du fichier c'est pas mal.
Bonne soirée

ViPHP
AB
ViPHP | 5818 Messages

07 avr. 2011, 02:14

Attention tu ne vérifie pas le dossier dans le code précédent, tu vérifie juste qu'il existe.
De sorte que si tu avais un dossier protégé par un .htaccess dans lequel tu mettrais par exemple des images .png confidentielles, et bien on pourrait les télécharger !