Static Analysis


In the earlier stage during the web enumeration, Burp Suite found the initialization file (boot resource file) for the target Blazor instance, blazor.boot.json, which contains a list of all the executables

Looking at the list, those highlighted ones appear to be particularly interesting

┌──(kali㉿kali)-[~/archive/htb/labs/blazorized]
└─$ wget http://blazorized.htb/_framework/Blazorized.DigitalGarden.dll
 
Blazorized.DigitalGar 100%[========================>]  52.00K  --.-KB/s    in 0.05s   
 
2024-07-02 08:39:01 (1.09 MB/s) - ‘Blazorized.DigitalGarden.dll’ saved [53248/53248]
 
┌──(kali㉿kali)-[~/archive/htb/labs/blazorized]
└─$ wget http://blazorized.htb/_framework/Blazored.LocalStorage.dll
 
Blazored.LocalStorage 100%[========================>]  30.50K  --.-KB/s    in 0.03s   
 
2024-07-02 08:39:10 (1.10 MB/s) - ‘Blazored.LocalStorage.dll’ saved [31232/31232]
 
┌──(kali㉿kali)-[~/archive/htb/labs/blazorized]
└─$ wget http://blazorized.htb/_framework/Blazorized.Shared.dll
 
Blazorized.Shared.dll 100%[========================>]  11.00K  --.-KB/s    in 0.003s  
 
2024-07-02 08:39:18 (4.14 MB/s) - ‘Blazorized.Shared.dll’ saved [11264/11264]
 
┌──(kali㉿kali)-[~/archive/htb/labs/blazorized]
└─$ wget http://blazorized.htb/_framework/Markdig.dll
 
Markdig.dll           100%[========================>] 452.50K  1.75MB/s    in 0.3s    
 
2024-07-02 08:39:50 (1.75 MB/s) - ‘Markdig.dll’ saved [463360/463360]
 
┌──(kali㉿kali)-[~/archive/htb/labs/blazorized]
└─$ wget http://blazorized.htb/_framework/Blazorized.Helpers.dll
 
Blazorized.Helpers.d 100%[===================>]  12.50K  --.-KB/s    in 0.002s  
 
2024-07-02 09:53:14 (5.75 MB/s) - ‘Blazorized.Helpers.dll’ saved [12800/12800]
 

Downloading them to Kali for static analysis

dnSpy


I transferred those DLL files to a Windows host and loaded onto dnSpy for decompilation

Blazorized.DigitalGarden.dll


The Blazorized.DigitalGarden.dll file contains the source code of the web application

It contains source code for the defined routes, layout of the web application, CheckUpdate feature, markdown feature, handling the APT requests for both Posts and Categories

Blazorized.Helpers.dll


The Blazorized.Helpers.dll file contains JWT class. This must be the source code file responsible for generating the JWT seen in the API requests earlier in both posts and categories

JWT Class


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";
	}
}

Checking the source code reveals the following;

JWT.GenerateTemporaryJWT()

JWT.GenerateTemporaryJWT() function is used to generate a temporary JWT key for the /check-update endpoint

Limited permissions are given

iss and aud parameters are set to api.blazorized.htb

JWT.GenerateSuperAdminJWT()

JWT.GenerateTemporaryJWT() function appears to be used for the login page at admin.blazorized.htb While the issuer(iss) is the same, permissions and aud are completely different

jwtSymmetricSecuritykey

The symmetric JWT key is hard-coded; 8697800004ee25fc33436978ab6e2ed6ee1a97da699a53a53d96cc4d08519e185d14727ca18728bf1efcde454eea6f65b8d466a4fb6550d5c795d9d9176ea6cf021ef9fa21ffc25ac40ed80f4a4473fc1ed10e69eaf957cfc4c67057e547fadfca95697242a2ffb21461e7f554caa4ab7db07d2d897e7dfbe2c0abbaf27f215c0ac51742c7fd58c3cbb89e55ebb4d96c8ab4234f2328e43e095c0f55f79704c49f07d5890236fe6b4fb50dcd770e0936a183d36e4d544dd4e9a40f5ccf6d471bc7f2e53376893ee7c699f48ef392b382839a845394b6b93a5179d33db24a2963f4ab0722c9bb15d361a34350a002de648f13ad8620750495bff687aa6e2f298429d6c12371be19b0daa77d40214cd6598f595712a952c20eddaae76a28d89fb15fa7c677d336e44e9642634f32a0127a5bee80838f435f163ee9b61a67e9fb2f178a0c7c96f160687e7626497115777b80b7b8133cef9a661892c1682ea2f67dd8f8993c87c8c9c32e093d2ade80464097e6e2d8cf1ff32bdbcd3dfd24ec4134fef2c544c75d5830285f55a34a525c7fad4b4fe8d2f11af289a1003a7034070c487a18602421988b74cc40eed4ee3d4c1bb747ae922c0b49fa770ff510726a4ea3ed5f8bf0b8f5e1684fb1bccb6494ea6cc2d73267f6517d2090af74ceded8c1cd32f3617f0da00bf1959d248e48912b26c3f574a1912ef1fcc2e77a28b53d0a