Quick was a hard machine on Hack the Box. Here’s my take on solving the challenge.


TL;DR: There’s a website of fictional ISP running on port 9001. On port 443 there’s a HTTP/3 UDP service where default password can found. The password can be then sprayed on potential logins scraped on the site, which gives access to a ticketing service. Ticketing service is vulnerable to a RCE through the esi tags injection. Local access reveals a virtual domain with a printing PHP application. Thanks to the local access to MySQL, the authorization to the service can be bypassed. The script uses temporary files in world writable directory, which can be exploited to write files as user srvadm and gain their SSH access. This user has access to the config file that contains a password that was also used for root.



Nmap TCP scan shows only one attack surface: webserver on an unusual port- 9001:

nmap scan

Main page is a landing page for some ISP:

Main page

There’s a list of clients that’ll come handy later:

Client list

Also, there’s a login page:

Login page

There’s not much to else the webserver on port 9001 (for now). There’s a hint on main page suggesting that there’s another portal running on some latest TLS and HTTP:

We are migrating our portal with latest 

Admittedly it took me a while to come up with a next step. There is a link that leads to portal.quick.htb on default HTTPS port, but browser can’t connect there. Finally, UDP scan shows a possible open port:

UDP scan, port 443

As it turns out there’s a HTTP/3 service running on that port. In order to be able to connect to it, I’ll need a client capable of communicating in this protocol. The only possibility I’ve found is cURL. The problem is, at the moment I was doing a challenge it was still experimental technology and as such it’s not enabled in the default build of cURL. I had to build my own version of with HTTP/3 enabled as described here. It was a long and painful process where lots of missing dependencies had to be installed. Also I didn’t manage to make ngtcp2 versions to work. Only Quiche finally compiled and allowed to see what’s being served on port 443:

HTTP/3 connection

I used cURL to download served website locally, and viewed it in the browser. The site is very barebone:

Quick portal landing page

About page offers some potential logins:

About us page

The most interesting is the docs page:

Docs page

The Connectivity guide contains default password for the ISP account:

Default password

Time to go back to the login page on 9001 port. There were a couple of places on the machine that gave up potential logins. At first I tried all the logins as the emails in the quick.htb domain. But that didn’t work. It turns out it was needed to take into account both companies and countries of the people from the http://quick.htb:9001/clients.php page. Finally I came up with two lists. One was all the potential logins/people with all names scraped from the pages:


Another were potential combinations of domains and country extensions:


Wfuzz shows that for elisa@wink.co.uk:Quick4cc3$$ login page returns redirect, meaning credentials worked:

Wfuzz result


After logging in I’m greeted with a simple ticketing system:

Quick ticketing system

The system is vulnerable to XSS/HTML injection. I can create a ticket with tags script using /ticket.php endpoint:

Request to create ticket

The tags will be returned on home page when the ticket is searched:


Additionaly the X-Powered-By reveals that the system is working on the Esigate system. As it turns out, Esigate allows using <esi> tags that are evaluated on the server side. That means I can turn HTML injection to server side injection. It can be exploited to various of vulnerabilities, from SSRF, to even RCE in some cases as decribed here. I’ll go for RCE through XSLT in the next few pargraphs.

First I’ll change my msg to esi tag payload:


After POSTing such ticket with id=tellico, every request to the /search endpoint with query tellico as shown below:

Request for tellico

will result in an attempt to download esi.xsl from my HTTP server:

Download attempt

Now, XSLT RCE payload in esi.xsl will be executed. First I’ll prepare the stager for reverse shell:

I’ll also serve a reverse shell in Python language:

Another attempt to search the tellico ticket should execute the stager:

Stager executed

Also my HTTP logs indicate that stager worked:

Staging log

Now, I’ll modyfy esi.xsl to run the execute the reverse shell, by changing the cmd paramter in the payload:

Reverese shell

Now, another request to display my ticket should yield the reverse shell and user flag:

Reverse shell

Privlege escalation

Lateral movement

The sites-available config of apache suggests that under virtual domain printerv2.quick.htb there’s another host running on port 9001 with srvadm privleges:


Reading files under /var/www/printer reveal source code of application working under this virtual domain. First, index.php reveals how passwords are stored:

Password check

The db.php reveal database credentials:

On my machine I’ll prepare a new password that fits to the hashing used in the app:

Preparing the hash

Using the access from db.php I can now swap the hash to my own to get access to the webapplication:

Swapping the password to the webapplication

Thanks to that, I can now access printerv2.quick.htb with credentials: srvadm@quick.htb:tellico:

When logged in, the user has access to the job.php script:


As can be read in the code, this script creates a temporary file in the /var/www/jobs with current timestamp as name and desc parameter value as content. As it turn out, the /var/www/jobs folder is world writable. It means, that if I create a symbolic link that’ll match the timestamp, the script will overwrite any file (with srvadm privleges) in the system, with whatever I’d supply in the desc parameter. The only difficulty is to create a file with proper name, as the name used by application changes eveery second. In order to achieve that I’ve created a simple bash script:


It’ll keep creating symbolic links to the SSH authorized_keys of srvadm,with a name of current timestamp + 1 second, in order to be alway ahead of a job.php script. Now with this script running i should by able to overwrite authorized_keys with a key of my own.

In order to achieve that I’ll first need to add a printer:

Adding printer

I’ll make a network printer that points to my machine. It doesn’t matter if it actually prints anything, I’ll just have NetCat listening on that port:

Creating printer

After the printer verifies, that my “printer” is up it’ll allow to add a job:

Add a job

Anything that’ll be placed in the main text area, will be POSTed as desc parameter and written to the temporary file. With my hunter.sh script running:


it should overwrite the authorized_keys of srvadm. I’ll put there my SSH public key:

After POSTing the form I’m able to log in to SSH as srvadm:

SSH login as srvadm

Root access

The srvadm’s home directory contains an interesting printers.conf file. The DeviceURI parameter of a second printer seems to contain some credentials:

$ cat ~/.cache/conf.d/printers.conf
<Printer OLD_Aviatar>
PrinterId 2
DeviceURI https://srvadm%40quick.htb:%26ftQ4K3SGde8%3F@printerv3.quick.htb/printer

Url decoding reveals a password &ftQ4K3SGde8? that was also reused for root:

Root access