Skip to content

Commit 41506fb

Browse files
authored
Merge pull request github#14666 from am0o0/amammad-js-hardcodedJWTKey
JS: Extends CredentialsNode class mostly related to JWT authentication packages
2 parents 2d3d46e + b64cb4d commit 41506fb

File tree

8 files changed

+434
-5
lines changed

8 files changed

+434
-5
lines changed

javascript/ql/lib/semmle/javascript/frameworks/Credentials.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import javascript
1212
abstract class CredentialsNode extends DataFlow::Node {
1313
/**
1414
* Gets a description of the kind of credential this expression is used as,
15-
* such as `"user name"`, `"password"`, `"key"`.
15+
* such as `"user name"`, `"password"`, `"key"`, `"jwt key"`.
1616
*/
1717
abstract string getCredentialsKind();
1818
}

javascript/ql/lib/semmle/javascript/frameworks/JWT.qll

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,111 @@ private module JsonWebToken {
4040
}
4141

4242
/**
43-
* The private key for a JWT as a `CredentialsNode`.
43+
* The secret or PrivateKey for a JWT as a `CredentialsNode`.
4444
*/
4545
private class JwtKey extends CredentialsNode {
46-
JwtKey() { this = DataFlow::moduleMember("jsonwebtoken", "sign").getACall().getArgument(1) }
46+
JwtKey() {
47+
this =
48+
API::moduleImport("jsonwebtoken").getMember(["sign", "verify"]).getParameter(1).asSink()
49+
}
50+
51+
override string getCredentialsKind() { result = "jwt key" }
52+
}
53+
}
54+
55+
/**
56+
* Provides classes and predicates modeling the `jose` library.
57+
*/
58+
private module Jose {
59+
/**
60+
* The asymmetric key or symmetric secret for verifying a JWT as a `CredentialsNode`.
61+
*/
62+
private class JwtVerifyKey extends CredentialsNode {
63+
JwtVerifyKey() {
64+
this = API::moduleImport("jose").getMember("jwtVerify").getParameter(1).asSink()
65+
}
66+
67+
override string getCredentialsKind() { result = "jwt key" }
68+
}
69+
}
70+
71+
/**
72+
* Provides classes and predicates modeling the `jwt-simple` library.
73+
*/
74+
private module JwtSimple {
75+
/**
76+
* The asymmetric key or symmetric secret for a JWT as a `CredentialsNode`.
77+
*/
78+
private class JwtKey extends CredentialsNode {
79+
JwtKey() { this = API::moduleImport("jwt-simple").getMember("decode").getParameter(1).asSink() }
80+
81+
override string getCredentialsKind() { result = "jwt key" }
82+
}
83+
}
84+
85+
/**
86+
* Provides classes and predicates modeling the `koa-jwt` library.
87+
*/
88+
private module KoaJwt {
89+
/**
90+
* The shared secret for a JWT as a `CredentialsNode`.
91+
*/
92+
private class SharedSecret extends CredentialsNode {
93+
SharedSecret() {
94+
this = API::moduleImport("koa-jwt").getParameter(0).getMember("secret").asSink()
95+
}
96+
97+
override string getCredentialsKind() { result = "jwt key" }
98+
}
99+
}
100+
101+
/**
102+
* Provides classes and predicates modeling the `express-jwt` library.
103+
*/
104+
private module ExpressJwt {
105+
/**
106+
* The shared secret for a JWT as a `CredentialsNode`.
107+
*/
108+
private class SharedSecret extends CredentialsNode {
109+
SharedSecret() {
110+
this =
111+
API::moduleImport("express-jwt")
112+
.getMember("expressjwt")
113+
.getParameter(0)
114+
.getMember("secret")
115+
.asSink()
116+
}
117+
118+
override string getCredentialsKind() { result = "jwt key" }
119+
}
120+
}
121+
122+
/**
123+
* Provides classes and predicates modeling the `passport-jwt` library.
124+
*/
125+
private module PassportJwt {
126+
/**
127+
* The secret (symmetric) or PEM-encoded public key (asymmetric) for a JWT as a `CredentialsNode`.
128+
*/
129+
private class JwtKey extends CredentialsNode {
130+
JwtKey() {
131+
this =
132+
API::moduleImport("passport-jwt")
133+
.getMember("Strategy")
134+
.getParameter(0)
135+
.getMember("secretOrKey")
136+
.asSink()
137+
or
138+
this =
139+
API::moduleImport("passport-jwt")
140+
.getMember("Strategy")
141+
.getParameter(0)
142+
.getMember("secretOrKeyProvider")
143+
.getParameter(2)
144+
.getParameter(1)
145+
.asSink()
146+
}
47147

48-
override string getCredentialsKind() { result = "key" }
148+
override string getCredentialsKind() { result = "jwt key" }
49149
}
50150
}

javascript/ql/lib/semmle/javascript/frameworks/Next.qll

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,20 @@ module NextJS {
255255
.getMember("router")
256256
.asSource()
257257
}
258+
259+
/**
260+
* Provides classes and predicates modeling the `next-auth` library.
261+
*/
262+
private module NextAuth {
263+
/**
264+
* A random string used to hash tokens, sign cookies and generate cryptographic keys as a `CredentialsNode`.
265+
*/
266+
private class SecretKey extends CredentialsNode {
267+
SecretKey() {
268+
this = API::moduleImport("next-auth").getParameter(0).getMember("secret").asSink()
269+
}
270+
271+
override string getCredentialsKind() { result = "jwt key" }
272+
}
273+
}
258274
}

javascript/ql/lib/semmle/javascript/security/dataflow/HardcodedCredentialsCustomizations.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* own.
55
*/
66

7+
import semmle.javascript.filters.ClassifyFiles
78
import javascript
89
private import semmle.javascript.security.SensitiveActions
910

@@ -38,5 +39,9 @@ module HardcodedCredentials {
3839
*/
3940
class DefaultCredentialsSink extends Sink instanceof CredentialsNode {
4041
override string getKind() { result = super.getCredentialsKind() }
42+
43+
DefaultCredentialsSink() {
44+
not (super.getCredentialsKind() = "jwt key" and isTestFile(this.getFile()))
45+
}
4146
}
4247
}

javascript/ql/lib/semmle/javascript/security/dataflow/HardcodedCredentialsQuery.qll

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,43 @@ class Configuration extends DataFlow::Configuration {
3535
trg = bufferFrom and
3636
src = bufferFrom.getArgument(0)
3737
)
38+
or
39+
exists(API::Node n |
40+
n = API::moduleImport("jose").getMember(["importSPKI", "importPKCS8", "importX509"])
41+
|
42+
src = n.getACall().getArgument(0) and
43+
trg = n.getReturn().getPromised().asSource()
44+
)
45+
or
46+
exists(API::Node n |
47+
n = API::moduleImport("jose").getMember(["importSPKI", "importPKCS8", "importX509"])
48+
|
49+
src = n.getACall().getArgument(0) and
50+
trg = n.getReturn().getPromised().asSource()
51+
)
52+
or
53+
exists(API::Node n | n = API::moduleImport("jose").getMember("importJWK") |
54+
src = n.getParameter(0).getMember(["x", "y", "n"]).asSink() and
55+
trg = n.getReturn().getPromised().asSource()
56+
)
57+
or
58+
exists(DataFlow::CallNode n |
59+
n = DataFlow::globalVarRef("TextEncoder").getAnInstantiation().getAMemberCall("encode")
60+
|
61+
src = n.getArgument(0) and
62+
trg = n
63+
)
64+
or
65+
exists(DataFlow::CallNode n | n = DataFlow::globalVarRef("Buffer").getAMemberCall("from") |
66+
src = n.getArgument(0) and
67+
trg = [n, n.getAChainedMethodCall(["toString", "toJSON"])]
68+
)
69+
or
70+
exists(API::Node n |
71+
n = API::moduleImport("jose").getMember("base64url").getMember(["decode", "encode"])
72+
|
73+
src = n.getACall().getArgument(0) and
74+
trg = n.getACall()
75+
)
3876
}
3977
}

0 commit comments

Comments
 (0)