Skip to content

Commit 8d84d63

Browse files
committed
Add Python-Jose modeling and tests
1 parent ce507be commit 8d84d63

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
private import python
2+
private import experimental.semmle.python.Concepts
3+
private import semmle.python.ApiGraphs
4+
private import experimental.semmle.python.frameworks.JWT
5+
6+
private module PythonJose {
7+
/** Gets a reference to `jose` */
8+
private API::Node jose() { result = API::moduleImport("jose") }
9+
10+
/** Gets a reference to `jwt` */
11+
private API::Node joseJWT() { result = jose().getMember("jwt") }
12+
13+
/** Gets a reference to `jwt.encode` */
14+
private API::Node joseJWTEncode() { result = joseJWT().getMember("encode") }
15+
16+
/** Gets a reference to `jwt.decode` */
17+
private API::Node joseJWTDecode() { result = joseJWT().getMember("decode") }
18+
19+
// def encode(claims, key, algorithm=ALGORITHMS.HS256, headers=None, access_token=None):
20+
private class JoseJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
21+
JoseJWTEncodeCall() { this = joseJWTEncode().getACall() }
22+
23+
override DataFlow::Node getPayload() { result = this.getArg(0) }
24+
25+
override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
26+
27+
override DataFlow::Node getAlgorithm() {
28+
result in [this.getArg(2), this.getArgByName("algorithm")]
29+
}
30+
31+
override string getAlgorithmString() {
32+
exists(StrConst str |
33+
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
34+
result = str.getText()
35+
)
36+
}
37+
}
38+
39+
// def decode(token, key, algorithms=None, options=None, audience=None, issuer=None, subject=None, access_token=None):
40+
private class JoseJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
41+
JoseJWTDecodeCall() { this = joseJWTDecode().getACall() }
42+
43+
override DataFlow::Node getPayload() { result = this.getArg(0) }
44+
45+
override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
46+
47+
override DataFlow::Node getAlgorithm() {
48+
result in [this.getArg(2), this.getArgByName("algorithms")]
49+
}
50+
51+
override string getAlgorithmString() {
52+
exists(StrConst str |
53+
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
54+
result = str.getText()
55+
)
56+
}
57+
58+
override DataFlow::Node getOptions() {
59+
result in [this.getArg(3), this.getArgByName("options")]
60+
}
61+
62+
override predicate verifiesSignature() {
63+
// jwt.decode(token, "key", "HS256")
64+
not exists(this.getOptions())
65+
or
66+
// jwt.decode(token, key, options={"verify_signature": False})
67+
not exists(KeyValuePair optionsDict, NameConstant falseName |
68+
falseName.getId() = "False" and
69+
optionsDict = this.getOptions().asExpr().(Dict).getItems().getAnItem() and
70+
optionsDict.getKey().(Str_).getS().matches("%verify%") and
71+
falseName = optionsDict.getValue()
72+
)
73+
}
74+
}
75+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from jose import jwt
2+
3+
# Encoding
4+
5+
# good - key and algorithm supplied
6+
jwt.encode(token, "key", "HS256")
7+
jwt.encode(token, key="key", algorithm="HS256")
8+
9+
# bad - empty key
10+
jwt.encode(token, "", algorithm="HS256")
11+
jwt.encode(token, key="", algorithm="HS256")
12+
13+
# Decoding
14+
15+
# good
16+
jwt.decode(token, "key", "HS256")
17+
18+
# bad - unverified decoding
19+
jwt.decode(token, key, options={"verify_signature": False})
20+
21+
# good - verified decoding
22+
jwt.decode(token, key, options={"verify_signature": True})

0 commit comments

Comments
 (0)