Web Parameter Tampering

mieux protéger une application internet

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
La règle d'or de sécurité sur le web consiste à ne jamais faire confiance à un tiers. Si l'on ne respecte pas cette condition, une nouvelle attaque qu'on va découvrir risque d'endommager notre application. Il s'agit du web parameter tempering.

Dans la première partie de l'article on verra de quoi on va parler. Ensuite, dans la seconde partie, on montrera comment une application web codée sans imagination, peut mettre en danger même de plus beaux business models. En dernier, on présentera une méthode de protection contre cette vulnérabilité.

Qu'est-ce que c'est web parameter tempering ?
Web parameter tempering correspond au trucage des paramètres de la requête. L'attaque consiste à modifier les paramètres de la requête attendues par l'application suite à l'affichage d'une page à l'utilisateur. Il peut s'agir, par exemple, de la modification d'un paramètre dans l'URL ou de la valeur d'un champ caché (input du type hidden).

Imaginons maintenant cette attaque en pratique. Supposons qu'on gère une boutique en ligne qui donne des codes promotionnels à un client sur 10. Les codes ne s'activent que pour les utilisateurs qui arrivent sur la page de confirmation. Un utilisateur malveillant est en train d'acheter plusieurs produits sur notre boutique. Il se retrouve sur le formulaire de confirmation dans laquel on utilise un champ caché is10Customer qui précise si le client mérite un code promotionnel. La valeur par défaut est 0. 1 est indiquée pour le client qui obtiendra le code. Pas difficile à voir, n'est-ce pas ? Notre utilisateur malveillant change la valeur de 0 à 1 et envoie le formulaire.

Si l'application est mal codée et aucune validation des valeurs envoyées n'est présente, le danger est réel. Notre boutique, censée de délivrer les codes promotionnels à 10% des clients, peut se retrouver à les donner à 30 ou 50% des acheteurs. En plus, si la marge était petite, les problèmes de rentabilité sont bien en vu.

Exemple du web parameter tempering
Illustrons maintenant notre cas de cette promotion dans une boutique par le code. On optera pour PHP à cause de sa consistance. Le test se fera sur une requête GET, aussi à cause de la facilité de manipulation :

<form method="get" action="#">
<p>You will buy a Smartphone for 100$.</p>
<input type="hidden" name="is10Customer" value="0" />
<input type="submit" name="send" value="Order now !" />
</form>

<?php
if (isset($_GET['send'])) {
$price = 100;
$isReducedPrice = (isset($_GET['is10Customer']) && (int)$_GET['is10Customer'] == 0) ? false : true;
if ($isReducedPrice) {
$price = $price - ($price*10/100);
}
echo "Final price is ".$price;
}


Si l'on envoie le formulaire sans rien changer, on aura un texte sur l'écran : Final price is 100. Cependant, il suffit de remplacer 0 par 1 dans l'URL http://localhost/tempering/?is10Customer=0&send=Order+now+!# afin de voir le prix réduit sur l'écran : Final price is 90. Comment remédier à ces dangers ?

Protection des tokens
Pour se protéger contre web parameter tempering, on peut utiliser un système des tokens générés sur la base des valeurs affichées dans les champs cachés ou dans les paramètres URLs. Idéalement, le token devrait être composé de la valeur initialement affichée dans le formulaire ainsi que du sel. Le sel est présent pour rendre la décodage du token plus difficile, voire même impossible. Le but de la manœuvre consiste donc à créer un token de sécurité, basée sur la valeur du sel et du champ caché du formulaire. Ensuite, après la soumission du formulaire, il faut comparer le token reçu via la requête à celui qui devrait être généré à partir des champs cachés du formulaire de la requête. Voici comment se présente le code modifié et sécurisé :

$salt = "teokgjhghdhgh";
?>

<form method="get" action="#">
<p>You will buy a Smartphone for 100$.</p>
<input type="hidden" name="is10Customer" value="0" />
<input type="hidden" name="key" value="<?php echo makeToken("0");?>" />
<input type="submit" name="send" value="Order now !" />
</form>

if (isset($_GET['send'])) {
$price = 100;
$isReducedPrice = (isset($_GET['is10Customer']) && (int)$_GET['is10Customer'] == 0) ? false : true;
$checkResult = checkKey();
if ($isReducedPrice && $checkResult) {
$price = $price - ($price*10/100);
} elseif (!$checkResult) {
echo "\$checkResult is false. User tried to tamper is10Customer parameter. ";
}
echo "Final price is ".$price;
}

function makeToken($value) {
global $salt;

return sha1($value.$salt);
}

function checkKey() {
global $salt;

return (isset($_GET['key']) && $_GET['key'] == makeToken($_GET['is10Customer']));

}


Désormais, si l'on tente de trafiquer les paramètres, sans connaître le bon token de sécurité, on verra le message d'erreur suivant :

$checkResult is false. User tried to tamper is10Customer parameter.Final price is 100.


La force de cette solution est le token qui peut être composé d'un ou plusieurs champs cachés, mais aussi d'autres éléments déterminant une fonctionnalité, comme les attributs GET ou les valeurs sauvegardées dans des cookies. D'ailleurs, plus de composants présents dans le token, plus de difficultés aura l'attaquant pour le deviner.

L'article a démontré que l'utilisation des tokens uniques ne s'applique pas que pour la protection contre CSRF. Cette idée peut aussi bien servir à un autre danger provoqué par le fait d'une confiance trop grande aux données provenant des internautes.
Bartosz KONIECZNY 01-07-2014 00:00 sécurité des applications web
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

Un problème avec la définition des valeurs par défaut pour input type checkbox ?

Si vous rencontrez un problème avec la définition des valeurs par défaut pour un champ du type checkbox sous Symfony2, assurez-vous de la conformité des types de ces valeurs. Par exemple, le code suivant ne va pas fonctionner (le checkboxes ne seront pas sélectionnés pour les valeurs indiquées) :

  private $gifts = array(1 => 'apple', 2 => 'orange');
  public function setPreferedGifts($value = array())
  {
    $vals = array();
    foreach($value as $v => $val)
    {
      $vals[] = $val;
    }
    $this->preferedGifts = $vals;
  }
Par contre, le code suivant fonctionnera correctement (les checkboxes seront sélectionnés pour des valeurs passées dans la boucle foreach) :
  private $gifts = array(1 => 'apple', 2 => 'orange');
  public function setPreferedGifts($value = array())
  {
    $vals = array();
    foreach($value as $v => $val)
    {
      $vals[] = (int)$val;
    }
    $this->preferedGifts = $vals;
  }
Pour résumer, si le tableau avec les choix ($gifts dans l'exemple) contient les clés qui sont des integers, les valeurs par défaut doivent aussi être des integers.