Skip to content

Commit ce507be

Browse files
committed
Add Authlib modeling and tests
1 parent e14b103 commit ce507be

File tree

2 files changed

+79
-0
lines changed
  • python/ql
    • src/experimental/semmle/python/libraries
    • test/experimental/query-tests/Security/CWE-437

2 files changed

+79
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 Authlib {
7+
/** Gets a reference to `authlib.jose` */
8+
private API::Node authlib() { result = API::moduleImport("authlib.jose") }
9+
10+
/** Gets a reference to `authlib.jose.(jwt|JsonWebToken)` */
11+
private API::Node authlibJWT() {
12+
result in [authlib().getMember("jwt"), authlib().getMember("JsonWebToken").getReturn()]
13+
}
14+
15+
/** Gets a reference to `jwt.encode` */
16+
private API::Node authlibJWTEncode() { result = authlibJWT().getMember("encode") }
17+
18+
/** Gets a reference to `jwt.decode` */
19+
private API::Node authlibJWTDecode() { result = authlibJWT().getMember("decode") }
20+
21+
// def encode(self, header, payload, key, check=True):
22+
private class AuthlibJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
23+
AuthlibJWTEncodeCall() { this = authlibJWTEncode().getACall() }
24+
25+
override DataFlow::Node getPayload() { result = this.getArg(1) }
26+
27+
override DataFlow::Node getKey() { result = this.getArg(2) }
28+
29+
override DataFlow::Node getAlgorithm() {
30+
exists(KeyValuePair headerDict |
31+
headerDict = this.getArg(0).asExpr().(Dict).getItems().getAnItem() and
32+
headerDict.getKey().(Str_).getS().matches("alg") and
33+
result.asExpr() = headerDict.getValue()
34+
)
35+
}
36+
37+
override string getAlgorithmString() {
38+
exists(StrConst str |
39+
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
40+
result = str.getText()
41+
)
42+
}
43+
}
44+
45+
// def decode(self, s, key, claims_cls=None, claims_options=None, claims_params=None):
46+
private class AuthlibJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
47+
AuthlibJWTDecodeCall() { this = authlibJWTDecode().getACall() }
48+
49+
override DataFlow::Node getPayload() { result = this.getArg(0) }
50+
51+
override DataFlow::Node getKey() { result = this.getArg(1) }
52+
53+
override DataFlow::Node getAlgorithm() { none() }
54+
55+
override string getAlgorithmString() { none() }
56+
57+
override DataFlow::Node getOptions() { none() }
58+
59+
override predicate verifiesSignature() { any() }
60+
}
61+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from authlib.jose import jwt # It is already a JsonWebToken object
2+
from authlib.jose import JsonWebToken
3+
4+
# Encoding
5+
6+
# good - key and algorithm supplied
7+
jwt.encode({"alg": "HS256"}, token, "key")
8+
JsonWebToken().encode({"alg": "HS256"}, token, "key")
9+
10+
# bad - empty key
11+
jwt.encode({"alg": "HS256"}, token, "")
12+
JsonWebToken().encode({"alg": "HS256"}, token, "")
13+
14+
# Decoding
15+
16+
# good - "it will raise BadSignatureError when signature doesn’t match"
17+
jwt.decode(token, key)
18+
JsonWebToken().decode(token, key)

0 commit comments

Comments
 (0)