Anti-XSS : Méthode de vérification de contenu normalisé

Ceux qui développent des sites Internet, ou plus simplement les personnes qui suivent l'actualité on déjà entendu parler de Cross-site scripting (abrégé XSS... vous aussi vous cherchez le X dans Cross ?).
Le principe est simple : arriver à faire exécuter un script non prévus par une application. Par exemple un pirate souhaitant aider les personnes agées qui auraient du mal à retenir leur mot passe... (!)

La faille la plus courante est l'affichage directe d'une variable entrée par l'utilisateur. Le PHP étant un langage "à la volé", l'entrée utilisateur peut être interprétée comme du code PHP ou JavaScript et être exécuté.
Dans la plus part des cas un htmlspecialchars($_GET['var 1']) est suffisant.
Ce problème réside d'une loi qui peut paraitre radicale, mais qui se révèle très adapté à l'informatique : Tout ce qui n'est pas prévus est potentiellement dangereux.
Rien ne nous prouve qu'un utilisateur ne va jamais écris un chiffre au lieu d'une lettre, ou pire, un script au lieu d'un simple texte.
Dans cet article nous allons voir plusieurs méthode automatiques de vérification du contenu des variables. Elles ont pour but de remplacer les méthodes manuelles qui sont sources d'oublis.

Méthode manuelle fonctionnelle :

Cette méthode est relativement courante, elle nécessite de "passer" toutes les variables dans des fonctions de vérifications. Il faut donc créer une fonction pour chaque type : checkInt(), checkHtml(), checkUrl()...

Par exemple pour checkInt() : nous pouvons utiliser le code :

checkInt($entier) {
  return intval($entier);
}
ou avec une expression régulière :
checkInt($entier) {
  if(preg_match('/^[0-9]+$/', $entier))
    return $entier;
  else
    return 0;
}
Dans ces deux cas l'entrée d'un valeur autre qu'un chiffre retournera 0.

Méthode singulière :

Cette méthode est la plus simple à mettre en place car elle ne nécessite pas la modification du code. Cependant le script ne doit pas contenir deux variables de même nom contenant des choses différentes.
Cette méthode est dite singulière car les règles sont fixées individuellement pour chaque variable.

Exemple : Ce code doit être appelé en début de programme :

# Tableau de règles
$type_var = array(
  'id' => '/^[0-9]+$/',
  'level' => '/^[0-9]{4}$/',
  'name' => '/^[a-zA-Z]+$/'
);

# Vérification des variables reçues en $_GET :
foreach ($_GET as $key => $value)
  if(!preg_match($type_var[$key],$value) || isset($type_var[$key])) 
    unset($_GET[$key]);
Dans cette solution nous vérifions si les variables sont bien présentes dans le tableau et si leur contenu correspond bien à la règle. Dans le cas échéant nous supprimons les variables.
Cette solution permet de ne pas oublier de variable, mais peut cependant devenir très lourde avec un grand nombre de variables.

Méthode typée :

Cette méthode part d'un constat simple : il n'est pas toujours possible de répertorier toutes les variables. Nous allons donc mettre un place un système de typage complet.
Adaptons l'exemple précédent avec cette méthode :

# Tableau de règles
$type_var = array(
  '_int' => '/^[0-9]+$/',
  '_4int' => '/^[0-9]{4}$/',
  '_alpha' => '/^[a-zA-Z]+$/'
);

# Vérification des variables reçues en $_GET :
foreach ($_GET as $key => $value)
  if(!preg_match($type_var[strrchr($key,"_")],$value))
    unset($_GET[$key]);
Avec cette solution les variables de l'exemple précédent id, level, name deviendraient id_int, level_4int, name_alpha.
Cette méthode nous oblige à renommer toutes la variables du projet mais peut s'avérer terriblement efficace. Ici nous supprimons les variables non valides, mais rien ne nous empêche de stocker dans notre tableau une valeur de remplacement par défaut. Il serait aussi possible de mélanger la méthode singulier avec la méthode typée. Cela permettrait de traiter à part des variables sans que le type ne soit dans son nom.

Méthode typée : Le cas particulier de l'URL :

Un URL est un cas particulier de notre Méthode typée, car il devient difficile de définir une regex pour une URL quand elle contient des paramètres. Nous allons donc analyser toutes les variables de l'URL, à l'aide de notre tableau. Nous ajoutons un type _url très peu restrictif qui fait un pré-tri.

$_GET["url_url"] = "http://amoweb.fr/index.php?i_int=31d2&c_alpha=abcde&b_int=332";

# Tableau de règles
$type_var = array(
  '_int' => '/^[0-9]+$/',
  '_4int' => '/^[0-9]{4}$/',
  '_alpha' => '/^[a-zA-Z]+$/',
  '_url' => '/^http(s)?:\/\/[-\/.\w?&]{0,64}$/i'

);

# Vérification d'une url :

# Extraction de la partie URL :
$url = explode("?",urldecode($_GET['url_url']), 2);
$_GET['url_url'] = $url[0] . "?";

# Vérifie chaque variable passée en paramètre
foreach (explode("&",$url[1]) as $param) {
	$varGET = explode("=",$param);
	# On fabrique l'URL avec les paramètre valides : dont le contenu correspond au type
	if(preg_match($type_var[strrchr($varGET[0],"_")],$varGET[1]))
		$_GET['url_url'] .= $varGET[0] . "=" . $varGET[1] . "&";
}

# Supprimer le caractère final : soit '?' soit '&'
$_GET["url_url"] = substr($_GET["url_url"], 0, -1);

# Et voilà le travail :
echo $_GET["url_url"]; # http://amoweb.fr/index.php?c_alpha=abcde&b_int=332

Je suggère aussi

2 commentaires

#1 jeudi 14 octobre 2010 @ 00:51 Azrielo a dit :

Salut !
Fait rapidement la mise a jours vers PluXML 5.0.2, il y a une faille XSS justement en cause ;).
Info sur le site !

Azrielo

#2 jeudi 14 octobre 2010 @ 14:21 amoweb a dit :

Merci @Azrielo, je suis au courant, j'ai fais les modification nécessaire, je n'ai juste pas mis à jours mon fichier "version"... À très bientôt :-)

Les commentaires sont fermés.