What better way to inaugurate this blog than a post about a subtle-as-low-impact mx vulnerability on Roundcube? With this in mind, let’s pretend to have fun!
PS: I’ll refer to code 1.3.5 where not specified.
The executive summary
Roundcube is a well regarded web mail client, used by many important Institutions/Universities and it’s the default choice of many hosting providers.
Roundcube versions from 1.2.0 to 1.3.5 with the “archive” plugin enabled and configured, suffer from a mx injection vulnerability.
An attacker may exploit this to inject IMAP commands and perform malicious actions, like deleting your emails.
Well, that would do me a favor eheh 15000 unreads and countin’. Do we really have to fix this?!
THE TECHNICAL STUFF: Is it $_POST or is it $_GET?
To answer this big dilemma we need to look at where everything starts: archive.php:113:move_messages()
Few lines below things get interesting...
First, our goal is to get into the last branch. This means that the archive folder must be a special one💖; in Roundcube the archive should archive according to a listed option (author, date etc). However in version 1.2.0 this requirement isn’t needed.
Why? Simply ‘cause move_message_worker() does its job right. That’s why, luv.
Well, then this happens:
- line 176 archive.php:move_messages() calls fetch_headers($mbox, $uids);
- line 1235 rcube_imap.php:fetch_headers() calls fetchHeaders($folder,$msgs) where $folder is $mbox and $msgs is $uids;
- line 2600 rcube_imap_generic.php:fetchHeaders() calls fetch($mailbox, $message_set, $is_uid, $query_items);
- rcube_imap_generic.php:fetch() is a core function used everywhere to do its job: fetching things 🐶.
$mailbox ($mbox) and $message_set ($uids) are still our guys; hence we get right where we wanted:
$request which then, ‘ll be executed.
So what’s the problem? Okay, few “fetch-this” and “fetch-that” but in the end everything runs smoothly.
Well, unfortunately this isn’t exploitable in a real scenario since Roundcube will perform rcube.php:check_request()
… and our special
$uids is passed by $_POST… wait! They are using
!= and == instead of the strict ones, that’s a type juggling issue right there! Maybe there’s still hope?
Nope, everything is passed with HTTP Parameters: no JSON, no typed stuff.
Let’s go back to the starting point, archive.php, on line 152 calls
rcmail::getuids() which does this:
this means we can pass
_uids as $_GET, this way check_request() will treat our request like an empty $_POST.
Security check: bypassed✅. Our mx injection is exploitable.
In latest versions, the archive’s plugin accepts just AJAX requests, making it harder to perform the attack, since SOP must be either complied or bypassed.
In version 1.2.0 (likely also 1.2.1 / 1.2.2 or prior— I haven’t checked when the AJAX-only rule was introduced) the attack is straightforward, tricking the victim to click on a link like:
would be enough.