
CalderaForms 1.5.9.1 XSS (plugin WordPress) - tutoriel
CalderaForms 1.5.9.1 XSS (WordPress plugin) - tutorial
CalderaForm est un plugin WordPress qui permet de créer facilement des formulaires par glisser-déposer. Lors d'une activité récente, j'ai eu l'occasion de tester plusieurs portails, dont l'un hébergeait un formulaire de contact créé avec ce plugin. La configuration personnalisée de l'instance concernée m'a permis d'y trouver une vulnérabilité : étant donné sa nature simple, digne d'un manuel, je pense que c'est un bon prétexte pour illustrer certains mécanismes aux débutants.
But exclusivement pédagogique - n'utilisez pas ces informations pour tester une cible sans autorisation explicite ni à des fins illicites - ne vous jetez pas dans l'eau froide pendant la digestion - habillez-vous en oignon quand il fait chaud
Pour l'exploit complet :
https://www.exploit-db.com/exploits/44489/
Pour la CVE :
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-7747
Dans la configuration analysée, le formulaire était paramétré pour répondre avec un message de remerciement adressé à l'utilisateur, l'appelant par le prénom qu'il venait de saisir.
Pour reproduire l'environnement de test, installez localement une instance WordPress et installez le plugin CalderaForms version 1.5.9.1 (disponible ici ou ici).
Une fois installé, depuis la console d'administration WordPress > colonne de gauche > "Caldera Forms" > boutons du haut > "New Form" > sélectionnez Contact Form, renommez et "Create Form"

Une fois créé, vous pouvez modifier sa configuration : boutons du haut > "Form Settings" > modifiez le Success Message pour qu'il inclue l'une des données saisies par l'utilisateur. Cliquez sur le champ et un menu déroulant de suggestions apparaît.
Ajoutez %first_name%

Boutons du haut > "Save Form"
Pour insérer le formulaire dans une page : colonne de gauche > "Pages" > "Sample Page" > "Edit" > "Caldera Form" > sélectionnez le formulaire que vous venez de créer > "Insert Form" > colonne de droite > "Update"

Terminé.
Abordons une par une les étapes nécessaires pour construire ce type d'attaque, selon le paradigme diviser pour régner.
Lors des tests, quand on interagit avec un composant, il faut toujours prêter attention à ses réactions en fonction des stimuli donnés ; en particulier, nous nous concentrons sur le "parcours" des données que nous avons saisies et les éventuelles transformations qu'elles subissent.
Un exemple spécifique pour notre cas est le suivant :
Visitons la page contenant le formulaire : http://127.0.0.1/wordpress/sample-page/
Remplissons le formulaire avec les données suivantes
"First Name" : myName
"Last Name" : myLast
"Email Address" : my@e.mail
"Comments" : myComm
Les données sont traitées selon la logique du plugin
Le message de remerciement reçu contient la chaîne que nous avons saisie dans le champ First Name
"Thank you myName, form has been successfully submitted."

La chaîne que nous avons saisie dans le champ "First Name" nous est retournée dans le message de remerciement.
En particulier, la chaîne est contenue dans une balise div HTML.
Notre entrée aboutit dans le HTML de la page.
Réflexion : "On aime ça. Nous avons un point de contact."
Faisons un pas en avant. Comment notre entrée est-elle traitée pendant la phase que nous avons appelée "traitement" (point 2) ? En particulier, ce que nous voulons savoir, c'est : avons-nous des limitations sur les caractères (et leurs combinaisons) que nous pouvons utiliser ? Évidemment, l'objectif est de réussir à injecter "des trucs". Quand on cherche à faire une injection, il faut garder à l'esprit où aboutit notre entrée et utiliser le "langage" approprié.
Notre entrée est-elle traitée par un interpréteur SQL ? Il faut parler son langage
Notre entrée est-elle traitée par un script PHP ? Il faut parler son langage
Notre entrée aboutit-elle sur une page HTML ? ...
Donc ce qui nous intéresse est de savoir si nous pouvons utiliser les caractères et les constructions typiques du HTML et, en particulier, compte tenu de la capacité de ce langage à contenir/interpréter du code JavaScript, de voir si nous trouvons une stratégie pour insérer notre code dans la "zone d'atterrissage", c'est-à-dire la balise div notée précédemment.
Pour cela, insérons dans le champ "First Name" une simple balise HTML et voyons si elle est "sanitisée" (de sanitized), c'est-à-dire modifiée de manière à être rendue inoffensive/non interprétable, ou si elle nous est retournée telle quelle. Utilisons à cette fin une balise <br>, utilisée pour insérer un saut de ligne dans le texte.
En suivant la numérotation précédente :
Remplissons le formulaire avec les données suivantes
"First Name" : m<br>yName
"Last Name" : myLast
"Email Address" : my@e.mail
"Comments" : myComm
Le message de remerciement contient notre balise HTML, qui n'a pas été modifiée, et elle est correctement interprétée, insérant un saut de ligne au milieu du message
"Thank you m
yName, form has been successfully submitted."

Réflexion : "On aime ça. Nous pouvons utiliser les symboles inférieur et supérieur, nous pouvons insérer des balises HTML qui ne sont pas sanitisées et sont interprétées."
Pas en avant. Remplaçons la balise de formatage par quelque chose de plus utile, comme une balise <script>, qui nous permet d'insérer et d'exécuter du code JavaScript dans la page.
Remplissons le formulaire avec les données suivantes
"First Name" : m<script>alert(1);</script>yName
"Last Name" : myLast
"Email Address" : my@e.mail
"Comments" : myComm
Le message de remerciement contient notre balise HTML, qui n'a pas été modifiée, et elle est correctement interprétée, nous montrant une boîte d'alerte

Réflexion 1 : "On aime ça. Nous pouvons exécuter du code JavaScript arbitraire dans le contexte du navigateur de l'utilisateur."
Réflexion 2 : "On n'aime pas ça. L'utilisateur qui exécute le JavaScript, c'est nous-mêmes."
La situation est la suivante : nous pouvons exécuter du JavaScript via un site que nous ne contrôlons pas dans le contexte du navigateur d'un utilisateur, mais cet utilisateur, pour l'instant, est celui qui saisit les valeurs dans le formulaire. Tout cela est assez inutile.
L'idée est la suivante : existe-t-il un moyen de rappeler le message de remerciement contenant notre code à exécuter ?
Revenons à notre première soumission, celle de "reconnaissance" (ou ré-exécutons les premières étapes).
En analysant le trafic réseau ou la source de la page, nous comprenons que le formulaire effectue une requête POST vers l'adresse
http://127.0.0.1/wordpress/cf-api/CF5ad9b3176c0f4
(la dernière partie peut varier, modifiez-la convenablement dans tous les exemples suivants)
c'est-à-dire vers l'adresse
http://<target>/cf-api/<form-id>
et que la réponse à cette requête est un JSON contenant certaines données, dont le message de remerciement, ayant la structure suivante :
{
"data":
{"cf_id":"48"},
"html":"<div class=\" alert alert-success\">Thank you myName, form has been successfully submitted.<\/div>",
"type":"complete",
"form_id":"CF5ad9b3176c0f4",
"form_name":"MyContactForm",
"status":"complete"
}
Gardons cette information de côté, nous y reviendrons bientôt ; notons surtout les champs "form_id" et "cf_id".
Qu'y a-t-il à l'adresse vers laquelle la POST est effectuée ? Sans trop de suppositions, voyons ce qui se passe si nous effectuons une GET, c'est-à-dire si nous visitons la page à l'adresse http://127.0.0.1/wordpress/cf-api/CF5ad9b3176c0f4 et nous trouvons, ni plus ni moins, le HTML du formulaire en question.
et contrôlons le trafic réseau résultant de la soumission

Cette fois, nous recevons un code HTTP 302 (redirection) vers la location /wordpress/cf-api/CF5ad9b3176c0f4/?cf_su=1&cf_id=49
qui nous retourne, ni plus ni moins, le HTML contenant la balise div du message de remerciement.
Notons le format de cette adresse :
http://<target>/cf-api/<form-id>/?cf_su=1&cf_id=<cf-id>
où les valeurs de <form-id> et <cf-id> sont exactement celles contenues dans le JSON analysé précédemment, respectivement "form_id" et "cf_id".
Avons-nous répondu à la question de départ ? Oui. Nous avons trouvé un moyen de rappeler le contenu du message de remerciement.
Réflexion : "On aime ça. Nous pouvons rappeler, selon les besoins, le message contenant nos données."
Faisons le point en rassemblant toutes les informations que nous avons collectées jusqu'à présent et préparons une attaque.
1) sauvegardons notre code malveillant sur la cible
2) collectons les données nécessaires pour récupérer ce code
3) construisons l'URL adaptée pour "déclencher" (to trigger) notre attaque
Remplissons le formulaire (depuis l'une des deux pages, c'est indifférent) avec les données suivantes
"First Name" : m<script>document.body.innerHTML=String.fromCodePoint(128046);</script>yName
"Last Name" : myLast
"Email Address" : my@e.mail
"Comments" : myComm
Grâce à l'analyse du trafic généré, que ce soit en lisant le JSON reçu dans le cas de la page sample-page ou en lisant la redirection dans le cas de la page contenant uniquement le formulaire, récupérons les identifiants form_id et cf_id
{
"data":
{"cf_id":"69"},
"html":"...",
"type":"...",
"form_id":"CF5ad9b3176c0f4",
"form_name":"...",
"status":"..."
}
http://127.0.0.1/wordpress/cf-api/CF5ad9b3176c0f4/?cf_su=1&cf_id=69

Quelques considérations :
Au lecteur, s'il est intéressé, on propose de :
' (apostrophe, simple quote), le caractère " (guillemets, doubles quotes) ou le caractère ` (accent grave, backtick ou backquote), absent des claviers français