diff --git a/javascript/ql/src/Security/CWE-347/MissingJWTKeyVerification.qhelp b/javascript/ql/src/Security/CWE-347/MissingJWTKeyVerification.qhelp new file mode 100644 index 000000000000..7e70c563cd6d --- /dev/null +++ b/javascript/ql/src/Security/CWE-347/MissingJWTKeyVerification.qhelp @@ -0,0 +1,42 @@ + + + +

+Applications decoding JSON Web Tokens (JWT) may be misconfigured due to the None algorithm. +

+

+The None algorithm is selected by calling the verify() function with a falsy value +instead of a cryptographic secret or key. The None algorithm disables the integrity enforcement of +a JWT payload and may allow a malicious actor to make unintended changes to a JWT payload leading +to critical security issues like privilege escalation. +

+ +
+ + +

+Calls to verify() functions should use a cryptographic secret or key to decode JWT payloads. +

+
+ + +

+In the example below, false is used to disable the integrity enforcement of a JWT payload. +This may allow a malicious actor to make changes to a JWT payload. +

+ + + +

+The following code fixes the problem by using a cryptographic secret or key to decode JWT payloads. +

+ + + +
+ + +
  • Auth0 Blog: Meet the "None" Algorithm.
  • + +
    +
    \ No newline at end of file diff --git a/javascript/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.ql b/javascript/ql/src/Security/CWE-347/MissingJWTKeyVerification.ql similarity index 56% rename from javascript/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.ql rename to javascript/ql/src/Security/CWE-347/MissingJWTKeyVerification.ql index 56d312f792b1..b19dba1ce112 100644 --- a/javascript/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.ql +++ b/javascript/ql/src/Security/CWE-347/MissingJWTKeyVerification.ql @@ -10,12 +10,11 @@ */ import javascript -import DataFlow import semmle.javascript.RestrictedLocations -from CallNode call +from DataFlow::CallNode call where - call = moduleMember("jsonwebtoken", "verify").getACall() and - unique(boolean b | b = call.getArgument(1).analyze().getABooleanValue()) = false -select call.asExpr().(FirstLineOf), - "does not verify the JWT payload with a cryptographic secret or public key." + call = DataFlow::moduleMember("jsonwebtoken", "verify").getACall() and + call.getArgument(1).analyze().getTheBooleanValue() = false +select call.getArgument(1), + "This argument disables the integrity enforcement of the token verification." diff --git a/javascript/ql/src/Security/CWE-347/examples/missing-key-verification-bad.js b/javascript/ql/src/Security/CWE-347/examples/missing-key-verification-bad.js new file mode 100644 index 000000000000..d00028da7bb1 --- /dev/null +++ b/javascript/ql/src/Security/CWE-347/examples/missing-key-verification-bad.js @@ -0,0 +1,6 @@ +const jwt = require("jsonwebtoken"); + +const secret = "my-secret-key"; + +var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: "none" }) +jwt.verify(token, false, { algorithms: ["HS256", "none"] }) \ No newline at end of file diff --git a/javascript/ql/src/Security/CWE-347/examples/missing-key-verification-good.js b/javascript/ql/src/Security/CWE-347/examples/missing-key-verification-good.js new file mode 100644 index 000000000000..f2df0d131c3c --- /dev/null +++ b/javascript/ql/src/Security/CWE-347/examples/missing-key-verification-good.js @@ -0,0 +1,7 @@ + +const jwt = require("jsonwebtoken"); + +const secret = "my-secret-key"; + +var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: "HS256" }) +jwt.verify(token, secret, { algorithms: ["HS256", "none"] }) \ No newline at end of file diff --git a/javascript/ql/src/change-notes/2022-01-25-missing-jwt-verification.md b/javascript/ql/src/change-notes/2022-01-25-missing-jwt-verification.md new file mode 100644 index 000000000000..7cbbd53b25b4 --- /dev/null +++ b/javascript/ql/src/change-notes/2022-01-25-missing-jwt-verification.md @@ -0,0 +1,4 @@ +--- +category: newQuery +--- +* A new query `js/jwt-missing-verification` has been added. The query detects applications that don't verify JWT tokens. diff --git a/javascript/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.help b/javascript/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.help deleted file mode 100644 index 29545b9edd90..000000000000 --- a/javascript/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.help +++ /dev/null @@ -1,30 +0,0 @@ - - - -

    Applications decoding JSON Web Token (JWT) may be misconfigured due to the none algorithm.

    -

    The none algorithm is selected by calling the verify() function with a falsy value -instead of a cryptographic secret or key. The none algorithm disables the integrity enforcement of -a JWT payload and may allow a malicious actor to make any desired changes to a JWT payload leading -to critical security issues like privilege escalation.

    - -
    - - -

    Call to verify() functions should use a cryptographic secret or key to decode JWT payloads.

    - -
    - - -

    In the example, the first case is signing an object with a secret and a HS256 algorithm. In the -second case, an empty string is provided, then an undefined value, and finally a false value. These -three misconfigured calls to jwt.verify() can cause vulnerabilities.

    - - - -
    - - -
  • Auth0 Blog: Meet the "None" Algorithm.
  • - -
    -
    \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-347/MissingJWTKeyVerification.expected b/javascript/ql/test/query-tests/Security/CWE-347/MissingJWTKeyVerification.expected new file mode 100644 index 000000000000..81d60e688166 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-347/MissingJWTKeyVerification.expected @@ -0,0 +1,3 @@ +| bad-jwt.js:10:19:10:20 | "" | This argument disables the integrity enforcement of the token verification. | +| bad-jwt.js:11:19:11:27 | undefined | This argument disables the integrity enforcement of the token verification. | +| bad-jwt.js:12:19:12:23 | false | This argument disables the integrity enforcement of the token verification. | diff --git a/javascript/ql/test/query-tests/Security/CWE-347/MissingJWTKeyVerification.qlref b/javascript/ql/test/query-tests/Security/CWE-347/MissingJWTKeyVerification.qlref new file mode 100644 index 000000000000..39d8056c9327 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-347/MissingJWTKeyVerification.qlref @@ -0,0 +1 @@ +Security/CWE-347/MissingJWTKeyVerification.ql diff --git a/javascript/ql/src/experimental/Security/CWE-347/examples/JWTMissingSecretOrPublicKeyVerification.js b/javascript/ql/test/query-tests/Security/CWE-347/bad-jwt.js similarity index 55% rename from javascript/ql/src/experimental/Security/CWE-347/examples/JWTMissingSecretOrPublicKeyVerification.js rename to javascript/ql/test/query-tests/Security/CWE-347/bad-jwt.js index 02e93f83fa13..947793289644 100644 --- a/javascript/ql/src/experimental/Security/CWE-347/examples/JWTMissingSecretOrPublicKeyVerification.js +++ b/javascript/ql/test/query-tests/Security/CWE-347/bad-jwt.js @@ -1,11 +1,12 @@ const jwt = require("jsonwebtoken"); -const secret = "buybtc"; -// #1 -var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: "HS256" }) // alg:HS256 -jwt.verify(token, secret, { algorithms: ["HS256", "none"] }) // pass -// #2 -var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: "none" }) // alg:none (unsafe) -jwt.verify(token, "", { algorithms: ["HS256", "none"] }) // detected -jwt.verify(token, undefined, { algorithms: ["HS256", "none"] }) // detected -jwt.verify(token, false, { algorithms: ["HS256", "none"] }) // detected \ No newline at end of file +const secret = "my-secret-key"; + +var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: "HS256" }) +jwt.verify(token, secret, { algorithms: ["HS256", "none"] }) // OK + + +var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: "none" }) +jwt.verify(token, "", { algorithms: ["HS256", "none"] }) // NOT OK +jwt.verify(token, undefined, { algorithms: ["HS256", "none"] }) // NOT OK +jwt.verify(token, false, { algorithms: ["HS256", "none"] }) // NOT OK \ No newline at end of file