Checklist sécurité pour les applications web en PHP
En quelques mots
Petite check-list contenant les vulnérabilités les plus communes des applications web et les solutions et bonnes pratiques applicables en PHP. Bien qu’axée sur le PHP cette checklist peut être étendue à tous les langages de programmation.
Validation des entrées
Conseils:
- pendant le développement la configuration
error_reporting = E_ALL
(sans enlever lesE_NOTICE
) permet de reconnaître les variables non initialisées; en production les erreurs ne devraient pas être affichées dans l’interface web; - les instructions
filter_var ()
etfilter_input ()
sont très pratiques pour filtrer et valider des entrées; - l’utilisation de l’égalité de contenu et de type
===
permet d’éviter des erreurs lors de la comparaison avec des booléens. L’expression(0 == false)
est vraie alors que(0 === false)
est fausse ; - le casting peut être plus rapide que le test (comparer la fonction
is_numeric
à une conversion de type (int) $valeur); - méfiez-vous de la variable super globale
$_REQUEST
car elle peut contenir des valeurs de plusieurs sources différentes ; - vérifiez l’existence de différentes entrées à l’aide de
isset
, comme par exempleisset ($ _ GET ['id'])
; - faites aussi attention à certains champs dans
$_SERVER
comme$_SERVER ['HTTP_REFERER'];
- vu que l’on ne peut pas faire confiance aux navigateurs passez
$_FILES ['file'] ['name']
par la fonctionbasename
lors d’un upload de fichiers ; (imaginez$_FILES ['file'] ['name'] = '../../../etc/passwd'
) - vérifiez le contenu des fichiers uploadés. Les images devraient être vérifiées avec la fonction
getimagesize()
qui renvoie false quand le fichier n’est pas de ce type. Pensez aussi à l’extension fileinfo ; - ne pas accepter des objets sérialisés en entrée.
En général il est conseillé d’utiliser le concept de liste blanche pour de nombreux cas de validation.
Pour avoir une couche de protection supplémentaire ou pour protéger une application déjà existante pensez aux ajouts « PHP Input Filter » et « PHPIDS » ou « mod_security » si vous utilisez Apache
Injection SQL
Utiliser des requêtes SQL brutes en concaténant les variables à la chaîne de la requête est une mauvaise pratique qui peut facilement mener à l’injection de SQL non désiré.
Conseils :
- Utiliser des requêtes paramétrées (prepared statements). En PHP la manière la plus facile de traiter l’échappement des variables est d’utiliser une couche d’abstraction comme PDO.
- Les requêtes paramétrées sont accessibles aussi pour certaines bases de données par des fonctions de base de PHP (postgresql par exemple, voir pg_prepare) ou avec des bibliothèques étendues comme mysqli.
- Si la concaténation s’avère impossible à éviter il est impératif d’échapper les variables à l’aide de
fonctions du type « (real_)escape_string ». Pour mysql, par exemple, renseignez-vous sur la
fonction
mysql_real_escape_string
. Il est utile d’ajouter que cette fonction est déconseillée par le manuel PHP, mais elle peut être utile pour rapidement sécuriser une vieille application. - Évitez à tout prix les techniques automatiques du type
magic_quotes
et/ou génériquesadd_slashes
. Chaque base de données réagira de manière différente à ces méthodes d’échappement.
Cross-site scripting (XSS)
Consiste en l’introduction de javascript en entrée qui sera alors exécuté lors de l’affichage. Il s’agit d’une vulnérabilité très commune et dangereuse, bien qu’elle soit facile à éviter dans beaucoup de cas. Vous trouverez une liste assez complète des attaques possibles sur le site de l’OWASP.
- transformer tous les caractères des variables affichées en entités html. En PHP il suffit donc souvent
de passer toutes les variables affichées par la fonction
htmlentities
. Certains « templating systems » comme « flexy » font un échappement automatique; - s’il faut passer du HTML dans les variables, il est possible d’utiliser la fonction strip_tags pour
filtrer les tags permis. Cette fonction est néanmoins dangereuse à cause de l’injection de javascript
dans les arguments permis ; exemple :
<img onmouseover="alert(‘XSS’);" src="" />
ou<a href="javascript:alert(‘XSS’);">link</a>
. Il faut donc en général créer sa propre fonction de filtre en addition àstrip_tags
; - ne bonne manière de filtrer le html est la classe « html purifier » (https://htmlpurifier.org/, licence LGPL).
En général, il faut écrire une fonction générique d’affichage, qui devra être impérativement utilisée lors de tout affichage.
Injection de code
Injection de code malicieux qui sera exécuté par l’application.
- l’utilisation de variables dans les instructions
include
ourequire
est à proscrire. S’il est impossible de l’éviter, il est important d’utiliser des filtres par liste blanche ; - éviter à tout prix ou traiter avec grand respect l’instruction « eval » ; (alternatives (https://www.php.net/manual/de/language.variables.variable.php),
closures (https://www.php.net/manual/de/functions.anonymous.php))
et la fonction
call_user_func()
) - dans php.ini mettre
allow_url_fopen
àoff
si la fonction n’est pas nécessaire. - éviter les variables dans les fonctions du type
preg_replace
.
Injection d´instructions
Certaines fonctions permettent d’exécuter des instructions système.
- évitez les variables dans les instructions du type
shell_exec
,exec
,system
,passthru
,popen
; - s’il est impossible de les éviter, utilisez impérativement les fonctions
escapeshellarg()
etescapeshellcmd()
; - utilisez des listes blanches ainsi que la fonction
basename
sur les noms de fichiers.
Sécurité de la session
En volant l’identifiant de la session, il est possible d’utiliser la session d’autres utilisateurs. Voir « firesheep » par exemple.
- évitez l’identifiant de session dans l’URL car un autre site pourrait voler l’identifiant en vérifiant
le « referer », voire fixer l’identifiant de votre session (imaginez un lien vers un site du
type
lien?PHPSESSID=123
). Mettre doncuse_only_cookies
à1
ouOn
dansphp.ini
; - utiliser uniquement des communications SSL (TLS), sinon le cookie de session peut être intercepté sur
des réseaux hostiles. Obliger le cookie de session à être communiqué uniquement en SSL :
session.cookie_secure=1;
- définir un moment d’expiration pour les sessions;
- associez les dresses IP des visiteurs à leurs sessions.
Cross-Site Request Forgery (XSRF)
Consiste en l’utilisation de la session ouverte dans un autre tab du navigateur par une page web malicieuse.
Le plus souvent les parties vulnérables seront les formulaires, qui pourraient être remplis par un site
malveillant. Dans ce cas il est facile d’éviter ce genre d’attaque en créant un identifiant unique pour le
formulaire et contrôlant cet identifiant lors de la soumission. Une bonne pratique consiste à utiliser un
nonce crée par une fonction du type uniqid ou pseudoaléatoire haché avec un code secret en
utilisant la fonction hash_hmac
, que l’on met dans un champ hidden du
formulaire ainsi qu’en session, et qui sera vérifié lors de la soumission.
Pour protéger les fonctionnalités en dehors des formulaires, il faudra faire une analyse au cas par cas.
Bonnes pratiques pour le stockage des mots de passe utilisateurs dans la base de données
Les mots de passe doivent être stockés de manière hachée dans la base de données. Pour cela il ne faut surtout par utiliser des algorithmes du type md5 ou sha1 pour lesquels il existe des « rainbow tables », qui permettent l’obtention rapide du mot de passe à partir du hash.
De plus, pour justement éviter l’utilisation de « rainbow tables », et pour masquer les hashs qui seraient identiques car issus du même mot de passe, il est important d’utiliser un « salt ». Ce « sel » sera déterminé de manière aléatoire et concaténé au mot de passe avant hachage. On stockera ensuite le sel ainsi que le hash dans la base de données.
Il est important aussi de rajouter un « salt » constant secret qui serait stocké ailleurs que dans la base, par exemple dans un fichier de configuration.
L’utilisation d’un salt concaténé peut avantageusement être remplacée par une fonction de
hachage hash_hmac
, mais nous recommandons l’utilisation de la fonction crypt
(https://php.net/manual/en/function.crypt.php)
qui permet d’ajouter un facteur de coût au calcul quand on utilise CRYPT_BLOWFISH
, CRYPT_SHA256
ou CRYPT_SHA512
(voir key stretching).
Au final le calcul de hash aura par exemple la forme suivante :
$hash=crypt(hash_hmac('sha512','secret password',$saltsecretconfig), '$2y$’.$cost.'$randomsaltDB$') ;
La variable $cout devra être ajustée à un nombre tel que la durée de l’opération soit viable pour votre application mais prohibitive pour une attaque brute-force (1/100 secondes par exemple).
PHP 5.5 introduit des nouvelles fonctions de hashage qui permettront de stocker de manière sûre les mots de passe (voir https://www.php.net/manual/en/function.password-hash.php).
La fonction ci-dessus devient alors:
$hash = password_hash(hash_hmac(‘sha512’, ‘secret password’, $saltsecretconfig), PASSWORD_BCRYPT, ['cost'=>$cost]); // from PHP 5.5
Utilisation de contremesures
En général une application web sera scannée à l’aide d’un outil automatique par un attaquant avant compromission. De ce fait, il est possible de parsemer son application de pièges à ours où autres « tar pits ». Un exemple pratique appelé « weblabyrinth » peut être téléchargé surhttp://www.mayhemiclabs.com/content/new-tool-weblabyrinth. Mais des scripts inutiles contenants des fonctions « sleep » ou autres fausses authentifications peuvent déjà faire l’affaire.
Webroot != Approot
Avoir ses bibliothèques et autres scripts dans des endroits inaccessibles directement depuis Internet est d’un avantage certain. En cas de vulnérabilités de certaines bibliothèques elles ne peuvent pas être exploitées directement.
Déni de service
Il est impossible de se défendre de manière absolue contre les attaques de type « déni de service ». Il est néanmoins possible d’essayer d’optimiser son application pour qu’elle ait un meilleur répondant. Bien que cela dépasse le cadre de ce document voici quelques concepts que vous pourrez approfondir :
- n’utiliser « foreach » que pour les tableaux associatifs et évitez en général toute itération non bornée sur les données venant des bases de données ;
- utilisez un moteur de cache de code compilé (memcache, eaccelerator, Zend Platform,…) ;
- certains outils de débogage peuvent vous indiquer les goulots d’étranglements (xdebug, yslow, firebug, …)
- compressez votre javascript ;
- contrôlez quelles sont les requêtes lentes (
mysql_slow_query
) ; - éventuellement comprimer la communication entre le serveur et le client (uniquement si le poids des pages est conséquent et le CPU du serveur assez puissant) ;
- si vous utilisez Apache pensez au module mod_evasion.
Frameworks
Beaucoup de frameworks sont écrits par des développeurs chevronnés et profitent de leur expérience étendue, non seulement en termes de sécurité, mais aussi de bonnes pratiques de programmation. Ils incluent donc souvent beaucoup des techniques exposées ci-dessus et permettent le développement rapide et plus sûr d’applications web. Exemples : Zend, Symfony,…
Conseils pour php.ini
La fonction de configuration open_basedir
permet de définir les répertoires auxquels
l’application a accès et apporte donc une sécurité accrue.
Les paramètres suivants dans php.ini doivent être ajustés aux besoins de votre application tout en essayant
de les garder le plus bas possible. En général il est avantageux de régler ces paramètres à la volée avec
ini_set
dans les scripts qui auraient besoin de plus de ressources que le gros de l’application :
max_execution_time
memory_limit
post_max_size
upload_max_filesize
Vérifier cette valeur en environnement de production :
display_errors = Off