From 07dfa9b58c5477cdaaa01a7b82d0f4a66f9230d9 Mon Sep 17 00:00:00 2001 From: Chanel Young Date: Thu, 9 Apr 2026 09:41:58 -0700 Subject: [PATCH 1/2] Add JWT none algorithm detection query for PowerShell Detects usage of 'none' algorithm in JWT token creation via PowerShell modules (New-Jwt, etc.) and .NET JWT APIs (CreateToken, etc.). Covers: Cryptography.10033 (CWE-347) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../security/cwe-347/JwtNoneAlgorithm.ql | 65 +++++++++++++++++++ .../JwtNoneAlgorithm.expected | 1 + .../JwtNoneAlgorithm/JwtNoneAlgorithm.qlref | 1 + .../cwe-347/JwtNoneAlgorithm/test.ps1 | 16 +++++ 4 files changed, 83 insertions(+) create mode 100644 powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.ql create mode 100644 powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.expected create mode 100644 powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.qlref create mode 100644 powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/test.ps1 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..cfb9def7398e --- /dev/null +++ b/powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.ql @@ -0,0 +1,65 @@ +/** + * @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 medium + * @id powershell/microsoft/security/jwt-none-algorithm + * @tags security + * external/cwe/cwe-347 + */ + +import powershell +import semmle.code.powershell.dataflow.DataFlow + +// NOTE: PowerShell is a beta language for CodeQL and has no built-in JWT library modeling. +// This query detects common patterns of JWT "none" algorithm usage in PowerShell modules +// such as PSJwt, JWTPS, and direct .NET JWT library calls. +// Coverage may be limited for less common JWT libraries. + +/** + * A string literal containing "none" used in a JWT-related cmdlet call. + */ +class JwtNoneAlgorithmLiteral extends StringConstExpr { + JwtNoneAlgorithmLiteral() { + this.getValueString().toLowerCase() = "none" and + exists(CmdCall call | + call.matchesName([ + "New-Jwt", "New-JsonWebToken", "ConvertTo-Jwt", + "New-JWTToken", "ConvertTo-JWTToken" + ]) and + this = call.getAnArgument() + ) + } +} + +/** + * A string literal "none" passed as an algorithm argument to .NET JWT methods. + */ +class JwtNoneInDotNetCall extends StringConstExpr { + JwtNoneInDotNetCall() { + this.getValueString().toLowerCase() = "none" and + exists(InvokeMemberExpr call | + call.matchesName([ + "CreateToken", "WriteToken", "CreateJwtSecurityToken", "CreateEncodedJwt" + ]) and + this = call.getAnArgument() + ) + } +} + +from StringConstExpr noneAlg, string msg +where + ( + noneAlg instanceof JwtNoneAlgorithmLiteral and + msg = "JWT token created with 'none' algorithm, disabling signature verification." + ) + or + ( + noneAlg instanceof JwtNoneInDotNetCall and + msg = + "JWT token created with 'none' algorithm via .NET API, disabling signature verification." + ) +select noneAlg, msg 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..434896718944 --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.expected @@ -0,0 +1 @@ +| test.ps1:6:29:6:34 | none | JWT token created with 'none' algorithm, 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..3cba0dad4601 --- /dev/null +++ b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/test.ps1 @@ -0,0 +1,16 @@ +# =================================================================== +# ========== TRUE POSITIVES (should trigger alert) ================== +# =================================================================== + +# --- Case 1: PSJwt module with "none" algorithm --- +$token = New-Jwt -Algorithm "none" -Payload @{sub="user"} # BAD + +# =================================================================== +# ========== TRUE NEGATIVES (should NOT trigger alert) ============== +# =================================================================== + +# --- Safe: JWT with HS256 --- +$token = New-Jwt -Algorithm "HS256" -Payload @{sub="user"} -Secret $key # GOOD + +# --- Safe: Unrelated string "none" --- +$value = "none" # GOOD From 1f0f8e5a31c07e85600d119d2aabd9ce698ebf69 Mon Sep 17 00:00:00 2001 From: Chanel Young Date: Mon, 13 Apr 2026 10:16:14 -0700 Subject: [PATCH 2/2] changed to just detect the .net usage, updated tests, added qhelp --- .../security/cwe-347/JwtNoneAlgorithm.qhelp | 45 +++++++++++++++ .../security/cwe-347/JwtNoneAlgorithm.ql | 56 +++++-------------- .../cwe-347/examples/JwtNoneAlgorithmBad.ps1 | 4 ++ .../cwe-347/examples/JwtNoneAlgorithmGood.ps1 | 4 ++ .../JwtNoneAlgorithm.expected | 3 +- .../cwe-347/JwtNoneAlgorithm/test.ps1 | 15 ++--- 6 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.qhelp create mode 100644 powershell/ql/src/queries/security/cwe-347/examples/JwtNoneAlgorithmBad.ps1 create mode 100644 powershell/ql/src/queries/security/cwe-347/examples/JwtNoneAlgorithmGood.ps1 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 index cfb9def7398e..cfe282b828ba 100644 --- a/powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.ql +++ b/powershell/ql/src/queries/security/cwe-347/JwtNoneAlgorithm.ql @@ -5,8 +5,8 @@ * @kind problem * @problem.severity error * @security-severity 9.8 - * @precision medium - * @id powershell/microsoft/security/jwt-none-algorithm + * @precision high + * @id powershell/jwt-none-algorithm * @tags security * external/cwe/cwe-347 */ @@ -14,52 +14,24 @@ import powershell import semmle.code.powershell.dataflow.DataFlow -// NOTE: PowerShell is a beta language for CodeQL and has no built-in JWT library modeling. -// This query detects common patterns of JWT "none" algorithm usage in PowerShell modules -// such as PSJwt, JWTPS, and direct .NET JWT library calls. -// Coverage may be limited for less common JWT libraries. - -/** - * A string literal containing "none" used in a JWT-related cmdlet call. - */ -class JwtNoneAlgorithmLiteral extends StringConstExpr { - JwtNoneAlgorithmLiteral() { - this.getValueString().toLowerCase() = "none" and - exists(CmdCall call | - call.matchesName([ - "New-Jwt", "New-JsonWebToken", "ConvertTo-Jwt", - "New-JWTToken", "ConvertTo-JWTToken" - ]) and - this = call.getAnArgument() - ) - } -} - /** - * A string literal "none" passed as an algorithm argument to .NET JWT methods. + * 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(InvokeMemberExpr call | - call.matchesName([ - "CreateToken", "WriteToken", "CreateJwtSecurityToken", "CreateEncodedJwt" - ]) and - this = call.getAnArgument() + 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 StringConstExpr noneAlg, string msg -where - ( - noneAlg instanceof JwtNoneAlgorithmLiteral and - msg = "JWT token created with 'none' algorithm, disabling signature verification." - ) - or - ( - noneAlg instanceof JwtNoneInDotNetCall and - msg = - "JWT token created with 'none' algorithm via .NET API, disabling signature verification." - ) -select noneAlg, msg +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 index 434896718944..99a8061d7f1b 100644 --- a/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.expected +++ b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/JwtNoneAlgorithm.expected @@ -1 +1,2 @@ -| test.ps1:6:29:6:34 | none | JWT token created with 'none' algorithm, disabling signature verification. | +| 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/test.ps1 b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/test.ps1 index 3cba0dad4601..4be810003ab5 100644 --- a/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/test.ps1 +++ b/powershell/ql/test/query-tests/security/cwe-347/JwtNoneAlgorithm/test.ps1 @@ -2,15 +2,16 @@ # ========== TRUE POSITIVES (should trigger alert) ================== # =================================================================== -# --- Case 1: PSJwt module with "none" algorithm --- -$token = New-Jwt -Algorithm "none" -Payload @{sub="user"} # BAD +# --- 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: JWT with HS256 --- -$token = New-Jwt -Algorithm "HS256" -Payload @{sub="user"} -Secret $key # GOOD - -# --- Safe: Unrelated string "none" --- -$value = "none" # GOOD +# --- Safe: .NET CreateToken with HS256 --- +$token = $handler.CreateToken("HS256") # GOOD