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