Un incident de sécurité ? Faites-vous assister : 01 47 28 38 39

Dans le cadre de ses activités de recherche, Intrinsec s’est intéressé aux fonctionnalités et risques introduits par l’arrivée d’HTML5. Ce billet traite des “Cross-Origin Resource Sharing” (CORS) dont la mauvaise utilisation pourrait avoir un impact important sur l’application et sur la société l’hébergeant.

Auparavant, par mesure de sécurité, des applications de domaines différents ne pouvaient pas communiquer de façon bidirectionnelle. Un domaine A pouvait ainsi envoyer des données à un second, B, mais ne pouvait pas obtenir de réponse. Plus précisément, B en fournissait bien, mais le navigateur la refusait, détectant qu’il ne s’agissait pas d’une communication intra-domaine.

Pour plus d’informations sur ces communications intra-domaine, se référer à la page Wikipédia du W3C sur la SOP.

L’API CORS est implémentée dans tous les moteurs de navigateurs majeurs (Gecko 1.9.1, Webkit, Trident 4.0, Presto) et fournit différentes directives via de nouveaux en-têtes HTTP (headers), notamment :

  • “Access-Control-Allow-Origin” : celui-ci permet d’indiquer au navigateur les domaines autorisés à recevoir une réponse du serveur.
  • “Access-Control-Allow-Credentials” : cet en-tête indique si les cookies doivent être transmis ou non dans la réponse.

Notons que, par mesure de sécurité, il n’est pas possible de positionner les en-têtes mentionnées précédemment aux valeurs suivantes dans une même réponse :
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Concrètement, comment utilise-t-on ces headers ?

Soit un serveur “S” dont le code est le suivant :
<?php
header("Access-Control-Allow-Origin: C");
echo "response";
?>

et soit un client “C” :
<script>
var http = new XMLHttpRequest();
http.open("GET", "S");
http.onload = function() {
var response = http.responseText;
document.write(response);
}
http.send();
</script>

Ici, C reçoit bien la réponse de S puisqu’il figure parmi les “origin” autorisées.

Exemples de mauvaise utilisation du header Access-Control-Allow-Origin ?

Autorisation universelle

Avec la configuration ci-dessous , la page autorise tout émetteur du COR à recevoir une réponse.
header("Access-Control-Allow-Origin: *");
// actions

Imaginons le cas de figure suivant : une entreprise dispose d’un intranet, uniquement accessible en interne, sur lequel la valeur du header “Access-Control-Allow-Origin” à un wildcard. Si un employé se connecte à un site malveillant depuis l’entreprise, ce dernier pourra par exemple effectuer une requête Ajax vers l’intranet et recevoir les données de ce dernier, via cette configuration.

Conclusion

Si l’application dispose d’un accès restreint, via un mécanisme d’authentification quelconque, il est impératif de donner une valeur précise à cet en-tête. Notons cependant que si ce n’est pas le cas, un wildcard peut être placé. Pour ces applications (ou portions) publiques, le W3C recommande d’ailleurs de les “ouvrir” aux CORS pour permettre à des applications de natures et d’auteurs différents de communiquer entre elles.

Accès contrôlé uniquement par l’en-tête Origin

Prenons un autre exemple de mauvaise utilisation. La page ci-dessous affiche deux informations différentes selon l’origin de la requête. Le contrôle d’accès à cette page est effectué en fonction de l’origine du navigateur.

if (isset($_SERVER["HTTP_ORIGIN"])) {
if ($_SERVER['HTTP_ORIGIN'] == "http://A") {
header('Access-Control-Allow-Origin: http://A');
echo "Confidential Data".
}
else {
echo "Denied".
}
}

Cette protection peut être contournée puisque l’en-tête Origin est, comme tout en-tête HTTP, falsifiable. Elle devrait être corrigée en n’acceptant que les requêtes transmettant le cookie authentifiant de l’utilisateur (avec le Credentials Flag, pour les requêtes Ajax par exemple). Si ce cookie est considéré comme valide et s’il y a présence d’un jeton (valide), la requête pourra alors être traitée.

Scénario d’attaque : CSRF “silencieuse”

Soient trois protagonistes : un utilisateur A ayant un compte sur un site B (et par conséquent un cookie de session) et un site malveillant O.
Celui-ci contient le script suivant :
request = new XMLHttpRequest();
request.open("GET","http://URL_B/service.php?buy=apple",true);
request.withCredentials = "true";
request.onreadystatechange = function() {
if (request.readyState == 4) {
var response = request.responseText;
document.getElementById("response").textContent = response;
}
}
request.send();

De ce fait, dès que l’utilisateur visite O, une requête sera envoyée à B, par le biais de A. L’attribut “withCredentials” fixé à true indique que le cookie de A sera transmis. En exécutant l’action “buy”, il y a donc eu CSRF sans que l’utilisateur ne la déclenche de lui-même.

Une protection d’un point de vue serveur contre ce type d’attaques est l’utilisation de jetons (tokens).

Pour rappel, un jeton, généré aléatoirement, est attribué à un utilisateur et stocké côté serveur, rattaché à l’objet représentant l’utilisateur. Il est aussi communiqué au client dans des cas comme celui étudié ci-dessus.
Ici, le jeton serait inséré dans le formulaire et, lors de sa validation, le serveur s’assurerait de son authenticité.

Pentesting

Comment vérifier qu’un site ou une application est vulnérable à une usurpation d’Origin ?
Dans un premier temps, il est nécessaire de connaître la valeur de l’en-tête Origin attendue par l’application. Les valeurs de celle-ci et du header sont souvent les mêmes.
Si les deux commandes ci-dessous diffèrent par le contenu du fichier téléchargé, alors l’application est vulnérable :
wget --header="Origin: BonDomaine" URL
wget --header="Origin: MauvaisDomaine" URL

Que faut-il en retenir ?
– Il est fortement déconseillé d’utiliser les wildcards dès lors qu’il y a restriction d’accès;
– cependant, si aucune authentification n’est nécessaire, il est possible de l’ouvrir via un wildcard;
– ne pas se fier uniquement à l’en-tête Origin : utiliser un système de cookies et de tokens;

Références

De la sécurité des CORS
Silent CSRF