Forge des requêtes inter-sites

This page is a translated version of the page Cross-site request forgery and the translation is 100% complete.

La falsification de requête inter-sites (en anglais Cross-site request forgery ou CSRF) est un type d’attaque qui utilise le comportement de mise en cache du navigateur pour exploiter des vulnérabilités dans la sécurité d’une application web.

Les attaques CSRF utilisent les données d’authentification mises en cache par le navigateur de la victime (données telles qu’un cookie, ou un nom d’utilisateur et mot de passe enregistrés) pour autoriser une requête HTTP malveillante. La requête HTTP malveillante peut être envoyée de différentes manières. Tant que les requêtes sont traitées par un navigateur qui a les données d’authentification en cache, une attaque CSRF peut être tentée. Voici des exemples :

  • les liens inclus dans les courriels ouverts dans un navigateur par la victime
  • des balises image intégrées dans un courriel formaté en HTML (images qui sont alors affichées dans le logiciel client de courriel)
  • des formulaires intégrés dans une page web
  • des requêtes de types AJAX faites par JavaScript

Lorsque l’attaque est réussie, elle peut permettre à l’attaquant de réaliser des actions sur le site web avec le même niveau de permissions que la victime.

Pour un point de vue plus large sur les attaques CSRF, vous pouvez consulter la page Wikipédia « Cross-site request forgery ».

Exemple

L'extrait de code suivant montre comment une attaque CSRF peut arriver. En supposant qu'il n'y ait pas d'autres contrôles ni de protections mises en place pour prévenir une attaque.

$user = $out->getUser();
$request = $out->getRequest();
if ( $user->isAllowed( 'delete' ) && $request->wasPosted() && $request->getCheck( 'delete' ) ) {
	$this->deleteItem( $request->getText( 'delete' ) );
}

Le code permet à un utilisateur de supprimer un élément dans un wiki, à condition qu'il dispose d'autorisations suffisantes et qu'il soit authentifié.

Si un utilisateur authentifié est trompé en visitant une page web externe sous le contrôle d'un attaquant, alors l'attaquant peut falsifier une demande et l'envoyer au wiki. Le wiki recevrait la demande, vérifierait les informations d'authentification, puis exécuterait la demande falsifiée comme si elle avait été faite par la victime.

Défendre MediaWiki contre les attaques CSRF

Nous réduisons le risque d'attaque CSRF dans MediaWiki en utilisant des jetons pseudo-aléatoires de défi. Ces jetons sont générés pour chaque page visitée par un utilisateur authentifié.

Lorsqu'un formulaire est soumis pour une page donnée, il doit inclure le jeton. Les soumissions qui n'incluent pas le jeton échoueront. Il est difficile pour les attaquants de deviner ou de voler ces jetons, ce qui rend les attaques CSRF plus difficiles.

Grâce à la politique de la même origine de JavaScript, les attaquants ne peuvent pas facilement utiliser JavaScript pour lire le jeton de défit du formulaire.

Implémenter les jetons de défi

Les jetons de défi doivent être ajoutés à tous les formulaires et toutes les API pouvant apporter des modifications (édition, mise à jour, suppression, blocage, etc.). Vous voudrez peut-être envisager de migrer votre code vers HTMLForm, qui prend en charge la protection CSRF.

Formulaires HTML

Pour ajouter les jetons, utiliser la méthode getEditToken() de l'instance User de la requête actuelle . Le code est similaire à :

function showForm( $out ) {
	...
	$user = $out->getUser();
	$out->addHTML( Html::hidden( 'token', $user->getEditToken() ) );
	...
}

function submitForm() {
	...
	if ( !$user->matchEditToken( $request->getVal( 'token' ) ) ) {
		... CSRF detected - stop the request right now ...
		return;
	}
	// Ok, continuer soumettre
	...
}

Chaque formulaire qui effectue une opération d'écriture doit être protégé de cette façon.

Les API

Si c'est une API qui réalise les opérations d'écriture, elle doit surcharger isWriteMode pour renvoyer true et needsToken pour renvoyer une chaîne indiquant le type correct de jeton (habituellement 'csrf'). Généralement, mustBePosted doit aussi renvoyer true dans ce cas.

Voir aussi