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;
« Cette protection peut être contournée puisque l’en-tête Origin est, comme tout en-tête HTTP, falsifiable. »
Apparemment non, cette entête est justement protégée par le navigateur et ne peut pas être modifiée.
Peut-être fais-tu allusion au fait que le navigateur lui-même crée cet entête ? C’est le cas effectivement. Cependant, tout entête HTTP, et quelle que soit la donnée, reste modifiable si tu places un proxy en interception, tel que Burp Suite. Tu peux donc fixer la valeur que tu souhaites dans l’entête Origin.