diff --git a/powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.qhelp b/powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.qhelp new file mode 100644 index 000000000000..c731102849ef --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.qhelp @@ -0,0 +1,45 @@ + + + + +

Using the "none" algorithm when creating a JWT token with +JwtSecurityTokenHandler disables signature verification. This allows attackers to +forge arbitrary tokens that will be accepted as valid, potentially leading to authentication +bypass and privilege escalation.

+ +
+ + +

Always use a secure signing algorithm such as HS256, RS256, or +ES256 when creating JWT tokens. Ensure that tokens are signed with a strong secret +or key pair.

+ +
+ + +

In this example, a JWT token is created using the "none" algorithm, which produces +an unsigned token:

+ + + +

The fix is to use a secure algorithm instead:

+ + + +
+ + +
  • +RFC 7518: +JSON Web Algorithms - Unsecured JWS. +
  • + +
  • +CWE-347: +Improper Verification of Cryptographic Signature. +
  • + +
    +
    diff --git a/powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.ql b/powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.ql new file mode 100644 index 000000000000..cfe282b828ba --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.ql @@ -0,0 +1,37 @@ +/** + * @name JWT none algorithm usage + * @description Using the "none" algorithm for JWT tokens disables signature verification, + * allowing token forgery. + * @kind problem + * @problem.severity error + * @security-severity 9.8 + * @precision high + * @id powershell/jwt-none-algorithm + * @tags security + * external/cwe/cwe-347 + */ + +import powershell +import semmle.code.powershell.dataflow.DataFlow + +/** + * A string literal "none" passed as an algorithm argument to .NET JWT methods + * on a JwtSecurityTokenHandler instance. + */ +class JwtNoneInDotNetCall extends StringConstExpr { + JwtNoneInDotNetCall() { + this.getValueString().toLowerCase() = "none" and + exists(DataFlow::CallNode cn, DataFlow::ObjectCreationNode ocn | + cn.getQualifier().getALocalSource() = ocn and + ocn.getConstructedTypeNode().asExpr().getExpr().(TypeNameExpr).hasQualifiedName("system.identitymodel.tokens.jwt", "jwtsecuritytokenhandler") and + cn.getLowerCaseName() in [ + "createtoken", "writetoken", "createjwtsecuritytoken", "createencodedjwt" + ] and + cn.getAnArgument().asExpr().getExpr() = this + ) + } +} + +from JwtNoneInDotNetCall noneAlg +select noneAlg, + "JWT token created with 'none' algorithm via .NET API, disabling signature verification." diff --git a/powershell/ql/src/queries/security/cwe-347/examples/JwtNoneAlgorithmBad.ps1 b/powershell/ql/src/queries/security/cwe-347/examples/JwtNoneAlgorithmBad.ps1 new file mode 100644 index 000000000000..535765c884d4 --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-347/examples/JwtNoneAlgorithmBad.ps1 @@ -0,0 +1,4 @@ +$handler = [System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler]::new() + +# BAD: Creating a JWT token with the "none" algorithm disables signature verification. +$token = $handler.CreateToken("none") diff --git a/powershell/ql/src/queries/security/cwe-347/examples/JwtNoneAlgorithmGood.ps1 b/powershell/ql/src/queries/security/cwe-347/examples/JwtNoneAlgorithmGood.ps1 new file mode 100644 index 000000000000..77d39023cb1f --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-347/examples/JwtNoneAlgorithmGood.ps1 @@ -0,0 +1,4 @@ +$handler = [System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler]::new() + +# GOOD: Creating a JWT token with a secure algorithm. +$token = $handler.CreateToken("HS256") diff --git a/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.expected b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.expected new file mode 100644 index 000000000000..99a8061d7f1b --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.expected @@ -0,0 +1,2 @@ +| test.ps1:7:31:7:36 | none | JWT token created with 'none' algorithm via .NET API, disabling signature verification. | +| test.ps1:10:36:10:41 | none | JWT token created with 'none' algorithm via .NET API, disabling signature verification. | diff --git a/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.qlref b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.qlref new file mode 100644 index 000000000000..454d269bd3d2 --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.qlref @@ -0,0 +1 @@ +queries/security/cwe-347/JwtNoneAlgorithm.ql diff --git a/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/test.ps1 b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/test.ps1 new file mode 100644 index 000000000000..4be810003ab5 --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/test.ps1 @@ -0,0 +1,17 @@ +# =================================================================== +# ========== TRUE POSITIVES (should trigger alert) ================== +# =================================================================== + +# --- Case 1: .NET JwtSecurityTokenHandler.CreateToken with "none" --- +$handler = [System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler]::new() +$token = $handler.CreateToken("none") # BAD + +# --- Case 2: .NET JwtSecurityTokenHandler.CreateEncodedJwt with "none" --- +$token = $handler.CreateEncodedJwt("none") # BAD + +# =================================================================== +# ========== TRUE NEGATIVES (should NOT trigger alert) ============== +# =================================================================== + +# --- Safe: .NET CreateToken with HS256 --- +$token = $handler.CreateToken("HS256") # GOOD