Skip to content

Commit 3ce7d47

Browse files
authored
Merge pull request #7452 from jorgectf/python_jwt
Python: Add Python_JWT to JWT security query
2 parents 7d55771 + 4aa1c0a commit 3ce7d47

File tree

4 files changed

+67
-0
lines changed

4 files changed

+67
-0
lines changed

python/ql/src/experimental/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ private import experimental.semmle.python.frameworks.NoSQL
1111
private import experimental.semmle.python.frameworks.Log
1212
private import experimental.semmle.python.frameworks.JWT
1313
private import experimental.semmle.python.libraries.PyJWT
14+
private import experimental.semmle.python.libraries.Python_JWT
1415
private import experimental.semmle.python.libraries.Authlib
1516
private import experimental.semmle.python.libraries.PythonJose
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
private import python
2+
private import experimental.semmle.python.Concepts
3+
private import semmle.python.ApiGraphs
4+
5+
private module Python_JWT {
6+
/**
7+
* Gets a call to `python_jwt.process_jwt`.
8+
*
9+
* Given the following example:
10+
*
11+
* ```py
12+
* python_jwt.process_jwt(token)
13+
* python_jwt.verify_jwt(token, "key", "HS256")
14+
* ```
15+
*
16+
* * `this` would be `jwt.process_jwt(token)`.
17+
* * `getPayload()`'s result would be `token`.
18+
* * `getKey()`'s result would be `"key"`.
19+
* * `getAlgorithm()`'s result would be `"HS256"`.
20+
* * `getAlgorithmstring()`'s result would be `HS256`.
21+
* * `getOptions()`'s result would be `none()`.
22+
* * `verifiesSignature()` predicate would succeed.
23+
*/
24+
private class PythonJwtProcessCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
25+
PythonJwtProcessCall() {
26+
this = API::moduleImport("python_jwt").getMember("process_jwt").getACall()
27+
}
28+
29+
private DataFlow::CallCfgNode verifyCall() {
30+
result = API::moduleImport("python_jwt").getMember("verify_jwt").getACall() and
31+
this.getPayload().getALocalSource() = result.getArg(0).getALocalSource()
32+
}
33+
34+
override DataFlow::Node getPayload() { result = this.getArg(0) }
35+
36+
override DataFlow::Node getKey() { result = this.verifyCall().getArg(1) }
37+
38+
override DataFlow::Node getAlgorithm() { result = this.verifyCall().getArg(2) }
39+
40+
override string getAlgorithmString() {
41+
exists(StrConst str |
42+
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(this.getAlgorithm()) and
43+
result = str.getText()
44+
)
45+
}
46+
47+
override DataFlow::Node getOptions() { none() }
48+
49+
override predicate verifiesSignature() { exists(this.verifyCall()) }
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
| pyjwt.py:22:12:22:16 | ControlFlowNode for token | is not verified with a cryptographic secret or public key. |
22
| pyjwt.py:23:12:23:16 | ControlFlowNode for token | is not verified with a cryptographic secret or public key. |
33
| python_jose.py:19:12:19:16 | ControlFlowNode for token | is not verified with a cryptographic secret or public key. |
4+
| python_jwt.py:14:28:14:32 | ControlFlowNode for token | is not verified with a cryptographic secret or public key. |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import python_jwt
2+
3+
# GOOD
4+
5+
6+
def good(token):
7+
python_jwt.process_jwt(token)
8+
python_jwt.verify_jwt(token, "key", "HS256")
9+
10+
# BAD
11+
12+
13+
def bad(token):
14+
python_jwt.process_jwt(token)

0 commit comments

Comments
 (0)