diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/System.model.yml b/powershell/ql/lib/semmle/code/powershell/frameworks/System.model.yml index dec6efc3108c..6d564cb3651b 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/System.model.yml +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/System.model.yml @@ -1,4 +1,9 @@ extensions: + - addsTo: + pack: microsoft/powershell-all + extensible: summaryModel + data: + - ["system.random", "Method[nextbytes]", "Argument[this]", "Argument[0]", "taint"] - addsTo: pack: microsoft/powershell-all extensible: sourceModel diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/data/internal/ApiGraphModelsSpecific.qll b/powershell/ql/lib/semmle/code/powershell/frameworks/data/internal/ApiGraphModelsSpecific.qll index cc1bec656997..9e4e15c4b7dd 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/data/internal/ApiGraphModelsSpecific.qll +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -126,9 +126,20 @@ API::Node getExtraNodeFromType(string rawType) { or exists(string name, API::TypeNameNode typeName | parseRelevantType(rawType, name, _) and - typeName = API::getTopLevelMember(name) and - typeName.isImplicit() and - result = typeName + ( + ( + typeName.getTypeName() = name and + not typeName.isImplicit() and + result = typeName.getInstance() + ) + or + ( + typeName = API::getTopLevelMember(name) and + typeName.isImplicit() and + result = typeName + ) + ) + ) } diff --git a/powershell/ql/lib/semmle/code/powershell/security/InsecureRandomnessCustomizations.qll b/powershell/ql/lib/semmle/code/powershell/security/InsecureRandomnessCustomizations.qll new file mode 100644 index 000000000000..e71d787faf69 --- /dev/null +++ b/powershell/ql/lib/semmle/code/powershell/security/InsecureRandomnessCustomizations.qll @@ -0,0 +1,107 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * insecure-randomness vulnerabilities, as well as extension points for + * adding your own. + */ + +private import semmle.code.powershell.dataflow.DataFlow +import semmle.code.powershell.ApiGraphs + +module InsecureRandomness { + /** + * A data flow source for insecure-randomness vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for insecure-randomness vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { + /** Gets a human-readable description of this sink. */ + abstract string getSinkDescription(); + } + + /** + * A sanitizer for insecure-randomness vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** A call to the `Get-Random` cmdlet. */ + class GetRandomSource extends Source instanceof DataFlow::CallNode { + GetRandomSource() { this.matchesName("Get-Random") } + } + + /** An instantiation of `System.Random` via `New-Object`. */ + class SystemRandomObjectCreation extends Source instanceof DataFlow::ObjectCreationNode { + SystemRandomObjectCreation() { + this.asExpr() + .getExpr() + .(ObjectCreation) + .getAnArgument() + .getValue() + .stringMatches("System.Random") + } + } + + /** A call to `[System.Random]::new()` via the API graph. */ + class SystemRandomNewCall extends Source instanceof DataFlow::CallNode { + SystemRandomNewCall() { + this = API::getTopLevelMember("system").getMember("random").getMember("new").asCall() + } + } + + /** Assignment to .Key or .IV on an object. */ + class CryptoKeyIvAssignmentSink extends Sink { + CryptoKeyIvAssignmentSink() { + exists(AssignStmt a, MemberExprWriteAccess m | + m = a.getLeftHandSide() and + m.getLowerCaseMemberName() in ["key", "iv"] and + this.asExpr().getExpr() = a.getRightHandSide() + ) + } + + override string getSinkDescription() { result = "a cryptographic key or IV" } + } + + /** Argument to a cryptographic constructor (HMAC key or KDF salt). */ + class CryptoConstructorSink extends Sink { + CryptoConstructorSink() { + exists(DataFlow::ObjectCreationNode oc | + ( + oc.getLowerCaseConstructedTypeName().matches("%hmac%") + or + oc.getLowerCaseConstructedTypeName().matches("%rfc2898derivebytes%") + ) and + this = oc.getAnArgument() and + not this = oc.getConstructedTypeNode() + ) + } + + override string getSinkDescription() { result = "a cryptographic operation" } + } + + /** First positional argument to ConvertTo-SecureString. */ + class SecureStringSink extends Sink { + SecureStringSink() { + exists(DataFlow::CallNode call | + call.matchesName("ConvertTo-SecureString") and + this = call.getPositionalArgument(0) + ) + } + + override string getSinkDescription() { result = "a secure string conversion" } + } + + /** Value used in HTTP headers passed to Invoke-RestMethod or Invoke-WebRequest. */ + class HttpHeaderValueSink extends Sink { + HttpHeaderValueSink() { + exists(DataFlow::CallNode call, HashTableExpr ht | + (call.matchesName("Invoke-RestMethod") or call.matchesName("Invoke-WebRequest")) and + call.getNamedArgument("headers").asExpr().getExpr() = ht and + this.asExpr().getExpr() = ht.getAValue() + ) + } + + override string getSinkDescription() { result = "an HTTP header" } + } +} diff --git a/powershell/ql/lib/semmle/code/powershell/security/InsecureRandomnessQuery.qll b/powershell/ql/lib/semmle/code/powershell/security/InsecureRandomnessQuery.qll new file mode 100644 index 000000000000..562974263b1a --- /dev/null +++ b/powershell/ql/lib/semmle/code/powershell/security/InsecureRandomnessQuery.qll @@ -0,0 +1,26 @@ +/** + * Provides a taint tracking configuration for reasoning about + * insecure-randomness vulnerabilities (CWE-338). + * + * Note, for performance reasons: only import this file if + * `InsecureRandomnessFlow` is needed, otherwise + * `InsecureRandomnessCustomizations` should be imported instead. + */ + +import powershell +import semmle.code.powershell.dataflow.TaintTracking +import InsecureRandomnessCustomizations::InsecureRandomness +import semmle.code.powershell.dataflow.DataFlow + +private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } +} + +/** + * Taint-tracking for reasoning about insecure-randomness vulnerabilities. + */ +module InsecureRandomnessFlow = TaintTracking::Global; diff --git a/powershell/ql/src/queries/security/cwe-338/InsecureRandomness.qhelp b/powershell/ql/src/queries/security/cwe-338/InsecureRandomness.qhelp new file mode 100644 index 000000000000..63ca18d0b8f4 --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-338/InsecureRandomness.qhelp @@ -0,0 +1,25 @@ + + + + +

Using non-cryptographic random number generators for security-sensitive operations can compromise security. `Get-Random` and `System.Random` are not suitable for generating cryptographic keys, security tokens, or other security-critical random values.

+
+ + +

Use a cryptographically secure random number generator such as `[System.Security.Cryptography.RandomNumberGenerator]` instead.

+
+ + +

Insecure random:

+ + +

Secure alternative:

+ +
+ + +
  • CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG).
  • +
  • System.Security.Cryptography.RandomNumberGenerator.
  • +
    + +
    diff --git a/powershell/ql/src/queries/security/cwe-338/InsecureRandomness.ql b/powershell/ql/src/queries/security/cwe-338/InsecureRandomness.ql new file mode 100644 index 000000000000..6a4a77ae77d8 --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-338/InsecureRandomness.ql @@ -0,0 +1,23 @@ +/** + * @name Use of insecure random number generator in security-sensitive context + * @description Using non-cryptographic random number generators such as Get-Random or System.Random + * for security-sensitive operations can compromise security. + * @kind path-problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id powershell/insecure-randomness + * @tags security + * external/cwe/cwe-330 + * external/cwe/cwe-338 + */ + +import powershell +import semmle.code.powershell.security.InsecureRandomnessQuery +import InsecureRandomnessFlow::PathGraph + +from InsecureRandomnessFlow::PathNode source, InsecureRandomnessFlow::PathNode sink +where InsecureRandomnessFlow::flowPath(source, sink) +select sink.getNode(), source, sink, + "Insecure random value flows to " + sink.getNode().(Sink).getSinkDescription() + " from $@.", + source.getNode(), "this insecure random source" diff --git a/powershell/ql/src/queries/security/cwe-338/examples/InsecureRandomness/InsecureRandomnessBad.ps1 b/powershell/ql/src/queries/security/cwe-338/examples/InsecureRandomness/InsecureRandomnessBad.ps1 new file mode 100644 index 000000000000..c3f968ead7fa --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-338/examples/InsecureRandomness/InsecureRandomnessBad.ps1 @@ -0,0 +1,42 @@ +# BAD: weak RNG output flows into security-sensitive .NET crypto sinks. + +# 1) Weak bytes flow into AES .Key and .IV properties +$weak = New-Object System.Random +$keyBytes = New-Object byte[] 32 +$ivBytes = New-Object byte[] 16 +$weak.NextBytes($keyBytes) +$weak.NextBytes($ivBytes) +$aes = [System.Security.Cryptography.Aes]::Create() +$aes.Key = $keyBytes # BAD: predictable key +$aes.IV = $ivBytes # BAD: predictable IV + +# 2) Weak bytes flow into HMAC constructor (signing key) +$hmacKeyBytes = New-Object byte[] 64 +$weak.NextBytes($hmacKeyBytes) +$hmac = New-Object System.Security.Cryptography.HMACSHA256(,$hmacKeyBytes) # BAD: predictable HMAC key + +# 3) Weak bytes used as salt for password-based key derivation +$saltBytes = New-Object byte[] 8 +$weak.NextBytes($saltBytes) +$kdf = New-Object System.Security.Cryptography.Rfc2898DeriveBytes("password", $saltBytes) # BAD: predictable salt + +# 4) Get-Random result flows into ConvertTo-SecureString → PSCredential +$tempPwd = (Get-Random -Maximum 999999999).ToString() +$securePwd = ConvertTo-SecureString $tempPwd -AsPlainText -Force +$cred = New-Object System.Management.Automation.PSCredential("admin", $securePwd) # BAD: predictable credential + +# 5) Weak random flows into HTTP Authorization header +$tokenValue = Get-Random -Maximum 999999999 +Invoke-RestMethod -Uri "https://api.example.com/data" ` + -Headers @{ Authorization = "Bearer $tokenValue" } # BAD: predictable bearer token + +# 6) Weak random used as RNGCryptoServiceProvider substitute for key bytes +$rng2 = [System.Random]::new() +$aesKey2 = New-Object byte[] 32 +$rng2.NextBytes($aesKey2) +[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($aesKey2) | Out-Null +# The above line re-fills the buffer, but the initial weak fill might persist +# if code logic is wrong. True BAD pattern: relying solely on System.Random: +$aes2 = [System.Security.Cryptography.Aes]::Create() +$rng2.NextBytes($aesKey2) # BAD: final value comes from weak RNG +$aes2.Key = $aesKey2 # BAD: predictable key assigned to cipher \ No newline at end of file diff --git a/powershell/ql/src/queries/security/cwe-338/examples/InsecureRandomness/InsecureRandomnessGood.ps1 b/powershell/ql/src/queries/security/cwe-338/examples/InsecureRandomness/InsecureRandomnessGood.ps1 new file mode 100644 index 000000000000..bfadf603bf0a --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-338/examples/InsecureRandomness/InsecureRandomnessGood.ps1 @@ -0,0 +1,41 @@ +# GOOD: cryptographic RNG output flows into security-sensitive .NET crypto sinks. + +# 1) CSPRNG bytes flow into AES .Key and .IV properties +$keyBytes = New-Object byte[] 32 +$ivBytes = New-Object byte[] 16 +[System.Security.Cryptography.RandomNumberGenerator]::Fill($keyBytes) +[System.Security.Cryptography.RandomNumberGenerator]::Fill($ivBytes) +$aes = [System.Security.Cryptography.Aes]::Create() +$aes.Key = $keyBytes # GOOD: cryptographically random key +$aes.IV = $ivBytes # GOOD: cryptographically random IV + +# 2) CSPRNG bytes flow into HMAC constructor (signing key) +$hmacKeyBytes = New-Object byte[] 64 +[System.Security.Cryptography.RandomNumberGenerator]::Fill($hmacKeyBytes) +$hmac = New-Object System.Security.Cryptography.HMACSHA256(,$hmacKeyBytes) # GOOD + +# 3) CSPRNG bytes used as salt for password-based key derivation +$saltBytes = New-Object byte[] 16 +[System.Security.Cryptography.RandomNumberGenerator]::Fill($saltBytes) +$kdf = New-Object System.Security.Cryptography.Rfc2898DeriveBytes("password", $saltBytes) # GOOD + +# 4) CSPRNG bytes flow into ConvertTo-SecureString → PSCredential +$pwdBytes = New-Object byte[] 32 +[System.Security.Cryptography.RandomNumberGenerator]::Fill($pwdBytes) +$tempPwd = [Convert]::ToBase64String($pwdBytes) +$securePwd = ConvertTo-SecureString $tempPwd -AsPlainText -Force +$cred = New-Object System.Management.Automation.PSCredential("admin", $securePwd) # GOOD + +# 5) CSPRNG bytes flow into HTTP Authorization header +$tokenBytes = New-Object byte[] 32 +[System.Security.Cryptography.RandomNumberGenerator]::Fill($tokenBytes) +$bearerToken = [Convert]::ToBase64String($tokenBytes) +Invoke-RestMethod -Uri "https://api.example.com/data" ` + -Headers @{ Authorization = "Bearer $bearerToken" } # GOOD + +# 6) CSPRNG via RNGCryptoServiceProvider fills key bytes for cipher +$rng = [Security.Cryptography.RNGCryptoServiceProvider]::Create() +$aesKey2 = New-Object byte[] 32 +$rng.GetBytes($aesKey2) # GOOD: filled by CSPRNG +$aes2 = [System.Security.Cryptography.Aes]::Create() +$aes2.Key = $aesKey2 # GOOD: unpredictable key \ No newline at end of file diff --git a/powershell/ql/test/query-tests/security/cwe-338/InsecureRandomness/InsecureRandomness.expected b/powershell/ql/test/query-tests/security/cwe-338/InsecureRandomness/InsecureRandomness.expected new file mode 100644 index 000000000000..f857afc01971 --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-338/InsecureRandomness/InsecureRandomness.expected @@ -0,0 +1,84 @@ +edges +| file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | file://:0:0:0:0 | [summary param] pos(0, {}) in system.random;Method[nextbytes] [Return] | provenance | MaD:123 | +| test.ps1:6:1:6:5 | weak | test.ps1:9:1:9:5 | weak | provenance | | +| test.ps1:6:1:6:5 | weak | test.ps1:10:1:10:5 | weak | provenance | | +| test.ps1:6:9:6:32 | Call to new-object | test.ps1:6:1:6:5 | weak | provenance | | +| test.ps1:9:1:9:5 | weak | file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | provenance | | +| test.ps1:9:1:9:5 | weak | test.ps1:9:17:9:25 | [post] keyBytes | provenance | MaD:123 | +| test.ps1:9:17:9:25 | [post] keyBytes | test.ps1:12:12:12:20 | keyBytes | provenance | | +| test.ps1:10:1:10:5 | weak | file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | provenance | | +| test.ps1:10:1:10:5 | weak | test.ps1:10:17:10:24 | [post] ivBytes | provenance | MaD:123 | +| test.ps1:10:17:10:24 | [post] ivBytes | test.ps1:13:12:13:19 | ivBytes | provenance | | +| test.ps1:16:1:16:6 | weak2 | test.ps1:18:1:18:6 | weak2 | provenance | | +| test.ps1:16:10:16:33 | Call to new-object | test.ps1:16:1:16:6 | weak2 | provenance | | +| test.ps1:18:1:18:6 | weak2 | file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | provenance | | +| test.ps1:18:1:18:6 | weak2 | test.ps1:18:18:18:30 | [post] hmacKeyBytes | provenance | MaD:123 | +| test.ps1:18:18:18:30 | [post] hmacKeyBytes | test.ps1:19:61:19:73 | hmacKeyBytes | provenance | | +| test.ps1:19:60:19:73 | ...,... [element 0] | test.ps1:19:59:19:74 | (...) | provenance | | +| test.ps1:19:61:19:73 | hmacKeyBytes | test.ps1:19:60:19:73 | ...,... [element 0] | provenance | | +| test.ps1:22:1:22:6 | weak3 | test.ps1:24:1:24:6 | weak3 | provenance | | +| test.ps1:22:10:22:33 | Call to new-object | test.ps1:22:1:22:6 | weak3 | provenance | | +| test.ps1:24:1:24:6 | weak3 | file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | provenance | | +| test.ps1:24:1:24:6 | weak3 | test.ps1:24:18:24:27 | [post] saltBytes | provenance | MaD:123 | +| test.ps1:24:18:24:27 | [post] saltBytes | test.ps1:25:79:25:88 | saltBytes | provenance | | +| test.ps1:25:67:25:88 | ...,... [element 1] | test.ps1:25:66:25:89 | (...) | provenance | | +| test.ps1:25:79:25:88 | saltBytes | test.ps1:25:67:25:88 | ...,... [element 1] | provenance | | +| test.ps1:28:1:28:8 | tempPwd | test.ps1:29:37:29:44 | tempPwd | provenance | | +| test.ps1:28:13:28:41 | Call to get-random | test.ps1:28:1:28:8 | tempPwd | provenance | | +| test.ps1:33:1:33:11 | tokenValue | test.ps1:35:33:35:52 | Bearer $tokenValue | provenance | | +| test.ps1:33:15:33:43 | Call to get-random | test.ps1:33:1:33:11 | tokenValue | provenance | | +| test.ps1:38:1:38:5 | rng3 | test.ps1:40:1:40:5 | rng3 | provenance | | +| test.ps1:38:9:38:30 | Call to new | test.ps1:38:1:38:5 | rng3 | provenance | | +| test.ps1:40:1:40:5 | rng3 | file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | provenance | | +| test.ps1:40:1:40:5 | rng3 | test.ps1:40:17:40:24 | [post] aesKey2 | provenance | MaD:123 | +| test.ps1:40:17:40:24 | [post] aesKey2 | test.ps1:42:13:42:20 | aesKey2 | provenance | | +nodes +| file://:0:0:0:0 | [summary param] pos(0, {}) in system.random;Method[nextbytes] [Return] | semmle.label | [summary param] pos(0, {}) in system.random;Method[nextbytes] [Return] | +| file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | semmle.label | [summary param] this in system.random;Method[nextbytes] | +| test.ps1:6:1:6:5 | weak | semmle.label | weak | +| test.ps1:6:9:6:32 | Call to new-object | semmle.label | Call to new-object | +| test.ps1:9:1:9:5 | weak | semmle.label | weak | +| test.ps1:9:17:9:25 | [post] keyBytes | semmle.label | [post] keyBytes | +| test.ps1:10:1:10:5 | weak | semmle.label | weak | +| test.ps1:10:17:10:24 | [post] ivBytes | semmle.label | [post] ivBytes | +| test.ps1:12:12:12:20 | keyBytes | semmle.label | keyBytes | +| test.ps1:13:12:13:19 | ivBytes | semmle.label | ivBytes | +| test.ps1:16:1:16:6 | weak2 | semmle.label | weak2 | +| test.ps1:16:10:16:33 | Call to new-object | semmle.label | Call to new-object | +| test.ps1:18:1:18:6 | weak2 | semmle.label | weak2 | +| test.ps1:18:18:18:30 | [post] hmacKeyBytes | semmle.label | [post] hmacKeyBytes | +| test.ps1:19:59:19:74 | (...) | semmle.label | (...) | +| test.ps1:19:60:19:73 | ...,... [element 0] | semmle.label | ...,... [element 0] | +| test.ps1:19:61:19:73 | hmacKeyBytes | semmle.label | hmacKeyBytes | +| test.ps1:22:1:22:6 | weak3 | semmle.label | weak3 | +| test.ps1:22:10:22:33 | Call to new-object | semmle.label | Call to new-object | +| test.ps1:24:1:24:6 | weak3 | semmle.label | weak3 | +| test.ps1:24:18:24:27 | [post] saltBytes | semmle.label | [post] saltBytes | +| test.ps1:25:66:25:89 | (...) | semmle.label | (...) | +| test.ps1:25:67:25:88 | ...,... [element 1] | semmle.label | ...,... [element 1] | +| test.ps1:25:79:25:88 | saltBytes | semmle.label | saltBytes | +| test.ps1:28:1:28:8 | tempPwd | semmle.label | tempPwd | +| test.ps1:28:13:28:41 | Call to get-random | semmle.label | Call to get-random | +| test.ps1:29:37:29:44 | tempPwd | semmle.label | tempPwd | +| test.ps1:33:1:33:11 | tokenValue | semmle.label | tokenValue | +| test.ps1:33:15:33:43 | Call to get-random | semmle.label | Call to get-random | +| test.ps1:35:33:35:52 | Bearer $tokenValue | semmle.label | Bearer $tokenValue | +| test.ps1:38:1:38:5 | rng3 | semmle.label | rng3 | +| test.ps1:38:9:38:30 | Call to new | semmle.label | Call to new | +| test.ps1:40:1:40:5 | rng3 | semmle.label | rng3 | +| test.ps1:40:17:40:24 | [post] aesKey2 | semmle.label | [post] aesKey2 | +| test.ps1:42:13:42:20 | aesKey2 | semmle.label | aesKey2 | +subpaths +| test.ps1:9:1:9:5 | weak | file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | file://:0:0:0:0 | [summary param] pos(0, {}) in system.random;Method[nextbytes] [Return] | test.ps1:9:17:9:25 | [post] keyBytes | +| test.ps1:10:1:10:5 | weak | file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | file://:0:0:0:0 | [summary param] pos(0, {}) in system.random;Method[nextbytes] [Return] | test.ps1:10:17:10:24 | [post] ivBytes | +| test.ps1:18:1:18:6 | weak2 | file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | file://:0:0:0:0 | [summary param] pos(0, {}) in system.random;Method[nextbytes] [Return] | test.ps1:18:18:18:30 | [post] hmacKeyBytes | +| test.ps1:24:1:24:6 | weak3 | file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | file://:0:0:0:0 | [summary param] pos(0, {}) in system.random;Method[nextbytes] [Return] | test.ps1:24:18:24:27 | [post] saltBytes | +| test.ps1:40:1:40:5 | rng3 | file://:0:0:0:0 | [summary param] this in system.random;Method[nextbytes] | file://:0:0:0:0 | [summary param] pos(0, {}) in system.random;Method[nextbytes] [Return] | test.ps1:40:17:40:24 | [post] aesKey2 | +#select +| test.ps1:12:12:12:20 | keyBytes | test.ps1:6:9:6:32 | Call to new-object | test.ps1:12:12:12:20 | keyBytes | Insecure random value flows to a cryptographic key or IV from $@. | test.ps1:6:9:6:32 | Call to new-object | this insecure random source | +| test.ps1:13:12:13:19 | ivBytes | test.ps1:6:9:6:32 | Call to new-object | test.ps1:13:12:13:19 | ivBytes | Insecure random value flows to a cryptographic key or IV from $@. | test.ps1:6:9:6:32 | Call to new-object | this insecure random source | +| test.ps1:19:59:19:74 | (...) | test.ps1:16:10:16:33 | Call to new-object | test.ps1:19:59:19:74 | (...) | Insecure random value flows to a cryptographic operation from $@. | test.ps1:16:10:16:33 | Call to new-object | this insecure random source | +| test.ps1:25:66:25:89 | (...) | test.ps1:22:10:22:33 | Call to new-object | test.ps1:25:66:25:89 | (...) | Insecure random value flows to a cryptographic operation from $@. | test.ps1:22:10:22:33 | Call to new-object | this insecure random source | +| test.ps1:29:37:29:44 | tempPwd | test.ps1:28:13:28:41 | Call to get-random | test.ps1:29:37:29:44 | tempPwd | Insecure random value flows to a secure string conversion from $@. | test.ps1:28:13:28:41 | Call to get-random | this insecure random source | +| test.ps1:35:33:35:52 | Bearer $tokenValue | test.ps1:33:15:33:43 | Call to get-random | test.ps1:35:33:35:52 | Bearer $tokenValue | Insecure random value flows to an HTTP header from $@. | test.ps1:33:15:33:43 | Call to get-random | this insecure random source | +| test.ps1:42:13:42:20 | aesKey2 | test.ps1:38:9:38:30 | Call to new | test.ps1:42:13:42:20 | aesKey2 | Insecure random value flows to a cryptographic key or IV from $@. | test.ps1:38:9:38:30 | Call to new | this insecure random source | diff --git a/powershell/ql/test/query-tests/security/cwe-338/InsecureRandomness/InsecureRandomness.qlref b/powershell/ql/test/query-tests/security/cwe-338/InsecureRandomness/InsecureRandomness.qlref new file mode 100644 index 000000000000..88f718b69757 --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-338/InsecureRandomness/InsecureRandomness.qlref @@ -0,0 +1 @@ +queries/security/cwe-338/InsecureRandomness.ql diff --git a/powershell/ql/test/query-tests/security/cwe-338/InsecureRandomness/test.ps1 b/powershell/ql/test/query-tests/security/cwe-338/InsecureRandomness/test.ps1 new file mode 100644 index 000000000000..0830033e3bfd --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-338/InsecureRandomness/test.ps1 @@ -0,0 +1,99 @@ +# =================================================================== +# ========== TRUE POSITIVES (should trigger alert) ================== +# =================================================================== + +# --- Case 1: Weak RNG bytes flow into AES .Key and .IV --- +$weak = New-Object System.Random # BAD +$keyBytes = New-Object byte[] 32 +$ivBytes = New-Object byte[] 16 +$weak.NextBytes($keyBytes) +$weak.NextBytes($ivBytes) +$aes = [System.Security.Cryptography.Aes]::Create() +$aes.Key = $keyBytes # BAD +$aes.IV = $ivBytes # BAD + +# --- Case 2: Weak RNG bytes flow into HMAC constructor --- +$weak2 = New-Object System.Random # BAD +$hmacKeyBytes = New-Object byte[] 64 +$weak2.NextBytes($hmacKeyBytes) +$hmac = New-Object System.Security.Cryptography.HMACSHA256(,$hmacKeyBytes) # BAD + +# --- Case 3: Weak RNG bytes used as KDF salt --- +$weak3 = New-Object System.Random # BAD +$saltBytes = New-Object byte[] 8 +$weak3.NextBytes($saltBytes) +$kdf = New-Object System.Security.Cryptography.Rfc2898DeriveBytes("password", $saltBytes) # BAD + +# --- Case 4: Get-Random flows into ConvertTo-SecureString → PSCredential --- +$tempPwd = (Get-Random -Maximum 999999999).ToString() +$securePwd = ConvertTo-SecureString $tempPwd -AsPlainText -Force # BAD +$cred = New-Object System.Management.Automation.PSCredential("admin", $securePwd) + +# --- Case 5: Get-Random flows into HTTP Authorization header --- +$tokenValue = Get-Random -Maximum 999999999 +Invoke-RestMethod -Uri "https://api.example.com/data" ` + -Headers @{ Authorization = "Bearer $tokenValue" } # BAD + +# --- Case 6: [System.Random]::new() bytes flow into AES .Key --- +$rng3 = [System.Random]::new() +$aesKey2 = New-Object byte[] 32 +$rng3.NextBytes($aesKey2) +$aes2 = [System.Security.Cryptography.Aes]::Create() +$aes2.Key = $aesKey2 # BAD + +# =================================================================== +# ========== TRUE NEGATIVES (should NOT trigger alert) ============== +# =================================================================== + +# --- Safe: Using RandomNumberGenerator --- +$secureRng = [System.Security.Cryptography.RandomNumberGenerator]::Create() +$bytes = New-Object byte[] 32 +$secureRng.GetBytes($bytes) # GOOD + +# --- Safe: Using RNGCryptoServiceProvider --- +$cspRng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider +$secureBytes = New-Object byte[] 16 +$cspRng.GetBytes($secureBytes) # GOOD + +# --- Safe: Using RandomNumberGenerator static method --- +$randomBytes = [System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32) # GOOD + +# --- Safe: CSPRNG bytes flow into AES .Key and .IV --- +$goodKeyBytes = New-Object byte[] 32 +$goodIvBytes = New-Object byte[] 16 +[System.Security.Cryptography.RandomNumberGenerator]::Fill($goodKeyBytes) +[System.Security.Cryptography.RandomNumberGenerator]::Fill($goodIvBytes) +$goodAes = [System.Security.Cryptography.Aes]::Create() +$goodAes.Key = $goodKeyBytes # GOOD +$goodAes.IV = $goodIvBytes # GOOD + +# --- Safe: CSPRNG bytes flow into HMAC constructor --- +$goodHmacKey = New-Object byte[] 64 +[System.Security.Cryptography.RandomNumberGenerator]::Fill($goodHmacKey) +$goodHmac = New-Object System.Security.Cryptography.HMACSHA256(,$goodHmacKey) # GOOD + +# --- Safe: CSPRNG bytes used as KDF salt --- +$goodSalt = New-Object byte[] 16 +[System.Security.Cryptography.RandomNumberGenerator]::Fill($goodSalt) +$goodKdf = New-Object System.Security.Cryptography.Rfc2898DeriveBytes("password", $goodSalt) # GOOD + +# --- Safe: CSPRNG bytes flow into ConvertTo-SecureString → PSCredential --- +$goodPwdBytes = New-Object byte[] 32 +[System.Security.Cryptography.RandomNumberGenerator]::Fill($goodPwdBytes) +$goodPwd = [Convert]::ToBase64String($goodPwdBytes) +$goodSecurePwd = ConvertTo-SecureString $goodPwd -AsPlainText -Force +$goodCred = New-Object System.Management.Automation.PSCredential("admin", $goodSecurePwd) # GOOD + +# --- Safe: CSPRNG bytes flow into HTTP Authorization header --- +$goodTokenBytes = New-Object byte[] 32 +[System.Security.Cryptography.RandomNumberGenerator]::Fill($goodTokenBytes) +$goodBearer = [Convert]::ToBase64String($goodTokenBytes) +Invoke-RestMethod -Uri "https://api.example.com/data" ` + -Headers @{ Authorization = "Bearer $goodBearer" } # GOOD + +# --- Safe: RNGCryptoServiceProvider fills key bytes for cipher --- +$goodRng = [Security.Cryptography.RNGCryptoServiceProvider]::Create() +$goodAesKey2 = New-Object byte[] 32 +$goodRng.GetBytes($goodAesKey2) +$goodAes2 = [System.Security.Cryptography.Aes]::Create() +$goodAes2.Key = $goodAesKey2 # GOOD