Skip to content

Commit e1d42fa

Browse files
committed
move new secret key sinks to existing CredentialsNode class,
add new additional global taint and dataflow steps update tests of CWE-798 add a new sanitizer for `semmle.javascript.security.dataflow.HardcodedCredentialsQuery`
1 parent 8e0f52c commit e1d42fa

6 files changed

Lines changed: 549 additions & 274 deletions

File tree

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

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

4242
/**
43-
* The private key for a JWT as a `CredentialsNode`.
43+
* The secret Or PublicKey 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 = "key" }
52+
}
53+
}
54+
55+
/**
56+
* Provides classes and predicates modeling the `jose` library.
57+
*/
58+
private module Jose {
59+
/**
60+
* A taint-step for `succ = await jose.importSPKI(pred, 'RS256')`.
61+
*/
62+
private class ImportSpkiStep extends TaintTracking::SharedTaintStep, DataFlow::SharedFlowStep {
63+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
64+
exists(API::Node n | n = API::moduleImport("jose").getMember("importSPKI") |
65+
pred = n.getACall().getArgument(0) and
66+
succ = n.getReturn().getPromised().asSource()
67+
)
68+
}
69+
}
70+
71+
/**
72+
* A taint-step for `succ = jose.base64url.encode(pred)` or `succ = jose.base64url.decode(pred)`.
73+
*/
74+
private class Base64urlStep extends TaintTracking::SharedTaintStep, DataFlow::SharedFlowStep {
75+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
76+
exists(API::Node n |
77+
n = API::moduleImport("jose").getMember("base64url").getMember(["decode", "encode"])
78+
|
79+
pred = n.getACall().getArgument(0) and
80+
succ = n.getACall()
81+
)
82+
}
83+
}
84+
85+
/**
86+
* The asymmetric key or symmetric secret for a JWT as a `CredentialsNode`.
87+
*/
88+
private class JwtKey extends CredentialsNode {
89+
JwtKey() { this = API::moduleImport("jose").getMember("jwtVerify").getParameter(1).asSink() }
90+
91+
override string getCredentialsKind() { result = "key" }
92+
}
93+
}
94+
95+
/**
96+
* Provides classes and predicates modeling the `jwt-simple` library.
97+
*/
98+
private module JwtSimple {
99+
/**
100+
* The asymmetric key or symmetric secret for a JWT as a `CredentialsNode`.
101+
*/
102+
private class JwtKey extends CredentialsNode {
103+
JwtKey() { this = API::moduleImport("jwt-simple").getMember("decode").getParameter(1).asSink() }
104+
105+
override string getCredentialsKind() { result = "key" }
106+
}
107+
}
108+
109+
/**
110+
* Provides classes and predicates modeling the `koa-jwt` library.
111+
*/
112+
private module KoaJwt {
113+
/**
114+
* The shared secret for a JWT as a `CredentialsNode`.
115+
*/
116+
private class SharedSecret extends CredentialsNode {
117+
SharedSecret() {
118+
this = API::moduleImport("koa-jwt").getParameter(0).getMember("secret").asSink()
119+
}
120+
121+
override string getCredentialsKind() { result = "key" }
122+
}
123+
}
124+
125+
/**
126+
* Provides classes and predicates modeling the `express-jwt` library.
127+
*/
128+
private module ExpressJwt {
129+
/**
130+
* The shared secret for a JWT as a `CredentialsNode`.
131+
*/
132+
private class SharedSecret extends CredentialsNode {
133+
SharedSecret() {
134+
this =
135+
API::moduleImport("express-jwt")
136+
.getMember("expressjwt")
137+
.getParameter(0)
138+
.getMember("secret")
139+
.asSink()
140+
}
141+
142+
override string getCredentialsKind() { result = "key" }
143+
}
144+
}
145+
146+
/**
147+
* Provides classes and predicates modeling the `passport-jwt` library.
148+
*/
149+
private module PassportJwt {
150+
/**
151+
* The secret (symmetric) or PEM-encoded public key (asymmetric) for a JWT as a `CredentialsNode`.
152+
*/
153+
private class JwtKey extends CredentialsNode {
154+
JwtKey() {
155+
this =
156+
API::moduleImport("passport-jwt")
157+
.getMember("Strategy")
158+
.getParameter(0)
159+
.getMember("secretOrKey")
160+
.asSink()
161+
or
162+
this =
163+
API::moduleImport("passport-jwt")
164+
.getMember("Strategy")
165+
.getParameter(0)
166+
.getMember("secretOrKeyProvider")
167+
.getParameter(2)
168+
.getParameter(1)
169+
.asSink()
170+
}
47171

48172
override string getCredentialsKind() { result = "key" }
49173
}
50174
}
175+
176+
/**
177+
* A taint-step for `succ = new TextEncoder().encode(pred)`.
178+
*/
179+
private class TextEncoderStep extends TaintTracking::SharedTaintStep, DataFlow::SharedFlowStep {
180+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
181+
exists(DataFlow::CallNode n, DataFlow::NewNode nn |
182+
n.getCalleeName() = "encode" and
183+
nn.flowsTo(n.getReceiver()) and
184+
nn.getCalleeName() = "TextEncoder"
185+
|
186+
pred = n.getArgument(0) and
187+
succ = n
188+
)
189+
}
190+
}
191+
192+
/**
193+
* A taint-step for `succ = Buffer.from(pred, "base64")`.
194+
*/
195+
private class BufferFromStep extends TaintTracking::SharedTaintStep, DataFlow::SharedFlowStep {
196+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
197+
exists(DataFlow::CallNode n | n = DataFlow::globalVarRef("Buffer").getAMemberCall("from") |
198+
pred = n.getArgument(0) and
199+
succ = [n, n.getAChainedMethodCall(["toString", "toJSON"])]
200+
)
201+
}
202+
}

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 = "key" }
272+
}
273+
}
258274
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ module HardcodedCredentials {
3232
ConstantStringSource() { not astNode.getStringValue() = "" }
3333
}
3434

35+
class NonProductionFiles extends Sanitizer {
36+
NonProductionFiles() {
37+
this.getFile()
38+
.getLocation()
39+
.hasLocationInfo(any(string s |
40+
s.regexpMatch(["/.*test[.].*", "/.*demo[.].*", "/.*example[.].*", "/.*sample[.].*"])
41+
), _, _, _, _)
42+
}
43+
}
44+
3545
/**
3646
* A subclass of `Sink` that includes every `CredentialsNode`
3747
* as a credentials sink.
Lines changed: 0 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,3 @@
11
nodes
2-
| Config.js:2:12:2:19 | "secret" |
3-
| Config.js:2:12:2:19 | "secret" |
4-
| ExpressJWT.js:5:36:5:46 | getSecret() |
5-
| ExpressJWT.js:5:36:5:46 | getSecret() |
6-
| ExpressJWT.js:12:13:12:46 | Buffer. ... ase64") |
7-
| ExpressJWT.js:12:13:12:46 | Buffer. ... ase64") |
8-
| ExpressJWT.js:12:25:12:35 | getSecret() |
9-
| jwtConstantKey.js:5:46:5:56 | getSecret() |
10-
| jwtConstantKey.js:5:46:5:56 | getSecret() |
11-
| jwtConstantKey.js:6:43:6:53 | getSecret() |
12-
| jwtConstantKey.js:6:43:6:53 | getSecret() |
13-
| jwtConstantKey.js:12:68:12:104 | new Tex ... cret()) |
14-
| jwtConstantKey.js:12:68:12:104 | new Tex ... cret()) |
15-
| jwtConstantKey.js:12:93:12:103 | getSecret() |
16-
| jwtConstantKey.js:21:7:29:25 | spki |
17-
| jwtConstantKey.js:21:14:29:25 | `-----B ... Y-----` |
18-
| jwtConstantKey.js:21:14:29:25 | `-----B ... Y-----` |
19-
| jwtConstantKey.js:34:9:34:52 | publicKey |
20-
| jwtConstantKey.js:34:21:34:52 | await j ... i, alg) |
21-
| jwtConstantKey.js:34:43:34:46 | spki |
22-
| jwtConstantKey.js:35:65:35:73 | publicKey |
23-
| jwtConstantKey.js:35:65:35:73 | publicKey |
24-
| jwtConstantKey.js:51:42:51:52 | getSecret() |
25-
| jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" |
26-
| jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" |
27-
| jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" |
28-
| jwtConstantKey.js:51:56:51:59 | "fj" |
29-
| jwtConstantKey.js:51:56:51:59 | "fj" |
30-
| jwtNoVerification.js:5:46:5:56 | getSecret() |
31-
| jwtNoVerification.js:5:46:5:56 | getSecret() |
32-
| jwtNoVerification.js:19:26:19:36 | getSecret() |
33-
| jwtNoVerification.js:19:26:19:36 | getSecret() |
34-
| koaJWT.js:29:22:29:32 | getSecret() |
35-
| koaJWT.js:29:22:29:32 | getSecret() |
36-
| netxAuth.js:10:13:10:23 | getSecret() |
37-
| netxAuth.js:10:13:10:23 | getSecret() |
38-
| passportJWT.js:6:20:6:30 | getSecret() |
39-
| passportJWT.js:6:20:6:30 | getSecret() |
402
edges
41-
| Config.js:2:12:2:19 | "secret" | ExpressJWT.js:5:36:5:46 | getSecret() |
42-
| Config.js:2:12:2:19 | "secret" | ExpressJWT.js:5:36:5:46 | getSecret() |
43-
| Config.js:2:12:2:19 | "secret" | ExpressJWT.js:5:36:5:46 | getSecret() |
44-
| Config.js:2:12:2:19 | "secret" | ExpressJWT.js:5:36:5:46 | getSecret() |
45-
| Config.js:2:12:2:19 | "secret" | ExpressJWT.js:12:25:12:35 | getSecret() |
46-
| Config.js:2:12:2:19 | "secret" | ExpressJWT.js:12:25:12:35 | getSecret() |
47-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:5:46:5:56 | getSecret() |
48-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:5:46:5:56 | getSecret() |
49-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:5:46:5:56 | getSecret() |
50-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:5:46:5:56 | getSecret() |
51-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:6:43:6:53 | getSecret() |
52-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:6:43:6:53 | getSecret() |
53-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:6:43:6:53 | getSecret() |
54-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:6:43:6:53 | getSecret() |
55-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:12:93:12:103 | getSecret() |
56-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:12:93:12:103 | getSecret() |
57-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:51:42:51:52 | getSecret() |
58-
| Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:51:42:51:52 | getSecret() |
59-
| Config.js:2:12:2:19 | "secret" | jwtNoVerification.js:5:46:5:56 | getSecret() |
60-
| Config.js:2:12:2:19 | "secret" | jwtNoVerification.js:5:46:5:56 | getSecret() |
61-
| Config.js:2:12:2:19 | "secret" | jwtNoVerification.js:5:46:5:56 | getSecret() |
62-
| Config.js:2:12:2:19 | "secret" | jwtNoVerification.js:5:46:5:56 | getSecret() |
63-
| Config.js:2:12:2:19 | "secret" | jwtNoVerification.js:19:26:19:36 | getSecret() |
64-
| Config.js:2:12:2:19 | "secret" | jwtNoVerification.js:19:26:19:36 | getSecret() |
65-
| Config.js:2:12:2:19 | "secret" | jwtNoVerification.js:19:26:19:36 | getSecret() |
66-
| Config.js:2:12:2:19 | "secret" | jwtNoVerification.js:19:26:19:36 | getSecret() |
67-
| Config.js:2:12:2:19 | "secret" | koaJWT.js:29:22:29:32 | getSecret() |
68-
| Config.js:2:12:2:19 | "secret" | koaJWT.js:29:22:29:32 | getSecret() |
69-
| Config.js:2:12:2:19 | "secret" | koaJWT.js:29:22:29:32 | getSecret() |
70-
| Config.js:2:12:2:19 | "secret" | koaJWT.js:29:22:29:32 | getSecret() |
71-
| Config.js:2:12:2:19 | "secret" | netxAuth.js:10:13:10:23 | getSecret() |
72-
| Config.js:2:12:2:19 | "secret" | netxAuth.js:10:13:10:23 | getSecret() |
73-
| Config.js:2:12:2:19 | "secret" | netxAuth.js:10:13:10:23 | getSecret() |
74-
| Config.js:2:12:2:19 | "secret" | netxAuth.js:10:13:10:23 | getSecret() |
75-
| Config.js:2:12:2:19 | "secret" | passportJWT.js:6:20:6:30 | getSecret() |
76-
| Config.js:2:12:2:19 | "secret" | passportJWT.js:6:20:6:30 | getSecret() |
77-
| Config.js:2:12:2:19 | "secret" | passportJWT.js:6:20:6:30 | getSecret() |
78-
| Config.js:2:12:2:19 | "secret" | passportJWT.js:6:20:6:30 | getSecret() |
79-
| ExpressJWT.js:12:25:12:35 | getSecret() | ExpressJWT.js:12:13:12:46 | Buffer. ... ase64") |
80-
| ExpressJWT.js:12:25:12:35 | getSecret() | ExpressJWT.js:12:13:12:46 | Buffer. ... ase64") |
81-
| jwtConstantKey.js:12:93:12:103 | getSecret() | jwtConstantKey.js:12:68:12:104 | new Tex ... cret()) |
82-
| jwtConstantKey.js:12:93:12:103 | getSecret() | jwtConstantKey.js:12:68:12:104 | new Tex ... cret()) |
83-
| jwtConstantKey.js:21:7:29:25 | spki | jwtConstantKey.js:34:43:34:46 | spki |
84-
| jwtConstantKey.js:21:14:29:25 | `-----B ... Y-----` | jwtConstantKey.js:21:7:29:25 | spki |
85-
| jwtConstantKey.js:21:14:29:25 | `-----B ... Y-----` | jwtConstantKey.js:21:7:29:25 | spki |
86-
| jwtConstantKey.js:34:9:34:52 | publicKey | jwtConstantKey.js:35:65:35:73 | publicKey |
87-
| jwtConstantKey.js:34:9:34:52 | publicKey | jwtConstantKey.js:35:65:35:73 | publicKey |
88-
| jwtConstantKey.js:34:21:34:52 | await j ... i, alg) | jwtConstantKey.js:34:9:34:52 | publicKey |
89-
| jwtConstantKey.js:34:43:34:46 | spki | jwtConstantKey.js:34:21:34:52 | await j ... i, alg) |
90-
| jwtConstantKey.js:51:42:51:52 | getSecret() | jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" |
91-
| jwtConstantKey.js:51:42:51:52 | getSecret() | jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" |
92-
| jwtConstantKey.js:51:56:51:59 | "fj" | jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" |
93-
| jwtConstantKey.js:51:56:51:59 | "fj" | jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" |
94-
| jwtConstantKey.js:51:56:51:59 | "fj" | jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" |
95-
| jwtConstantKey.js:51:56:51:59 | "fj" | jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" |
963
#select
97-
| ExpressJWT.js:5:36:5:46 | getSecret() | Config.js:2:12:2:19 | "secret" | ExpressJWT.js:5:36:5:46 | getSecret() | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |
98-
| ExpressJWT.js:12:13:12:46 | Buffer. ... ase64") | Config.js:2:12:2:19 | "secret" | ExpressJWT.js:12:13:12:46 | Buffer. ... ase64") | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |
99-
| jwtConstantKey.js:5:46:5:56 | getSecret() | Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:5:46:5:56 | getSecret() | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |
100-
| jwtConstantKey.js:6:43:6:53 | getSecret() | Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:6:43:6:53 | getSecret() | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |
101-
| jwtConstantKey.js:12:68:12:104 | new Tex ... cret()) | Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:12:68:12:104 | new Tex ... cret()) | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |
102-
| jwtConstantKey.js:35:65:35:73 | publicKey | jwtConstantKey.js:21:14:29:25 | `-----B ... Y-----` | jwtConstantKey.js:35:65:35:73 | publicKey | this $@. is used as a secret key | jwtConstantKey.js:21:14:29:25 | `-----B ... Y-----` | Constant |
103-
| jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" | Config.js:2:12:2:19 | "secret" | jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |
104-
| jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" | jwtConstantKey.js:51:56:51:59 | "fj" | jwtConstantKey.js:51:42:51:59 | getSecret() + "fj" | this $@. is used as a secret key | jwtConstantKey.js:51:56:51:59 | "fj" | Constant |
105-
| jwtNoVerification.js:5:46:5:56 | getSecret() | Config.js:2:12:2:19 | "secret" | jwtNoVerification.js:5:46:5:56 | getSecret() | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |
106-
| jwtNoVerification.js:19:26:19:36 | getSecret() | Config.js:2:12:2:19 | "secret" | jwtNoVerification.js:19:26:19:36 | getSecret() | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |
107-
| koaJWT.js:29:22:29:32 | getSecret() | Config.js:2:12:2:19 | "secret" | koaJWT.js:29:22:29:32 | getSecret() | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |
108-
| netxAuth.js:10:13:10:23 | getSecret() | Config.js:2:12:2:19 | "secret" | netxAuth.js:10:13:10:23 | getSecret() | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |
109-
| passportJWT.js:6:20:6:30 | getSecret() | Config.js:2:12:2:19 | "secret" | passportJWT.js:6:20:6:30 | getSecret() | this $@. is used as a secret key | Config.js:2:12:2:19 | "secret" | Constant |

0 commit comments

Comments
 (0)