diff --git a/ReleaseHistory.md b/ReleaseHistory.md index 3d1d3617..37796153 100644 --- a/ReleaseHistory.md +++ b/ReleaseHistory.md @@ -20,6 +20,8 @@ ## v4.5.8 UNRELEASED - DEP: Update SARIF SDK submodule from [7e8def7 to dd3741f(https://github.com/microsoft/sarif-sdk/compare/7e8def7..dd3741f). [Full SARIF SDK release history](https://github.com/microsoft/sarif-sdk/blob/dd3741f/ReleaseHistory.md). +- DEP: Upgrade `Microsoft.Security.Utilities` from 6.2.1 to 6.5.0. [#788](https://github.com/microsoft/sarif-pattern-matcher/pull/788) +- FNC: Update `SEC101/041.RabbitMqCredentials` in `Security` to check loose credential combinations. [#788](https://github.com/microsoft/sarif-pattern-matcher/pull/788) - FPD: Removed dynamic analysis entirely for `SEC101/047.CratesApiKey` rule due to outdated validation always returning status code 200 to all tokens. No API endpoint seems to return different status codes to distinguish between valid and invalid API keys. [#786](https://github.com/microsoft/sarif-pattern-matcher/pull/786) ## v4.5.7 6/28/2023 diff --git a/Src/Plugins/Security/SEC101.SecurePlaintextSecrets.json b/Src/Plugins/Security/SEC101.SecurePlaintextSecrets.json index aa48040a..e1ce792c 100644 --- a/Src/Plugins/Security/SEC101.SecurePlaintextSecrets.json +++ b/Src/Plugins/Security/SEC101.SecurePlaintextSecrets.json @@ -410,6 +410,18 @@ "ContentsRegex": "$SEC101/041.RabbitMqCredentials", "MessageArguments": { "secretKind": "RabbitMq credential" } }, + { + "Id": "SEC101/041", + "Name": "RabbitMqCredentials", + "IntrafileRegexes": [ + "$SEC101/041.RabbitMqCredentialsHost", + "?SEC101/041.RabbitMqCredentialsPort", + "$SEC101/041.RabbitMqCredentialsUser", + "$SEC101/041.RabbitMqCredentialsPassword", + "?SEC101/041.RabbitMqCredentialsVirtualHost" + ], + "MessageArguments": { "secretKind": "RabbitMq credential" } + }, { "Id": "SEC101/042", "Name": "DynatraceToken", diff --git a/Src/Plugins/Security/SecurePlaintextSecretsValidators/SEC101_041.RabbitMqCredentialsValidator.cs b/Src/Plugins/Security/SecurePlaintextSecretsValidators/SEC101_041.RabbitMqCredentialsValidator.cs index cc3a4c4e..315e51c4 100644 --- a/Src/Plugins/Security/SecurePlaintextSecretsValidators/SEC101_041.RabbitMqCredentialsValidator.cs +++ b/Src/Plugins/Security/SecurePlaintextSecretsValidators/SEC101_041.RabbitMqCredentialsValidator.cs @@ -16,10 +16,13 @@ public class RabbitMqCredentialsValidator : DynamicValidatorBase { protected override IEnumerable IsValidStaticHelper(IDictionary groups) { - if (!groups.TryGetNonEmptyValue("id", out FlexMatch id) || - !groups.TryGetNonEmptyValue("host", out FlexMatch host) || - !groups.TryGetNonEmptyValue("secret", out FlexMatch secret) || - !groups.TryGetNonEmptyValue("resource", out FlexMatch resource)) + groups.TryGetValue("id", out FlexMatch id); + groups.TryGetValue("host", out FlexMatch host); + groups.TryGetValue("secret", out FlexMatch secret); + groups.TryGetValue("resource", out FlexMatch resource); + groups.TryGetValue("port", out FlexMatch port); + + if (FilteringHelpers.PasswordIsInCommonVariableContext(secret.Value)) { return ValidationResult.CreateNoMatch(); } @@ -32,8 +35,9 @@ protected override IEnumerable IsValidStaticHelper(IDictionary { Id = id.Value, Host = hostValue, + Port = port?.Value, Secret = secret.Value, - Resource = resource.Value, + Resource = resource?.Value, }, }; @@ -46,6 +50,7 @@ protected override ValidationState IsValidDynamicHelper(ref Fingerprint fingerpr ref ResultLevelKind resultLevelKind) { string host = fingerprint.Host; + string port = fingerprint.Port; string account = fingerprint.Id; string password = fingerprint.Secret; string resource = fingerprint.Resource; @@ -55,6 +60,8 @@ protected override ValidationState IsValidDynamicHelper(ref Fingerprint fingerpr return ValidationState.Unknown; } + host += string.IsNullOrWhiteSpace(port) ? string.Empty : $":{port}"; + try { var factory = new ConnectionFactory diff --git a/Src/Plugins/Security/Security.SharedStrings.txt b/Src/Plugins/Security/Security.SharedStrings.txt index f3fcdb2a..1e3d67d7 100644 --- a/Src/Plugins/Security/Security.SharedStrings.txt +++ b/Src/Plugins/Security/Security.SharedStrings.txt @@ -129,9 +129,14 @@ $SEC101/038.PostgreSqlCredentialsAdoSecret=(?i)(?:password|pwd)\s*=\s*(?P[^,;"'<\s]{8,128})(?:[,;"'<\s]|$) $SEC101/038.PostgreSqlCredentialsAdoResource=(?i)(?:database|db|dbname)\s*=\s*(?P[^,;"'=|&\]\[><\s]+)(?:[,;"'=|&\]\[><\s]|$) - $SEC101/041.RabbitMqCredentials=(?i)amqps?:\/\/(?P[^:"]+):(?P[^@\s]+)@(?P[\w_\-\:]+)\/(?P[\w]+)(?:[^0-9a-z]|$) + $SEC101/041.RabbitMqCredentials=(?i)amqps?:\/\/(?P[^:"]+):(?P[^@\s]+)@(?P[\w_-]+)(?::?(?P[0-9]{4,5}))?\/(?P[\w]+)?(?:[^0-9a-z]|$) + $SEC101/041.RabbitMqCredentialsHost=(?i)rabbitmq[-_\s]?host[\s,:='"]+(?:value?[\s,:='"]+)?(?P[.\w_-]+) + $SEC101/041.RabbitMqCredentialsPort=(?i)rabbitmq[-_\s]?port[\s,:='"]+(?:value?[\s,:='"]+)?(?P[0-9]{4,5})(?:[^0-9]|$) + $SEC101/041.RabbitMqCredentialsUser=(?i)rabbitmq[-_\s]?user(?:name)?[\s,:='"]+(?:value?[\s,:='"]+)?(?P[0-9a-z._-]+)(?:[^0-9a-z._-]|$) + $SEC101/041.RabbitMqCredentialsPassword=(?i)rabbitmq[-_\s]?password[\s,:='"]+(?:value?[\s,:='"]+)?(?P[^,:='"]+)(?:[,:='"]|$) + $SEC101/041.RabbitMqCredentialsVirtualHost=(?i)rabbitmq[-_\s]?v(irtual)?host[\s,:='"]+(?:value?[\s,:='"]+)?(?P[^;,"<'\s]+)(?:[;,"<'\s]|$) $SEC101/043.NuGetPackageSourceCredentialsXml=(?i)(?P<\s*packageSources\s*>(?s).{0,500}?(?-s)<\\?\/packageSources\s*>)(?s).{0,200}?(?-s)[^\/](?P<\s*packageSourceCredentials\s*>(?s).{0,500}?(?-s)<\\?\/packageSourceCredentials\s*>) - + $SEC101/044.NpmCredentialsRegistry=(?i)(registry\s*=\s*|-r\s+)https:\/\/(?P\S+)(?:\s|$) $SEC101/044.NpmCredentialsAuth=(?i)_auth(Token)?\s*=\s*(?P[0-9A-Za-z\/+]+[=]{0,2})(?:[^0-9A-Za-z\/+]|$) $SEC101/044.NpmCredentialsUser=(?i)(?:(?:email|user(name)?)\s*=\s*|-u\s+)(?-i)(?P\S+)(?:\s|$) diff --git a/Src/Plugins/Security/Security.csproj b/Src/Plugins/Security/Security.csproj index 00eb82cd..f054fdc5 100644 --- a/Src/Plugins/Security/Security.csproj +++ b/Src/Plugins/Security/Security.csproj @@ -28,7 +28,7 @@ - + diff --git a/Src/Plugins/Security/Utilities/FilteringHelpers.cs b/Src/Plugins/Security/Utilities/FilteringHelpers.cs index 2b6d51d0..fbaf6c49 100644 --- a/Src/Plugins/Security/Utilities/FilteringHelpers.cs +++ b/Src/Plugins/Security/Utilities/FilteringHelpers.cs @@ -48,5 +48,24 @@ public static bool LikelyPowershellVariable(string input) return true; } + + public static bool PasswordIsInCommonVariableContext(string secret) + { + var passwordContextList = new List> + { + new Tuple("{", "}"), + new Tuple("$(", ")"), + }; + + foreach (Tuple tuplePair in passwordContextList) + { + if (secret.StartsWith(tuplePair.Item1)) + { + return secret.EndsWith(tuplePair.Item2); + } + } + + return false; + } } } diff --git a/Src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/ExpectedOutputs/SEC101_041.RabbitMqCredentials.sarif b/Src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/ExpectedOutputs/SEC101_041.RabbitMqCredentials.sarif index c4152baf..5ed192a9 100644 --- a/Src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/ExpectedOutputs/SEC101_041.RabbitMqCredentials.sarif +++ b/Src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/ExpectedOutputs/SEC101_041.RabbitMqCredentials.sarif @@ -245,12 +245,110 @@ ], "fingerprints": { "secretHashSha256/v0": "8ee3280c2b16572db6de68a544e1359b024e02f0883855579a2ad30377342476", - "assetFingerprint/v0": "{\"host\":\"host:1234\",\"id\":\"user\",\"resource\":\"database4\"}", - "validationFingerprintHashSha256/v0": "ae3c9e40e7e50921fa67195289b1cb2be76a61b4504a6554fdb1339749ec4949", + "assetFingerprint/v0": "{\"host\":\"host\",\"id\":\"user\",\"resource\":\"database4\"}", + "validationFingerprintHashSha256/v0": "b072b04dc20528de96afa27d61ec069f4100f631d3417d29266767821935e9e6", "secretFingerprint/v0": "{\"secret\":\"password\"}", - "validationFingerprint/v0": "{\"host\":\"host:1234\",\"id\":\"user\",\"resource\":\"database4\",\"secret\":\"password\"}" + "validationFingerprint/v0": "{\"host\":\"host\",\"id\":\"user\",\"port\":\"1234\",\"resource\":\"database4\",\"secret\":\"password\"}" }, "rank": 39.29 + }, + { + "rule": { + "id": "SEC101/041", + "index": 0, + "toolComponent": { + "index": 0 + } + }, + "message": { + "id": "Default", + "arguments": [ + "…ssword", + "an apparent ", + "", + "RabbitMq credential", + "", + " (no validation occurred as it was not enabled. Pass '--dynamic-validation' on the command-line to validate this match)" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/Inputs/SEC101_041.RabbitMqCredentials.ps1", + "uriBaseId": "SRC_ROOT" + }, + "region": { + "startLine": 14, + "startColumn": 15, + "endLine": 14, + "endColumn": 23, + "charOffset": 319, + "charLength": 8, + "snippet": { + "text": "password" + } + } + } + } + ], + "fingerprints": { + "secretHashSha256/v0": "8ee3280c2b16572db6de68a544e1359b024e02f0883855579a2ad30377342476", + "assetFingerprint/v0": "{\"host\":\"host\",\"id\":\"user\"}", + "validationFingerprintHashSha256/v0": "d95f5afa621b3965946933ad24d5c7dfddf04f79f9a84c770044c71b14f43b0b", + "secretFingerprint/v0": "{\"secret\":\"password\"}", + "validationFingerprint/v0": "{\"host\":\"host\",\"id\":\"user\",\"port\":\"1234\",\"secret\":\"password\"}" + }, + "rank": 39.29 + }, + { + "rule": { + "id": "SEC101/041", + "index": 0, + "toolComponent": { + "index": 0 + } + }, + "message": { + "id": "Default", + "arguments": [ + "…s@word", + "an apparent ", + "", + "RabbitMq credential", + "", + " (no validation occurred as it was not enabled. Pass '--dynamic-validation' on the command-line to validate this match)" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/Inputs/SEC101_041.RabbitMqCredentials.ps1", + "uriBaseId": "SRC_ROOT" + }, + "region": { + "startLine": 17, + "startColumn": 95, + "endLine": 17, + "endColumn": 104, + "charOffset": 457, + "charLength": 9, + "snippet": { + "text": "pass@word" + } + } + } + } + ], + "fingerprints": { + "secretHashSha256/v0": "80cd575b97ae03b1a21b78bb7f69ad2afa6f3d2830b24bd07ce040090c8146a6", + "assetFingerprint/v0": "{\"host\":\"12.23.45.78\",\"id\":\"guest\"}", + "validationFingerprintHashSha256/v0": "8adb2e5e9ac8ca6c4c30426aaba4b3136af298520fb074839ecd42b0bd89ee33", + "secretFingerprint/v0": "{\"secret\":\"pass@word\"}", + "validationFingerprint/v0": "{\"host\":\"12.23.45.78\",\"id\":\"guest\",\"port\":\"5672\",\"secret\":\"pass@word\"}" + }, + "rank": 42.11 } ], "columnKind": "utf16CodeUnits" diff --git a/Src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/Inputs/SEC101_041.RabbitMqCredentials.ps1 b/Src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/Inputs/SEC101_041.RabbitMqCredentials.ps1 index f0595897..a6fc7486 100644 --- a/Src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/Inputs/SEC101_041.RabbitMqCredentials.ps1 +++ b/Src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/Inputs/SEC101_041.RabbitMqCredentials.ps1 @@ -10,7 +10,14 @@ amqp://user:password@host/database1 # Simple string with port amqp://user:password@host:1234/database4 +# Simple string with default vhost +"amqps://user:password@host:1234/" + +# Name/value pairs +{ "name": "RABBITMQ_HOST", "value": "12.23.45.78" }, { "name": "RABBITMQ_PASSWORD", "value": "pass@word" }, { "name": "RABBITMQ_PORT", "value": "5672" }, { "name": "RABBITMQ_USERNAME", "value": "guest" }, + # This is invalid and should not be captured +amqp://user:{{rabbitmq_password}}@host:1234/database4 amqp://user:password@host:1234 /database5 amqp://user:password@host:1234 amqp://id:secret @host/resource