mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge pull request #5588 from jorgectf/jorgectf/python/jwt-queries
Python: Add JWT security-related queries
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import jwt
|
||||
|
||||
# algorithm set to None
|
||||
jwt.encode(payload, "somekey", None)
|
||||
|
||||
# empty key
|
||||
jwt.encode(payload, key="", algorithm="HS256")
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Applications encoding a JSON Web Token (JWT) may be vulnerable when the applied key or algorithm
|
||||
is empty or <code>None</code>.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Use non-empty nor <code>None</code> values while encoding JWT payloads.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>This example shows two PyJWT encoding calls.
|
||||
|
||||
In the first place, the encoding process use a None algorithm whereas the second example uses an
|
||||
empty key. Both examples leave the payload insecurely encoded.
|
||||
</p>
|
||||
|
||||
<sample src="JWTEmptyKeyOrAlgorithm.py" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>PyJWT: <a href="https://pyjwt.readthedocs.io/en/stable/">Documentation</a>.</li>
|
||||
<li>Authlib JWT: <a href="https://docs.authlib.org/en/latest/specs/rfc7519.html">Documentation</a>.</li>
|
||||
<li>Python-Jose: <a href="https://github.com/mpdavis/python-jose">Documentation</a>.</li>
|
||||
<li>Auth0 Blog: <a href="https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/#Meet-the--None--Algorithm">Meet the "None" Algorithm</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name JWT encoding using empty key or algorithm
|
||||
* @description The application uses an empty secret or algorithm while encoding a JWT Token.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id py/jwt-empty-secret-or-algorithm
|
||||
* @tags security
|
||||
*/
|
||||
|
||||
// determine precision above
|
||||
import python
|
||||
import experimental.semmle.python.Concepts
|
||||
import experimental.semmle.python.frameworks.JWT
|
||||
|
||||
from JWTEncoding jwtEncoding, string affectedComponent
|
||||
where
|
||||
affectedComponent = "algorithm" and
|
||||
isEmptyOrNone(jwtEncoding.getAlgorithm())
|
||||
or
|
||||
affectedComponent = "key" and
|
||||
isEmptyOrNone(jwtEncoding.getKey())
|
||||
select jwtEncoding, "This JWT encoding has an empty " + affectedComponent + "."
|
||||
@@ -0,0 +1,4 @@
|
||||
import jwt
|
||||
|
||||
# unverified decoding
|
||||
jwt.decode(payload, key="somekey", verify=False)
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Applications decoding a JSON Web Token (JWT) may be vulnerable when the
|
||||
key isn't verified in the process.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Set the <code>verify</code> argument to <code>True</code> or use
|
||||
a framework that does it by default.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>This example shows a PyJWT encoding call with the <code>verify</code>
|
||||
argument set to <code>False</code>.
|
||||
</p>
|
||||
|
||||
<sample src="JWTMissingSecretOrPublicKeyVerification.py" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>PyJWT: <a href="https://pyjwt.readthedocs.io/en/stable/">Documentation</a>.</li>
|
||||
<li>Authlib JWT: <a href="https://docs.authlib.org/en/latest/specs/rfc7519.html">Documentation</a>.</li>
|
||||
<li>Python-Jose: <a href="https://github.com/mpdavis/python-jose">Documentation</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @name JWT missing secret or public key verification
|
||||
* @description The application does not verify the JWT payload with a cryptographic secret or public key.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id py/jwt-missing-verification
|
||||
* @tags security
|
||||
* external/cwe/cwe-347
|
||||
*/
|
||||
|
||||
// determine precision above
|
||||
import python
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
from JWTDecoding jwtDecoding
|
||||
where not jwtDecoding.verifiesSignature()
|
||||
select jwtDecoding.getPayload(), "is not verified with a cryptographic secret or public key."
|
||||
@@ -296,3 +296,141 @@ class HeaderDeclaration extends DataFlow::Node {
|
||||
*/
|
||||
DataFlow::Node getValueArg() { result = range.getValueArg() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling JWT encoding-related APIs. */
|
||||
module JWTEncoding {
|
||||
/**
|
||||
* A data-flow node that collects methods encoding a JWT token.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `JWTEncoding` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the argument containing the encoding payload.
|
||||
*/
|
||||
abstract DataFlow::Node getPayload();
|
||||
|
||||
/**
|
||||
* Gets the argument containing the encoding key.
|
||||
*/
|
||||
abstract DataFlow::Node getKey();
|
||||
|
||||
/**
|
||||
* Gets the argument for the algorithm used in the encoding.
|
||||
*/
|
||||
abstract DataFlow::Node getAlgorithm();
|
||||
|
||||
/**
|
||||
* Gets a string representation of the algorithm used in the encoding.
|
||||
*/
|
||||
abstract string getAlgorithmString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that collects methods encoding a JWT token.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `JWTEncoding::Range` instead.
|
||||
*/
|
||||
class JWTEncoding extends DataFlow::Node instanceof JWTEncoding::Range {
|
||||
/**
|
||||
* Gets the argument containing the payload.
|
||||
*/
|
||||
DataFlow::Node getPayload() { result = super.getPayload() }
|
||||
|
||||
/**
|
||||
* Gets the argument containing the encoding key.
|
||||
*/
|
||||
DataFlow::Node getKey() { result = super.getKey() }
|
||||
|
||||
/**
|
||||
* Gets the argument for the algorithm used in the encoding.
|
||||
*/
|
||||
DataFlow::Node getAlgorithm() { result = super.getAlgorithm() }
|
||||
|
||||
/**
|
||||
* Gets a string representation of the algorithm used in the encoding.
|
||||
*/
|
||||
string getAlgorithmString() { result = super.getAlgorithmString() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling JWT decoding-related APIs. */
|
||||
module JWTDecoding {
|
||||
/**
|
||||
* A data-flow node that collects methods decoding a JWT token.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `JWTDecoding` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the argument containing the encoding payload.
|
||||
*/
|
||||
abstract DataFlow::Node getPayload();
|
||||
|
||||
/**
|
||||
* Gets the argument containing the encoding key.
|
||||
*/
|
||||
abstract DataFlow::Node getKey();
|
||||
|
||||
/**
|
||||
* Gets the argument for the algorithm used in the encoding.
|
||||
*/
|
||||
abstract DataFlow::Node getAlgorithm();
|
||||
|
||||
/**
|
||||
* Gets a string representation of the algorithm used in the encoding.
|
||||
*/
|
||||
abstract string getAlgorithmString();
|
||||
|
||||
/**
|
||||
* Gets the options Node used in the encoding.
|
||||
*/
|
||||
abstract DataFlow::Node getOptions();
|
||||
|
||||
/**
|
||||
* Checks if the signature gets verified while decoding.
|
||||
*/
|
||||
abstract predicate verifiesSignature();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that collects methods encoding a JWT token.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `JWTDecoding::Range` instead.
|
||||
*/
|
||||
class JWTDecoding extends DataFlow::Node instanceof JWTDecoding::Range {
|
||||
/**
|
||||
* Gets the argument containing the payload.
|
||||
*/
|
||||
DataFlow::Node getPayload() { result = super.getPayload() }
|
||||
|
||||
/**
|
||||
* Gets the argument containing the encoding key.
|
||||
*/
|
||||
DataFlow::Node getKey() { result = super.getKey() }
|
||||
|
||||
/**
|
||||
* Gets the argument for the algorithm used in the encoding.
|
||||
*/
|
||||
DataFlow::Node getAlgorithm() { result = super.getAlgorithm() }
|
||||
|
||||
/**
|
||||
* Gets a string representation of the algorithm used in the encoding.
|
||||
*/
|
||||
string getAlgorithmString() { result = super.getAlgorithmString() }
|
||||
|
||||
/**
|
||||
* Gets the options Node used in the encoding.
|
||||
*/
|
||||
DataFlow::Node getOptions() { result = super.getOptions() }
|
||||
|
||||
/**
|
||||
* Checks if the signature gets verified while decoding.
|
||||
*/
|
||||
predicate verifiesSignature() { super.verifiesSignature() }
|
||||
}
|
||||
|
||||
@@ -9,3 +9,7 @@ private import experimental.semmle.python.frameworks.Werkzeug
|
||||
private import experimental.semmle.python.frameworks.LDAP
|
||||
private import experimental.semmle.python.frameworks.NoSQL
|
||||
private import experimental.semmle.python.frameworks.Log
|
||||
private import experimental.semmle.python.frameworks.JWT
|
||||
private import experimental.semmle.python.libraries.PyJWT
|
||||
private import experimental.semmle.python.libraries.Authlib
|
||||
private import experimental.semmle.python.libraries.PythonJose
|
||||
|
||||
23
python/ql/src/experimental/semmle/python/frameworks/JWT.qll
Normal file
23
python/ql/src/experimental/semmle/python/frameworks/JWT.qll
Normal file
@@ -0,0 +1,23 @@
|
||||
private import python
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/** Checks if the argument is empty or none. */
|
||||
predicate isEmptyOrNone(DataFlow::Node arg) { isEmpty(arg) or isNone(arg) }
|
||||
|
||||
/** Checks if an empty string `""` flows to `arg` */
|
||||
predicate isEmpty(DataFlow::Node arg) {
|
||||
exists(StrConst emptyString |
|
||||
emptyString.getText() = "" and
|
||||
DataFlow::exprNode(emptyString).(DataFlow::LocalSourceNode).flowsTo(arg)
|
||||
)
|
||||
}
|
||||
|
||||
/** Checks if `None` flows to `arg` */
|
||||
predicate isNone(DataFlow::Node arg) {
|
||||
DataFlow::exprNode(any(None no)).(DataFlow::LocalSourceNode).flowsTo(arg)
|
||||
}
|
||||
|
||||
/** Checks if `False` flows to `arg` */
|
||||
predicate isFalse(DataFlow::Node arg) {
|
||||
DataFlow::exprNode(any(False falseExpr)).(DataFlow::LocalSourceNode).flowsTo(arg)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
private import python
|
||||
private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import experimental.semmle.python.frameworks.JWT
|
||||
|
||||
private module Authlib {
|
||||
/** Gets a reference to `authlib.jose.(jwt|JsonWebToken)` */
|
||||
private API::Node authlibJWT() {
|
||||
result in [
|
||||
API::moduleImport("authlib").getMember("jose").getMember("jwt"),
|
||||
API::moduleImport("authlib").getMember("jose").getMember("JsonWebToken").getReturn()
|
||||
]
|
||||
}
|
||||
|
||||
/** Gets a reference to `jwt.encode` */
|
||||
private API::Node authlibJWTEncode() { result = authlibJWT().getMember("encode") }
|
||||
|
||||
/** Gets a reference to `jwt.decode` */
|
||||
private API::Node authlibJWTDecode() { result = authlibJWT().getMember("decode") }
|
||||
|
||||
/**
|
||||
* Gets a call to `authlib.jose.(jwt|JsonWebToken).encode`.
|
||||
*
|
||||
* Given the following example:
|
||||
*
|
||||
* ```py
|
||||
* jwt.encode({"alg": "HS256"}, token, "key")
|
||||
* ```
|
||||
*
|
||||
* * `this` would be `jwt.encode({"alg": "HS256"}, token, "key")`.
|
||||
* * `getPayload()`'s result would be `token`.
|
||||
* * `getKey()`'s result would be `"key"`.
|
||||
* * `getAlgorithm()`'s result would be `"HS256"`.
|
||||
* * `getAlgorithmstring()`'s result would be `HS256`.
|
||||
*/
|
||||
private class AuthlibJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
|
||||
AuthlibJWTEncodeCall() { this = authlibJWTEncode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() { result = this.getArg(1) }
|
||||
|
||||
override DataFlow::Node getKey() { result = this.getArg(2) }
|
||||
|
||||
override DataFlow::Node getAlgorithm() {
|
||||
exists(KeyValuePair headerDict |
|
||||
headerDict = this.getArg(0).asExpr().(Dict).getItem(_) and
|
||||
headerDict.getKey().(Str_).getS().matches("alg") and
|
||||
result.asExpr() = headerDict.getValue()
|
||||
)
|
||||
}
|
||||
|
||||
override string getAlgorithmString() {
|
||||
exists(StrConst str |
|
||||
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
|
||||
result = str.getText()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call to `authlib.jose.(jwt|JsonWebToken).decode`
|
||||
*
|
||||
* Given the following example:
|
||||
*
|
||||
* ```py
|
||||
* jwt.decode(token, key)
|
||||
* ```
|
||||
*
|
||||
* * `this` would be `jwt.decode(token, key)`.
|
||||
* * `getPayload()`'s result would be `token`.
|
||||
* * `getKey()`'s result would be `key`.
|
||||
*/
|
||||
private class AuthlibJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
|
||||
AuthlibJWTDecodeCall() { this = authlibJWTDecode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() { result = this.getArg(0) }
|
||||
|
||||
override DataFlow::Node getKey() { result = this.getArg(1) }
|
||||
|
||||
override DataFlow::Node getAlgorithm() { none() }
|
||||
|
||||
override string getAlgorithmString() { none() }
|
||||
|
||||
override DataFlow::Node getOptions() { none() }
|
||||
|
||||
override predicate verifiesSignature() { any() }
|
||||
}
|
||||
}
|
||||
108
python/ql/src/experimental/semmle/python/libraries/PyJWT.qll
Normal file
108
python/ql/src/experimental/semmle/python/libraries/PyJWT.qll
Normal file
@@ -0,0 +1,108 @@
|
||||
private import python
|
||||
private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import experimental.semmle.python.frameworks.JWT
|
||||
|
||||
private module PyJWT {
|
||||
/** Gets a reference to `jwt.encode` */
|
||||
private API::Node pyjwtEncode() { result = API::moduleImport("jwt").getMember("encode") }
|
||||
|
||||
/** Gets a reference to `jwt.decode` */
|
||||
private API::Node pyjwtDecode() { result = API::moduleImport("jwt").getMember("decode") }
|
||||
|
||||
/**
|
||||
* Gets a call to `jwt.encode`.
|
||||
*
|
||||
* Given the following example:
|
||||
*
|
||||
* ```py
|
||||
* jwt.encode(token, "key", "HS256")
|
||||
* ```
|
||||
*
|
||||
* * `this` would be `jwt.encode(token, "key", "HS256")`.
|
||||
* * `getPayload()`'s result would be `token`.
|
||||
* * `getKey()`'s result would be `"key"`.
|
||||
* * `getAlgorithm()`'s result would be `"HS256"`.
|
||||
* * `getAlgorithmstring()`'s result would be `HS256`.
|
||||
*/
|
||||
private class PyJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
|
||||
PyJWTEncodeCall() { this = pyjwtEncode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() {
|
||||
result in [this.getArg(0), this.getArgByName("payload")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
|
||||
|
||||
override DataFlow::Node getAlgorithm() {
|
||||
result in [this.getArg(2), this.getArgByName("algorithm")]
|
||||
}
|
||||
|
||||
override string getAlgorithmString() {
|
||||
exists(StrConst str |
|
||||
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
|
||||
result = str.getText()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call to `jwt.decode`.
|
||||
*
|
||||
* Given the following example:
|
||||
*
|
||||
* ```py
|
||||
* jwt.decode(token, key, "HS256", options={"verify_signature": True})
|
||||
* ```
|
||||
*
|
||||
* * `this` would be `jwt.decode(token, key, options={"verify_signature": True})`.
|
||||
* * `getPayload()`'s result would be `token`.
|
||||
* * `getKey()`'s result would be `key`.
|
||||
* * `getAlgorithm()`'s result would be `"HS256"`.
|
||||
* * `getAlgorithmstring()`'s result would be `HS256`.
|
||||
* * `getOptions()`'s result would be `{"verify_signature": True}`.
|
||||
* * `verifiesSignature()` predicate would succeed.
|
||||
*/
|
||||
private class PyJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
|
||||
PyJWTDecodeCall() { this = pyjwtDecode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() { result in [this.getArg(0), this.getArgByName("jwt")] }
|
||||
|
||||
override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
|
||||
|
||||
override DataFlow::Node getAlgorithm() {
|
||||
result in [this.getArg(2), this.getArgByName("algorithms")]
|
||||
}
|
||||
|
||||
override string getAlgorithmString() {
|
||||
exists(StrConst str |
|
||||
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
|
||||
result = str.getText()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOptions() {
|
||||
result in [this.getArg(3), this.getArgByName("options")]
|
||||
}
|
||||
|
||||
override predicate verifiesSignature() {
|
||||
not this.hasVerifySetToFalse() and
|
||||
not this.hasVerifySignatureSetToFalse()
|
||||
}
|
||||
|
||||
predicate hasNoVerifyArgumentOrOptions() {
|
||||
not exists(this.getArgByName("verify")) and not exists(this.getOptions())
|
||||
}
|
||||
|
||||
predicate hasVerifySetToFalse() { isFalse(this.getArgByName("verify")) }
|
||||
|
||||
predicate hasVerifySignatureSetToFalse() {
|
||||
exists(KeyValuePair optionsDict, NameConstant falseName |
|
||||
falseName.getId() = "False" and
|
||||
optionsDict = this.getOptions().asExpr().(Dict).getItem(_) and
|
||||
optionsDict.getKey().(Str_).getS().matches("%verify%") and
|
||||
falseName = optionsDict.getValue()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
private import python
|
||||
private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import experimental.semmle.python.frameworks.JWT
|
||||
|
||||
private module PythonJose {
|
||||
/** Gets a reference to `jwt` */
|
||||
private API::Node joseJWT() { result = API::moduleImport("jose").getMember("jwt") }
|
||||
|
||||
/** Gets a reference to `jwt.encode` */
|
||||
private API::Node joseJWTEncode() { result = joseJWT().getMember("encode") }
|
||||
|
||||
/** Gets a reference to `jwt.decode` */
|
||||
private API::Node joseJWTDecode() { result = joseJWT().getMember("decode") }
|
||||
|
||||
/**
|
||||
* Gets a call to `jwt.encode`.
|
||||
*
|
||||
* Given the following example:
|
||||
*
|
||||
* ```py
|
||||
* jwt.encode(token, key="key", algorithm="HS256")
|
||||
* ```
|
||||
*
|
||||
* * `this` would be `jwt.encode(token, key="key", algorithm="HS256")`.
|
||||
* * `getPayload()`'s result would be `token`.
|
||||
* * `getKey()`'s result would be `"key"`.
|
||||
* * `getAlgorithm()`'s result would be `"HS256"`.
|
||||
* * `getAlgorithmstring()`'s result would be `HS256`.
|
||||
*/
|
||||
private class JoseJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
|
||||
JoseJWTEncodeCall() { this = joseJWTEncode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() { result = this.getArg(0) }
|
||||
|
||||
override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
|
||||
|
||||
override DataFlow::Node getAlgorithm() {
|
||||
result in [this.getArg(2), this.getArgByName("algorithm")]
|
||||
}
|
||||
|
||||
override string getAlgorithmString() {
|
||||
exists(StrConst str |
|
||||
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
|
||||
result = str.getText()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call to `jwt.decode`.
|
||||
*
|
||||
* Given the following example:
|
||||
*
|
||||
* ```py
|
||||
* jwt.decode(token, "key", "HS256")
|
||||
* ```
|
||||
*
|
||||
* * `this` would be `jwt.decode(token, "key", "HS256")`.
|
||||
* * `getPayload()`'s result would be `token`.
|
||||
* * `getKey()`'s result would be `"key"`.
|
||||
* * `getAlgorithm()`'s result would be `"HS256"`.
|
||||
* * `getAlgorithmstring()`'s result would be `HS256`.
|
||||
* * `getOptions()`'s result would be none.
|
||||
* * `verifiesSignature()` predicate would succeed.
|
||||
*/
|
||||
private class JoseJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
|
||||
JoseJWTDecodeCall() { this = joseJWTDecode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() { result = this.getArg(0) }
|
||||
|
||||
override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
|
||||
|
||||
override DataFlow::Node getAlgorithm() {
|
||||
result in [this.getArg(2), this.getArgByName("algorithms")]
|
||||
}
|
||||
|
||||
override string getAlgorithmString() {
|
||||
exists(StrConst str |
|
||||
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
|
||||
result = str.getText()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOptions() {
|
||||
result in [this.getArg(3), this.getArgByName("options")]
|
||||
}
|
||||
|
||||
override predicate verifiesSignature() {
|
||||
// jwt.decode(token, key, options={"verify_signature": False})
|
||||
not this.hasVerifySignatureSetToFalse()
|
||||
}
|
||||
|
||||
predicate hasNoOptions() { not exists(this.getOptions()) }
|
||||
|
||||
predicate hasVerifySignatureSetToFalse() {
|
||||
exists(KeyValuePair optionsDict, NameConstant falseName |
|
||||
falseName.getId() = "False" and
|
||||
optionsDict = this.getOptions().asExpr().(Dict).getItem(_) and
|
||||
optionsDict.getKey().(Str_).getS().matches("%verify%") and
|
||||
falseName = optionsDict.getValue()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
| authlib.py:11:1:11:39 | ControlFlowNode for Attribute() | This JWT encoding has an empty key. |
|
||||
| authlib.py:12:1:12:50 | ControlFlowNode for Attribute() | This JWT encoding has an empty key. |
|
||||
| pyjwt.py:10:1:10:29 | ControlFlowNode for Attribute() | This JWT encoding has an empty algorithm. |
|
||||
| pyjwt.py:10:1:10:29 | ControlFlowNode for Attribute() | This JWT encoding has an empty key. |
|
||||
| pyjwt.py:13:1:13:40 | ControlFlowNode for Attribute() | This JWT encoding has an empty key. |
|
||||
| pyjwt.py:14:1:14:44 | ControlFlowNode for Attribute() | This JWT encoding has an empty key. |
|
||||
| python_jose.py:10:1:10:40 | ControlFlowNode for Attribute() | This JWT encoding has an empty key. |
|
||||
| python_jose.py:11:1:11:44 | ControlFlowNode for Attribute() | This JWT encoding has an empty key. |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-347/JWTEmptyKeyOrAlgorithm.ql
|
||||
@@ -0,0 +1,3 @@
|
||||
| pyjwt.py:22:12:22:16 | ControlFlowNode for token | is not verified with a cryptographic secret or public key. |
|
||||
| pyjwt.py:23:12:23:16 | ControlFlowNode for token | is not verified with a cryptographic secret or public key. |
|
||||
| python_jose.py:19:12:19:16 | ControlFlowNode for token | is not verified with a cryptographic secret or public key. |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.ql
|
||||
@@ -0,0 +1,18 @@
|
||||
from authlib.jose import jwt # It is already a JsonWebToken object
|
||||
from authlib.jose import JsonWebToken
|
||||
|
||||
# Encoding
|
||||
|
||||
# good - key and algorithm supplied
|
||||
jwt.encode({"alg": "HS256"}, token, "key")
|
||||
JsonWebToken().encode({"alg": "HS256"}, token, "key")
|
||||
|
||||
# bad - empty key
|
||||
jwt.encode({"alg": "HS256"}, token, "")
|
||||
JsonWebToken().encode({"alg": "HS256"}, token, "")
|
||||
|
||||
# Decoding
|
||||
|
||||
# good - "it will raise BadSignatureError when signature doesn’t match"
|
||||
jwt.decode(token, key)
|
||||
JsonWebToken().decode(token, key)
|
||||
@@ -0,0 +1,31 @@
|
||||
import jwt
|
||||
|
||||
# Encoding
|
||||
|
||||
# good - key and algorithm supplied
|
||||
jwt.encode(token, "key", "HS256")
|
||||
jwt.encode(token, key="key", algorithm="HS256")
|
||||
|
||||
# bad - both key and algorithm set to None
|
||||
jwt.encode(token, None, None)
|
||||
|
||||
# bad - empty key
|
||||
jwt.encode(token, "", algorithm="HS256")
|
||||
jwt.encode(token, key="", algorithm="HS256")
|
||||
|
||||
# Decoding
|
||||
|
||||
# good
|
||||
jwt.decode(token, "key", "HS256")
|
||||
|
||||
# bad - unverified decoding
|
||||
jwt.decode(token, verify=False)
|
||||
jwt.decode(token, key, options={"verify_signature": False})
|
||||
|
||||
# good - verified decoding
|
||||
jwt.decode(token, verify=True)
|
||||
jwt.decode(token, key, options={"verify_signature": True})
|
||||
|
||||
|
||||
def indeterminate(verify):
|
||||
jwt.decode(token, key, verify)
|
||||
@@ -0,0 +1,22 @@
|
||||
from jose import jwt
|
||||
|
||||
# Encoding
|
||||
|
||||
# good - key and algorithm supplied
|
||||
jwt.encode(token, "key", "HS256")
|
||||
jwt.encode(token, key="key", algorithm="HS256")
|
||||
|
||||
# bad - empty key
|
||||
jwt.encode(token, "", algorithm="HS256")
|
||||
jwt.encode(token, key="", algorithm="HS256")
|
||||
|
||||
# Decoding
|
||||
|
||||
# good
|
||||
jwt.decode(token, "key", "HS256")
|
||||
|
||||
# bad - unverified decoding
|
||||
jwt.decode(token, key, options={"verify_signature": False})
|
||||
|
||||
# good - verified decoding
|
||||
jwt.decode(token, key, options={"verify_signature": True})
|
||||
Reference in New Issue
Block a user