From 2ea3235cd3fbe66da4004bfbfc68e9355846339b Mon Sep 17 00:00:00 2001 From: sebastien-andrivet-sonarsource Date: Thu, 28 Mar 2024 18:11:59 +0100 Subject: [PATCH] Update RSPEC for .NET --- rules/S6781/csharp/how-to-fix/net-core.adoc | 115 ++++++++++++++++++ .../csharp/how-to-fix/net-framework.adoc | 70 +++++++++++ rules/S6781/csharp/rule.adoc | 67 +++++----- 3 files changed, 223 insertions(+), 29 deletions(-) create mode 100644 rules/S6781/csharp/how-to-fix/net-core.adoc create mode 100644 rules/S6781/csharp/how-to-fix/net-framework.adoc diff --git a/rules/S6781/csharp/how-to-fix/net-core.adoc b/rules/S6781/csharp/how-to-fix/net-core.adoc new file mode 100644 index 00000000000..73866691d64 --- /dev/null +++ b/rules/S6781/csharp/how-to-fix/net-core.adoc @@ -0,0 +1,115 @@ +== How to fix it in ASP.NET Core + +=== Code examples + +The following noncompliant code contains a hard-coded secret that can be exposed unintentionally. + +==== Noncompliant code example + +[source,csharp,diff-id=1,diff-type=noncompliant] +---- +[ApiController] +[Route("login-config")] +public class LoginConfigController : ControllerBase +{ + private readonly IConfiguration _config; + public LoginConfigController(IConfiguration config) + { + _config = config; + } + + [HttpPost] + public IActionResult Post([FromBody] UserModel user) + { + var key = _config["Jwt:Key"] ?? ""; + + // Code to validate user omitted + + var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)); // Noncompliant (key in appsettings.json) + var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + + var Sectoken = new JwtSecurityToken( + "sonarsource.com", + "sonarsource.com", + null, + expires: DateTime.Now.AddMinutes(120), + signingCredentials: credentials); + + return Ok(new JwtSecurityTokenHandler().WriteToken(Sectoken)); + } +} +---- + +==== Compliant solution + +[source,csharp,diff-id=1,diff-type=compliant] +---- +[ApiController] +[Route("login-env")] +public class LoginEnvController : ControllerBase +{ + private readonly IConfiguration _config; + public LoginEnvController(IConfiguration config) + { + _config = config; + } + + [HttpPost] + public IActionResult Post([FromBody] UserModel user) + { + var key = Environment.GetEnvironmentVariable("JWT_KEY") ?? ""; + + // Code to validate user omitted + + var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)); + var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + + var Sectoken = new JwtSecurityToken( + "sonarsource.com", + "sonarsource.com", + null, + expires: DateTime.Now.AddMinutes(120), + signingCredentials: credentials); + + var token = new JwtSecurityTokenHandler().WriteToken(Sectoken); + + return Ok(token); + } +} +---- + +=== How does this work? + +Here, the compliant solution uses an environement variable for the secret. + +=== Going the extra mile + +==== Use a secret vault + +A secret vault should be used to generate and store the new secret. This will ensure the secret's security and prevent any further unexpected disclosure. The recommended way for .NET Core applications is to use Azure Key Vault: + +[source,csharp] +---- +var builder = WebApplication.CreateBuilder(args); + +// Get the name of the key vault +var keyVaultName = Environment.GetEnvironmentVariable("AZURE_KEYVAULT") ?? ""; +// Add Azure Key Vault in the configuration +builder.Configuration.AddAzureKeyVault(new Uri($"https://{keyVaultName}.vault.azure.net/"), new EnvironmentCredential()); +// Get the JWT secret from Azure Key Vault +var jwtKey = builder.Configuration.GetSection("JWT-KEY").Get() ?? ""; + +builder.Services + .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => { + options.TokenValidationParameters = new TokenValidationParameters{ + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = "sonarsource.com", + ValidAudience = "sonarsource.com", + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey!)) // Compliant: key from Azure Key Vault + }; + }); +---- diff --git a/rules/S6781/csharp/how-to-fix/net-framework.adoc b/rules/S6781/csharp/how-to-fix/net-framework.adoc new file mode 100644 index 00000000000..89c36d0e008 --- /dev/null +++ b/rules/S6781/csharp/how-to-fix/net-framework.adoc @@ -0,0 +1,70 @@ +== How to fix it in ASP.NET + +=== Code examples + +The following noncompliant code contains a hard-coded secret that can be exposed unintentionally. + +==== Noncompliant code example + +[source,csharp,diff-id=2,diff-type=noncompliant] +---- +public class ConstLoginController : ApiController +{ + private const string key = "SecretSecretSecretSecretSecretSecretSecretSecret"; + + public IHttpActionResult Post([FromBody] Login login) + { + // Code to validate user omitted + + var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)); // Noncompliant: hard-coded key in code + var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + + var secToken = new JwtSecurityToken( + "sonarsource.com", + "sonarsource.com", + null, + expires: DateTime.Now.AddMinutes(128), + signingCredentials: credentials + ); + + var token = new JwtSecurityTokenHandler().WriteToken(secToken); + + return Ok(token); + } +} +---- + +==== Compliant solution + +[source,csharp,diff-id=2,diff-type=compliant] +---- +public class ConstLoginController : ApiController +{ + private const string key = "SecretSecretSecretSecretSecretSecretSecretSecret"; + + public IHttpActionResult Post([FromBody] Login login) + { + // Code to validate user omitted + + var key = Environment.GetEnvironmentVariable("JWT_KEY"); + var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + + var secToken = new JwtSecurityToken( + "sonarsource.com", + "sonarsource.com", + null, + expires: DateTime.Now.AddMinutes(128), + signingCredentials: credentials + ); + + var token = new JwtSecurityTokenHandler().WriteToken(secToken); + + return Ok(token); + } +} +---- + +=== How does this work? + +Here, the compliant solution uses an environement variable for the secret. + diff --git a/rules/S6781/csharp/rule.adoc b/rules/S6781/csharp/rule.adoc index 4bd440f87a8..5a51afc0b02 100644 --- a/rules/S6781/csharp/rule.adoc +++ b/rules/S6781/csharp/rule.adoc @@ -1,44 +1,53 @@ -FIXME: add a description - -// If you want to factorize the description uncomment the following line and create the file. -//include::../description.adoc[] +include::../../../shared_content/secrets/description.adoc[] == Why is this an issue? -FIXME: remove the unused optional headers (that are commented out) +include::../../../shared_content/secrets/rationale.adoc[] + +=== What is the potential impact? + +If a JWT secret key leaks to an unintended audience, it can have serious +security implications for the corresponding application. The secret key is used +to encode and decode JWTs when using a symmetric signing algorithm, and an +attacker could potentially use it to perform malicious actions. + +For example, an attacker could use the secret key to create their own +authentication tokens that appear to be legitimate, allowing them to bypass +authentication and gain access to sensitive data or functionality. + +In the worst-case scenario, an attacker could be able to execute arbitrary code +on the application by abusing administrative features, and take over its hosting +server. + +// How to fix it section + +include::./how-to-fix/net-core.adoc[] + +include::./how-to-fix/net-framework.adoc[] -//=== What is the potential impact? +== Resources -== How to fix it -//== How to fix it in FRAMEWORK NAME +=== Documentation -=== Code examples +* Microsoft Learn - https://learn.microsoft.com/en-us/dotnet/api/system.identitymodel.tokens.jwt.jwtsecuritytoken?view=msal-web-dotnet-latest[JwtSecurityToken Class Class] +* Microsoft Learn - https://learn.microsoft.com/en-us/dotnet/api/system.identitymodel.tokens.symmetricsecuritykey?view=dotnet-plat-ext-8.0[SymmetricSecurityKey Class] -==== Noncompliant code example -[source,text,diff-id=1,diff-type=noncompliant] ----- -FIXME ----- +include::../../../shared_content/secrets/resources/standards.adoc[] -==== Compliant solution +ifdef::env-github,rspecator-view[] -[source,text,diff-id=1,diff-type=compliant] ----- -FIXME ----- +''' +== Implementation Specification +(visible only on this page) -//=== How does this work? +=== Message -//=== Pitfalls +JWT secret keys should not be disclosed. -//=== Going the extra mile +=== Highlight +The call to create a new instance of `SymmetricSecurityKey`. -//== Resources -//=== Documentation -//=== Articles & blog posts -//=== Conference presentations -//=== Standards -//=== External coding guidelines -//=== Benchmarks +''' +endif::env-github,rspecator-view[]