Review credentials built from hard-coded values

Rule ND3115 Security

Matches methods that create a NetworkCredential, set the Password of a connection string builder, or load a PKCS#12 certificate with a password, while nothing in the method can supply that secret at runtime: no string parameter, no string field read, no call returning a string (configuration, environment, property getter, secret store). The only remaining source is a string literal.

This is CWE-798 Use of Hard-coded Credentials.

Flagged Code
static HttpClientHandler CreateHandler() {
   var handler = new HttpClientHandler();
   handler.Credentials = new NetworkCredential("svc-account", "P@ssw0rd!");   // flagged
   return handler;
}
Fixed Code
static HttpClientHandler CreateHandler(IConfiguration config) {
   var handler = new HttpClientHandler();
   string user = config["Svc:User"];           // the secret comes from configuration
   string password = config["Svc:Password"];
   handler.Credentials = new NetworkCredential(user, password);
   return handler;
}

How to fix

Take the secret from a configuration source that is not committed (environment variable, dotnet user-secrets, Azure Key Vault, AWS Secrets Manager, HashiCorp Vault...) and pass it to the method or inject it through options. Rotate the credential that was committed.

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Review credentials built from hard-coded values</Name>
// <Id>ND3115:ReviewCredentialsBuiltFromHardCodedValues</Id>
warnif count > 0

// Third-party members are part of the code model only when the application uses them.
let credentialMembers =
   // new NetworkCredential("user", "password")
   ThirdParty.Types.WithFullName("System.Net.NetworkCredential")
      .ChildMethods().Where(m => m.IsConstructor && m.NbParameters >= 2)
   // builder.Password = "..."   (SqlConnectionStringBuilder, NpgsqlConnectionStringBuilder, MySqlConnectionStringBuilder...)
   .Concat(ThirdParty.Methods.Where(m => m.SimpleName == "set_Password" && m.ParentType.SimpleName.EndsWith("ConnectionStringBuilder")))
   // new X509Certificate2(file, "password") / X509CertificateLoader.LoadPkcs12FromFile(file, "password")
   .Concat(ThirdParty.Types.WithFullNameIn("System.Security.Cryptography.X509Certificates.X509Certificate2",
                                            "System.Security.Cryptography.X509Certificates.X509Certificate")
      .ChildMethods().Where(m => m.IsConstructor && m.NbParameters >= 2 && m.Name.Contains("String")))
   .Concat(ThirdParty.Types.WithFullName("System.Security.Cryptography.X509Certificates.X509CertificateLoader")
      .ChildMethods().Where(m => m.SimpleName.StartsWith("LoadPkcs12") && m.Name.Contains("String")))
   .ToHashSetEx()

where credentialMembers.Any()

from m in Application.Methods.UsingAny(credentialMembers)
let credentialMembersUsed = m.MethodsCalled.Intersect(credentialMembers).ToArray()

// The secret can come from... a string parameter
where !m.Name.Contains("String") &&
      // ...a field of the type (injected options, configuration)
      !m.FieldsUsed.Any(f => f.FieldType != null && f.FieldType.FullNameEqualsAny("System.String", "System.Security.SecureString", "System.Char[]")) &&
      // ...any other call returning a string (configuration, environment variable, property getter, secret store, prompt)
      !m.MethodsCalled.Except(credentialMembers).Any(mc =>
           mc.ReturnType != null &&
           mc.ReturnType.FullNameEqualsAny("System.String", "System.Security.SecureString") &&
           mc.ParentType.FullName != "System.String")

select new {
  m,
  credentialMembersUsed,
  Debt = 30.ToMinutes().ToDebt(),
  Severity = Severity.High
}

//<Expl>
// The *{0}* builds a credential with *{1}* without any string parameter,
// string field or call returning a string: the user name, password or
// certificate password can only be a literal baked into the assembly.
//</Expl>

//<Desc>
// Matches methods that create a *NetworkCredential*, set the *Password* of
// a connection string builder, or load a PKCS#12 certificate with a
// password, while nothing in the method can supply that secret at runtime:
// no string parameter, no string field read, no call returning a string
// (configuration, environment, property getter, secret store). The only
// remaining source is a string literal.
//
// This is CWE-798 *Use of Hard-coded Credentials*.
//
// **Flagged Code**
// ```csharp
// static HttpClientHandler CreateHandler() {
//    var handler = new HttpClientHandler();
//    handler.Credentials = new NetworkCredential("svc-account", "P@ssw0rd!");   // flagged
//    return handler;
// }
// ```
//
// **Fixed Code**
// ```csharp
// static HttpClientHandler CreateHandler(IConfiguration config) {
//    var handler = new HttpClientHandler();
//    string user = config["Svc:User"];           // the secret comes from configuration
//    string password = config["Svc:Password"];
//    handler.Credentials = new NetworkCredential(user, password);
//    return handler;
// }
// ```
//</Desc>

//<HowToFix>
// Take the secret from a configuration source that is not committed
// (environment variable, *dotnet user-secrets*, Azure Key Vault, AWS
// Secrets Manager, HashiCorp Vault...) and pass it to the method or inject
// it through options. Rotate the credential that was committed.
//</HowToFix>

Security: all 39 items