narvy-cli 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- narvy/__init__.py +3 -0
- narvy/android/__init__.py +0 -0
- narvy/android/source_analyzer.py +268 -0
- narvy/android/split_bundle.py +475 -0
- narvy/android_rule_context.py +196 -0
- narvy/apk_memory_preflight.py +248 -0
- narvy/auth.py +40 -0
- narvy/ci_templates/bitbucket.yml +25 -0
- narvy/ci_templates/github.yml +60 -0
- narvy/ci_templates/gitlab.yml +32 -0
- narvy/cloud/__init__.py +0 -0
- narvy/cloud/aws_scan.py +1188 -0
- narvy/comment_filter.py +134 -0
- narvy/crypto_taint_lite.py +82 -0
- narvy/decompiler.py +337 -0
- narvy/doctor.py +244 -0
- narvy/host/__init__.py +0 -0
- narvy/host/audit.py +157 -0
- narvy/host/host_knowledge.py +982 -0
- narvy/host/lynis_bootstrap.py +400 -0
- narvy/host/lynis_parser.py +358 -0
- narvy/host/narvy_checks.py +789 -0
- narvy/host/report.py +69 -0
- narvy/host/ssh_exec.py +219 -0
- narvy/ios/__init__.py +0 -0
- narvy/ios/binary_analyzer.py +1201 -0
- narvy/ios/plist_checks.py +249 -0
- narvy/ios/source_analyzer.py +301 -0
- narvy/ios/third_party_filter.py +242 -0
- narvy/ios/trust_all_context.py +66 -0
- narvy/main.py +1983 -0
- narvy/native_hardening.py +503 -0
- narvy/reporter.py +151 -0
- narvy/rule_engine.py +57 -0
- narvy/rules/android/config.yml +77 -0
- narvy/rules/android/crypto.yml +34 -0
- narvy/rules/android/secrets.yml +161 -0
- narvy/rules/android/storage.yml +24 -0
- narvy/rules/android/webview.yml +23 -0
- narvy/rules/android.yml +219 -0
- narvy/rules/ios/objc/crypto.yml +56 -0
- narvy/rules/ios/objc/network.yml +67 -0
- narvy/rules/ios/objc/secrets.yml +79 -0
- narvy/rules/ios/objc/storage.yml +45 -0
- narvy/rules/ios/objc/webview.yml +45 -0
- narvy/rules/ios_swift.yml +327 -0
- narvy/rules/web/go.yml +2837 -0
- narvy/rules/web/java.yml +1576 -0
- narvy/rules/web/javascript.yml +3683 -0
- narvy/rules/web/kotlin.yml +413 -0
- narvy/rules/web/local/csharp_narvy/config.yml +61 -0
- narvy/rules/web/local/csharp_narvy/crypto.yml +64 -0
- narvy/rules/web/local/csharp_narvy/deserialization.yml +59 -0
- narvy/rules/web/local/csharp_narvy/injection.yml +122 -0
- narvy/rules/web/local/csharp_narvy/xxe.yml +48 -0
- narvy/rules/web/local/java_narvy/auth_jwt.yml +134 -0
- narvy/rules/web/local/java_narvy/deserialization.yml +108 -0
- narvy/rules/web/local/java_narvy/mybatis.yml +39 -0
- narvy/rules/web/local/java_narvy/snakeyaml.yml +34 -0
- narvy/rules/web/local/java_narvy/spring_authz.yml +32 -0
- narvy/rules/web/local/java_narvy/spring_config.yml +67 -0
- narvy/rules/web/local/java_narvy/spring_hardening.yml +291 -0
- narvy/rules/web/local/java_narvy/sqli.yml +235 -0
- narvy/rules/web/local/java_narvy/xxe.yml +212 -0
- narvy/rules/web/php.yml +1644 -0
- narvy/rules/web/python.yml +3967 -0
- narvy/rules/web/ruby.yml +703 -0
- narvy/rules/web/rust.yml +258 -0
- narvy/rules/web/secrets.yml +1420 -0
- narvy/rules/web/secrets_supplement.yml +383 -0
- narvy/sca/__init__.py +1 -0
- narvy/sca/android_deps.py +349 -0
- narvy/sca/ios_deps.py +578 -0
- narvy/sca/osv_client.py +617 -0
- narvy/sca/web_deps.py +955 -0
- narvy/scope_config.py +195 -0
- narvy/semgrep_engine.py +219 -0
- narvy/stack_protector_evidence.py +96 -0
- narvy/third_party_filter.py +211 -0
- narvy/uploader.py +92 -0
- narvy/weak_prng_context.py +270 -0
- narvy/web/__init__.py +0 -0
- narvy/web/nuclei_binary.py +105 -0
- narvy/web/scan_blocklist.py +96 -0
- narvy/web/scanner.py +842 -0
- narvy/web/source_analyzer.py +714 -0
- narvy/web/ssrf_guard.py +374 -0
- narvy_cli-1.0.0.dist-info/METADATA +165 -0
- narvy_cli-1.0.0.dist-info/RECORD +93 -0
- narvy_cli-1.0.0.dist-info/WHEEL +5 -0
- narvy_cli-1.0.0.dist-info/entry_points.txt +2 -0
- narvy_cli-1.0.0.dist-info/licenses/LICENSE +202 -0
- narvy_cli-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,3683 @@
|
|
|
1
|
+
# Narvy JavaScript / TypeScript security rules (.js/.jsx/.ts/.tsx).
|
|
2
|
+
# Injection rules run in taint mode and pin the vulnerable arg with focus-metavariable.
|
|
3
|
+
|
|
4
|
+
rules:
|
|
5
|
+
|
|
6
|
+
# CLEARTEXT HTTP
|
|
7
|
+
|
|
8
|
+
- id: narvy.javascript.transport.cleartext-http-literal.cleartext-http-literal
|
|
9
|
+
message: >-
|
|
10
|
+
Cleartext HTTP URL detected. Traffic to this endpoint is unencrypted
|
|
11
|
+
and vulnerable to MITM (eavesdropping, tampering, session hijacking).
|
|
12
|
+
Use HTTPS. CWE-319.
|
|
13
|
+
severity: WARNING
|
|
14
|
+
languages: [javascript, typescript]
|
|
15
|
+
metadata:
|
|
16
|
+
cwe:
|
|
17
|
+
- "CWE-319: Cleartext Transmission of Sensitive Information"
|
|
18
|
+
owasp:
|
|
19
|
+
- "A02:2021 - Cryptographic Failures"
|
|
20
|
+
- "A03:2017 - Sensitive Data Exposure"
|
|
21
|
+
category: security
|
|
22
|
+
subcategory: [vuln]
|
|
23
|
+
technology: [javascript, typescript]
|
|
24
|
+
likelihood: MEDIUM
|
|
25
|
+
impact: MEDIUM
|
|
26
|
+
confidence: HIGH
|
|
27
|
+
vulnerability_class:
|
|
28
|
+
- Insecure Communication
|
|
29
|
+
references:
|
|
30
|
+
- https://cwe.mitre.org/data/definitions/319.html
|
|
31
|
+
- https://owasp.org/www-community/vulnerabilities/Insecure_Transport
|
|
32
|
+
paths:
|
|
33
|
+
exclude:
|
|
34
|
+
- "**/fuzzer/payloads.js"
|
|
35
|
+
- "**/fuzzer/payloads.ts"
|
|
36
|
+
- "**/payloads/*.js"
|
|
37
|
+
- "**/payloads/*.ts"
|
|
38
|
+
- "**/test/**"
|
|
39
|
+
- "**/tests/**"
|
|
40
|
+
- "**/__tests__/**"
|
|
41
|
+
- "**/*.test.js"
|
|
42
|
+
- "**/*.test.ts"
|
|
43
|
+
- "**/*.spec.js"
|
|
44
|
+
- "**/*.spec.ts"
|
|
45
|
+
patterns:
|
|
46
|
+
- pattern-either:
|
|
47
|
+
- pattern: "\"$URL\""
|
|
48
|
+
- pattern: "'$URL'"
|
|
49
|
+
- pattern: "`$URL`"
|
|
50
|
+
- metavariable-regex:
|
|
51
|
+
metavariable: $URL
|
|
52
|
+
regex: ^http://(?!localhost|127\.0\.0\.1|0\.0\.0\.0|169\.254\.|10\.\d+\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|\[::1\]|[a-zA-Z0-9.-]+\.(local|internal|test|example)(/|$|:)|schemas\.|www\.w3\.org|xmlns\.com|purl\.org|ns\.adobe\.com|java\.sun\.com|jabber\.org|x\.org|evil\.com)[a-zA-Z0-9._~:/?#\[\]@!$&'()*+,;=%-]+$
|
|
53
|
+
|
|
54
|
+
# CORS ORIGIN REFLECTION
|
|
55
|
+
|
|
56
|
+
- id: narvy.javascript.cors.origin-callback-always-allows.origin-callback-always-allows
|
|
57
|
+
message: >-
|
|
58
|
+
The CORS origin callback calls back with `true` unconditionally,
|
|
59
|
+
without checking the incoming origin against an allow-list. This
|
|
60
|
+
accepts requests from every origin, same as `origin: "*"`, but
|
|
61
|
+
combined with `credentials: true` this is worse: unlike a literal
|
|
62
|
+
wildcard, per the CORS spec a dynamic origin callback CAN be
|
|
63
|
+
combined with credentialed requests, letting any site make
|
|
64
|
+
authenticated cross-origin requests on behalf of a logged-in user
|
|
65
|
+
(CWE-942). Check the `origin` argument against an explicit
|
|
66
|
+
allow-list before calling back with true.
|
|
67
|
+
severity: WARNING
|
|
68
|
+
languages: [javascript, typescript]
|
|
69
|
+
metadata:
|
|
70
|
+
cwe:
|
|
71
|
+
- "CWE-942: Permissive Cross-domain Policy with Untrusted Domains"
|
|
72
|
+
owasp:
|
|
73
|
+
- "A05:2021 - Security Misconfiguration"
|
|
74
|
+
category: security
|
|
75
|
+
subcategory: [vuln]
|
|
76
|
+
technology: [express, cors, nodejs, javascript, typescript]
|
|
77
|
+
likelihood: LOW
|
|
78
|
+
impact: HIGH
|
|
79
|
+
confidence: MEDIUM
|
|
80
|
+
vulnerability_class:
|
|
81
|
+
- Other
|
|
82
|
+
references:
|
|
83
|
+
- https://cwe.mitre.org/data/definitions/942.html
|
|
84
|
+
patterns:
|
|
85
|
+
- pattern-either:
|
|
86
|
+
- pattern: |
|
|
87
|
+
cors({..., origin: function ($ORIGIN, $CB) { $CB(null, true) }, ...})
|
|
88
|
+
- pattern: |
|
|
89
|
+
cors({..., origin: ($ORIGIN, $CB) => { $CB(null, true) }, ...})
|
|
90
|
+
- pattern: |
|
|
91
|
+
cors({..., origin: ($ORIGIN, $CB) => $CB(null, true), ...})
|
|
92
|
+
|
|
93
|
+
- id: narvy.javascript.cors.header-reflects-request-origin.header-reflects-request-origin
|
|
94
|
+
message: >-
|
|
95
|
+
Access-Control-Allow-Origin is set directly from the incoming
|
|
96
|
+
request's Origin header, with no allow-list check in between. This
|
|
97
|
+
lets any origin pass CORS, functionally identical to a wildcard,
|
|
98
|
+
and if paired with Access-Control-Allow-Credentials: true it
|
|
99
|
+
allows any site to make authenticated cross-origin requests
|
|
100
|
+
(CWE-942). Look the incoming origin up in an explicit allow-list
|
|
101
|
+
and only echo it back if it matches.
|
|
102
|
+
severity: WARNING
|
|
103
|
+
languages: [javascript, typescript]
|
|
104
|
+
metadata:
|
|
105
|
+
cwe:
|
|
106
|
+
- "CWE-942: Permissive Cross-domain Policy with Untrusted Domains"
|
|
107
|
+
owasp:
|
|
108
|
+
- "A05:2021 - Security Misconfiguration"
|
|
109
|
+
category: security
|
|
110
|
+
subcategory: [vuln]
|
|
111
|
+
technology: [express, nodejs, javascript, typescript]
|
|
112
|
+
likelihood: LOW
|
|
113
|
+
impact: HIGH
|
|
114
|
+
confidence: MEDIUM
|
|
115
|
+
vulnerability_class:
|
|
116
|
+
- Other
|
|
117
|
+
patterns:
|
|
118
|
+
- pattern-either:
|
|
119
|
+
- pattern: $RES.header("Access-Control-Allow-Origin", $REQ.headers.origin)
|
|
120
|
+
- pattern: $RES.header('Access-Control-Allow-Origin', $REQ.headers.origin)
|
|
121
|
+
- pattern: $RES.setHeader("Access-Control-Allow-Origin", $REQ.headers.origin)
|
|
122
|
+
- pattern: $RES.setHeader('Access-Control-Allow-Origin', $REQ.headers.origin)
|
|
123
|
+
- pattern: $RES.set("Access-Control-Allow-Origin", $REQ.headers.origin)
|
|
124
|
+
|
|
125
|
+
# EXPRESS SECURITY HARDENING
|
|
126
|
+
|
|
127
|
+
# 1. Helmet with Content-Security-Policy explicitly disabled.
|
|
128
|
+
- id: narvy.javascript.express.helmet-csp-disabled.helmet-csp-disabled
|
|
129
|
+
message: >-
|
|
130
|
+
Helmet is initialised with `contentSecurityPolicy: false`, which turns
|
|
131
|
+
OFF the Content-Security-Policy header. Without a CSP the app has no
|
|
132
|
+
defence-in-depth against XSS, clickjacking via script/style injection,
|
|
133
|
+
or malicious resource loads. Configure an explicit CSP directive set
|
|
134
|
+
instead of disabling it. CWE-1021 / CWE-693.
|
|
135
|
+
severity: WARNING
|
|
136
|
+
languages: [javascript, typescript]
|
|
137
|
+
metadata:
|
|
138
|
+
cwe:
|
|
139
|
+
- "CWE-1021: Improper Restriction of Rendered UI Layers or Frames"
|
|
140
|
+
- "CWE-693: Protection Mechanism Failure"
|
|
141
|
+
owasp:
|
|
142
|
+
- "A05:2021 - Security Misconfiguration"
|
|
143
|
+
category: security
|
|
144
|
+
subcategory: [vuln]
|
|
145
|
+
technology: [express, nodejs, helmet, javascript, typescript]
|
|
146
|
+
likelihood: MEDIUM
|
|
147
|
+
impact: MEDIUM
|
|
148
|
+
confidence: HIGH
|
|
149
|
+
vulnerability_class:
|
|
150
|
+
- Security Misconfiguration
|
|
151
|
+
references:
|
|
152
|
+
- https://helmetjs.github.io/
|
|
153
|
+
- https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html
|
|
154
|
+
paths:
|
|
155
|
+
exclude:
|
|
156
|
+
- "**/test/**"
|
|
157
|
+
- "**/tests/**"
|
|
158
|
+
- "**/__tests__/**"
|
|
159
|
+
- "**/*.test.js"
|
|
160
|
+
- "**/*.test.ts"
|
|
161
|
+
- "**/*.spec.js"
|
|
162
|
+
- "**/*.spec.ts"
|
|
163
|
+
patterns:
|
|
164
|
+
- pattern-either:
|
|
165
|
+
- pattern: |
|
|
166
|
+
helmet({..., contentSecurityPolicy: false, ...})
|
|
167
|
+
- pattern: |
|
|
168
|
+
helmet.contentSecurityPolicy(false)
|
|
169
|
+
|
|
170
|
+
# 2. CORS configured to allow any origin.
|
|
171
|
+
- id: narvy.javascript.express.cors-wildcard-origin.cors-wildcard-origin
|
|
172
|
+
message: >-
|
|
173
|
+
CORS is configured to accept requests from ANY origin
|
|
174
|
+
(`origin: "*"` or `origin: true`). Combined with credentialed requests
|
|
175
|
+
this lets any website read authenticated responses on behalf of a
|
|
176
|
+
logged-in user. Restrict `origin` to an explicit allow-list. CWE-942.
|
|
177
|
+
severity: WARNING
|
|
178
|
+
languages: [javascript, typescript]
|
|
179
|
+
metadata:
|
|
180
|
+
cwe:
|
|
181
|
+
- "CWE-942: Permissive Cross-domain Policy with Untrusted Domains"
|
|
182
|
+
owasp:
|
|
183
|
+
- "A05:2021 - Security Misconfiguration"
|
|
184
|
+
category: security
|
|
185
|
+
subcategory: [vuln]
|
|
186
|
+
technology: [express, nodejs, cors, javascript, typescript]
|
|
187
|
+
likelihood: MEDIUM
|
|
188
|
+
impact: MEDIUM
|
|
189
|
+
confidence: MEDIUM
|
|
190
|
+
vulnerability_class:
|
|
191
|
+
- Security Misconfiguration
|
|
192
|
+
references:
|
|
193
|
+
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
|
194
|
+
paths:
|
|
195
|
+
exclude:
|
|
196
|
+
- "**/test/**"
|
|
197
|
+
- "**/tests/**"
|
|
198
|
+
- "**/__tests__/**"
|
|
199
|
+
- "**/*.test.js"
|
|
200
|
+
- "**/*.test.ts"
|
|
201
|
+
- "**/*.spec.js"
|
|
202
|
+
- "**/*.spec.ts"
|
|
203
|
+
patterns:
|
|
204
|
+
- pattern-either:
|
|
205
|
+
- pattern: |
|
|
206
|
+
cors({..., origin: "*", ...})
|
|
207
|
+
- pattern: |
|
|
208
|
+
cors({..., origin: true, ...})
|
|
209
|
+
|
|
210
|
+
# 3. MongoDB connection string with no credentials.
|
|
211
|
+
- id: narvy.javascript.database.mongodb-uri-without-auth.mongodb-uri-without-auth
|
|
212
|
+
message: >-
|
|
213
|
+
MongoDB connection string has no user:password credentials, so it
|
|
214
|
+
connects to an UNAUTHENTICATED database. If this URI is ever used in
|
|
215
|
+
production (e.g. an unset env var falling back to this literal), any
|
|
216
|
+
network-adjacent actor can read/write the entire database. Require
|
|
217
|
+
authentication and never fall back to a credential-less URI. CWE-306.
|
|
218
|
+
severity: INFO
|
|
219
|
+
languages: [javascript, typescript]
|
|
220
|
+
metadata:
|
|
221
|
+
cwe:
|
|
222
|
+
- "CWE-306: Missing Authentication for Critical Function"
|
|
223
|
+
owasp:
|
|
224
|
+
- "A05:2021 - Security Misconfiguration"
|
|
225
|
+
- "A07:2021 - Identification and Authentication Failures"
|
|
226
|
+
category: security
|
|
227
|
+
subcategory: [audit]
|
|
228
|
+
technology: [mongodb, nodejs, javascript, typescript]
|
|
229
|
+
likelihood: LOW
|
|
230
|
+
impact: HIGH
|
|
231
|
+
confidence: MEDIUM
|
|
232
|
+
vulnerability_class:
|
|
233
|
+
- Security Misconfiguration
|
|
234
|
+
references:
|
|
235
|
+
- https://www.mongodb.com/docs/manual/administration/security-checklist/
|
|
236
|
+
paths:
|
|
237
|
+
exclude:
|
|
238
|
+
- "**/test/**"
|
|
239
|
+
- "**/tests/**"
|
|
240
|
+
- "**/__tests__/**"
|
|
241
|
+
- "**/*.test.js"
|
|
242
|
+
- "**/*.test.ts"
|
|
243
|
+
- "**/*.spec.js"
|
|
244
|
+
- "**/*.spec.ts"
|
|
245
|
+
patterns:
|
|
246
|
+
- pattern-either:
|
|
247
|
+
- pattern: "\"$URI\""
|
|
248
|
+
- pattern: "'$URI'"
|
|
249
|
+
- metavariable-regex:
|
|
250
|
+
metavariable: $URI
|
|
251
|
+
# mongodb:// or mongodb+srv:// with a host but NO "@" (credentials).
|
|
252
|
+
regex: ^mongodb(\+srv)?://(?!.*@)[a-zA-Z0-9._-]+(:[0-9]+)?(/[a-zA-Z0-9._-]*)?$
|
|
253
|
+
|
|
254
|
+
# HARDCODED KDF FALLBACK
|
|
255
|
+
|
|
256
|
+
# Rule 1: env-fallback fed into `crypto.createHash(...).update(...)`
|
|
257
|
+
- id: narvy.javascript.crypto.hardcoded-env-fallback-to-hash.hardcoded-env-fallback-to-hash
|
|
258
|
+
message: >-
|
|
259
|
+
Hardcoded fallback secret '$LITERAL' is used when the environment
|
|
260
|
+
variable is unset, and is then fed into a cryptographic hash. If the
|
|
261
|
+
env var is missing in any deployment (e.g. local dev, CI, misconfigured
|
|
262
|
+
prod), the encryption key reduces to a public constant. Furthermore,
|
|
263
|
+
a single hash round is not a key-derivation function - use HKDF
|
|
264
|
+
(crypto.hkdfSync), PBKDF2 (crypto.pbkdf2Sync), scrypt, or Argon2 with
|
|
265
|
+
a per-tenant salt. Fix the env loading (fail fast if absent) AND
|
|
266
|
+
switch to a real KDF.
|
|
267
|
+
severity: ERROR
|
|
268
|
+
languages: [javascript, typescript]
|
|
269
|
+
metadata:
|
|
270
|
+
cwe:
|
|
271
|
+
- "CWE-321: Use of Hard-coded Cryptographic Key"
|
|
272
|
+
- "CWE-798: Use of Hard-coded Credentials"
|
|
273
|
+
- "CWE-916: Use of Password Hash With Insufficient Computational Effort"
|
|
274
|
+
owasp:
|
|
275
|
+
- "A02:2021 - Cryptographic Failures"
|
|
276
|
+
- "A07:2021 - Identification and Authentication Failures"
|
|
277
|
+
category: security
|
|
278
|
+
subcategory: [vuln]
|
|
279
|
+
technology: [node-crypto, javascript]
|
|
280
|
+
likelihood: HIGH
|
|
281
|
+
impact: HIGH
|
|
282
|
+
confidence: HIGH
|
|
283
|
+
vulnerability_class:
|
|
284
|
+
- Cryptographic Issues
|
|
285
|
+
- Hard-coded Credentials
|
|
286
|
+
references:
|
|
287
|
+
- https://cwe.mitre.org/data/definitions/321.html
|
|
288
|
+
- https://cwe.mitre.org/data/definitions/916.html
|
|
289
|
+
- https://datatracker.ietf.org/doc/html/rfc5869
|
|
290
|
+
patterns:
|
|
291
|
+
- pattern-either:
|
|
292
|
+
# Variant 1: `||` operator
|
|
293
|
+
- pattern: |
|
|
294
|
+
$CRYPTO.createHash($ALGO).update(process.env.$ENV || "$LITERAL")
|
|
295
|
+
- pattern: |
|
|
296
|
+
$CRYPTO.createHash($ALGO).update(process.env.$ENV || '$LITERAL')
|
|
297
|
+
- pattern: |
|
|
298
|
+
$CRYPTO.createHash($ALGO).update(process.env[$X] || "$LITERAL")
|
|
299
|
+
# Variant 2: `??` nullish coalescing
|
|
300
|
+
- pattern: |
|
|
301
|
+
$CRYPTO.createHash($ALGO).update(process.env.$ENV ?? "$LITERAL")
|
|
302
|
+
- pattern: |
|
|
303
|
+
$CRYPTO.createHash($ALGO).update(process.env.$ENV ?? '$LITERAL')
|
|
304
|
+
- pattern: |
|
|
305
|
+
$CRYPTO.createHash($ALGO).update(process.env[$X] ?? "$LITERAL")
|
|
306
|
+
# Variant 3: ternary
|
|
307
|
+
- pattern: |
|
|
308
|
+
$CRYPTO.createHash($ALGO).update(process.env.$ENV ? process.env.$ENV : "$LITERAL")
|
|
309
|
+
- pattern: |
|
|
310
|
+
$CRYPTO.createHash($ALGO).update(process.env.$ENV ? process.env.$ENV : '$LITERAL')
|
|
311
|
+
# Variant 4: two-statement (split hash chain)
|
|
312
|
+
- pattern: |
|
|
313
|
+
$HASH = $CRYPTO.createHash($ALGO)
|
|
314
|
+
...
|
|
315
|
+
$HASH.update(process.env.$ENV || "$LITERAL")
|
|
316
|
+
- pattern: |
|
|
317
|
+
$HASH = $CRYPTO.createHash($ALGO)
|
|
318
|
+
...
|
|
319
|
+
$HASH.update(process.env.$ENV ?? "$LITERAL")
|
|
320
|
+
# Variant 5: separate-assign (let K=env.X; if (!K) K="lit"; ...update(K))
|
|
321
|
+
- pattern: |
|
|
322
|
+
$K = process.env.$ENV
|
|
323
|
+
...
|
|
324
|
+
if (!$K) { $K = "$LITERAL" }
|
|
325
|
+
...
|
|
326
|
+
$CRYPTO.createHash($ALGO).update($K)
|
|
327
|
+
- pattern: |
|
|
328
|
+
let $K = process.env.$ENV
|
|
329
|
+
...
|
|
330
|
+
if (!$K) { $K = "$LITERAL" }
|
|
331
|
+
...
|
|
332
|
+
$CRYPTO.createHash($ALGO).update($K)
|
|
333
|
+
# Variant 6: chained-var (const K = env.X; const F = K || "lit"; update(F))
|
|
334
|
+
- pattern: |
|
|
335
|
+
$K = process.env.$ENV
|
|
336
|
+
...
|
|
337
|
+
$F = $K || "$LITERAL"
|
|
338
|
+
...
|
|
339
|
+
$CRYPTO.createHash($ALGO).update($F)
|
|
340
|
+
- pattern: |
|
|
341
|
+
$K = process.env.$ENV
|
|
342
|
+
...
|
|
343
|
+
$F = $K ?? "$LITERAL"
|
|
344
|
+
...
|
|
345
|
+
$CRYPTO.createHash($ALGO).update($F)
|
|
346
|
+
# Variant 7: ternary in assignment chained to hash
|
|
347
|
+
- pattern: |
|
|
348
|
+
$K = process.env.$ENV ? process.env.$ENV : "$LITERAL"
|
|
349
|
+
...
|
|
350
|
+
$CRYPTO.createHash($ALGO).update($K)
|
|
351
|
+
- metavariable-regex:
|
|
352
|
+
metavariable: $LITERAL
|
|
353
|
+
regex: ^[A-Za-z0-9 _!@#$%^&*()+=,.:?<>\[\]{}|/\\-]{1,64}$
|
|
354
|
+
|
|
355
|
+
# Rule 2: env-fallback as symmetric-cipher key / HMAC key
|
|
356
|
+
- id: narvy.javascript.crypto.hardcoded-env-fallback-to-cipher-key.hardcoded-env-fallback-to-cipher-key
|
|
357
|
+
message: >-
|
|
358
|
+
Hardcoded fallback secret '$LITERAL' from `process.env.$ENV` is passed
|
|
359
|
+
directly to a symmetric cipher or HMAC. If the env var is unset the
|
|
360
|
+
key becomes a public constant and all ciphertexts can be decrypted by
|
|
361
|
+
anyone reading this source. Fail fast on missing env at startup, and
|
|
362
|
+
derive the cipher key via HKDF / PBKDF2 with a per-record salt.
|
|
363
|
+
severity: ERROR
|
|
364
|
+
languages: [javascript, typescript]
|
|
365
|
+
metadata:
|
|
366
|
+
cwe:
|
|
367
|
+
- "CWE-321: Use of Hard-coded Cryptographic Key"
|
|
368
|
+
- "CWE-798: Use of Hard-coded Credentials"
|
|
369
|
+
owasp:
|
|
370
|
+
- "A02:2021 - Cryptographic Failures"
|
|
371
|
+
category: security
|
|
372
|
+
subcategory: [vuln]
|
|
373
|
+
technology: [node-crypto, javascript]
|
|
374
|
+
likelihood: HIGH
|
|
375
|
+
impact: HIGH
|
|
376
|
+
confidence: HIGH
|
|
377
|
+
vulnerability_class:
|
|
378
|
+
- Cryptographic Issues
|
|
379
|
+
- Hard-coded Credentials
|
|
380
|
+
patterns:
|
|
381
|
+
- pattern-either:
|
|
382
|
+
# `||` operator
|
|
383
|
+
- pattern: |
|
|
384
|
+
$CRYPTO.createCipheriv($ALGO, process.env.$ENV || "$LITERAL", ...)
|
|
385
|
+
- pattern: |
|
|
386
|
+
$CRYPTO.createDecipheriv($ALGO, process.env.$ENV || "$LITERAL", ...)
|
|
387
|
+
- pattern: |
|
|
388
|
+
$CRYPTO.createHmac($ALGO, process.env.$ENV || "$LITERAL")
|
|
389
|
+
- pattern: |
|
|
390
|
+
$CRYPTO.createCipher($ALGO, process.env.$ENV || "$LITERAL")
|
|
391
|
+
# `??` nullish
|
|
392
|
+
- pattern: |
|
|
393
|
+
$CRYPTO.createCipheriv($ALGO, process.env.$ENV ?? "$LITERAL", ...)
|
|
394
|
+
- pattern: |
|
|
395
|
+
$CRYPTO.createDecipheriv($ALGO, process.env.$ENV ?? "$LITERAL", ...)
|
|
396
|
+
- pattern: |
|
|
397
|
+
$CRYPTO.createHmac($ALGO, process.env.$ENV ?? "$LITERAL")
|
|
398
|
+
# Ternary
|
|
399
|
+
- pattern: |
|
|
400
|
+
$CRYPTO.createHmac($ALGO, process.env.$ENV ? process.env.$ENV : "$LITERAL")
|
|
401
|
+
- pattern: |
|
|
402
|
+
$CRYPTO.createCipheriv($ALGO, process.env.$ENV ? process.env.$ENV : "$LITERAL", ...)
|
|
403
|
+
# Two-statement: separate-assign
|
|
404
|
+
- pattern: |
|
|
405
|
+
$K = process.env.$ENV
|
|
406
|
+
...
|
|
407
|
+
if (!$K) { $K = "$LITERAL" }
|
|
408
|
+
...
|
|
409
|
+
$CRYPTO.createHmac($ALGO, $K)
|
|
410
|
+
- pattern: |
|
|
411
|
+
let $K = process.env.$ENV
|
|
412
|
+
...
|
|
413
|
+
if (!$K) { $K = "$LITERAL" }
|
|
414
|
+
...
|
|
415
|
+
$CRYPTO.createHmac($ALGO, $K)
|
|
416
|
+
# Chained-var
|
|
417
|
+
- pattern: |
|
|
418
|
+
$K = process.env.$ENV
|
|
419
|
+
...
|
|
420
|
+
$F = $K || "$LITERAL"
|
|
421
|
+
...
|
|
422
|
+
$CRYPTO.createHmac($ALGO, $F)
|
|
423
|
+
- pattern: |
|
|
424
|
+
$K = process.env.$ENV
|
|
425
|
+
...
|
|
426
|
+
$F = $K ?? "$LITERAL"
|
|
427
|
+
...
|
|
428
|
+
$CRYPTO.createHmac($ALGO, $F)
|
|
429
|
+
- pattern: |
|
|
430
|
+
$K = process.env.$ENV
|
|
431
|
+
...
|
|
432
|
+
$F = $K || "$LITERAL"
|
|
433
|
+
...
|
|
434
|
+
$CRYPTO.createCipheriv($ALGO, $F, ...)
|
|
435
|
+
- metavariable-regex:
|
|
436
|
+
metavariable: $LITERAL
|
|
437
|
+
regex: ^[A-Za-z0-9 _!@#$%^&*()+=,.:?<>\[\]{}|/\\-]{1,64}$
|
|
438
|
+
|
|
439
|
+
# Rule 3: env-fallback fed into KDF (HKDF / PBKDF2 / scrypt)
|
|
440
|
+
- id: narvy.javascript.crypto.hardcoded-env-fallback-to-kdf.hardcoded-env-fallback-to-kdf
|
|
441
|
+
message: >-
|
|
442
|
+
Hardcoded fallback secret '$LITERAL' is used as input keying material
|
|
443
|
+
(IKM) to a KDF. When the env var is unset the IKM reduces to a public
|
|
444
|
+
constant - defeats the entire KDF. Fail fast on missing env at app
|
|
445
|
+
startup (`assert process.env.X, 'X must be set'`).
|
|
446
|
+
severity: ERROR
|
|
447
|
+
languages: [javascript, typescript]
|
|
448
|
+
metadata:
|
|
449
|
+
cwe:
|
|
450
|
+
- "CWE-321: Use of Hard-coded Cryptographic Key"
|
|
451
|
+
- "CWE-798: Use of Hard-coded Credentials"
|
|
452
|
+
owasp:
|
|
453
|
+
- "A02:2021 - Cryptographic Failures"
|
|
454
|
+
category: security
|
|
455
|
+
subcategory: [vuln]
|
|
456
|
+
technology: [node-crypto, javascript]
|
|
457
|
+
likelihood: HIGH
|
|
458
|
+
impact: HIGH
|
|
459
|
+
confidence: HIGH
|
|
460
|
+
vulnerability_class:
|
|
461
|
+
- Cryptographic Issues
|
|
462
|
+
- Hard-coded Credentials
|
|
463
|
+
patterns:
|
|
464
|
+
- pattern-either:
|
|
465
|
+
# Direct call with `||`
|
|
466
|
+
- pattern: |
|
|
467
|
+
$CRYPTO.hkdfSync($ALGO, Buffer.from(process.env.$ENV || "$LITERAL", ...), ...)
|
|
468
|
+
- pattern: |
|
|
469
|
+
$CRYPTO.hkdfSync($ALGO, Buffer.from(process.env.$ENV || '$LITERAL', ...), ...)
|
|
470
|
+
- pattern: |
|
|
471
|
+
$CRYPTO.hkdfSync($ALGO, process.env.$ENV || "$LITERAL", ...)
|
|
472
|
+
- pattern: |
|
|
473
|
+
$CRYPTO.pbkdf2Sync(process.env.$ENV || "$LITERAL", ...)
|
|
474
|
+
- pattern: |
|
|
475
|
+
$CRYPTO.scryptSync(process.env.$ENV || "$LITERAL", ...)
|
|
476
|
+
# Direct call with `??`
|
|
477
|
+
- pattern: |
|
|
478
|
+
$CRYPTO.hkdfSync($ALGO, Buffer.from(process.env.$ENV ?? "$LITERAL", ...), ...)
|
|
479
|
+
- pattern: |
|
|
480
|
+
$CRYPTO.hkdfSync($ALGO, process.env.$ENV ?? "$LITERAL", ...)
|
|
481
|
+
- pattern: |
|
|
482
|
+
$CRYPTO.pbkdf2Sync(process.env.$ENV ?? "$LITERAL", ...)
|
|
483
|
+
- pattern: |
|
|
484
|
+
$CRYPTO.scryptSync(process.env.$ENV ?? "$LITERAL", ...)
|
|
485
|
+
# Ternary
|
|
486
|
+
- pattern: |
|
|
487
|
+
$CRYPTO.pbkdf2Sync(process.env.$ENV ? process.env.$ENV : "$LITERAL", ...)
|
|
488
|
+
- pattern: |
|
|
489
|
+
$CRYPTO.scryptSync(process.env.$ENV ? process.env.$ENV : "$LITERAL", ...)
|
|
490
|
+
# Two-step pattern: assign IKM from env-fallback, then use in KDF
|
|
491
|
+
- pattern: |
|
|
492
|
+
$IKM = Buffer.from(process.env.$ENV || "$LITERAL", ...)
|
|
493
|
+
...
|
|
494
|
+
$CRYPTO.hkdfSync($ALGO, $IKM, ...)
|
|
495
|
+
- pattern: |
|
|
496
|
+
$IKM = Buffer.from(process.env.$ENV ?? "$LITERAL", ...)
|
|
497
|
+
...
|
|
498
|
+
$CRYPTO.hkdfSync($ALGO, $IKM, ...)
|
|
499
|
+
# Separate-assign feeding KDF
|
|
500
|
+
- pattern: |
|
|
501
|
+
$K = process.env.$ENV
|
|
502
|
+
...
|
|
503
|
+
if (!$K) { $K = "$LITERAL" }
|
|
504
|
+
...
|
|
505
|
+
$CRYPTO.pbkdf2Sync($K, ...)
|
|
506
|
+
- pattern: |
|
|
507
|
+
$K = process.env.$ENV
|
|
508
|
+
...
|
|
509
|
+
if (!$K) { $K = "$LITERAL" }
|
|
510
|
+
...
|
|
511
|
+
$CRYPTO.hkdfSync($ALGO, $K, ...)
|
|
512
|
+
# Chained-var feeding KDF
|
|
513
|
+
- pattern: |
|
|
514
|
+
$K = process.env.$ENV
|
|
515
|
+
...
|
|
516
|
+
$F = $K || "$LITERAL"
|
|
517
|
+
...
|
|
518
|
+
$CRYPTO.pbkdf2Sync($F, ...)
|
|
519
|
+
- pattern: |
|
|
520
|
+
$K = process.env.$ENV
|
|
521
|
+
...
|
|
522
|
+
$F = $K || "$LITERAL"
|
|
523
|
+
...
|
|
524
|
+
$CRYPTO.hkdfSync($ALGO, $F, ...)
|
|
525
|
+
- metavariable-regex:
|
|
526
|
+
metavariable: $LITERAL
|
|
527
|
+
regex: ^[A-Za-z0-9 _!@#$%^&*()+=,.:?<>\[\]{}|/\\-]{1,64}$
|
|
528
|
+
|
|
529
|
+
# Rule 4: single-hash-as-KDF (weak KDF - CWE-916)
|
|
530
|
+
- id: narvy.javascript.crypto.single-hash-as-kdf.single-hash-as-kdf
|
|
531
|
+
message: >-
|
|
532
|
+
A single SHA-1 / SHA-256 / MD5 round is used to derive an encryption
|
|
533
|
+
key from `process.env.$ENV`. This is NOT a key-derivation function:
|
|
534
|
+
brute-force attacks can recompute billions of candidates per second on
|
|
535
|
+
modern GPUs. Use HKDF (crypto.hkdfSync, RFC 5869) for high-entropy
|
|
536
|
+
inputs, PBKDF2 / scrypt / Argon2 for low-entropy passwords.
|
|
537
|
+
Per-tenant salt is mandatory.
|
|
538
|
+
severity: WARNING
|
|
539
|
+
languages: [javascript, typescript]
|
|
540
|
+
metadata:
|
|
541
|
+
cwe:
|
|
542
|
+
- "CWE-916: Use of Password Hash With Insufficient Computational Effort"
|
|
543
|
+
- "CWE-326: Inadequate Encryption Strength"
|
|
544
|
+
owasp:
|
|
545
|
+
- "A02:2021 - Cryptographic Failures"
|
|
546
|
+
category: security
|
|
547
|
+
subcategory: [vuln]
|
|
548
|
+
technology: [node-crypto, javascript]
|
|
549
|
+
likelihood: MEDIUM
|
|
550
|
+
impact: HIGH
|
|
551
|
+
confidence: MEDIUM
|
|
552
|
+
vulnerability_class:
|
|
553
|
+
- Cryptographic Issues
|
|
554
|
+
patterns:
|
|
555
|
+
- pattern-either:
|
|
556
|
+
- pattern: |
|
|
557
|
+
$CRYPTO.createCipheriv($ALGO, $CRYPTO.createHash($H).update(process.env.$ENV).digest(), ...)
|
|
558
|
+
- pattern: |
|
|
559
|
+
$CRYPTO.createCipheriv($ALGO, $CRYPTO.createHash($H).update(process.env.$ENV || $X).digest(), ...)
|
|
560
|
+
- pattern: |
|
|
561
|
+
$CRYPTO.createCipheriv($ALGO, $CRYPTO.createHash($H).update(process.env.$ENV ?? $X).digest(), ...)
|
|
562
|
+
- pattern: |
|
|
563
|
+
$CRYPTO.createDecipheriv($ALGO, $CRYPTO.createHash($H).update(process.env.$ENV).digest(), ...)
|
|
564
|
+
- pattern: |
|
|
565
|
+
$CRYPTO.createDecipheriv($ALGO, $CRYPTO.createHash($H).update(process.env.$ENV || $X).digest(), ...)
|
|
566
|
+
- pattern: |
|
|
567
|
+
$CRYPTO.createDecipheriv($ALGO, $CRYPTO.createHash($H).update(process.env.$ENV ?? $X).digest(), ...)
|
|
568
|
+
- pattern: |
|
|
569
|
+
function $F() {
|
|
570
|
+
return $CRYPTO.createHash($H)
|
|
571
|
+
.update(process.env.$ENV || $X)
|
|
572
|
+
.digest()
|
|
573
|
+
}
|
|
574
|
+
- pattern: |
|
|
575
|
+
function $F() {
|
|
576
|
+
return $CRYPTO.createHash($H).update(process.env.$ENV || $X).digest()
|
|
577
|
+
}
|
|
578
|
+
- pattern: |
|
|
579
|
+
function $F() {
|
|
580
|
+
return $CRYPTO.createHash($H)
|
|
581
|
+
.update(process.env.$ENV ?? $X)
|
|
582
|
+
.digest()
|
|
583
|
+
}
|
|
584
|
+
- metavariable-regex:
|
|
585
|
+
metavariable: $H
|
|
586
|
+
regex: ^['"](md5|sha1|sha-?1|sha256|sha-?256)['"]$
|
|
587
|
+
|
|
588
|
+
# Rule 5 (NEW): env-fallback fed into jwt.sign / jwt.verify
|
|
589
|
+
- id: narvy.javascript.crypto.hardcoded-env-fallback-to-jwt-sign.hardcoded-env-fallback-to-jwt-sign
|
|
590
|
+
message: >-
|
|
591
|
+
Hardcoded fallback secret '$LITERAL' is used as the JWT signing key
|
|
592
|
+
when `process.env.$ENV` is unset. If the env var is missing in any
|
|
593
|
+
deployment (local dev, CI, misconfigured prod), tokens are signed
|
|
594
|
+
with a public constant - anyone reading this source can mint valid
|
|
595
|
+
tokens. THE canonical real-world `jwt.sign(...)` bug. Fix: fail fast
|
|
596
|
+
at app startup if the secret env var is absent
|
|
597
|
+
(`assert process.env.$ENV, 'JWT_SECRET must be set'`).
|
|
598
|
+
severity: ERROR
|
|
599
|
+
languages: [javascript, typescript]
|
|
600
|
+
metadata:
|
|
601
|
+
cwe:
|
|
602
|
+
- "CWE-321: Use of Hard-coded Cryptographic Key"
|
|
603
|
+
- "CWE-798: Use of Hard-coded Credentials"
|
|
604
|
+
- "CWE-547: Use of Hard-coded, Security-relevant Constants"
|
|
605
|
+
owasp:
|
|
606
|
+
- "A02:2021 - Cryptographic Failures"
|
|
607
|
+
- "A07:2021 - Identification and Authentication Failures"
|
|
608
|
+
category: security
|
|
609
|
+
subcategory: [vuln]
|
|
610
|
+
technology: [jsonwebtoken, jwt, javascript]
|
|
611
|
+
likelihood: HIGH
|
|
612
|
+
impact: HIGH
|
|
613
|
+
confidence: HIGH
|
|
614
|
+
vulnerability_class:
|
|
615
|
+
- Cryptographic Issues
|
|
616
|
+
- Hard-coded Credentials
|
|
617
|
+
patterns:
|
|
618
|
+
- pattern-either:
|
|
619
|
+
# jwt.sign / jsonwebtoken.sign - `||` operator
|
|
620
|
+
- pattern: |
|
|
621
|
+
$JWT.sign($PAYLOAD, process.env.$ENV || "$LITERAL", ...)
|
|
622
|
+
- pattern: |
|
|
623
|
+
$JWT.sign($PAYLOAD, process.env.$ENV || "$LITERAL")
|
|
624
|
+
- pattern: |
|
|
625
|
+
$JWT.sign($PAYLOAD, process.env.$ENV || '$LITERAL', ...)
|
|
626
|
+
- pattern: |
|
|
627
|
+
$JWT.sign($PAYLOAD, process.env.$ENV || '$LITERAL')
|
|
628
|
+
# jwt.sign - `??` nullish
|
|
629
|
+
- pattern: |
|
|
630
|
+
$JWT.sign($PAYLOAD, process.env.$ENV ?? "$LITERAL", ...)
|
|
631
|
+
- pattern: |
|
|
632
|
+
$JWT.sign($PAYLOAD, process.env.$ENV ?? "$LITERAL")
|
|
633
|
+
# jwt.verify - `||`
|
|
634
|
+
- pattern: |
|
|
635
|
+
$JWT.verify($TOKEN, process.env.$ENV || "$LITERAL", ...)
|
|
636
|
+
- pattern: |
|
|
637
|
+
$JWT.verify($TOKEN, process.env.$ENV || "$LITERAL")
|
|
638
|
+
- pattern: |
|
|
639
|
+
$JWT.verify($TOKEN, process.env.$ENV || '$LITERAL')
|
|
640
|
+
# jwt.verify - `??`
|
|
641
|
+
- pattern: |
|
|
642
|
+
$JWT.verify($TOKEN, process.env.$ENV ?? "$LITERAL", ...)
|
|
643
|
+
- pattern: |
|
|
644
|
+
$JWT.verify($TOKEN, process.env.$ENV ?? "$LITERAL")
|
|
645
|
+
# Ternary
|
|
646
|
+
- pattern: |
|
|
647
|
+
$JWT.sign($PAYLOAD, process.env.$ENV ? process.env.$ENV : "$LITERAL", ...)
|
|
648
|
+
- pattern: |
|
|
649
|
+
$JWT.verify($TOKEN, process.env.$ENV ? process.env.$ENV : "$LITERAL", ...)
|
|
650
|
+
# Separate-assign feeding jwt.sign
|
|
651
|
+
- pattern: |
|
|
652
|
+
$K = process.env.$ENV
|
|
653
|
+
...
|
|
654
|
+
if (!$K) { $K = "$LITERAL" }
|
|
655
|
+
...
|
|
656
|
+
$JWT.sign($PAYLOAD, $K, ...)
|
|
657
|
+
# Chained-var feeding jwt.sign
|
|
658
|
+
- pattern: |
|
|
659
|
+
$K = process.env.$ENV
|
|
660
|
+
...
|
|
661
|
+
$F = $K || "$LITERAL"
|
|
662
|
+
...
|
|
663
|
+
$JWT.sign($PAYLOAD, $F, ...)
|
|
664
|
+
- pattern: |
|
|
665
|
+
$K = process.env.$ENV
|
|
666
|
+
...
|
|
667
|
+
$F = $K ?? "$LITERAL"
|
|
668
|
+
...
|
|
669
|
+
$JWT.sign($PAYLOAD, $F, ...)
|
|
670
|
+
- metavariable-regex:
|
|
671
|
+
metavariable: $LITERAL
|
|
672
|
+
regex: ^[A-Za-z0-9 _!@#$%^&*()+=,.:?<>\[\]{}|/\\-]{1,64}$
|
|
673
|
+
|
|
674
|
+
# Rule 6 (NEW): env-fallback fed into Web Crypto API (crypto.subtle)
|
|
675
|
+
- id: narvy.javascript.crypto.hardcoded-env-fallback-to-web-crypto.hardcoded-env-fallback-to-web-crypto
|
|
676
|
+
message: >-
|
|
677
|
+
Hardcoded fallback secret '$LITERAL' is fed into the Web Crypto API
|
|
678
|
+
(crypto.subtle.*). Common in Cloudflare Workers, Deno, and modern
|
|
679
|
+
Node 18+ runtimes. If the env var is unset the key material reduces
|
|
680
|
+
to a public constant - defeats the entire crypto operation. Fail
|
|
681
|
+
fast on missing env at startup.
|
|
682
|
+
severity: ERROR
|
|
683
|
+
languages: [javascript, typescript]
|
|
684
|
+
metadata:
|
|
685
|
+
cwe:
|
|
686
|
+
- "CWE-321: Use of Hard-coded Cryptographic Key"
|
|
687
|
+
- "CWE-798: Use of Hard-coded Credentials"
|
|
688
|
+
owasp:
|
|
689
|
+
- "A02:2021 - Cryptographic Failures"
|
|
690
|
+
category: security
|
|
691
|
+
subcategory: [vuln]
|
|
692
|
+
technology: [web-crypto, cloudflare-workers, deno, javascript]
|
|
693
|
+
likelihood: HIGH
|
|
694
|
+
impact: HIGH
|
|
695
|
+
confidence: HIGH
|
|
696
|
+
vulnerability_class:
|
|
697
|
+
- Cryptographic Issues
|
|
698
|
+
- Hard-coded Credentials
|
|
699
|
+
patterns:
|
|
700
|
+
- pattern-either:
|
|
701
|
+
# crypto.subtle.importKey called directly with env-fallback string
|
|
702
|
+
- pattern: |
|
|
703
|
+
$SUBTLE.importKey($FORMAT, process.env.$ENV || "$LITERAL", ...)
|
|
704
|
+
- pattern: |
|
|
705
|
+
$SUBTLE.importKey($FORMAT, process.env.$ENV ?? "$LITERAL", ...)
|
|
706
|
+
# Wrapped in TextEncoder().encode(...) (the canonical browser shape)
|
|
707
|
+
- pattern: |
|
|
708
|
+
$SUBTLE.importKey($FORMAT, new TextEncoder().encode(process.env.$ENV || "$LITERAL"), ...)
|
|
709
|
+
- pattern: |
|
|
710
|
+
$SUBTLE.importKey($FORMAT, new TextEncoder().encode(process.env.$ENV ?? "$LITERAL"), ...)
|
|
711
|
+
- pattern: |
|
|
712
|
+
$SUBTLE.importKey($FORMAT, new TextEncoder().encode(process.env.$ENV || '$LITERAL'), ...)
|
|
713
|
+
- pattern: |
|
|
714
|
+
$SUBTLE.importKey($FORMAT, new TextEncoder().encode(process.env.$ENV ?? '$LITERAL'), ...)
|
|
715
|
+
# Two-statement: encode into local, then importKey
|
|
716
|
+
- pattern: |
|
|
717
|
+
$RAW = new TextEncoder().encode(process.env.$ENV || "$LITERAL")
|
|
718
|
+
...
|
|
719
|
+
$SUBTLE.importKey($FORMAT, $RAW, ...)
|
|
720
|
+
- pattern: |
|
|
721
|
+
$RAW = new TextEncoder().encode(process.env.$ENV ?? "$LITERAL")
|
|
722
|
+
...
|
|
723
|
+
$SUBTLE.importKey($FORMAT, $RAW, ...)
|
|
724
|
+
# Separate-assign feeding Web Crypto
|
|
725
|
+
- pattern: |
|
|
726
|
+
$K = process.env.$ENV
|
|
727
|
+
...
|
|
728
|
+
if (!$K) { $K = "$LITERAL" }
|
|
729
|
+
...
|
|
730
|
+
$SUBTLE.importKey($FORMAT, new TextEncoder().encode($K), ...)
|
|
731
|
+
- metavariable-regex:
|
|
732
|
+
metavariable: $LITERAL
|
|
733
|
+
regex: ^[A-Za-z0-9 _!@#$%^&*()+=,.:?<>\[\]{}|/\\-]{1,64}$
|
|
734
|
+
|
|
735
|
+
# Rule 7: bare-literal secret reaching a JWT sign/verify call (no env var
|
|
736
|
+
# at all). Covers jsonwebtoken, jose and passport-jwt in one taint rule.
|
|
737
|
+
- id: narvy.javascript.crypto.hardcoded-jwt-sign-secret.hardcoded-jwt-sign-secret
|
|
738
|
+
message: >-
|
|
739
|
+
A hardcoded string literal secret reaches a JWT signing or
|
|
740
|
+
verification call (jsonwebtoken, jose, or passport-jwt). The literal
|
|
741
|
+
is a public constant baked into the source - anyone who reads it can
|
|
742
|
+
forge valid tokens (CWE-321 / CWE-798). Load the secret from an
|
|
743
|
+
environment variable or a secrets manager, and fail startup if it is
|
|
744
|
+
missing.
|
|
745
|
+
severity: ERROR
|
|
746
|
+
languages: [javascript, typescript]
|
|
747
|
+
mode: taint
|
|
748
|
+
metadata:
|
|
749
|
+
cwe:
|
|
750
|
+
- "CWE-321: Use of Hard-coded Cryptographic Key"
|
|
751
|
+
- "CWE-798: Use of Hard-coded Credentials"
|
|
752
|
+
owasp:
|
|
753
|
+
- "A02:2021 - Cryptographic Failures"
|
|
754
|
+
- "A07:2021 - Identification and Authentication Failures"
|
|
755
|
+
category: security
|
|
756
|
+
subcategory: [vuln]
|
|
757
|
+
technology: [jsonwebtoken, jose, passport-jwt, jwt, javascript, typescript]
|
|
758
|
+
likelihood: HIGH
|
|
759
|
+
impact: HIGH
|
|
760
|
+
confidence: HIGH
|
|
761
|
+
vulnerability_class:
|
|
762
|
+
- Cryptographic Issues
|
|
763
|
+
- Hard-coded Credentials
|
|
764
|
+
pattern-sources:
|
|
765
|
+
# Bare literal reaching a JWT secret argument; narrow sinks keep it off other strings.
|
|
766
|
+
- pattern: '"..."'
|
|
767
|
+
pattern-sinks:
|
|
768
|
+
# jsonwebtoken sign/verify; $JWT name-constrained to jwt/jsonwebtoken.
|
|
769
|
+
- patterns:
|
|
770
|
+
- pattern-either:
|
|
771
|
+
- pattern: $JWT.sign($PAYLOAD, $SECRET, ...)
|
|
772
|
+
- pattern: $JWT.sign($PAYLOAD, $SECRET)
|
|
773
|
+
- pattern: $JWT.verify($TOKEN, $SECRET, ...)
|
|
774
|
+
- pattern: $JWT.verify($TOKEN, $SECRET)
|
|
775
|
+
- focus-metavariable: $SECRET
|
|
776
|
+
- metavariable-regex:
|
|
777
|
+
metavariable: $JWT
|
|
778
|
+
regex: (?i)^(jwt|jsonwebtoken|jsonWebToken)$
|
|
779
|
+
# jose: new SignJWT(payload)....sign(secretBytes), secret wrapped
|
|
780
|
+
# as a Uint8Array via TextEncoder/Buffer (jose's documented shape).
|
|
781
|
+
- patterns:
|
|
782
|
+
- pattern-either:
|
|
783
|
+
- pattern: $CHAIN.sign(new TextEncoder().encode($SECRET))
|
|
784
|
+
- pattern: $CHAIN.sign(Buffer.from($SECRET))
|
|
785
|
+
- focus-metavariable: $SECRET
|
|
786
|
+
# passport-jwt: new JwtStrategy({ secretOrKey: secret, ... }, cb).
|
|
787
|
+
- patterns:
|
|
788
|
+
- pattern: |
|
|
789
|
+
new JwtStrategy({..., secretOrKey: $SECRET, ...}, ...)
|
|
790
|
+
- focus-metavariable: $SECRET
|
|
791
|
+
|
|
792
|
+
# JWT SESSION
|
|
793
|
+
|
|
794
|
+
- id: narvy.javascript.jwt.sign-alg-none.sign-alg-none
|
|
795
|
+
message: >-
|
|
796
|
+
A JWT is signed with algorithm 'none'. A 'none'-algorithm token has
|
|
797
|
+
no signature at all - anyone can craft a token with arbitrary
|
|
798
|
+
claims and it will pass verification on a server that also
|
|
799
|
+
accepts 'none' (CWE-347). Never use algorithm 'none'; sign with
|
|
800
|
+
HS256/RS256 and a properly managed secret or key pair.
|
|
801
|
+
severity: ERROR
|
|
802
|
+
languages: [javascript, typescript]
|
|
803
|
+
metadata:
|
|
804
|
+
cwe:
|
|
805
|
+
- "CWE-347: Improper Verification of Cryptographic Signature"
|
|
806
|
+
owasp:
|
|
807
|
+
- "A02:2021 - Cryptographic Failures"
|
|
808
|
+
category: security
|
|
809
|
+
subcategory: [vuln]
|
|
810
|
+
technology: [jsonwebtoken, javascript, typescript]
|
|
811
|
+
likelihood: LOW
|
|
812
|
+
impact: HIGH
|
|
813
|
+
confidence: HIGH
|
|
814
|
+
vulnerability_class:
|
|
815
|
+
- Improper Authentication
|
|
816
|
+
references:
|
|
817
|
+
- https://cwe.mitre.org/data/definitions/347.html
|
|
818
|
+
patterns:
|
|
819
|
+
- pattern-either:
|
|
820
|
+
- pattern: 'jwt.sign($PAYLOAD, ..., {..., algorithm: "none", ...})'
|
|
821
|
+
- pattern: jwt.sign($PAYLOAD, null, ...)
|
|
822
|
+
- pattern: jwt.sign($PAYLOAD, "", ...)
|
|
823
|
+
|
|
824
|
+
- id: narvy.javascript.jwt.verify-missing-algorithms-allowlist.verify-missing-algorithms-allowlist
|
|
825
|
+
message: >-
|
|
826
|
+
jwt.verify is called without an `algorithms` allow-list in its
|
|
827
|
+
options. Without pinning the accepted algorithm(s), a library that
|
|
828
|
+
supports both symmetric (HS256) and asymmetric (RS256) signing can
|
|
829
|
+
be tricked into verifying an HS256 token that was HMAC-signed
|
|
830
|
+
using the server's own RSA public key as the secret - a well-known
|
|
831
|
+
JWT algorithm-confusion forgery (CWE-347). Always pass an explicit
|
|
832
|
+
`algorithms: ['HS256']` (or whichever single algorithm is
|
|
833
|
+
expected) to jwt.verify.
|
|
834
|
+
severity: ERROR
|
|
835
|
+
languages: [javascript, typescript]
|
|
836
|
+
metadata:
|
|
837
|
+
cwe:
|
|
838
|
+
- "CWE-347: Improper Verification of Cryptographic Signature"
|
|
839
|
+
owasp:
|
|
840
|
+
- "A02:2021 - Cryptographic Failures"
|
|
841
|
+
category: security
|
|
842
|
+
subcategory: [vuln]
|
|
843
|
+
technology: [jsonwebtoken, javascript, typescript]
|
|
844
|
+
likelihood: LOW
|
|
845
|
+
impact: HIGH
|
|
846
|
+
confidence: MEDIUM
|
|
847
|
+
vulnerability_class:
|
|
848
|
+
- Improper Authentication
|
|
849
|
+
patterns:
|
|
850
|
+
- pattern-either:
|
|
851
|
+
- pattern: jwt.verify($TOKEN, $SECRET)
|
|
852
|
+
- pattern: jwt.verify($TOKEN, $SECRET, {...})
|
|
853
|
+
- pattern-not: 'jwt.verify($TOKEN, $SECRET, {..., algorithms: [...], ...})'
|
|
854
|
+
|
|
855
|
+
- id: narvy.javascript.session.cookie-secure-explicitly-disabled.cookie-secure-explicitly-disabled
|
|
856
|
+
message: >-
|
|
857
|
+
A session cookie is configured with `secure: false`. This allows
|
|
858
|
+
the session cookie to be sent over plain HTTP, exposing it to
|
|
859
|
+
network eavesdroppers who can then hijack the session (CWE-614).
|
|
860
|
+
Set `secure: true` (typically gated on NODE_ENV === 'production'
|
|
861
|
+
for local HTTP development) so the cookie is only ever sent over
|
|
862
|
+
HTTPS.
|
|
863
|
+
severity: WARNING
|
|
864
|
+
languages: [javascript, typescript]
|
|
865
|
+
metadata:
|
|
866
|
+
cwe:
|
|
867
|
+
- "CWE-614: Sensitive Cookie Without 'Secure' Attribute"
|
|
868
|
+
owasp:
|
|
869
|
+
- "A02:2021 - Cryptographic Failures"
|
|
870
|
+
category: security
|
|
871
|
+
subcategory: [vuln]
|
|
872
|
+
technology: [express, nodejs, javascript, typescript]
|
|
873
|
+
likelihood: MEDIUM
|
|
874
|
+
impact: MEDIUM
|
|
875
|
+
confidence: HIGH
|
|
876
|
+
vulnerability_class:
|
|
877
|
+
- Improper Authentication
|
|
878
|
+
patterns:
|
|
879
|
+
- pattern-either:
|
|
880
|
+
- pattern: |
|
|
881
|
+
session({..., cookie: {..., secure: false, ...}, ...})
|
|
882
|
+
- pattern: |
|
|
883
|
+
{..., cookie: {..., secure: false, ...}, ...}
|
|
884
|
+
|
|
885
|
+
# Rule 1b: cookie security options omitted, not just false; scoped to session() calls.
|
|
886
|
+
- id: narvy.javascript.session.cookie-security-options-omitted.cookie-security-options-omitted
|
|
887
|
+
message: >-
|
|
888
|
+
express-session is configured with the cookie security options
|
|
889
|
+
(secure / httpOnly / sameSite) either entirely absent from the
|
|
890
|
+
`cookie` block or the `cookie` block itself absent from the
|
|
891
|
+
session() config. Express defaults these to insecure values
|
|
892
|
+
(secure defaults off, sameSite defaults off), so silence here is
|
|
893
|
+
silently insecure-by-default, not a neutral choice (CWE-614 /
|
|
894
|
+
CWE-1004). Set an explicit `cookie: { secure: true, httpOnly:
|
|
895
|
+
true, sameSite: 'strict' }` block (gate `secure` on
|
|
896
|
+
NODE_ENV === 'production' for local HTTP dev).
|
|
897
|
+
severity: WARNING
|
|
898
|
+
languages: [javascript, typescript]
|
|
899
|
+
metadata:
|
|
900
|
+
cwe:
|
|
901
|
+
- "CWE-614: Sensitive Cookie Without 'Secure' Attribute"
|
|
902
|
+
- "CWE-1004: Sensitive Cookie Without 'HttpOnly' Flag"
|
|
903
|
+
owasp:
|
|
904
|
+
- "A02:2021 - Cryptographic Failures"
|
|
905
|
+
category: security
|
|
906
|
+
subcategory: [vuln]
|
|
907
|
+
technology: [express, nodejs, javascript, typescript]
|
|
908
|
+
likelihood: MEDIUM
|
|
909
|
+
impact: MEDIUM
|
|
910
|
+
confidence: MEDIUM
|
|
911
|
+
vulnerability_class:
|
|
912
|
+
- Improper Authentication
|
|
913
|
+
patterns:
|
|
914
|
+
- pattern: session($OPTS)
|
|
915
|
+
- pattern-either:
|
|
916
|
+
# secure never mentioned in the cookie block (block itself may
|
|
917
|
+
# be missing entirely - that also satisfies "not mentioned").
|
|
918
|
+
- patterns:
|
|
919
|
+
- pattern: session($OPTS)
|
|
920
|
+
- pattern-not: |
|
|
921
|
+
session({..., cookie: {..., secure: $S, ...}, ...})
|
|
922
|
+
# httpOnly never mentioned.
|
|
923
|
+
- patterns:
|
|
924
|
+
- pattern: session($OPTS)
|
|
925
|
+
- pattern-not: |
|
|
926
|
+
session({..., cookie: {..., httpOnly: $H, ...}, ...})
|
|
927
|
+
# sameSite never mentioned.
|
|
928
|
+
- patterns:
|
|
929
|
+
- pattern: session($OPTS)
|
|
930
|
+
- pattern-not: |
|
|
931
|
+
session({..., cookie: {..., sameSite: $SS, ...}, ...})
|
|
932
|
+
|
|
933
|
+
# MISC SECURITY
|
|
934
|
+
|
|
935
|
+
# Rule 1: prototype pollution via merge of request body
|
|
936
|
+
- id: narvy.javascript.misc.prototype-pollution-merge-reqbody.prototype-pollution-merge-reqbody
|
|
937
|
+
message: >-
|
|
938
|
+
Request-derived data (`req.body` / `req.query` / `req.params`) is
|
|
939
|
+
merged into an object via a deep-merge/extend helper or
|
|
940
|
+
`Object.assign`. If the merge implementation does not block
|
|
941
|
+
`__proto__` / `constructor.prototype` keys, an attacker can pollute
|
|
942
|
+
`Object.prototype` and change behavior application-wide (CWE-1321).
|
|
943
|
+
Use a merge library with prototype-pollution protection (lodash
|
|
944
|
+
>=4.17.21 with a key filter, or a schema-validated merge) and never
|
|
945
|
+
merge raw request bodies directly.
|
|
946
|
+
severity: WARNING
|
|
947
|
+
languages: [javascript, typescript]
|
|
948
|
+
metadata:
|
|
949
|
+
cwe:
|
|
950
|
+
- "CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')"
|
|
951
|
+
owasp:
|
|
952
|
+
- "A08:2021 - Software and Data Integrity Failures"
|
|
953
|
+
category: security
|
|
954
|
+
subcategory: [audit]
|
|
955
|
+
technology: [javascript, typescript, express, node]
|
|
956
|
+
likelihood: LOW
|
|
957
|
+
impact: HIGH
|
|
958
|
+
confidence: LOW
|
|
959
|
+
vulnerability_class:
|
|
960
|
+
- Improper Input Validation
|
|
961
|
+
patterns:
|
|
962
|
+
- pattern-either:
|
|
963
|
+
- pattern: $_.merge($TARGET, $REQ.body)
|
|
964
|
+
- pattern: $_.merge($TARGET, $REQ.query)
|
|
965
|
+
- pattern: $_.extend($TARGET, $REQ.body)
|
|
966
|
+
- pattern: $_.extend(true, $TARGET, $REQ.body)
|
|
967
|
+
- pattern: Object.assign($TARGET, $REQ.body)
|
|
968
|
+
- pattern: merge($TARGET, $REQ.body)
|
|
969
|
+
- pattern: deepmerge($TARGET, $REQ.body)
|
|
970
|
+
|
|
971
|
+
# Rule 2: ReDoS - RegExp from unsanitized input; focus pins the PATTERN arg.
|
|
972
|
+
- id: narvy.javascript.misc.redos-dynamic-regexp.redos-dynamic-regexp
|
|
973
|
+
message: >-
|
|
974
|
+
A request-derived value (`req.body` / `req.query` / `req.params`)
|
|
975
|
+
flows into the pattern argument of `new RegExp()`. An attacker who
|
|
976
|
+
controls the pattern can supply a catastrophically-backtracking regex,
|
|
977
|
+
causing a denial of service when it is later evaluated (ReDoS,
|
|
978
|
+
CWE-1333). Validate/allow-list the pattern, cap its length, escape it
|
|
979
|
+
with a regex-escape helper, or run matching with a timeout / a
|
|
980
|
+
linear-time engine (e.g. re2).
|
|
981
|
+
severity: WARNING
|
|
982
|
+
languages: [javascript, typescript]
|
|
983
|
+
mode: taint
|
|
984
|
+
metadata:
|
|
985
|
+
cwe:
|
|
986
|
+
- "CWE-1333: Inefficient Regular Expression Complexity"
|
|
987
|
+
- "CWE-400: Uncontrolled Resource Consumption"
|
|
988
|
+
owasp:
|
|
989
|
+
- "A05:2021 - Security Misconfiguration"
|
|
990
|
+
category: security
|
|
991
|
+
subcategory: [audit]
|
|
992
|
+
technology: [javascript, typescript]
|
|
993
|
+
likelihood: LOW
|
|
994
|
+
impact: MEDIUM
|
|
995
|
+
confidence: MEDIUM
|
|
996
|
+
vulnerability_class:
|
|
997
|
+
- Denial-of-Service (DoS)
|
|
998
|
+
pattern-sources:
|
|
999
|
+
- patterns:
|
|
1000
|
+
- pattern-either:
|
|
1001
|
+
- pattern: $REQ.body
|
|
1002
|
+
- pattern: $REQ.query
|
|
1003
|
+
- pattern: $REQ.params
|
|
1004
|
+
- metavariable-regex:
|
|
1005
|
+
metavariable: $REQ
|
|
1006
|
+
regex: ^(req|request|_req|ctx|httpRequest|httpReq)$
|
|
1007
|
+
- patterns:
|
|
1008
|
+
- pattern: $REQ.$KIND.$X
|
|
1009
|
+
- metavariable-regex:
|
|
1010
|
+
metavariable: $KIND
|
|
1011
|
+
regex: ^(body|params|query)$
|
|
1012
|
+
pattern-sanitizers:
|
|
1013
|
+
# Regex-escaping the value makes every metacharacter literal, which
|
|
1014
|
+
# removes the attacker's ability to introduce backtracking.
|
|
1015
|
+
- pattern: escapeRegExp(...)
|
|
1016
|
+
- pattern: escapeRegex(...)
|
|
1017
|
+
- pattern: escapeStringRegexp(...)
|
|
1018
|
+
- pattern: $M.escapeRegExp(...)
|
|
1019
|
+
- pattern: $M.escapeRegex(...)
|
|
1020
|
+
- pattern: RegExp.escape(...)
|
|
1021
|
+
- pattern: parseInt(...)
|
|
1022
|
+
- pattern: Number(...)
|
|
1023
|
+
- patterns:
|
|
1024
|
+
- pattern: $V
|
|
1025
|
+
- pattern-inside: |
|
|
1026
|
+
if (<... $ALLOW.includes(...) ...>) { ... }
|
|
1027
|
+
- patterns:
|
|
1028
|
+
- pattern: $V
|
|
1029
|
+
- pattern-inside: |
|
|
1030
|
+
if (!$ALLOW.includes($V)) return ...;
|
|
1031
|
+
...
|
|
1032
|
+
pattern-sinks:
|
|
1033
|
+
# First argument only - see the conversion note above.
|
|
1034
|
+
- patterns:
|
|
1035
|
+
- pattern: new RegExp($SINK, ...)
|
|
1036
|
+
- focus-metavariable: $SINK
|
|
1037
|
+
|
|
1038
|
+
# Rule 3: mass assignment via ORM create/update of raw request body
|
|
1039
|
+
- id: narvy.javascript.misc.mass-assignment-orm-reqbody.mass-assignment-orm-reqbody
|
|
1040
|
+
message: >-
|
|
1041
|
+
An ORM `.create()` / `.update()` call is passed the raw request body
|
|
1042
|
+
directly, with no field allow-list. If the model has
|
|
1043
|
+
security-sensitive fields (e.g. `role`, `isAdmin`, `verified`,
|
|
1044
|
+
`balance`), an attacker can set them by adding extra JSON keys to
|
|
1045
|
+
the request (mass assignment, CWE-915). Explicitly whitelist
|
|
1046
|
+
permitted fields before passing them to the ORM.
|
|
1047
|
+
severity: WARNING
|
|
1048
|
+
languages: [javascript, typescript]
|
|
1049
|
+
metadata:
|
|
1050
|
+
cwe:
|
|
1051
|
+
- "CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes"
|
|
1052
|
+
owasp:
|
|
1053
|
+
- "A08:2021 - Software and Data Integrity Failures"
|
|
1054
|
+
category: security
|
|
1055
|
+
subcategory: [audit]
|
|
1056
|
+
technology: [javascript, typescript, mongoose, sequelize, express]
|
|
1057
|
+
likelihood: MEDIUM
|
|
1058
|
+
impact: MEDIUM
|
|
1059
|
+
confidence: LOW
|
|
1060
|
+
vulnerability_class:
|
|
1061
|
+
- Improper Access Control
|
|
1062
|
+
patterns:
|
|
1063
|
+
- pattern-either:
|
|
1064
|
+
- pattern: $MODEL.create($REQ.body)
|
|
1065
|
+
- pattern: $MODEL.update($REQ.body, ...)
|
|
1066
|
+
- pattern: $MODEL.findByIdAndUpdate($ID, $REQ.body, ...)
|
|
1067
|
+
- pattern: $MODEL.findOneAndUpdate($COND, $REQ.body, ...)
|
|
1068
|
+
|
|
1069
|
+
# Rule 4: world-writable file permissions
|
|
1070
|
+
- id: narvy.javascript.misc.insecure-file-permission.insecure-file-permission
|
|
1071
|
+
message: >-
|
|
1072
|
+
`fs.chmod` / `fs.chmodSync` sets world-writable (and often
|
|
1073
|
+
world-readable) permissions (0o777 / 0o666 / 0777 / 0666). Any local
|
|
1074
|
+
user or process can then modify or read this file, which is
|
|
1075
|
+
dangerous for config files, key material, or scripts that get
|
|
1076
|
+
executed later (CWE-732). Use the narrowest mode that works, e.g.
|
|
1077
|
+
0o600 for secrets or 0o644 for regular files.
|
|
1078
|
+
severity: WARNING
|
|
1079
|
+
languages: [javascript, typescript]
|
|
1080
|
+
metadata:
|
|
1081
|
+
cwe:
|
|
1082
|
+
- "CWE-732: Incorrect Permission Assignment for Critical Resource"
|
|
1083
|
+
owasp:
|
|
1084
|
+
- "A01:2021 - Broken Access Control"
|
|
1085
|
+
category: security
|
|
1086
|
+
subcategory: [audit]
|
|
1087
|
+
technology: [javascript, typescript, node]
|
|
1088
|
+
likelihood: LOW
|
|
1089
|
+
impact: MEDIUM
|
|
1090
|
+
confidence: HIGH
|
|
1091
|
+
vulnerability_class:
|
|
1092
|
+
- Improper Access Control
|
|
1093
|
+
patterns:
|
|
1094
|
+
- pattern-either:
|
|
1095
|
+
- pattern: fs.chmod($PATH, 0o777, ...)
|
|
1096
|
+
- pattern: fs.chmod($PATH, 0777, ...)
|
|
1097
|
+
- pattern: fs.chmod($PATH, 0o666, ...)
|
|
1098
|
+
- pattern: fs.chmod($PATH, 0666, ...)
|
|
1099
|
+
- pattern: fs.chmodSync($PATH, 0o777)
|
|
1100
|
+
- pattern: fs.chmodSync($PATH, 0777)
|
|
1101
|
+
- pattern: fs.chmodSync($PATH, 0o666)
|
|
1102
|
+
- pattern: fs.chmodSync($PATH, 0666)
|
|
1103
|
+
- pattern: fsPromises.chmod($PATH, 0o777)
|
|
1104
|
+
- pattern: fsPromises.chmod($PATH, 0o666)
|
|
1105
|
+
|
|
1106
|
+
# OPEN REDIRECT
|
|
1107
|
+
|
|
1108
|
+
# 1. Server-side: res.redirect with an unvalidated request value.
|
|
1109
|
+
- id: narvy.javascript.openredirect.res-redirect-request-value.res-redirect-request-value
|
|
1110
|
+
message: >-
|
|
1111
|
+
res.redirect is called with a request-derived value used directly
|
|
1112
|
+
as the redirect target, with no allow-list or same-origin check.
|
|
1113
|
+
An attacker can craft a link that redirects victims to an
|
|
1114
|
+
attacker-controlled domain, useful for phishing after a trusted
|
|
1115
|
+
click (CWE-601). Validate that the target is a relative path or
|
|
1116
|
+
matches an explicit allow-list of hosts before redirecting.
|
|
1117
|
+
severity: WARNING
|
|
1118
|
+
languages: [javascript, typescript]
|
|
1119
|
+
metadata:
|
|
1120
|
+
cwe:
|
|
1121
|
+
- "CWE-601: URL Redirection to Untrusted Site ('Open Redirect')"
|
|
1122
|
+
owasp:
|
|
1123
|
+
- "A01:2021 - Broken Access Control"
|
|
1124
|
+
category: security
|
|
1125
|
+
subcategory: [vuln]
|
|
1126
|
+
technology: [express, nodejs, javascript, typescript]
|
|
1127
|
+
likelihood: MEDIUM
|
|
1128
|
+
impact: MEDIUM
|
|
1129
|
+
confidence: MEDIUM
|
|
1130
|
+
vulnerability_class:
|
|
1131
|
+
- Open Redirect
|
|
1132
|
+
references:
|
|
1133
|
+
- https://cwe.mitre.org/data/definitions/601.html
|
|
1134
|
+
patterns:
|
|
1135
|
+
- pattern-either:
|
|
1136
|
+
- pattern: $RES.redirect($REQ.$KIND.$X)
|
|
1137
|
+
- pattern: $RES.redirect($CODE, $REQ.$KIND.$X)
|
|
1138
|
+
- pattern: |
|
|
1139
|
+
$V = $REQ.$KIND.$X
|
|
1140
|
+
...
|
|
1141
|
+
$RES.redirect($V)
|
|
1142
|
+
- pattern: |
|
|
1143
|
+
$V = $REQ.$KIND.$X
|
|
1144
|
+
...
|
|
1145
|
+
$RES.redirect($CODE, $V)
|
|
1146
|
+
- metavariable-regex:
|
|
1147
|
+
metavariable: $KIND
|
|
1148
|
+
regex: ^(query|params|body)$
|
|
1149
|
+
- pattern-not-inside: |
|
|
1150
|
+
if (<... $ALLOWLIST.includes(...) ...>) { ... }
|
|
1151
|
+
- pattern-not-inside: |
|
|
1152
|
+
if (<... $V.startsWith("/") ...>) { ... }
|
|
1153
|
+
|
|
1154
|
+
# 2. Client-side: location assigned from a URL parameter (DOM open
|
|
1155
|
+
# redirect).
|
|
1156
|
+
- id: narvy.javascript.openredirect.dom-location-from-url-param.dom-location-from-url-param
|
|
1157
|
+
message: >-
|
|
1158
|
+
window.location (or .href) is assigned a value read straight from
|
|
1159
|
+
a URL query/hash parameter. A crafted link can point this at an
|
|
1160
|
+
attacker-controlled domain, i.e. DOM-based open redirect
|
|
1161
|
+
(CWE-601), and depending on how the value reaches the sink this
|
|
1162
|
+
can also enable javascript: URI execution. Validate the target is
|
|
1163
|
+
a relative, same-origin path before assigning it.
|
|
1164
|
+
severity: WARNING
|
|
1165
|
+
languages: [javascript, typescript]
|
|
1166
|
+
mode: taint
|
|
1167
|
+
metadata:
|
|
1168
|
+
cwe:
|
|
1169
|
+
- "CWE-601: URL Redirection to Untrusted Site ('Open Redirect')"
|
|
1170
|
+
owasp:
|
|
1171
|
+
- "A01:2021 - Broken Access Control"
|
|
1172
|
+
category: security
|
|
1173
|
+
subcategory: [vuln]
|
|
1174
|
+
technology: [javascript, typescript, dom]
|
|
1175
|
+
likelihood: LOW
|
|
1176
|
+
impact: MEDIUM
|
|
1177
|
+
confidence: LOW
|
|
1178
|
+
vulnerability_class:
|
|
1179
|
+
- Open Redirect
|
|
1180
|
+
pattern-sources:
|
|
1181
|
+
- pattern: new URLSearchParams(location.search).get(...)
|
|
1182
|
+
- pattern: new URLSearchParams(window.location.search).get(...)
|
|
1183
|
+
- pattern: location.hash
|
|
1184
|
+
- pattern: window.location.hash
|
|
1185
|
+
pattern-sinks:
|
|
1186
|
+
- pattern: window.location = $SINK
|
|
1187
|
+
- pattern: window.location.href = $SINK
|
|
1188
|
+
- pattern: location.href = $SINK
|
|
1189
|
+
- pattern: window.location.assign($SINK)
|
|
1190
|
+
- pattern: window.location.replace($SINK)
|
|
1191
|
+
|
|
1192
|
+
# PATH TRAVERSAL
|
|
1193
|
+
|
|
1194
|
+
# 1. fs.* sink fed by a path built from request data.
|
|
1195
|
+
- id: narvy.javascript.pathtraversal.fs-read-write-tainted-path.fs-read-write-tainted-path
|
|
1196
|
+
message: >-
|
|
1197
|
+
A filesystem call (readFile/writeFile/unlink/createReadStream) is
|
|
1198
|
+
given a path built from request data (path.join with a req.*
|
|
1199
|
+
segment, or direct string concatenation). Without validation an
|
|
1200
|
+
attacker can supply `../../` sequences to read or write files
|
|
1201
|
+
outside the intended directory (CWE-22). Resolve the path with
|
|
1202
|
+
path.resolve, then verify it still starts with the intended base
|
|
1203
|
+
directory before using it, or reject any segment containing `..`.
|
|
1204
|
+
severity: ERROR
|
|
1205
|
+
languages: [javascript, typescript]
|
|
1206
|
+
metadata:
|
|
1207
|
+
cwe:
|
|
1208
|
+
- "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')"
|
|
1209
|
+
owasp:
|
|
1210
|
+
- "A01:2021 - Broken Access Control"
|
|
1211
|
+
category: security
|
|
1212
|
+
subcategory: [vuln]
|
|
1213
|
+
technology: [nodejs, javascript, typescript]
|
|
1214
|
+
likelihood: MEDIUM
|
|
1215
|
+
impact: HIGH
|
|
1216
|
+
confidence: MEDIUM
|
|
1217
|
+
vulnerability_class:
|
|
1218
|
+
- Path Traversal
|
|
1219
|
+
references:
|
|
1220
|
+
- https://cwe.mitre.org/data/definitions/22.html
|
|
1221
|
+
patterns:
|
|
1222
|
+
- pattern-either:
|
|
1223
|
+
- pattern: $FS.readFile(path.join($BASE, $REQ.$KIND.$X), ...)
|
|
1224
|
+
- pattern: $FS.readFileSync(path.join($BASE, $REQ.$KIND.$X), ...)
|
|
1225
|
+
- pattern: $FS.writeFile(path.join($BASE, $REQ.$KIND.$X), ...)
|
|
1226
|
+
- pattern: $FS.writeFileSync(path.join($BASE, $REQ.$KIND.$X), ...)
|
|
1227
|
+
- pattern: $FS.unlink(path.join($BASE, $REQ.$KIND.$X), ...)
|
|
1228
|
+
- pattern: $FS.unlinkSync(path.join($BASE, $REQ.$KIND.$X))
|
|
1229
|
+
- pattern: $FS.createReadStream(path.join($BASE, $REQ.$KIND.$X))
|
|
1230
|
+
- pattern: $FS.readFile("$DIR" + $REQ.$KIND.$X, ...)
|
|
1231
|
+
- pattern: $FS.readFileSync("$DIR" + $REQ.$KIND.$X)
|
|
1232
|
+
- pattern: $FS.readFile(`$DIR${$REQ.$KIND.$X}`, ...)
|
|
1233
|
+
- pattern: $FS.readFileSync(`$DIR${$REQ.$KIND.$X}`)
|
|
1234
|
+
- metavariable-regex:
|
|
1235
|
+
metavariable: $KIND
|
|
1236
|
+
regex: ^(query|params|body|headers|cookies)$
|
|
1237
|
+
- pattern-not-inside: |
|
|
1238
|
+
if (<... $REQ.$KIND.$X.includes("..") ...>) { ... }
|
|
1239
|
+
- pattern-not-inside: |
|
|
1240
|
+
if (<... !$RESOLVED.startsWith($BASE2) ...>) { ... }
|
|
1241
|
+
paths:
|
|
1242
|
+
exclude:
|
|
1243
|
+
- "**/test/**"
|
|
1244
|
+
- "**/tests/**"
|
|
1245
|
+
- "**/__tests__/**"
|
|
1246
|
+
|
|
1247
|
+
# 2. res.sendFile with a path built from request data.
|
|
1248
|
+
- id: narvy.javascript.pathtraversal.sendfile-tainted-path.sendfile-tainted-path
|
|
1249
|
+
message: >-
|
|
1250
|
+
res.sendFile is called with a path built from a request field via
|
|
1251
|
+
path.join, with no visible containment check. An attacker can
|
|
1252
|
+
submit `..` segments in the request field to read files outside
|
|
1253
|
+
the served directory (CWE-22). Use `root` option combined with a
|
|
1254
|
+
validated, normalized relative path, and reject values containing
|
|
1255
|
+
`..`.
|
|
1256
|
+
severity: ERROR
|
|
1257
|
+
languages: [javascript, typescript]
|
|
1258
|
+
metadata:
|
|
1259
|
+
cwe:
|
|
1260
|
+
- "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')"
|
|
1261
|
+
owasp:
|
|
1262
|
+
- "A01:2021 - Broken Access Control"
|
|
1263
|
+
category: security
|
|
1264
|
+
subcategory: [vuln]
|
|
1265
|
+
technology: [express, nodejs, javascript, typescript]
|
|
1266
|
+
likelihood: MEDIUM
|
|
1267
|
+
impact: HIGH
|
|
1268
|
+
confidence: MEDIUM
|
|
1269
|
+
vulnerability_class:
|
|
1270
|
+
- Path Traversal
|
|
1271
|
+
patterns:
|
|
1272
|
+
- pattern: $RES.sendFile(path.join($BASE, $REQ.$KIND.$X), ...)
|
|
1273
|
+
- metavariable-regex:
|
|
1274
|
+
metavariable: $KIND
|
|
1275
|
+
regex: ^(query|params|body)$
|
|
1276
|
+
- pattern-not-inside: |
|
|
1277
|
+
if (<... $REQ.$KIND.$X.includes("..") ...>) { ... }
|
|
1278
|
+
|
|
1279
|
+
# POSTMESSAGE
|
|
1280
|
+
|
|
1281
|
+
- id: narvy.javascript.postmessage.wildcard-target-origin.wildcard-target-origin
|
|
1282
|
+
message: >-
|
|
1283
|
+
postMessage is called with "*" as the target origin. The message
|
|
1284
|
+
is delivered to whatever origin the target window currently has,
|
|
1285
|
+
which an attacker can control by first navigating the window/frame
|
|
1286
|
+
to their own page. If the message carries a token, session data,
|
|
1287
|
+
or any sensitive value, this leaks it to an attacker-controlled
|
|
1288
|
+
origin (CWE-346). Pass the exact expected origin instead of "*".
|
|
1289
|
+
severity: WARNING
|
|
1290
|
+
languages: [javascript, typescript]
|
|
1291
|
+
metadata:
|
|
1292
|
+
cwe:
|
|
1293
|
+
- "CWE-346: Origin Validation Error"
|
|
1294
|
+
owasp:
|
|
1295
|
+
- "A05:2021 - Security Misconfiguration"
|
|
1296
|
+
category: security
|
|
1297
|
+
subcategory: [vuln]
|
|
1298
|
+
technology: [javascript, typescript, dom]
|
|
1299
|
+
likelihood: LOW
|
|
1300
|
+
impact: MEDIUM
|
|
1301
|
+
confidence: MEDIUM
|
|
1302
|
+
vulnerability_class:
|
|
1303
|
+
- Other
|
|
1304
|
+
references:
|
|
1305
|
+
- https://cwe.mitre.org/data/definitions/346.html
|
|
1306
|
+
- https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#security_concerns
|
|
1307
|
+
patterns:
|
|
1308
|
+
- pattern-either:
|
|
1309
|
+
- pattern: $TARGET.postMessage($DATA, "*")
|
|
1310
|
+
- pattern: $TARGET.postMessage($DATA, '*')
|
|
1311
|
+
|
|
1312
|
+
- id: narvy.javascript.postmessage.handler-missing-origin-check.handler-missing-origin-check
|
|
1313
|
+
message: >-
|
|
1314
|
+
A "message" event handler is registered but never checks
|
|
1315
|
+
event.origin. Any page that can obtain a reference to this window
|
|
1316
|
+
(an iframe it embeds, a window it opened) can send it a message,
|
|
1317
|
+
and without an origin check the handler will treat that message as
|
|
1318
|
+
trusted (CWE-346). Validate event.origin against an exact expected
|
|
1319
|
+
value before acting on event.data.
|
|
1320
|
+
severity: WARNING
|
|
1321
|
+
languages: [javascript, typescript]
|
|
1322
|
+
metadata:
|
|
1323
|
+
cwe:
|
|
1324
|
+
- "CWE-346: Origin Validation Error"
|
|
1325
|
+
owasp:
|
|
1326
|
+
- "A05:2021 - Security Misconfiguration"
|
|
1327
|
+
category: security
|
|
1328
|
+
subcategory: [vuln]
|
|
1329
|
+
technology: [javascript, typescript, dom]
|
|
1330
|
+
likelihood: LOW
|
|
1331
|
+
impact: MEDIUM
|
|
1332
|
+
confidence: LOW
|
|
1333
|
+
vulnerability_class:
|
|
1334
|
+
- Other
|
|
1335
|
+
patterns:
|
|
1336
|
+
- pattern-either:
|
|
1337
|
+
- pattern: |
|
|
1338
|
+
window.addEventListener("message", function ($EVT) { ... })
|
|
1339
|
+
- pattern: |
|
|
1340
|
+
window.addEventListener("message", ($EVT) => { ... })
|
|
1341
|
+
- pattern: |
|
|
1342
|
+
addEventListener("message", function ($EVT) { ... })
|
|
1343
|
+
- pattern: |
|
|
1344
|
+
addEventListener("message", ($EVT) => { ... })
|
|
1345
|
+
- pattern-not: |
|
|
1346
|
+
window.addEventListener("message", function ($EVT) { <... $EVT.origin ...> })
|
|
1347
|
+
- pattern-not: |
|
|
1348
|
+
window.addEventListener("message", ($EVT) => { <... $EVT.origin ...> })
|
|
1349
|
+
- pattern-not: |
|
|
1350
|
+
addEventListener("message", function ($EVT) { <... $EVT.origin ...> })
|
|
1351
|
+
- pattern-not: |
|
|
1352
|
+
addEventListener("message", ($EVT) => { <... $EVT.origin ...> })
|
|
1353
|
+
|
|
1354
|
+
# SQL CONCAT
|
|
1355
|
+
|
|
1356
|
+
# Rule 2a: string-concat user input into .query/.execute/.raw (taint); focus $SINK, SQL-looking regex, receiver-scoped.
|
|
1357
|
+
- id: narvy.javascript.sqli.user-input-concat-query.user-input-concat-query
|
|
1358
|
+
message: >-
|
|
1359
|
+
User input from the request flows into a SQL string built by
|
|
1360
|
+
CONCATENATION and passed to a query/execute/raw sink. Classic SQL
|
|
1361
|
+
injection (CWE-89). Use a parameterized query: pass user input as the
|
|
1362
|
+
2nd arg of .query(sql, [params]) or use a query builder / ORM with
|
|
1363
|
+
bound parameters.
|
|
1364
|
+
severity: ERROR
|
|
1365
|
+
languages: [javascript, typescript]
|
|
1366
|
+
mode: taint
|
|
1367
|
+
metadata:
|
|
1368
|
+
cwe:
|
|
1369
|
+
- "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
|
|
1370
|
+
owasp:
|
|
1371
|
+
- "A03:2021 - Injection"
|
|
1372
|
+
- "A01:2017 - Injection"
|
|
1373
|
+
category: security
|
|
1374
|
+
subcategory: [vuln]
|
|
1375
|
+
technology: [pg, mysql, mysql2, sqlite3, knex, sequelize, typeorm, javascript, typescript]
|
|
1376
|
+
likelihood: HIGH
|
|
1377
|
+
impact: HIGH
|
|
1378
|
+
confidence: HIGH
|
|
1379
|
+
vulnerability_class:
|
|
1380
|
+
- Improper Validation
|
|
1381
|
+
references:
|
|
1382
|
+
- https://cwe.mitre.org/data/definitions/89.html
|
|
1383
|
+
- https://owasp.org/www-community/attacks/SQL_Injection
|
|
1384
|
+
pattern-sources:
|
|
1385
|
+
# Whole request container, so destructuring propagates taint.
|
|
1386
|
+
# Receiver name constrained - see PRECISION GUARD 4 above.
|
|
1387
|
+
- patterns:
|
|
1388
|
+
- pattern-either:
|
|
1389
|
+
- pattern: $REQ.body
|
|
1390
|
+
- pattern: $REQ.query
|
|
1391
|
+
- pattern: $REQ.params
|
|
1392
|
+
- pattern: $REQ.headers
|
|
1393
|
+
- pattern: $REQ.cookies
|
|
1394
|
+
- metavariable-regex:
|
|
1395
|
+
metavariable: $REQ
|
|
1396
|
+
regex: ^(req|request|_req|ctx|httpRequest|httpReq)$
|
|
1397
|
+
# Two-level access keeps the pre-taint rule's unconstrained receiver.
|
|
1398
|
+
- patterns:
|
|
1399
|
+
- pattern: $REQ.$KIND.$X
|
|
1400
|
+
- metavariable-regex:
|
|
1401
|
+
metavariable: $KIND
|
|
1402
|
+
regex: ^(body|params|query|headers|cookies)$
|
|
1403
|
+
# AWS Lambda proxy-integration handler event.
|
|
1404
|
+
- patterns:
|
|
1405
|
+
- pattern: $EVENT.$EKIND
|
|
1406
|
+
- metavariable-regex:
|
|
1407
|
+
metavariable: $EKIND
|
|
1408
|
+
regex: ^(queryStringParameters|pathParameters|multiValueQueryStringParameters|multiValueHeaders)$
|
|
1409
|
+
- pattern: JSON.parse($EVENT.body)
|
|
1410
|
+
# Raw (unparsed) Lambda body. Receiver pinned to event-shaped names,
|
|
1411
|
+
# because a bare `$X.body` also matches an OUTBOUND response body.
|
|
1412
|
+
- patterns:
|
|
1413
|
+
- pattern: $EVENT.body
|
|
1414
|
+
- metavariable-regex:
|
|
1415
|
+
metavariable: $EVENT
|
|
1416
|
+
regex: ^(event|evt|_event|lambdaEvent|apiEvent)$
|
|
1417
|
+
pattern-sanitizers:
|
|
1418
|
+
# Numeric coercion makes SQL metacharacters unrepresentable.
|
|
1419
|
+
- pattern: parseInt(...)
|
|
1420
|
+
- pattern: parseFloat(...)
|
|
1421
|
+
- pattern: Number(...)
|
|
1422
|
+
- pattern: BigInt(...)
|
|
1423
|
+
# Driver-provided escaping (mysql/mysql2 escape+escapeId, pg
|
|
1424
|
+
# escapeLiteral/escapeIdentifier, SqlString.escape, knex .escape).
|
|
1425
|
+
- pattern: escape(...)
|
|
1426
|
+
- pattern: $M.escape(...)
|
|
1427
|
+
- pattern: $M.escapeId(...)
|
|
1428
|
+
- pattern: $M.escapeLiteral(...)
|
|
1429
|
+
- pattern: $M.escapeIdentifier(...)
|
|
1430
|
+
# Allow-list / validator guards (wrapping-if and negated early-return).
|
|
1431
|
+
- patterns:
|
|
1432
|
+
- pattern: $V
|
|
1433
|
+
- pattern-inside: |
|
|
1434
|
+
if (<... $ALLOW.includes(...) ...>) { ... }
|
|
1435
|
+
- patterns:
|
|
1436
|
+
- pattern: $V
|
|
1437
|
+
- pattern-inside: |
|
|
1438
|
+
if (!$ALLOW.includes($V)) { ... }
|
|
1439
|
+
...
|
|
1440
|
+
- patterns:
|
|
1441
|
+
- pattern: $V
|
|
1442
|
+
- pattern-inside: |
|
|
1443
|
+
if (!$ALLOW.includes($V)) return ...;
|
|
1444
|
+
...
|
|
1445
|
+
- patterns:
|
|
1446
|
+
- pattern: $V
|
|
1447
|
+
- pattern-inside: |
|
|
1448
|
+
if (!$RE.test($V)) { ... }
|
|
1449
|
+
...
|
|
1450
|
+
- patterns:
|
|
1451
|
+
- pattern: $V
|
|
1452
|
+
- pattern-inside: |
|
|
1453
|
+
if (!$RE.test($V)) return ...;
|
|
1454
|
+
...
|
|
1455
|
+
pattern-sinks:
|
|
1456
|
+
- patterns:
|
|
1457
|
+
- pattern-either:
|
|
1458
|
+
- pattern: $CONN.query($SINK, ...)
|
|
1459
|
+
- pattern: $CONN.execute($SINK, ...)
|
|
1460
|
+
- pattern: $CONN.raw($SINK, ...)
|
|
1461
|
+
- focus-metavariable: $SINK
|
|
1462
|
+
# Concatenation only; the template-literal shape is the sibling
|
|
1463
|
+
# rule user-input-template-query.
|
|
1464
|
+
- metavariable-pattern:
|
|
1465
|
+
metavariable: $SINK
|
|
1466
|
+
pattern: $A + $B
|
|
1467
|
+
- metavariable-regex:
|
|
1468
|
+
metavariable: $SINK
|
|
1469
|
+
regex: '(?is).*\b(select|insert|update|delete|drop|alter|create|merge|truncate)\b.*'
|
|
1470
|
+
|
|
1471
|
+
# Rule 2b: template-literal user input into .query/.execute/.raw; tagged templates not matched.
|
|
1472
|
+
- id: narvy.javascript.sqli.user-input-template-query.user-input-template-query
|
|
1473
|
+
message: >-
|
|
1474
|
+
User input from the request is interpolated into a template-literal
|
|
1475
|
+
SQL string passed to a query/execute/raw sink. SQL injection (CWE-89).
|
|
1476
|
+
Use parameterized queries with positional placeholders ($1, ?).
|
|
1477
|
+
severity: ERROR
|
|
1478
|
+
languages: [javascript, typescript]
|
|
1479
|
+
mode: taint
|
|
1480
|
+
metadata:
|
|
1481
|
+
cwe:
|
|
1482
|
+
- "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
|
|
1483
|
+
owasp:
|
|
1484
|
+
- "A03:2021 - Injection"
|
|
1485
|
+
category: security
|
|
1486
|
+
subcategory: [vuln]
|
|
1487
|
+
technology: [pg, mysql, mysql2, sqlite3, knex, sequelize, typeorm, javascript, typescript]
|
|
1488
|
+
likelihood: HIGH
|
|
1489
|
+
impact: HIGH
|
|
1490
|
+
confidence: HIGH
|
|
1491
|
+
vulnerability_class:
|
|
1492
|
+
- Improper Validation
|
|
1493
|
+
pattern-sources:
|
|
1494
|
+
- patterns:
|
|
1495
|
+
- pattern-either:
|
|
1496
|
+
- pattern: $REQ.body
|
|
1497
|
+
- pattern: $REQ.query
|
|
1498
|
+
- pattern: $REQ.params
|
|
1499
|
+
- pattern: $REQ.headers
|
|
1500
|
+
- pattern: $REQ.cookies
|
|
1501
|
+
- metavariable-regex:
|
|
1502
|
+
metavariable: $REQ
|
|
1503
|
+
regex: ^(req|request|_req|ctx|httpRequest|httpReq)$
|
|
1504
|
+
- patterns:
|
|
1505
|
+
- pattern: $REQ.$KIND.$X
|
|
1506
|
+
- metavariable-regex:
|
|
1507
|
+
metavariable: $KIND
|
|
1508
|
+
regex: ^(body|params|query|headers|cookies)$
|
|
1509
|
+
- patterns:
|
|
1510
|
+
- pattern: $EVENT.$EKIND
|
|
1511
|
+
- metavariable-regex:
|
|
1512
|
+
metavariable: $EKIND
|
|
1513
|
+
regex: ^(queryStringParameters|pathParameters|multiValueQueryStringParameters|multiValueHeaders)$
|
|
1514
|
+
- pattern: JSON.parse($EVENT.body)
|
|
1515
|
+
# Raw (unparsed) Lambda body. Receiver pinned to event-shaped names,
|
|
1516
|
+
# because a bare `$X.body` also matches an OUTBOUND response body.
|
|
1517
|
+
- patterns:
|
|
1518
|
+
- pattern: $EVENT.body
|
|
1519
|
+
- metavariable-regex:
|
|
1520
|
+
metavariable: $EVENT
|
|
1521
|
+
regex: ^(event|evt|_event|lambdaEvent|apiEvent)$
|
|
1522
|
+
pattern-sanitizers:
|
|
1523
|
+
- pattern: parseInt(...)
|
|
1524
|
+
- pattern: parseFloat(...)
|
|
1525
|
+
- pattern: Number(...)
|
|
1526
|
+
- pattern: BigInt(...)
|
|
1527
|
+
- pattern: escape(...)
|
|
1528
|
+
- pattern: $M.escape(...)
|
|
1529
|
+
- pattern: $M.escapeId(...)
|
|
1530
|
+
- pattern: $M.escapeLiteral(...)
|
|
1531
|
+
- pattern: $M.escapeIdentifier(...)
|
|
1532
|
+
- patterns:
|
|
1533
|
+
- pattern: $V
|
|
1534
|
+
- pattern-inside: |
|
|
1535
|
+
if (<... $ALLOW.includes(...) ...>) { ... }
|
|
1536
|
+
- patterns:
|
|
1537
|
+
- pattern: $V
|
|
1538
|
+
- pattern-inside: |
|
|
1539
|
+
if (!$ALLOW.includes($V)) { ... }
|
|
1540
|
+
...
|
|
1541
|
+
- patterns:
|
|
1542
|
+
- pattern: $V
|
|
1543
|
+
- pattern-inside: |
|
|
1544
|
+
if (!$ALLOW.includes($V)) return ...;
|
|
1545
|
+
...
|
|
1546
|
+
- patterns:
|
|
1547
|
+
- pattern: $V
|
|
1548
|
+
- pattern-inside: |
|
|
1549
|
+
if (!$RE.test($V)) { ... }
|
|
1550
|
+
...
|
|
1551
|
+
- patterns:
|
|
1552
|
+
- pattern: $V
|
|
1553
|
+
- pattern-inside: |
|
|
1554
|
+
if (!$RE.test($V)) return ...;
|
|
1555
|
+
...
|
|
1556
|
+
pattern-sinks:
|
|
1557
|
+
- patterns:
|
|
1558
|
+
- pattern-either:
|
|
1559
|
+
- pattern: $CONN.query($SINK, ...)
|
|
1560
|
+
- pattern: $CONN.execute($SINK, ...)
|
|
1561
|
+
- pattern: $CONN.raw($SINK, ...)
|
|
1562
|
+
- focus-metavariable: $SINK
|
|
1563
|
+
# Template-literal shape and the SQL-looking check in one regex.
|
|
1564
|
+
- metavariable-regex:
|
|
1565
|
+
metavariable: $SINK
|
|
1566
|
+
regex: '(?is)^`.*\b(select|insert|update|delete|drop|alter|create|merge|truncate)\b.*'
|
|
1567
|
+
|
|
1568
|
+
# Rule 2c: SQL built into a variable then passed to sink; syntactic, requires request data + a SQL keyword in one RHS.
|
|
1569
|
+
- id: narvy.javascript.sqli.user-input-concat-query-var-indirect.user-input-concat-query-var-indirect
|
|
1570
|
+
message: >-
|
|
1571
|
+
A SQL string that concatenates / interpolates request data
|
|
1572
|
+
(query/params/body/headers/cookies) is built into a variable and then
|
|
1573
|
+
passed to a query/execute/raw sink. SQL injection (CWE-89), with one
|
|
1574
|
+
level of variable indirection. Use a parameterized query: pass user
|
|
1575
|
+
input as bound parameters, e.g. .query(sql, [params]).
|
|
1576
|
+
severity: ERROR
|
|
1577
|
+
languages: [javascript, typescript]
|
|
1578
|
+
metadata:
|
|
1579
|
+
cwe:
|
|
1580
|
+
- "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
|
|
1581
|
+
owasp:
|
|
1582
|
+
- "A03:2021 - Injection"
|
|
1583
|
+
category: security
|
|
1584
|
+
subcategory: [vuln]
|
|
1585
|
+
technology: [pg, mysql, mysql2, sqlite3, knex, sequelize, typeorm, javascript, typescript]
|
|
1586
|
+
likelihood: HIGH
|
|
1587
|
+
impact: HIGH
|
|
1588
|
+
confidence: HIGH
|
|
1589
|
+
vulnerability_class:
|
|
1590
|
+
- Improper Validation
|
|
1591
|
+
references:
|
|
1592
|
+
- https://cwe.mitre.org/data/definitions/89.html
|
|
1593
|
+
- https://owasp.org/www-community/attacks/SQL_Injection
|
|
1594
|
+
patterns:
|
|
1595
|
+
- pattern-either:
|
|
1596
|
+
# concatenation carrying request data + a SQL-keyword string
|
|
1597
|
+
- patterns:
|
|
1598
|
+
- pattern: |
|
|
1599
|
+
$Q = <... $REQ.$KIND.$X ...>
|
|
1600
|
+
...
|
|
1601
|
+
$CONN.$M($Q, ...)
|
|
1602
|
+
- pattern: |
|
|
1603
|
+
$Q = <... "$SQL" ...>
|
|
1604
|
+
...
|
|
1605
|
+
$CONN.$M($Q, ...)
|
|
1606
|
+
- metavariable-regex:
|
|
1607
|
+
metavariable: $SQL
|
|
1608
|
+
regex: '(?is).*\b(select|insert|update|delete|drop|alter|create|merge|truncate)\b.*'
|
|
1609
|
+
# template literal carrying request data
|
|
1610
|
+
- patterns:
|
|
1611
|
+
- pattern: |
|
|
1612
|
+
$Q = <... $REQ.$KIND.$X ...>
|
|
1613
|
+
...
|
|
1614
|
+
$CONN.$M($Q, ...)
|
|
1615
|
+
- pattern: |
|
|
1616
|
+
$Q = `...${...}...`
|
|
1617
|
+
...
|
|
1618
|
+
$CONN.$M($Q, ...)
|
|
1619
|
+
- metavariable-regex:
|
|
1620
|
+
metavariable: $KIND
|
|
1621
|
+
regex: ^(query|params|body|headers|cookies)$
|
|
1622
|
+
- metavariable-regex:
|
|
1623
|
+
metavariable: $REQ
|
|
1624
|
+
regex: ^(req|request|_req|ctx|httpRequest|httpReq)$
|
|
1625
|
+
- metavariable-regex:
|
|
1626
|
+
metavariable: $M
|
|
1627
|
+
regex: ^(query|execute|raw)$
|
|
1628
|
+
|
|
1629
|
+
# SQLI ORM
|
|
1630
|
+
|
|
1631
|
+
- id: narvy.javascript.sqli.sequelize-literal-tainted.sequelize-literal-tainted
|
|
1632
|
+
message: >-
|
|
1633
|
+
sequelize.literal is called with a SQL fragment built from request
|
|
1634
|
+
data. literal() inserts its argument into the query completely
|
|
1635
|
+
unescaped - it exists specifically to bypass Sequelize's
|
|
1636
|
+
parameterization, so request data reaching it is direct SQL
|
|
1637
|
+
injection (CWE-89). Use a parameterized where-clause or bind
|
|
1638
|
+
variables instead of sequelize.literal for anything derived from
|
|
1639
|
+
user input.
|
|
1640
|
+
severity: ERROR
|
|
1641
|
+
languages: [javascript, typescript]
|
|
1642
|
+
metadata:
|
|
1643
|
+
cwe:
|
|
1644
|
+
- "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
|
|
1645
|
+
owasp:
|
|
1646
|
+
- "A03:2021 - Injection"
|
|
1647
|
+
category: security
|
|
1648
|
+
subcategory: [vuln]
|
|
1649
|
+
technology: [sequelize, nodejs, javascript, typescript]
|
|
1650
|
+
likelihood: HIGH
|
|
1651
|
+
impact: HIGH
|
|
1652
|
+
confidence: HIGH
|
|
1653
|
+
vulnerability_class:
|
|
1654
|
+
- SQL Injection
|
|
1655
|
+
references:
|
|
1656
|
+
- https://cwe.mitre.org/data/definitions/89.html
|
|
1657
|
+
patterns:
|
|
1658
|
+
- pattern-either:
|
|
1659
|
+
- patterns:
|
|
1660
|
+
- pattern-either:
|
|
1661
|
+
- pattern: sequelize.literal("$SQL" + $REQ.$KIND.$X + ...)
|
|
1662
|
+
- pattern: sequelize.literal(`...${$REQ.$KIND.$X}...`)
|
|
1663
|
+
- pattern: |
|
|
1664
|
+
$V = $REQ.$KIND.$X
|
|
1665
|
+
...
|
|
1666
|
+
sequelize.literal("$SQL" + $V + ...)
|
|
1667
|
+
- pattern: |
|
|
1668
|
+
$V = $REQ.$KIND.$X
|
|
1669
|
+
...
|
|
1670
|
+
sequelize.literal(`...${$V}...`)
|
|
1671
|
+
- metavariable-regex:
|
|
1672
|
+
metavariable: $KIND
|
|
1673
|
+
regex: ^(query|params|body|headers|cookies)$
|
|
1674
|
+
# AWS Lambda handler taint source (see user-input-concat-query for
|
|
1675
|
+
# the nesting rationale).
|
|
1676
|
+
- patterns:
|
|
1677
|
+
- pattern-either:
|
|
1678
|
+
- pattern: sequelize.literal("$SQL" + $EVENT.$EKIND.$X + ...)
|
|
1679
|
+
- pattern: sequelize.literal(`...${$EVENT.$EKIND.$X}...`)
|
|
1680
|
+
- pattern: |
|
|
1681
|
+
$V = $EVENT.$EKIND.$X
|
|
1682
|
+
...
|
|
1683
|
+
sequelize.literal("$SQL" + $V + ...)
|
|
1684
|
+
- pattern: |
|
|
1685
|
+
$V = $EVENT.$EKIND.$X
|
|
1686
|
+
...
|
|
1687
|
+
sequelize.literal(`...${$V}...`)
|
|
1688
|
+
- metavariable-regex:
|
|
1689
|
+
metavariable: $EKIND
|
|
1690
|
+
regex: ^(queryStringParameters|pathParameters|headers|multiValueQueryStringParameters|multiValueHeaders)$
|
|
1691
|
+
- pattern: sequelize.literal("$SQL" + JSON.parse($EVENT.body).$X + ...)
|
|
1692
|
+
|
|
1693
|
+
- id: narvy.javascript.sqli.mongoose-where-tainted.mongoose-where-tainted
|
|
1694
|
+
message: >-
|
|
1695
|
+
A Mongoose query's `$where` clause is built from request data.
|
|
1696
|
+
$where runs its string as server-side JavaScript against every
|
|
1697
|
+
document in the collection, so request-controlled input here is
|
|
1698
|
+
effectively server-side code injection through the database layer
|
|
1699
|
+
(CWE-943 / CWE-94), not just NoSQL injection. Remove $where and
|
|
1700
|
+
express the same condition with regular MongoDB query operators.
|
|
1701
|
+
severity: ERROR
|
|
1702
|
+
languages: [javascript, typescript]
|
|
1703
|
+
metadata:
|
|
1704
|
+
cwe:
|
|
1705
|
+
- "CWE-943: Improper Neutralization of Special Elements in Data Query Logic"
|
|
1706
|
+
owasp:
|
|
1707
|
+
- "A03:2021 - Injection"
|
|
1708
|
+
category: security
|
|
1709
|
+
subcategory: [vuln]
|
|
1710
|
+
technology: [mongoose, mongodb, nodejs, javascript, typescript]
|
|
1711
|
+
likelihood: HIGH
|
|
1712
|
+
impact: HIGH
|
|
1713
|
+
confidence: HIGH
|
|
1714
|
+
vulnerability_class:
|
|
1715
|
+
- SQL Injection
|
|
1716
|
+
patterns:
|
|
1717
|
+
- pattern-either:
|
|
1718
|
+
- patterns:
|
|
1719
|
+
- pattern-either:
|
|
1720
|
+
- pattern: |
|
|
1721
|
+
{..., $where: "$JS" + $REQ.$KIND.$X + ..., ...}
|
|
1722
|
+
- pattern: |
|
|
1723
|
+
{..., $where: `...${$REQ.$KIND.$X}...`, ...}
|
|
1724
|
+
- metavariable-regex:
|
|
1725
|
+
metavariable: $KIND
|
|
1726
|
+
regex: ^(query|params|body|headers|cookies)$
|
|
1727
|
+
# AWS Lambda handler taint source (see user-input-concat-query for
|
|
1728
|
+
# the nesting rationale).
|
|
1729
|
+
- patterns:
|
|
1730
|
+
- pattern-either:
|
|
1731
|
+
- pattern: |
|
|
1732
|
+
{..., $where: "$JS" + $EVENT.$EKIND.$X + ..., ...}
|
|
1733
|
+
- pattern: |
|
|
1734
|
+
{..., $where: `...${$EVENT.$EKIND.$X}...`, ...}
|
|
1735
|
+
- metavariable-regex:
|
|
1736
|
+
metavariable: $EKIND
|
|
1737
|
+
regex: ^(queryStringParameters|pathParameters|headers|multiValueQueryStringParameters|multiValueHeaders)$
|
|
1738
|
+
|
|
1739
|
+
# SSRF REQUEST
|
|
1740
|
+
|
|
1741
|
+
- id: narvy.javascript.ssrf.request-url-from-user.request-url-from-user
|
|
1742
|
+
message: >-
|
|
1743
|
+
A request-derived value flows into an outbound HTTP client (http/https
|
|
1744
|
+
get/request, axios, fetch, got, node-fetch, request) as the target URL.
|
|
1745
|
+
An attacker controlling this value can force the server to make requests
|
|
1746
|
+
to internal services (cloud metadata, localhost admin ports), i.e.
|
|
1747
|
+
Server-Side Request Forgery (CWE-918). Validate the URL against an
|
|
1748
|
+
allow-list of hosts/schemes before the request.
|
|
1749
|
+
severity: ERROR
|
|
1750
|
+
languages:
|
|
1751
|
+
- javascript
|
|
1752
|
+
- typescript
|
|
1753
|
+
mode: taint
|
|
1754
|
+
metadata:
|
|
1755
|
+
cwe:
|
|
1756
|
+
- "CWE-918: Server-Side Request Forgery (SSRF)"
|
|
1757
|
+
owasp:
|
|
1758
|
+
- "A10:2021 - Server-Side Request Forgery (SSRF)"
|
|
1759
|
+
category: security
|
|
1760
|
+
confidence: MEDIUM
|
|
1761
|
+
references:
|
|
1762
|
+
- https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/
|
|
1763
|
+
pattern-sources:
|
|
1764
|
+
- pattern: req.query.$Q
|
|
1765
|
+
- pattern: req.params.$P
|
|
1766
|
+
- pattern: req.body.$B
|
|
1767
|
+
- pattern: req.headers.$H
|
|
1768
|
+
- pattern: req.query
|
|
1769
|
+
- pattern: req.params
|
|
1770
|
+
- pattern: request.query.$Q
|
|
1771
|
+
- pattern: request.params.$P
|
|
1772
|
+
- pattern: $CTX.request.query.$Q
|
|
1773
|
+
pattern-sanitizers:
|
|
1774
|
+
# An allow-list / URL-validator guard (safeFetch wrappers, host/scheme
|
|
1775
|
+
# validators) is the canonical SSRF mitigation and kills the taint.
|
|
1776
|
+
- pattern: safeFetch(...)
|
|
1777
|
+
- pattern: isAllowedUrl(...)
|
|
1778
|
+
- pattern: isAllowedHost(...)
|
|
1779
|
+
- pattern: isSafeUrl(...)
|
|
1780
|
+
- pattern: validateUrl(...)
|
|
1781
|
+
- pattern: validateHost(...)
|
|
1782
|
+
- patterns:
|
|
1783
|
+
- pattern: $V
|
|
1784
|
+
- pattern-inside: |
|
|
1785
|
+
if (<... $ALLOW.includes(...) ...>) { ... }
|
|
1786
|
+
pattern-sinks:
|
|
1787
|
+
# Every sink pins focus $SINK to the URL arg; $HTTP scoped to http/https.
|
|
1788
|
+
- patterns:
|
|
1789
|
+
- pattern: $HTTP.get($SINK, ...)
|
|
1790
|
+
- focus-metavariable: $SINK
|
|
1791
|
+
- metavariable-regex:
|
|
1792
|
+
metavariable: $HTTP
|
|
1793
|
+
regex: ^(http|https)$
|
|
1794
|
+
- patterns:
|
|
1795
|
+
- pattern: $HTTP.request($SINK, ...)
|
|
1796
|
+
- focus-metavariable: $SINK
|
|
1797
|
+
- metavariable-regex:
|
|
1798
|
+
metavariable: $HTTP
|
|
1799
|
+
regex: ^(http|https)$
|
|
1800
|
+
- patterns:
|
|
1801
|
+
- pattern: axios($SINK, ...)
|
|
1802
|
+
- focus-metavariable: $SINK
|
|
1803
|
+
- patterns:
|
|
1804
|
+
- pattern: axios.get($SINK, ...)
|
|
1805
|
+
- focus-metavariable: $SINK
|
|
1806
|
+
- patterns:
|
|
1807
|
+
- pattern: axios.request($SINK, ...)
|
|
1808
|
+
- focus-metavariable: $SINK
|
|
1809
|
+
- patterns:
|
|
1810
|
+
- pattern: fetch($SINK, ...)
|
|
1811
|
+
- focus-metavariable: $SINK
|
|
1812
|
+
- patterns:
|
|
1813
|
+
- pattern: got($SINK, ...)
|
|
1814
|
+
- focus-metavariable: $SINK
|
|
1815
|
+
- patterns:
|
|
1816
|
+
- pattern: got.get($SINK, ...)
|
|
1817
|
+
- focus-metavariable: $SINK
|
|
1818
|
+
# Scope request($SINK, ...) to a request/needle/node-fetch client assigned to a local.
|
|
1819
|
+
- patterns:
|
|
1820
|
+
- pattern: $REQ($SINK, ...)
|
|
1821
|
+
- focus-metavariable: $SINK
|
|
1822
|
+
- metavariable-pattern:
|
|
1823
|
+
metavariable: $REQ
|
|
1824
|
+
patterns:
|
|
1825
|
+
- pattern-either:
|
|
1826
|
+
- pattern: request
|
|
1827
|
+
- pattern: needle
|
|
1828
|
+
- pattern-inside: |
|
|
1829
|
+
$REQ = require("...");
|
|
1830
|
+
...
|
|
1831
|
+
- patterns:
|
|
1832
|
+
- pattern: needle($M, $SINK, ...)
|
|
1833
|
+
- focus-metavariable: $SINK
|
|
1834
|
+
# needle method-style helpers; identifier must be a local from require("needle").
|
|
1835
|
+
- patterns:
|
|
1836
|
+
- pattern: $NEEDLE.$METHOD($SINK, ...)
|
|
1837
|
+
- focus-metavariable: $SINK
|
|
1838
|
+
- metavariable-regex:
|
|
1839
|
+
metavariable: $METHOD
|
|
1840
|
+
regex: ^(get|post|put|patch|delete|head)$
|
|
1841
|
+
- metavariable-pattern:
|
|
1842
|
+
metavariable: $NEEDLE
|
|
1843
|
+
patterns:
|
|
1844
|
+
- pattern: needle
|
|
1845
|
+
- pattern-inside: |
|
|
1846
|
+
$NEEDLE = require("needle");
|
|
1847
|
+
...
|
|
1848
|
+
|
|
1849
|
+
# SSTI
|
|
1850
|
+
|
|
1851
|
+
- id: narvy.javascript.ssti.template-engine-compiles-request-data.template-engine-compiles-request-data
|
|
1852
|
+
message: >-
|
|
1853
|
+
A request-derived value is passed as the TEMPLATE STRING to a
|
|
1854
|
+
template engine's render/compile function (ejs, handlebars, pug,
|
|
1855
|
+
lodash template, Mustache). These engines evaluate JS expressions
|
|
1856
|
+
found inside the template source, so an attacker-controlled
|
|
1857
|
+
template string means arbitrary server-side code execution
|
|
1858
|
+
(CWE-1336 / effectively CWE-94). Never compile a template from
|
|
1859
|
+
request data - only bind request data as template variables into a
|
|
1860
|
+
fixed, trusted template.
|
|
1861
|
+
severity: ERROR
|
|
1862
|
+
languages: [javascript, typescript]
|
|
1863
|
+
mode: taint
|
|
1864
|
+
metadata:
|
|
1865
|
+
cwe:
|
|
1866
|
+
- "CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine"
|
|
1867
|
+
owasp:
|
|
1868
|
+
- "A03:2021 - Injection"
|
|
1869
|
+
category: security
|
|
1870
|
+
subcategory: [vuln]
|
|
1871
|
+
technology: [ejs, handlebars, pug, lodash, mustache, javascript, typescript]
|
|
1872
|
+
likelihood: LOW
|
|
1873
|
+
impact: HIGH
|
|
1874
|
+
confidence: MEDIUM
|
|
1875
|
+
vulnerability_class:
|
|
1876
|
+
- Code Injection
|
|
1877
|
+
references:
|
|
1878
|
+
- https://cwe.mitre.org/data/definitions/1336.html
|
|
1879
|
+
- https://portswigger.net/web-security/server-side-template-injection
|
|
1880
|
+
pattern-sources:
|
|
1881
|
+
- pattern: req.query.$Q
|
|
1882
|
+
- pattern: req.params.$P
|
|
1883
|
+
- pattern: req.body.$B
|
|
1884
|
+
pattern-sinks:
|
|
1885
|
+
- pattern: ejs.render($SINK, ...)
|
|
1886
|
+
- pattern: ejs.compile($SINK, ...)
|
|
1887
|
+
- pattern: handlebars.compile($SINK, ...)
|
|
1888
|
+
- pattern: Handlebars.compile($SINK, ...)
|
|
1889
|
+
- pattern: pug.render($SINK, ...)
|
|
1890
|
+
- pattern: pug.compile($SINK, ...)
|
|
1891
|
+
- pattern: _.template($SINK)
|
|
1892
|
+
- pattern: Mustache.render($SINK, ...)
|
|
1893
|
+
|
|
1894
|
+
- id: narvy.javascript.ssti.res-render-view-name-from-request.res-render-view-name-from-request
|
|
1895
|
+
message: >-
|
|
1896
|
+
The view name passed to res.render is taken directly from a
|
|
1897
|
+
request field. An attacker who controls the view name can render
|
|
1898
|
+
views not meant to be reachable directly, and on engines/setups
|
|
1899
|
+
that resolve the name to a filesystem path this can be combined
|
|
1900
|
+
with path traversal to render arbitrary files as a template
|
|
1901
|
+
(CWE-73). Restrict the view name to a fixed allow-list instead of
|
|
1902
|
+
passing the request value through.
|
|
1903
|
+
severity: WARNING
|
|
1904
|
+
languages: [javascript, typescript]
|
|
1905
|
+
metadata:
|
|
1906
|
+
cwe:
|
|
1907
|
+
- "CWE-73: External Control of File Name or Path"
|
|
1908
|
+
owasp:
|
|
1909
|
+
- "A03:2021 - Injection"
|
|
1910
|
+
category: security
|
|
1911
|
+
subcategory: [vuln]
|
|
1912
|
+
technology: [express, nodejs, javascript, typescript]
|
|
1913
|
+
likelihood: LOW
|
|
1914
|
+
impact: MEDIUM
|
|
1915
|
+
confidence: MEDIUM
|
|
1916
|
+
vulnerability_class:
|
|
1917
|
+
- Code Injection
|
|
1918
|
+
patterns:
|
|
1919
|
+
- pattern-either:
|
|
1920
|
+
- pattern: $RES.render($REQ.$KIND.$X, ...)
|
|
1921
|
+
- pattern: |
|
|
1922
|
+
$V = $REQ.$KIND.$X
|
|
1923
|
+
...
|
|
1924
|
+
$RES.render($V, ...)
|
|
1925
|
+
- metavariable-regex:
|
|
1926
|
+
metavariable: $KIND
|
|
1927
|
+
regex: ^(query|params|body)$
|
|
1928
|
+
|
|
1929
|
+
# WEAK CRYPTO CIPHER
|
|
1930
|
+
|
|
1931
|
+
# Rule 1: weak cipher algorithm / ECB mode
|
|
1932
|
+
- id: narvy.javascript.crypto.weak-cipher-createcipheriv.weak-cipher-createcipheriv
|
|
1933
|
+
message: >-
|
|
1934
|
+
Weak or broken cipher algorithm passed to crypto.createCipheriv /
|
|
1935
|
+
createCipher. DES and RC4 are cryptographically broken; ECB mode
|
|
1936
|
+
leaks plaintext structure regardless of the underlying block cipher.
|
|
1937
|
+
Use AES-256-GCM (authenticated encryption) instead. CWE-327.
|
|
1938
|
+
severity: ERROR
|
|
1939
|
+
languages: [javascript, typescript]
|
|
1940
|
+
metadata:
|
|
1941
|
+
cwe:
|
|
1942
|
+
- "CWE-327: Use of a Broken or Risky Cryptographic Algorithm"
|
|
1943
|
+
- "CWE-329: Generation of Predictable IV with CBC Mode"
|
|
1944
|
+
owasp:
|
|
1945
|
+
- "A02:2021 - Cryptographic Failures"
|
|
1946
|
+
category: security
|
|
1947
|
+
subcategory: [vuln]
|
|
1948
|
+
technology: [node-crypto, javascript, typescript]
|
|
1949
|
+
likelihood: MEDIUM
|
|
1950
|
+
impact: HIGH
|
|
1951
|
+
confidence: HIGH
|
|
1952
|
+
vulnerability_class:
|
|
1953
|
+
- Cryptographic Issues
|
|
1954
|
+
patterns:
|
|
1955
|
+
- pattern-either:
|
|
1956
|
+
- pattern: crypto.createCipheriv("$ALGO", ...)
|
|
1957
|
+
- pattern: crypto.createCipheriv('$ALGO', ...)
|
|
1958
|
+
- pattern: crypto.createCipher("$ALGO", ...)
|
|
1959
|
+
- pattern: crypto.createCipher('$ALGO', ...)
|
|
1960
|
+
- pattern: $CRYPTO.createCipheriv("$ALGO", ...)
|
|
1961
|
+
- pattern: $CRYPTO.createCipheriv('$ALGO', ...)
|
|
1962
|
+
- metavariable-regex:
|
|
1963
|
+
metavariable: $ALGO
|
|
1964
|
+
regex: ^(?i).*(des|rc4|arcfour|-ecb|^ecb).*$
|
|
1965
|
+
|
|
1966
|
+
# Rule 1b: GCM decipher never finalized; without .final() tampered ciphertext decrypts.
|
|
1967
|
+
- id: narvy.javascript.crypto.gcm-decrypt-missing-final.gcm-decrypt-missing-final
|
|
1968
|
+
message: >-
|
|
1969
|
+
A GCM-mode decipher is created and fed ciphertext via .update(), but
|
|
1970
|
+
.final() is never called on it. In Node's crypto module the GCM
|
|
1971
|
+
authentication tag is only checked when .final() runs - it throws if
|
|
1972
|
+
the tag doesn't match the ciphertext. Skipping .final() means
|
|
1973
|
+
tampered ciphertext is silently accepted as if it were genuine
|
|
1974
|
+
(CWE-354). Always call .final() on a GCM decipher and let it throw.
|
|
1975
|
+
severity: ERROR
|
|
1976
|
+
languages: [javascript, typescript]
|
|
1977
|
+
metadata:
|
|
1978
|
+
cwe:
|
|
1979
|
+
- "CWE-354: Improper Validation of Integrity Check Value"
|
|
1980
|
+
owasp:
|
|
1981
|
+
- "A02:2021 - Cryptographic Failures"
|
|
1982
|
+
category: security
|
|
1983
|
+
subcategory: [vuln]
|
|
1984
|
+
technology: [node-crypto, javascript, typescript]
|
|
1985
|
+
likelihood: MEDIUM
|
|
1986
|
+
impact: HIGH
|
|
1987
|
+
confidence: MEDIUM
|
|
1988
|
+
vulnerability_class:
|
|
1989
|
+
- Cryptographic Issues
|
|
1990
|
+
patterns:
|
|
1991
|
+
- pattern: $CRYPTO.createDecipheriv("$ALGO", ...)
|
|
1992
|
+
- metavariable-regex:
|
|
1993
|
+
metavariable: $ALGO
|
|
1994
|
+
regex: (?i).*gcm.*
|
|
1995
|
+
- pattern-not-inside: |
|
|
1996
|
+
$DECIPHER = $CRYPTO.createDecipheriv("$ALGO", ...)
|
|
1997
|
+
...
|
|
1998
|
+
$DECIPHER.final(...)
|
|
1999
|
+
|
|
2000
|
+
# Rule 1c: deprecated createCipher/createDecipher derives a static IV from the key.
|
|
2001
|
+
- id: narvy.javascript.crypto.deprecated-cipher-static-iv.deprecated-cipher-static-iv
|
|
2002
|
+
message: >-
|
|
2003
|
+
crypto.createCipher()/createDecipher() is deprecated and derives the
|
|
2004
|
+
IV internally from the key material via an undocumented KDF instead
|
|
2005
|
+
of accepting an explicit, random IV. The same key therefore always
|
|
2006
|
+
produces the same derived IV, so encrypting the same plaintext twice
|
|
2007
|
+
with the same key yields the same ciphertext, and CBC-mode chosen-
|
|
2008
|
+
plaintext attacks become possible (CWE-329). Use createCipheriv() /
|
|
2009
|
+
createDecipheriv() with a fresh, cryptographically random IV per
|
|
2010
|
+
encryption instead.
|
|
2011
|
+
severity: ERROR
|
|
2012
|
+
languages: [javascript, typescript]
|
|
2013
|
+
metadata:
|
|
2014
|
+
cwe:
|
|
2015
|
+
- "CWE-329: Generation of Predictable IV with CBC Mode"
|
|
2016
|
+
- "CWE-330: Use of Insufficiently Random Values"
|
|
2017
|
+
owasp:
|
|
2018
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2019
|
+
category: security
|
|
2020
|
+
subcategory: [vuln]
|
|
2021
|
+
technology: [node-crypto, javascript, typescript]
|
|
2022
|
+
likelihood: MEDIUM
|
|
2023
|
+
impact: HIGH
|
|
2024
|
+
confidence: HIGH
|
|
2025
|
+
vulnerability_class:
|
|
2026
|
+
- Cryptographic Issues
|
|
2027
|
+
patterns:
|
|
2028
|
+
- pattern-either:
|
|
2029
|
+
- pattern: crypto.createCipher($ALGO, ...)
|
|
2030
|
+
- pattern: crypto.createDecipher($ALGO, ...)
|
|
2031
|
+
- pattern: $CRYPTO.createCipher($ALGO, ...)
|
|
2032
|
+
- pattern: $CRYPTO.createDecipher($ALGO, ...)
|
|
2033
|
+
|
|
2034
|
+
# Rule 1d: GCM auth tag from request data used without a length check.
|
|
2035
|
+
- id: narvy.javascript.crypto.gcm-tag-unvalidated-length.gcm-tag-unvalidated-length
|
|
2036
|
+
message: >-
|
|
2037
|
+
A GCM auth tag taken directly from request data is passed to
|
|
2038
|
+
setAuthTag() with no length check beforehand. GCM's forgery
|
|
2039
|
+
resistance depends on the full-length tag (16 bytes); an
|
|
2040
|
+
attacker-supplied truncated tag has a much higher chance of
|
|
2041
|
+
passing verification by luck, and some deployments accept
|
|
2042
|
+
whatever length is given (CWE-327). Reject the request unless the
|
|
2043
|
+
tag is exactly the expected length (normally 16 bytes) before
|
|
2044
|
+
calling setAuthTag().
|
|
2045
|
+
severity: WARNING
|
|
2046
|
+
languages: [javascript, typescript]
|
|
2047
|
+
metadata:
|
|
2048
|
+
cwe:
|
|
2049
|
+
- "CWE-327: Use of a Broken or Risky Cryptographic Algorithm"
|
|
2050
|
+
owasp:
|
|
2051
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2052
|
+
category: security
|
|
2053
|
+
subcategory: [vuln]
|
|
2054
|
+
technology: [node-crypto, javascript, typescript]
|
|
2055
|
+
likelihood: LOW
|
|
2056
|
+
impact: HIGH
|
|
2057
|
+
confidence: MEDIUM
|
|
2058
|
+
vulnerability_class:
|
|
2059
|
+
- Cryptographic Issues
|
|
2060
|
+
patterns:
|
|
2061
|
+
- pattern-either:
|
|
2062
|
+
- pattern: $DECIPHER.setAuthTag(Buffer.from($REQ.$KIND.$X, ...))
|
|
2063
|
+
- pattern: $DECIPHER.setAuthTag($REQ.$KIND.$X)
|
|
2064
|
+
- metavariable-regex:
|
|
2065
|
+
metavariable: $KIND
|
|
2066
|
+
regex: ^(query|params|body|headers|cookies)$
|
|
2067
|
+
- pattern-not-inside: |
|
|
2068
|
+
if (<... $LEN.length ...>) { ... }
|
|
2069
|
+
|
|
2070
|
+
# Rule 2: Math.random() feeding a token / secret / password variable
|
|
2071
|
+
- id: narvy.javascript.crypto.math-random-security-token.math-random-security-token
|
|
2072
|
+
message: >-
|
|
2073
|
+
`Math.random()` is assigned to a variable whose name indicates
|
|
2074
|
+
security-sensitive use (token, secret, password, otp, session, nonce,
|
|
2075
|
+
apikey, csrf). Math.random() is not cryptographically secure - its
|
|
2076
|
+
output is predictable from a handful of samples. Use
|
|
2077
|
+
`crypto.randomBytes()` / `crypto.randomUUID()` for anything
|
|
2078
|
+
security-relevant. CWE-338.
|
|
2079
|
+
severity: WARNING
|
|
2080
|
+
languages: [javascript, typescript]
|
|
2081
|
+
metadata:
|
|
2082
|
+
cwe:
|
|
2083
|
+
- "CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)"
|
|
2084
|
+
- "CWE-330: Use of Insufficiently Random Values"
|
|
2085
|
+
owasp:
|
|
2086
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2087
|
+
category: security
|
|
2088
|
+
subcategory: [audit]
|
|
2089
|
+
technology: [javascript, typescript]
|
|
2090
|
+
likelihood: MEDIUM
|
|
2091
|
+
impact: HIGH
|
|
2092
|
+
confidence: MEDIUM
|
|
2093
|
+
vulnerability_class:
|
|
2094
|
+
- Insufficient Entropy
|
|
2095
|
+
patterns:
|
|
2096
|
+
- pattern-either:
|
|
2097
|
+
- pattern: const $TOKEN = Math.random()
|
|
2098
|
+
- pattern: let $TOKEN = Math.random()
|
|
2099
|
+
- pattern: var $TOKEN = Math.random()
|
|
2100
|
+
- pattern: $TOKEN = Math.random()
|
|
2101
|
+
- metavariable-regex:
|
|
2102
|
+
metavariable: $TOKEN
|
|
2103
|
+
regex: (?i)(token|secret|password|passwd|pwd|otp|session|nonce|apikey|api_key|csrf|auth)
|
|
2104
|
+
|
|
2105
|
+
# Rule 3: undersized RSA key generation
|
|
2106
|
+
- id: narvy.javascript.crypto.weak-rsa-keysize.weak-rsa-keysize
|
|
2107
|
+
message: >-
|
|
2108
|
+
RSA key pair generated with `modulusLength` below 2048 bits. Keys
|
|
2109
|
+
under 2048 bits are considered breakable with modern compute and are
|
|
2110
|
+
rejected by NIST guidance for new key generation. Use at least 2048
|
|
2111
|
+
(prefer 3072/4096) for RSA, or switch to an elliptic-curve algorithm.
|
|
2112
|
+
CWE-326.
|
|
2113
|
+
severity: WARNING
|
|
2114
|
+
languages: [javascript, typescript]
|
|
2115
|
+
metadata:
|
|
2116
|
+
cwe:
|
|
2117
|
+
- "CWE-326: Inadequate Encryption Strength"
|
|
2118
|
+
owasp:
|
|
2119
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2120
|
+
category: security
|
|
2121
|
+
subcategory: [audit]
|
|
2122
|
+
technology: [node-crypto, javascript, typescript]
|
|
2123
|
+
likelihood: LOW
|
|
2124
|
+
impact: HIGH
|
|
2125
|
+
confidence: HIGH
|
|
2126
|
+
vulnerability_class:
|
|
2127
|
+
- Cryptographic Issues
|
|
2128
|
+
patterns:
|
|
2129
|
+
- pattern-either:
|
|
2130
|
+
- pattern: 'crypto.generateKeyPair("rsa", { modulusLength: $N, ... }, ...)'
|
|
2131
|
+
- pattern: 'crypto.generateKeyPairSync("rsa", { modulusLength: $N, ... }, ...)'
|
|
2132
|
+
- metavariable-comparison:
|
|
2133
|
+
metavariable: $N
|
|
2134
|
+
comparison: $N < 2048
|
|
2135
|
+
|
|
2136
|
+
# WEAK CRYPTO
|
|
2137
|
+
|
|
2138
|
+
# Rule 1a: Node crypto.createHash('md5' | 'sha1')
|
|
2139
|
+
- id: narvy.javascript.crypto.weak-hash-create-hash.weak-hash-create-hash
|
|
2140
|
+
message: >-
|
|
2141
|
+
Weak hash MD5/SHA1 used via crypto.createHash. Both algorithms are
|
|
2142
|
+
cryptographically broken (MD5 collision, SHA1 collision). Use SHA-256
|
|
2143
|
+
or stronger for integrity. For password hashing use scrypt, Argon2,
|
|
2144
|
+
or PBKDF2 with high iteration count. CWE-327.
|
|
2145
|
+
severity: ERROR
|
|
2146
|
+
languages: [javascript, typescript]
|
|
2147
|
+
metadata:
|
|
2148
|
+
cwe:
|
|
2149
|
+
- "CWE-327: Use of a Broken or Risky Cryptographic Algorithm"
|
|
2150
|
+
owasp:
|
|
2151
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2152
|
+
- "A03:2017 - Sensitive Data Exposure"
|
|
2153
|
+
category: security
|
|
2154
|
+
subcategory: [vuln]
|
|
2155
|
+
technology: [node-crypto, javascript, typescript]
|
|
2156
|
+
likelihood: HIGH
|
|
2157
|
+
impact: HIGH
|
|
2158
|
+
confidence: HIGH
|
|
2159
|
+
vulnerability_class:
|
|
2160
|
+
- Cryptographic Issues
|
|
2161
|
+
references:
|
|
2162
|
+
- https://cwe.mitre.org/data/definitions/327.html
|
|
2163
|
+
- https://tools.ietf.org/html/rfc6151
|
|
2164
|
+
patterns:
|
|
2165
|
+
- pattern-either:
|
|
2166
|
+
- pattern: crypto.createHash("$ALGO")
|
|
2167
|
+
- pattern: crypto.createHash('$ALGO')
|
|
2168
|
+
- pattern: $CRYPTO.createHash("$ALGO")
|
|
2169
|
+
- pattern: $CRYPTO.createHash('$ALGO')
|
|
2170
|
+
- metavariable-regex:
|
|
2171
|
+
metavariable: $ALGO
|
|
2172
|
+
regex: ^(?i)(md5|md4|md2|sha-?1)$
|
|
2173
|
+
|
|
2174
|
+
# Rule 1b: crypto-js MD5 / SHA1 module method
|
|
2175
|
+
- id: narvy.javascript.crypto.weak-hash-crypto-js.weak-hash-crypto-js
|
|
2176
|
+
message: >-
|
|
2177
|
+
Weak hash MD5/SHA1 used via crypto-js. Both algorithms are
|
|
2178
|
+
cryptographically broken. Use crypto-js SHA-256 / SHA-512 or
|
|
2179
|
+
stronger. CWE-327.
|
|
2180
|
+
severity: ERROR
|
|
2181
|
+
languages: [javascript, typescript]
|
|
2182
|
+
metadata:
|
|
2183
|
+
cwe:
|
|
2184
|
+
- "CWE-327: Use of a Broken or Risky Cryptographic Algorithm"
|
|
2185
|
+
owasp:
|
|
2186
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2187
|
+
category: security
|
|
2188
|
+
subcategory: [vuln]
|
|
2189
|
+
technology: [crypto-js, javascript, typescript]
|
|
2190
|
+
likelihood: HIGH
|
|
2191
|
+
impact: HIGH
|
|
2192
|
+
confidence: HIGH
|
|
2193
|
+
vulnerability_class:
|
|
2194
|
+
- Cryptographic Issues
|
|
2195
|
+
patterns:
|
|
2196
|
+
- pattern-either:
|
|
2197
|
+
- pattern: $CRYPTOJS.MD5(...)
|
|
2198
|
+
- pattern: $CRYPTOJS.SHA1(...)
|
|
2199
|
+
- pattern: $CRYPTOJS.HmacMD5(...)
|
|
2200
|
+
- pattern: $CRYPTOJS.HmacSHA1(...)
|
|
2201
|
+
|
|
2202
|
+
# Rule 1c: node-forge md5 / sha1
|
|
2203
|
+
- id: narvy.javascript.crypto.weak-hash-forge.weak-hash-forge
|
|
2204
|
+
message: >-
|
|
2205
|
+
Weak hash MD5/SHA1 used via node-forge. Both algorithms are
|
|
2206
|
+
cryptographically broken. Use forge.md.sha256.create() or stronger.
|
|
2207
|
+
CWE-327.
|
|
2208
|
+
severity: ERROR
|
|
2209
|
+
languages: [javascript, typescript]
|
|
2210
|
+
metadata:
|
|
2211
|
+
cwe:
|
|
2212
|
+
- "CWE-327: Use of a Broken or Risky Cryptographic Algorithm"
|
|
2213
|
+
owasp:
|
|
2214
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2215
|
+
category: security
|
|
2216
|
+
subcategory: [vuln]
|
|
2217
|
+
technology: [node-forge, javascript, typescript]
|
|
2218
|
+
likelihood: HIGH
|
|
2219
|
+
impact: HIGH
|
|
2220
|
+
confidence: HIGH
|
|
2221
|
+
vulnerability_class:
|
|
2222
|
+
- Cryptographic Issues
|
|
2223
|
+
patterns:
|
|
2224
|
+
- pattern-either:
|
|
2225
|
+
- pattern: $FORGE.md.md5.create()
|
|
2226
|
+
- pattern: $FORGE.md.sha1.create()
|
|
2227
|
+
- pattern: forge.md.md5.create()
|
|
2228
|
+
- pattern: forge.md.sha1.create()
|
|
2229
|
+
|
|
2230
|
+
# Rule 1d: Web Crypto subtle.digest with SHA-1
|
|
2231
|
+
- id: narvy.javascript.crypto.weak-hash-web-crypto.weak-hash-web-crypto
|
|
2232
|
+
message: >-
|
|
2233
|
+
Weak hash SHA-1 used via Web Crypto API (crypto.subtle.digest).
|
|
2234
|
+
SHA-1 is collision-broken. Use 'SHA-256' or 'SHA-512' instead.
|
|
2235
|
+
CWE-327.
|
|
2236
|
+
severity: ERROR
|
|
2237
|
+
languages: [javascript, typescript]
|
|
2238
|
+
metadata:
|
|
2239
|
+
cwe:
|
|
2240
|
+
- "CWE-327: Use of a Broken or Risky Cryptographic Algorithm"
|
|
2241
|
+
owasp:
|
|
2242
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2243
|
+
category: security
|
|
2244
|
+
subcategory: [vuln]
|
|
2245
|
+
technology: [web-crypto, javascript, typescript]
|
|
2246
|
+
likelihood: HIGH
|
|
2247
|
+
impact: HIGH
|
|
2248
|
+
confidence: HIGH
|
|
2249
|
+
vulnerability_class:
|
|
2250
|
+
- Cryptographic Issues
|
|
2251
|
+
patterns:
|
|
2252
|
+
- pattern-either:
|
|
2253
|
+
- pattern: $SUBTLE.digest("$ALGO", ...)
|
|
2254
|
+
- pattern: $SUBTLE.digest('$ALGO', ...)
|
|
2255
|
+
- metavariable-regex:
|
|
2256
|
+
metavariable: $ALGO
|
|
2257
|
+
regex: ^(?i)(sha-?1|md5)$
|
|
2258
|
+
|
|
2259
|
+
# WEAK TLS
|
|
2260
|
+
|
|
2261
|
+
- id: narvy.javascript.tls.reject-unauthorized-disabled.reject-unauthorized-disabled
|
|
2262
|
+
message: >-
|
|
2263
|
+
TLS certificate validation is disabled via `rejectUnauthorized:
|
|
2264
|
+
false`. This accepts ANY certificate, including a self-signed one
|
|
2265
|
+
presented by an attacker performing a man-in-the-middle attack,
|
|
2266
|
+
defeating the entire purpose of TLS (CWE-295). Remove this option;
|
|
2267
|
+
if a private/internal CA is in use, add its certificate to the
|
|
2268
|
+
trust store (the `ca` option) instead of disabling verification.
|
|
2269
|
+
severity: ERROR
|
|
2270
|
+
languages: [javascript, typescript]
|
|
2271
|
+
metadata:
|
|
2272
|
+
cwe:
|
|
2273
|
+
- "CWE-295: Improper Certificate Validation"
|
|
2274
|
+
owasp:
|
|
2275
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2276
|
+
category: security
|
|
2277
|
+
subcategory: [vuln]
|
|
2278
|
+
technology: [nodejs, javascript, typescript]
|
|
2279
|
+
likelihood: MEDIUM
|
|
2280
|
+
impact: HIGH
|
|
2281
|
+
confidence: HIGH
|
|
2282
|
+
vulnerability_class:
|
|
2283
|
+
- Cryptographic Issues
|
|
2284
|
+
references:
|
|
2285
|
+
- https://cwe.mitre.org/data/definitions/295.html
|
|
2286
|
+
paths:
|
|
2287
|
+
exclude:
|
|
2288
|
+
- "**/test/**"
|
|
2289
|
+
- "**/tests/**"
|
|
2290
|
+
- "**/__tests__/**"
|
|
2291
|
+
- "**/*.test.js"
|
|
2292
|
+
- "**/*.test.ts"
|
|
2293
|
+
patterns:
|
|
2294
|
+
- pattern-either:
|
|
2295
|
+
- pattern: |
|
|
2296
|
+
{..., rejectUnauthorized: false, ...}
|
|
2297
|
+
- pattern: 'new https.Agent({..., rejectUnauthorized: false, ...})'
|
|
2298
|
+
- pattern: 'new tls.TLSSocket(..., {..., rejectUnauthorized: false, ...})'
|
|
2299
|
+
|
|
2300
|
+
- id: narvy.javascript.tls.node-tls-reject-unauthorized-env-disabled.node-tls-reject-unauthorized-env-disabled
|
|
2301
|
+
message: >-
|
|
2302
|
+
NODE_TLS_REJECT_UNAUTHORIZED is set to '0' at runtime. This
|
|
2303
|
+
disables certificate validation for EVERY TLS connection made by
|
|
2304
|
+
the Node process for as long as this remains set, not just one
|
|
2305
|
+
request (CWE-295). Remove this and fix the underlying certificate
|
|
2306
|
+
problem (add the CA to the trust store, or fix the hostname/cert
|
|
2307
|
+
mismatch) instead of disabling verification process-wide.
|
|
2308
|
+
severity: ERROR
|
|
2309
|
+
languages: [javascript, typescript]
|
|
2310
|
+
metadata:
|
|
2311
|
+
cwe:
|
|
2312
|
+
- "CWE-295: Improper Certificate Validation"
|
|
2313
|
+
owasp:
|
|
2314
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2315
|
+
category: security
|
|
2316
|
+
subcategory: [vuln]
|
|
2317
|
+
technology: [nodejs, javascript, typescript]
|
|
2318
|
+
likelihood: MEDIUM
|
|
2319
|
+
impact: HIGH
|
|
2320
|
+
confidence: HIGH
|
|
2321
|
+
vulnerability_class:
|
|
2322
|
+
- Cryptographic Issues
|
|
2323
|
+
patterns:
|
|
2324
|
+
- pattern-either:
|
|
2325
|
+
- pattern: process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
|
|
2326
|
+
- pattern: process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
2327
|
+
- pattern: process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0
|
|
2328
|
+
|
|
2329
|
+
- id: narvy.javascript.tls.min-version-pinned-obsolete.min-version-pinned-obsolete
|
|
2330
|
+
message: >-
|
|
2331
|
+
TLS minVersion (or secureProtocol) is pinned to an obsolete
|
|
2332
|
+
protocol (SSLv3 or TLSv1/TLSv1.0/TLSv1.1). These protocol versions
|
|
2333
|
+
have known cryptographic weaknesses (POODLE, BEAST) and are
|
|
2334
|
+
disallowed by modern compliance baselines (CWE-327). Set minVersion
|
|
2335
|
+
to 'TLSv1.2' or 'TLSv1.3'.
|
|
2336
|
+
severity: WARNING
|
|
2337
|
+
languages: [javascript, typescript]
|
|
2338
|
+
metadata:
|
|
2339
|
+
cwe:
|
|
2340
|
+
- "CWE-327: Use of a Broken or Risky Cryptographic Algorithm"
|
|
2341
|
+
owasp:
|
|
2342
|
+
- "A02:2021 - Cryptographic Failures"
|
|
2343
|
+
category: security
|
|
2344
|
+
subcategory: [vuln]
|
|
2345
|
+
technology: [nodejs, javascript, typescript]
|
|
2346
|
+
likelihood: LOW
|
|
2347
|
+
impact: MEDIUM
|
|
2348
|
+
confidence: HIGH
|
|
2349
|
+
vulnerability_class:
|
|
2350
|
+
- Cryptographic Issues
|
|
2351
|
+
patterns:
|
|
2352
|
+
- pattern-either:
|
|
2353
|
+
- pattern: |
|
|
2354
|
+
{..., minVersion: "$V", ...}
|
|
2355
|
+
- pattern: |
|
|
2356
|
+
{..., secureProtocol: "$V", ...}
|
|
2357
|
+
- metavariable-regex:
|
|
2358
|
+
metavariable: $V
|
|
2359
|
+
regex: (?i)^(SSLv2|SSLv3|SSLv23_method|TLSv1|TLSv1_method|TLSv1\.0|TLSv1\.1|TLSv1_1_method)$
|
|
2360
|
+
|
|
2361
|
+
# XSS DOM
|
|
2362
|
+
|
|
2363
|
+
# Rule 1: innerHTML / outerHTML assignment
|
|
2364
|
+
- id: narvy.javascript.xss.dom-innerhtml-assign.dom-innerhtml-assign
|
|
2365
|
+
message: >-
|
|
2366
|
+
Assignment to `.innerHTML` / `.outerHTML` with a non-literal value.
|
|
2367
|
+
The browser parses this value as HTML, so if it contains
|
|
2368
|
+
attacker-controlled data this is DOM-based XSS (CWE-79). Use
|
|
2369
|
+
`.textContent` for plain text, or sanitize with DOMPurify before
|
|
2370
|
+
assigning.
|
|
2371
|
+
severity: ERROR
|
|
2372
|
+
languages: [javascript, typescript]
|
|
2373
|
+
metadata:
|
|
2374
|
+
cwe:
|
|
2375
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
2376
|
+
owasp:
|
|
2377
|
+
- "A03:2021 - Injection"
|
|
2378
|
+
category: security
|
|
2379
|
+
subcategory: [vuln]
|
|
2380
|
+
technology: [javascript, typescript, dom]
|
|
2381
|
+
likelihood: MEDIUM
|
|
2382
|
+
impact: HIGH
|
|
2383
|
+
confidence: MEDIUM
|
|
2384
|
+
vulnerability_class:
|
|
2385
|
+
- Cross-Site-Scripting (XSS)
|
|
2386
|
+
patterns:
|
|
2387
|
+
- pattern-either:
|
|
2388
|
+
- pattern: $EL.innerHTML = $X
|
|
2389
|
+
- pattern: $EL.outerHTML = $X
|
|
2390
|
+
- pattern-not: $EL.innerHTML = "..."
|
|
2391
|
+
- pattern-not: $EL.outerHTML = "..."
|
|
2392
|
+
- pattern-not: $EL.innerHTML = `...`
|
|
2393
|
+
- pattern-not: $EL.outerHTML = `...`
|
|
2394
|
+
- pattern-not: $EL.innerHTML = ""
|
|
2395
|
+
- pattern-not: $EL.innerHTML = $SANITIZER.sanitize(...)
|
|
2396
|
+
- pattern-not: $EL.outerHTML = $SANITIZER.sanitize(...)
|
|
2397
|
+
# Exclude sanitizer-named calls and SCREAMING_SNAKE_CASE markup constants (naming heuristic, RE2-safe).
|
|
2398
|
+
- pattern-not:
|
|
2399
|
+
patterns:
|
|
2400
|
+
- pattern-either:
|
|
2401
|
+
- pattern: $EL.innerHTML = $SAN(...)
|
|
2402
|
+
- pattern: $EL.outerHTML = $SAN(...)
|
|
2403
|
+
- pattern: $EL.innerHTML = await $SAN(...)
|
|
2404
|
+
- pattern: $EL.outerHTML = await $SAN(...)
|
|
2405
|
+
- metavariable-regex:
|
|
2406
|
+
metavariable: $SAN
|
|
2407
|
+
regex: (?i).*(saniti[sz]|purif|dompurify|xss[_-]?clean|clean[_-]?html|html[_-]?clean|escape[_-]?html|html[_-]?escape|encode[_-]?html|html[_-]?encode|safe[_-]?html|html[_-]?safe).*
|
|
2408
|
+
# -- sanitizer-named method call (optionally awaited).
|
|
2409
|
+
- pattern-not:
|
|
2410
|
+
patterns:
|
|
2411
|
+
- pattern-either:
|
|
2412
|
+
- pattern: $EL.innerHTML = $O.$SAN(...)
|
|
2413
|
+
- pattern: $EL.outerHTML = $O.$SAN(...)
|
|
2414
|
+
- pattern: $EL.innerHTML = await $O.$SAN(...)
|
|
2415
|
+
- pattern: $EL.outerHTML = await $O.$SAN(...)
|
|
2416
|
+
- metavariable-regex:
|
|
2417
|
+
metavariable: $SAN
|
|
2418
|
+
regex: (?i).*(saniti[sz]|purif|xss[_-]?clean|clean[_-]?html|html[_-]?clean|escape[_-]?html|html[_-]?escape|encode[_-]?html|html[_-]?encode|safe[_-]?html|html[_-]?safe).*
|
|
2419
|
+
# -- module CONSTANT of static markup (SCREAMING_SNAKE_CASE identifier).
|
|
2420
|
+
- pattern-not:
|
|
2421
|
+
patterns:
|
|
2422
|
+
- pattern-either:
|
|
2423
|
+
- pattern: $EL.innerHTML = $CONST
|
|
2424
|
+
- pattern: $EL.outerHTML = $CONST
|
|
2425
|
+
- metavariable-regex:
|
|
2426
|
+
metavariable: $CONST
|
|
2427
|
+
regex: ^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$
|
|
2428
|
+
# Exclude static icon/SVG constants and no-input builders (icon/SVG naming) so real data flow still fires.
|
|
2429
|
+
- pattern-not:
|
|
2430
|
+
patterns:
|
|
2431
|
+
- pattern-either:
|
|
2432
|
+
- pattern: $EL.innerHTML = $ICONS.$ICO
|
|
2433
|
+
- pattern: $EL.outerHTML = $ICONS.$ICO
|
|
2434
|
+
- metavariable-regex:
|
|
2435
|
+
metavariable: $ICONS
|
|
2436
|
+
regex: ^(IC|ICO|ICON|ICONS|Icon|Icons|SVG|SVGS|Svg|Svgs|GLYPH|GLYPHS|Glyph|Glyphs)$
|
|
2437
|
+
# Static icon constant concatenated with a string literal:
|
|
2438
|
+
# `tag.innerHTML = IC.crown + ' Owner'` (either operand order).
|
|
2439
|
+
- pattern-not:
|
|
2440
|
+
patterns:
|
|
2441
|
+
- pattern-either:
|
|
2442
|
+
- pattern: $EL.innerHTML = $ICONS.$ICO + "..."
|
|
2443
|
+
- pattern: $EL.innerHTML = "..." + $ICONS.$ICO
|
|
2444
|
+
- pattern: $EL.outerHTML = $ICONS.$ICO + "..."
|
|
2445
|
+
- pattern: $EL.outerHTML = "..." + $ICONS.$ICO
|
|
2446
|
+
- metavariable-regex:
|
|
2447
|
+
metavariable: $ICONS
|
|
2448
|
+
regex: ^(IC|ICO|ICON|ICONS|Icon|Icons|SVG|SVGS|Svg|Svgs|GLYPH|GLYPHS|Glyph|Glyphs)$
|
|
2449
|
+
# Ternary between two icon constants of the SAME registry object:
|
|
2450
|
+
# `btn.innerHTML = muted ? IC.micOff : IC.mic`.
|
|
2451
|
+
- pattern-not:
|
|
2452
|
+
patterns:
|
|
2453
|
+
- pattern-either:
|
|
2454
|
+
- pattern: "$EL.innerHTML = $C ? $ICONS.$A : $ICONS.$B"
|
|
2455
|
+
- pattern: "$EL.outerHTML = $C ? $ICONS.$A : $ICONS.$B"
|
|
2456
|
+
- metavariable-regex:
|
|
2457
|
+
metavariable: $ICONS
|
|
2458
|
+
regex: ^(IC|ICO|ICON|ICONS|Icon|Icons|SVG|SVGS|Svg|Svgs|GLYPH|GLYPHS|Glyph|Glyphs)$
|
|
2459
|
+
# No-input static icon/SVG builder call, gated on the method name.
|
|
2460
|
+
- pattern-not:
|
|
2461
|
+
patterns:
|
|
2462
|
+
- pattern-either:
|
|
2463
|
+
- pattern: $EL.innerHTML = $O.$M(...)
|
|
2464
|
+
- pattern: $EL.outerHTML = $O.$M(...)
|
|
2465
|
+
- pattern: $EL.innerHTML = $M(...)
|
|
2466
|
+
- pattern: $EL.outerHTML = $M(...)
|
|
2467
|
+
- metavariable-regex:
|
|
2468
|
+
metavariable: $M
|
|
2469
|
+
regex: (?i).*(icon|svg|glyph|chevron|arrow|caret|spinner).*
|
|
2470
|
+
|
|
2471
|
+
# Rule 2: insertAdjacentHTML
|
|
2472
|
+
- id: narvy.javascript.xss.insert-adjacent-html.insert-adjacent-html
|
|
2473
|
+
message: >-
|
|
2474
|
+
`insertAdjacentHTML()` with a non-literal HTML fragment. Like
|
|
2475
|
+
`innerHTML`, this parses the string as HTML markup. If the fragment
|
|
2476
|
+
contains attacker-controlled data this is DOM-based XSS (CWE-79).
|
|
2477
|
+
Sanitize with DOMPurify first, or build the DOM with `createElement`
|
|
2478
|
+
/ `textContent` instead.
|
|
2479
|
+
severity: ERROR
|
|
2480
|
+
languages: [javascript, typescript]
|
|
2481
|
+
metadata:
|
|
2482
|
+
cwe:
|
|
2483
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
2484
|
+
owasp:
|
|
2485
|
+
- "A03:2021 - Injection"
|
|
2486
|
+
category: security
|
|
2487
|
+
subcategory: [vuln]
|
|
2488
|
+
technology: [javascript, typescript, dom]
|
|
2489
|
+
likelihood: MEDIUM
|
|
2490
|
+
impact: HIGH
|
|
2491
|
+
confidence: MEDIUM
|
|
2492
|
+
vulnerability_class:
|
|
2493
|
+
- Cross-Site-Scripting (XSS)
|
|
2494
|
+
patterns:
|
|
2495
|
+
- pattern: $EL.insertAdjacentHTML($POS, $X)
|
|
2496
|
+
- pattern-not: $EL.insertAdjacentHTML($POS, "...")
|
|
2497
|
+
- pattern-not: $EL.insertAdjacentHTML($POS, `...`)
|
|
2498
|
+
- pattern-not: $EL.insertAdjacentHTML($POS, $SANITIZER.sanitize(...))
|
|
2499
|
+
|
|
2500
|
+
# Rule 3: document.write / document.writeln
|
|
2501
|
+
- id: narvy.javascript.xss.document-write.document-write
|
|
2502
|
+
message: >-
|
|
2503
|
+
`document.write()` / `document.writeln()` with a non-literal argument.
|
|
2504
|
+
The string is parsed and injected directly into the page HTML stream.
|
|
2505
|
+
If it contains attacker-controlled data this is DOM-based XSS
|
|
2506
|
+
(CWE-79). Avoid `document.write`; use DOM APIs with `.textContent`,
|
|
2507
|
+
or sanitize the content first.
|
|
2508
|
+
severity: ERROR
|
|
2509
|
+
languages: [javascript, typescript]
|
|
2510
|
+
metadata:
|
|
2511
|
+
cwe:
|
|
2512
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
2513
|
+
owasp:
|
|
2514
|
+
- "A03:2021 - Injection"
|
|
2515
|
+
category: security
|
|
2516
|
+
subcategory: [vuln]
|
|
2517
|
+
technology: [javascript, typescript, dom]
|
|
2518
|
+
likelihood: MEDIUM
|
|
2519
|
+
impact: HIGH
|
|
2520
|
+
confidence: MEDIUM
|
|
2521
|
+
vulnerability_class:
|
|
2522
|
+
- Cross-Site-Scripting (XSS)
|
|
2523
|
+
patterns:
|
|
2524
|
+
- pattern-either:
|
|
2525
|
+
- pattern: document.write($X)
|
|
2526
|
+
- pattern: document.writeln($X)
|
|
2527
|
+
- pattern-not: document.write("...")
|
|
2528
|
+
- pattern-not: document.writeln("...")
|
|
2529
|
+
- pattern-not: document.write(`...`)
|
|
2530
|
+
- pattern-not: document.writeln(`...`)
|
|
2531
|
+
|
|
2532
|
+
# Rule 4: jQuery .html()
|
|
2533
|
+
- id: narvy.javascript.xss.jquery-html.jquery-html
|
|
2534
|
+
message: >-
|
|
2535
|
+
jQuery `.html()` called with a non-literal argument. Unlike `.text()`,
|
|
2536
|
+
`.html()` parses its argument as markup. If the value is
|
|
2537
|
+
attacker-controlled this is DOM-based XSS (CWE-79). Use `.text()` for
|
|
2538
|
+
plain text, or sanitize with DOMPurify before passing to `.html()`.
|
|
2539
|
+
severity: WARNING
|
|
2540
|
+
languages: [javascript, typescript]
|
|
2541
|
+
metadata:
|
|
2542
|
+
cwe:
|
|
2543
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
2544
|
+
owasp:
|
|
2545
|
+
- "A03:2021 - Injection"
|
|
2546
|
+
category: security
|
|
2547
|
+
subcategory: [vuln]
|
|
2548
|
+
technology: [javascript, typescript, jquery]
|
|
2549
|
+
likelihood: MEDIUM
|
|
2550
|
+
impact: MEDIUM
|
|
2551
|
+
confidence: MEDIUM
|
|
2552
|
+
vulnerability_class:
|
|
2553
|
+
- Cross-Site-Scripting (XSS)
|
|
2554
|
+
patterns:
|
|
2555
|
+
- pattern-either:
|
|
2556
|
+
- pattern: $(...).html($X)
|
|
2557
|
+
- pattern: $EL.html($X)
|
|
2558
|
+
- pattern-not: $(...).html("...")
|
|
2559
|
+
- pattern-not: $EL.html("...")
|
|
2560
|
+
- pattern-not: $(...).html()
|
|
2561
|
+
- pattern-not: $EL.html()
|
|
2562
|
+
- pattern-not: $EL.html($SANITIZER.sanitize(...))
|
|
2563
|
+
- metavariable-pattern:
|
|
2564
|
+
metavariable: $X
|
|
2565
|
+
patterns:
|
|
2566
|
+
- pattern-not: "..."
|
|
2567
|
+
|
|
2568
|
+
# Rule 5: React dangerouslySetInnerHTML without a recognized sanitizer
|
|
2569
|
+
- id: narvy.javascript.xss.react-dangerously-set-innerhtml.react-dangerously-set-innerhtml
|
|
2570
|
+
message: >-
|
|
2571
|
+
`dangerouslySetInnerHTML` is set to a value that does not come from a
|
|
2572
|
+
recognized sanitizer call (DOMPurify.sanitize / sanitizeHtml). React
|
|
2573
|
+
does not escape this value; it is injected as raw HTML. If it
|
|
2574
|
+
contains attacker-controlled data this is stored/reflected XSS
|
|
2575
|
+
(CWE-79). Wrap the value with `DOMPurify.sanitize(...)` before use.
|
|
2576
|
+
severity: ERROR
|
|
2577
|
+
languages: [javascript, typescript]
|
|
2578
|
+
metadata:
|
|
2579
|
+
cwe:
|
|
2580
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
2581
|
+
owasp:
|
|
2582
|
+
- "A03:2021 - Injection"
|
|
2583
|
+
category: security
|
|
2584
|
+
subcategory: [vuln]
|
|
2585
|
+
technology: [javascript, typescript, react]
|
|
2586
|
+
likelihood: MEDIUM
|
|
2587
|
+
impact: HIGH
|
|
2588
|
+
confidence: MEDIUM
|
|
2589
|
+
vulnerability_class:
|
|
2590
|
+
- Cross-Site-Scripting (XSS)
|
|
2591
|
+
patterns:
|
|
2592
|
+
- pattern: '<$EL dangerouslySetInnerHTML={{__html: $X}} />'
|
|
2593
|
+
- pattern-not: '<$EL dangerouslySetInnerHTML={{__html: "..."}} />'
|
|
2594
|
+
- pattern-not: '<$EL dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(...)}} />'
|
|
2595
|
+
- pattern-not: '<$EL dangerouslySetInnerHTML={{__html: $ANY.sanitize(...)}} />'
|
|
2596
|
+
- pattern-not: '<$EL dangerouslySetInnerHTML={{__html: sanitizeHtml(...)}} />'
|
|
2597
|
+
|
|
2598
|
+
# XSS
|
|
2599
|
+
|
|
2600
|
+
# Reflected request value written into an HTML response (taint); focus $SINK, HTML-context regex; res.json/res.render excluded.
|
|
2601
|
+
- id: narvy.javascript.xss.reflected-request-value-in-html-response.reflected-request-value-in-html-response
|
|
2602
|
+
message: >-
|
|
2603
|
+
A request value (query/params/body/headers/cookies) reaches an HTML
|
|
2604
|
+
string passed to res.send/res.write/res.end with no escaping. Reflected
|
|
2605
|
+
Cross-Site Scripting (CWE-79). Escape the value for HTML context (a
|
|
2606
|
+
templating engine's auto-escaping, or a helper like `escape-html`)
|
|
2607
|
+
before writing it into the response body.
|
|
2608
|
+
severity: ERROR
|
|
2609
|
+
languages: [javascript, typescript]
|
|
2610
|
+
mode: taint
|
|
2611
|
+
metadata:
|
|
2612
|
+
cwe:
|
|
2613
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
2614
|
+
owasp:
|
|
2615
|
+
- "A03:2021 - Injection"
|
|
2616
|
+
category: security
|
|
2617
|
+
subcategory: [vuln]
|
|
2618
|
+
technology: [express, nodejs, javascript, typescript]
|
|
2619
|
+
likelihood: MEDIUM
|
|
2620
|
+
impact: HIGH
|
|
2621
|
+
confidence: MEDIUM
|
|
2622
|
+
vulnerability_class:
|
|
2623
|
+
- Cross-Site-Scripting (XSS)
|
|
2624
|
+
references:
|
|
2625
|
+
- https://cwe.mitre.org/data/definitions/79.html
|
|
2626
|
+
pattern-sources:
|
|
2627
|
+
- patterns:
|
|
2628
|
+
- pattern-either:
|
|
2629
|
+
- pattern: $REQ.body
|
|
2630
|
+
- pattern: $REQ.query
|
|
2631
|
+
- pattern: $REQ.params
|
|
2632
|
+
- pattern: $REQ.cookies
|
|
2633
|
+
- metavariable-regex:
|
|
2634
|
+
metavariable: $REQ
|
|
2635
|
+
regex: ^(req|request|_req|ctx|httpRequest|httpReq)$
|
|
2636
|
+
- patterns:
|
|
2637
|
+
- pattern: $REQ.$KIND.$X
|
|
2638
|
+
- metavariable-regex:
|
|
2639
|
+
metavariable: $KIND
|
|
2640
|
+
regex: ^(body|params|query|headers|cookies)$
|
|
2641
|
+
pattern-sanitizers:
|
|
2642
|
+
- pattern: escapeHtml(...)
|
|
2643
|
+
- pattern: escapeHTML(...)
|
|
2644
|
+
- pattern: escape(...)
|
|
2645
|
+
- pattern: $M.escapeHtml(...)
|
|
2646
|
+
- pattern: $M.escapeHTML(...)
|
|
2647
|
+
- pattern: $M.escape(...)
|
|
2648
|
+
- pattern: sanitizeHtml(...)
|
|
2649
|
+
- pattern: $M.sanitize(...)
|
|
2650
|
+
- pattern: $M.sanitizeHtml(...)
|
|
2651
|
+
- pattern: encodeURIComponent(...)
|
|
2652
|
+
- pattern: encodeURI(...)
|
|
2653
|
+
- pattern: parseInt(...)
|
|
2654
|
+
- pattern: Number(...)
|
|
2655
|
+
# JSON.stringify output is used for embedding data, not raw markup.
|
|
2656
|
+
- pattern: JSON.stringify(...)
|
|
2657
|
+
pattern-sinks:
|
|
2658
|
+
- patterns:
|
|
2659
|
+
- pattern-either:
|
|
2660
|
+
- pattern: $RES.send($SINK, ...)
|
|
2661
|
+
- pattern: $RES.write($SINK, ...)
|
|
2662
|
+
- pattern: $RES.end($SINK, ...)
|
|
2663
|
+
- focus-metavariable: $SINK
|
|
2664
|
+
- metavariable-regex:
|
|
2665
|
+
metavariable: $SINK
|
|
2666
|
+
regex: '(?s).*<\s*[a-zA-Z!/].*'
|
|
2667
|
+
paths:
|
|
2668
|
+
exclude:
|
|
2669
|
+
- "**/test/**"
|
|
2670
|
+
- "**/tests/**"
|
|
2671
|
+
- "**/__tests__/**"
|
|
2672
|
+
- "**/*.test.js"
|
|
2673
|
+
- "**/*.test.ts"
|
|
2674
|
+
|
|
2675
|
+
# Rule 6b: reflected value built into a variable then written to response; requires request value + HTML markup in one RHS.
|
|
2676
|
+
- id: narvy.javascript.xss.reflected-request-value-in-html-response-var-indirect.reflected-request-value-in-html-response-var-indirect
|
|
2677
|
+
message: >-
|
|
2678
|
+
A request value (query/params/body/headers/cookies) is concatenated /
|
|
2679
|
+
interpolated into an HTML string held in a variable and then written
|
|
2680
|
+
to the response via res.send/res.write/res.end. Reflected Cross-Site
|
|
2681
|
+
Scripting (CWE-79), with one level of variable indirection. Escape the
|
|
2682
|
+
value for the HTML context before writing it into the response.
|
|
2683
|
+
severity: ERROR
|
|
2684
|
+
languages: [javascript, typescript]
|
|
2685
|
+
metadata:
|
|
2686
|
+
cwe:
|
|
2687
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
2688
|
+
owasp:
|
|
2689
|
+
- "A03:2021 - Injection"
|
|
2690
|
+
category: security
|
|
2691
|
+
subcategory: [vuln]
|
|
2692
|
+
technology: [express, nodejs, javascript, typescript]
|
|
2693
|
+
likelihood: MEDIUM
|
|
2694
|
+
impact: HIGH
|
|
2695
|
+
confidence: MEDIUM
|
|
2696
|
+
vulnerability_class:
|
|
2697
|
+
- Cross-Site-Scripting (XSS)
|
|
2698
|
+
references:
|
|
2699
|
+
- https://cwe.mitre.org/data/definitions/79.html
|
|
2700
|
+
patterns:
|
|
2701
|
+
- pattern-either:
|
|
2702
|
+
# concatenation carrying request data + an HTML-markup string
|
|
2703
|
+
- patterns:
|
|
2704
|
+
- pattern: |
|
|
2705
|
+
$H = <... $REQ.$KIND.$X ...>
|
|
2706
|
+
...
|
|
2707
|
+
$RES.$M($H, ...)
|
|
2708
|
+
- pattern: |
|
|
2709
|
+
$H = <... "$MARKUP" ...>
|
|
2710
|
+
...
|
|
2711
|
+
$RES.$M($H, ...)
|
|
2712
|
+
- metavariable-regex:
|
|
2713
|
+
metavariable: $MARKUP
|
|
2714
|
+
regex: '(?s).*<\s*[a-zA-Z!/].*'
|
|
2715
|
+
# template literal carrying request data, markup in its text
|
|
2716
|
+
- patterns:
|
|
2717
|
+
- pattern: |
|
|
2718
|
+
$H = `...${$REQ.$KIND.$X}...`
|
|
2719
|
+
...
|
|
2720
|
+
$RES.$M($H, ...)
|
|
2721
|
+
- pattern: |
|
|
2722
|
+
$H = $TMPL
|
|
2723
|
+
...
|
|
2724
|
+
$RES.$M($H, ...)
|
|
2725
|
+
- metavariable-regex:
|
|
2726
|
+
metavariable: $TMPL
|
|
2727
|
+
regex: '(?s).*<\s*[a-zA-Z!/].*'
|
|
2728
|
+
- metavariable-regex:
|
|
2729
|
+
metavariable: $KIND
|
|
2730
|
+
regex: ^(body|params|query|headers|cookies)$
|
|
2731
|
+
- metavariable-regex:
|
|
2732
|
+
metavariable: $REQ
|
|
2733
|
+
regex: ^(req|request|_req|ctx|httpRequest|httpReq)$
|
|
2734
|
+
- metavariable-regex:
|
|
2735
|
+
metavariable: $M
|
|
2736
|
+
regex: ^(send|write|end)$
|
|
2737
|
+
paths:
|
|
2738
|
+
exclude:
|
|
2739
|
+
- "**/test/**"
|
|
2740
|
+
- "**/tests/**"
|
|
2741
|
+
- "**/__tests__/**"
|
|
2742
|
+
- "**/*.test.js"
|
|
2743
|
+
- "**/*.test.ts"
|
|
2744
|
+
|
|
2745
|
+
# Rule 7: Angular DomSanitizer.
|
|
2746
|
+
# bypassSecurityTrust*() with a non-literal argument.
|
|
2747
|
+
- id: narvy.javascript.xss.angular-bypass-security-trust.angular-bypass-security-trust
|
|
2748
|
+
message: >-
|
|
2749
|
+
Angular's DomSanitizer.bypassSecurityTrust*() is called with a
|
|
2750
|
+
non-literal value. These methods exist specifically to turn OFF
|
|
2751
|
+
Angular's built-in sanitization for the value they wrap - marking
|
|
2752
|
+
attacker-influenced data as "trusted" reintroduces DOM-based XSS
|
|
2753
|
+
that Angular would otherwise have blocked (CWE-79). Sanitize the
|
|
2754
|
+
value yourself first (or better, avoid needing to bypass
|
|
2755
|
+
sanitization at all) before wrapping it.
|
|
2756
|
+
severity: ERROR
|
|
2757
|
+
languages: [javascript, typescript]
|
|
2758
|
+
metadata:
|
|
2759
|
+
cwe:
|
|
2760
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
2761
|
+
owasp:
|
|
2762
|
+
- "A03:2021 - Injection"
|
|
2763
|
+
category: security
|
|
2764
|
+
subcategory: [vuln]
|
|
2765
|
+
technology: [angular, javascript, typescript]
|
|
2766
|
+
likelihood: MEDIUM
|
|
2767
|
+
impact: HIGH
|
|
2768
|
+
confidence: MEDIUM
|
|
2769
|
+
vulnerability_class:
|
|
2770
|
+
- Cross-Site-Scripting (XSS)
|
|
2771
|
+
patterns:
|
|
2772
|
+
- pattern-either:
|
|
2773
|
+
- pattern: $SANITIZER.bypassSecurityTrustHtml($X)
|
|
2774
|
+
- pattern: $SANITIZER.bypassSecurityTrustUrl($X)
|
|
2775
|
+
- pattern: $SANITIZER.bypassSecurityTrustResourceUrl($X)
|
|
2776
|
+
- pattern: $SANITIZER.bypassSecurityTrustScript($X)
|
|
2777
|
+
- pattern: $SANITIZER.bypassSecurityTrustStyle($X)
|
|
2778
|
+
- pattern-not: $SANITIZER.bypassSecurityTrustHtml("...")
|
|
2779
|
+
- pattern-not: $SANITIZER.bypassSecurityTrustUrl("...")
|
|
2780
|
+
- pattern-not: $SANITIZER.bypassSecurityTrustResourceUrl("...")
|
|
2781
|
+
- pattern-not: $SANITIZER.bypassSecurityTrustScript("...")
|
|
2782
|
+
- pattern-not: $SANITIZER.bypassSecurityTrustStyle("...")
|
|
2783
|
+
- pattern-not: $SANITIZER.bypassSecurityTrustHtml(`...`)
|
|
2784
|
+
- pattern-not: $SANITIZER.bypassSecurityTrustUrl(`...`)
|
|
2785
|
+
- pattern-not: $SANITIZER.bypassSecurityTrustResourceUrl(`...`)
|
|
2786
|
+
- pattern-not: $SANITIZER.bypassSecurityTrustScript(`...`)
|
|
2787
|
+
- pattern-not: $SANITIZER.bypassSecurityTrustStyle(`...`)
|
|
2788
|
+
|
|
2789
|
+
# XXE
|
|
2790
|
+
|
|
2791
|
+
- id: narvy.javascript.xxe.libxmljs-noent-enabled.libxmljs-noent-enabled
|
|
2792
|
+
message: >-
|
|
2793
|
+
libxmljs is called with `noent: true`, which turns on external
|
|
2794
|
+
entity substitution while parsing XML. If the XML document being
|
|
2795
|
+
parsed can be influenced by an attacker, this allows XML External
|
|
2796
|
+
Entity injection (CWE-611): reading local files via a crafted
|
|
2797
|
+
`<!ENTITY>` declaration, or triggering out-of-band requests from
|
|
2798
|
+
the server. Remove `noent: true` unless entity substitution is
|
|
2799
|
+
required, and if it is, first strip any `<!DOCTYPE` declaration
|
|
2800
|
+
from untrusted input before parsing.
|
|
2801
|
+
severity: ERROR
|
|
2802
|
+
languages: [javascript, typescript]
|
|
2803
|
+
metadata:
|
|
2804
|
+
cwe:
|
|
2805
|
+
- "CWE-611: Improper Restriction of XML External Entity Reference"
|
|
2806
|
+
owasp:
|
|
2807
|
+
- "A05:2021 - Security Misconfiguration"
|
|
2808
|
+
- "A04:2017 - XML External Entities (XXE)"
|
|
2809
|
+
category: security
|
|
2810
|
+
subcategory: [vuln]
|
|
2811
|
+
technology: [libxmljs, javascript, typescript]
|
|
2812
|
+
likelihood: LOW
|
|
2813
|
+
impact: HIGH
|
|
2814
|
+
confidence: HIGH
|
|
2815
|
+
vulnerability_class:
|
|
2816
|
+
- XML Injection
|
|
2817
|
+
references:
|
|
2818
|
+
- https://cwe.mitre.org/data/definitions/611.html
|
|
2819
|
+
- https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing
|
|
2820
|
+
patterns:
|
|
2821
|
+
- pattern-either:
|
|
2822
|
+
- pattern: '$LIBXML.parseXml($DATA, {..., noent: true, ...})'
|
|
2823
|
+
- pattern: '$LIBXML.parseXmlString($DATA, {..., noent: true, ...})'
|
|
2824
|
+
|
|
2825
|
+
- id: narvy.javascript.xxe.libxmljs-dtd-load-enabled.libxmljs-dtd-load-enabled
|
|
2826
|
+
message: >-
|
|
2827
|
+
libxmljs is called with `dtdload: true` or `dtdvalid: true`, which
|
|
2828
|
+
allows the parser to fetch an external DTD referenced in the
|
|
2829
|
+
document. If the XML being parsed is attacker-controlled this
|
|
2830
|
+
enables blind/out-of-band XXE (CWE-611): the server will make a
|
|
2831
|
+
network or file request to a URL the attacker put in the DOCTYPE.
|
|
2832
|
+
Remove dtdload/dtdvalid for untrusted input, or restrict outbound
|
|
2833
|
+
network access from the parsing process.
|
|
2834
|
+
severity: ERROR
|
|
2835
|
+
languages: [javascript, typescript]
|
|
2836
|
+
metadata:
|
|
2837
|
+
cwe:
|
|
2838
|
+
- "CWE-611: Improper Restriction of XML External Entity Reference"
|
|
2839
|
+
owasp:
|
|
2840
|
+
- "A05:2021 - Security Misconfiguration"
|
|
2841
|
+
category: security
|
|
2842
|
+
subcategory: [vuln]
|
|
2843
|
+
technology: [libxmljs, javascript, typescript]
|
|
2844
|
+
likelihood: LOW
|
|
2845
|
+
impact: HIGH
|
|
2846
|
+
confidence: MEDIUM
|
|
2847
|
+
vulnerability_class:
|
|
2848
|
+
- XML Injection
|
|
2849
|
+
patterns:
|
|
2850
|
+
- pattern-either:
|
|
2851
|
+
- pattern: '$LIBXML.parseXml($DATA, {..., dtdload: true, ...})'
|
|
2852
|
+
- pattern: '$LIBXML.parseXmlString($DATA, {..., dtdload: true, ...})'
|
|
2853
|
+
- pattern: '$LIBXML.parseXml($DATA, {..., dtdvalid: true, ...})'
|
|
2854
|
+
- pattern: '$LIBXML.parseXmlString($DATA, {..., dtdvalid: true, ...})'
|
|
2855
|
+
|
|
2856
|
+
# COMMAND INJECTION
|
|
2857
|
+
|
|
2858
|
+
# 1. exec / execSync with request data concatenated into the command.
|
|
2859
|
+
- id: narvy.javascript.cmdi.child-process-exec-concat.child-process-exec-concat
|
|
2860
|
+
message: >-
|
|
2861
|
+
A request-derived value is concatenated into the command string
|
|
2862
|
+
passed to child_process.exec/execSync. exec runs the string through
|
|
2863
|
+
a shell, so shell metacharacters in the request (`;`, `|`, `&&`,
|
|
2864
|
+
backticks) let an attacker run arbitrary OS commands (CWE-78). Use
|
|
2865
|
+
execFile/execFileSync with an argument array instead of a shell
|
|
2866
|
+
string, or strictly allow-list the input.
|
|
2867
|
+
severity: ERROR
|
|
2868
|
+
languages: [javascript, typescript]
|
|
2869
|
+
metadata:
|
|
2870
|
+
cwe:
|
|
2871
|
+
- "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')"
|
|
2872
|
+
owasp:
|
|
2873
|
+
- "A03:2021 - Injection"
|
|
2874
|
+
category: security
|
|
2875
|
+
subcategory: [vuln]
|
|
2876
|
+
technology: [nodejs, javascript, typescript]
|
|
2877
|
+
likelihood: HIGH
|
|
2878
|
+
impact: HIGH
|
|
2879
|
+
confidence: HIGH
|
|
2880
|
+
vulnerability_class:
|
|
2881
|
+
- Command Injection
|
|
2882
|
+
references:
|
|
2883
|
+
- https://cwe.mitre.org/data/definitions/78.html
|
|
2884
|
+
patterns:
|
|
2885
|
+
- pattern-either:
|
|
2886
|
+
- patterns:
|
|
2887
|
+
- pattern-either:
|
|
2888
|
+
- pattern: $CP.exec("$CMD" + $REQ.$KIND.$X + ..., ...)
|
|
2889
|
+
- pattern: $CP.exec(`...${$REQ.$KIND.$X}...`, ...)
|
|
2890
|
+
- pattern: $CP.execSync("$CMD" + $REQ.$KIND.$X + ...)
|
|
2891
|
+
- pattern: $CP.execSync(`...${$REQ.$KIND.$X}...`)
|
|
2892
|
+
- pattern: |
|
|
2893
|
+
$V = $REQ.$KIND.$X
|
|
2894
|
+
...
|
|
2895
|
+
$CP.exec("$CMD" + $V + ..., ...)
|
|
2896
|
+
- pattern: |
|
|
2897
|
+
$V = $REQ.$KIND.$X
|
|
2898
|
+
...
|
|
2899
|
+
$CP.exec(`...${$V}...`, ...)
|
|
2900
|
+
- pattern: |
|
|
2901
|
+
$V = $REQ.$KIND.$X
|
|
2902
|
+
...
|
|
2903
|
+
$CP.execSync("$CMD" + $V + ...)
|
|
2904
|
+
- pattern: |
|
|
2905
|
+
$V = $REQ.$KIND.$X
|
|
2906
|
+
...
|
|
2907
|
+
$CP.execSync(`...${$V}...`)
|
|
2908
|
+
# Destructured `const { exec } = require('child_process')` then bare exec(), gated on the taint shape.
|
|
2909
|
+
- pattern: exec("$CMD" + $REQ.$KIND.$X, ...)
|
|
2910
|
+
- pattern: exec("$CMD" + $REQ.$KIND.$X + ..., ...)
|
|
2911
|
+
- pattern: exec(`...${$REQ.$KIND.$X}...`, ...)
|
|
2912
|
+
# Taint last with nothing after it; covers receiver, destructured execSync, one level of indirection.
|
|
2913
|
+
- pattern: $CP.exec("$CMD" + $REQ.$KIND.$X, ...)
|
|
2914
|
+
- pattern: $CP.execSync("$CMD" + $REQ.$KIND.$X)
|
|
2915
|
+
- pattern: execSync("$CMD" + $REQ.$KIND.$X)
|
|
2916
|
+
- pattern: |
|
|
2917
|
+
$V = $REQ.$KIND.$X
|
|
2918
|
+
...
|
|
2919
|
+
$CP.exec("$CMD" + $V, ...)
|
|
2920
|
+
- pattern: |
|
|
2921
|
+
$V = $REQ.$KIND.$X
|
|
2922
|
+
...
|
|
2923
|
+
$CP.execSync("$CMD" + $V)
|
|
2924
|
+
- pattern: |
|
|
2925
|
+
$V = $REQ.$KIND.$X
|
|
2926
|
+
...
|
|
2927
|
+
exec("$CMD" + $V, ...)
|
|
2928
|
+
- pattern: |
|
|
2929
|
+
$V = $REQ.$KIND.$X
|
|
2930
|
+
...
|
|
2931
|
+
execSync("$CMD" + $V)
|
|
2932
|
+
- metavariable-regex:
|
|
2933
|
+
metavariable: $KIND
|
|
2934
|
+
regex: ^(query|params|body|headers|cookies)$
|
|
2935
|
+
# AWS Lambda taint source: event.queryStringParameters/.pathParameters/.headers into the command string.
|
|
2936
|
+
- patterns:
|
|
2937
|
+
- pattern-either:
|
|
2938
|
+
- pattern: $CP.exec("$CMD" + $EVENT.$EKIND.$X + ..., ...)
|
|
2939
|
+
- pattern: $CP.exec(`...${$EVENT.$EKIND.$X}...`, ...)
|
|
2940
|
+
- pattern: $CP.execSync("$CMD" + $EVENT.$EKIND.$X + ...)
|
|
2941
|
+
- pattern: $CP.execSync(`...${$EVENT.$EKIND.$X}...`)
|
|
2942
|
+
- pattern: |
|
|
2943
|
+
$V = $EVENT.$EKIND.$X
|
|
2944
|
+
...
|
|
2945
|
+
$CP.exec("$CMD" + $V + ..., ...)
|
|
2946
|
+
- pattern: |
|
|
2947
|
+
$V = $EVENT.$EKIND.$X
|
|
2948
|
+
...
|
|
2949
|
+
$CP.execSync("$CMD" + $V + ...)
|
|
2950
|
+
- pattern: exec("$CMD" + $EVENT.$EKIND.$X, ...)
|
|
2951
|
+
- pattern: exec("$CMD" + $EVENT.$EKIND.$X + ..., ...)
|
|
2952
|
+
- pattern: exec(`...${$EVENT.$EKIND.$X}...`, ...)
|
|
2953
|
+
- metavariable-regex:
|
|
2954
|
+
metavariable: $EKIND
|
|
2955
|
+
regex: ^(queryStringParameters|pathParameters|headers|multiValueQueryStringParameters|multiValueHeaders)$
|
|
2956
|
+
# event.body is flat, own branch with no $EKIND regex.
|
|
2957
|
+
- pattern-either:
|
|
2958
|
+
- pattern: $CP.exec("$CMD" + $EVENT.body + ..., ...)
|
|
2959
|
+
- pattern: $CP.exec("$CMD" + JSON.parse($EVENT.body).$X + ..., ...)
|
|
2960
|
+
|
|
2961
|
+
# 2. spawn/execFile with shell: true and a tainted command/argument.
|
|
2962
|
+
- id: narvy.javascript.cmdi.spawn-shell-true-tainted.spawn-shell-true-tainted
|
|
2963
|
+
message: >-
|
|
2964
|
+
child_process.spawn/execFile is called with `shell: true` and a
|
|
2965
|
+
command or argument derived from the request. With shell: true the
|
|
2966
|
+
command line is interpreted by a shell, so request-controlled
|
|
2967
|
+
shell metacharacters allow OS command injection (CWE-78). Drop
|
|
2968
|
+
shell: true and pass the command and arguments as a plain array,
|
|
2969
|
+
or allow-list the accepted values.
|
|
2970
|
+
severity: ERROR
|
|
2971
|
+
languages: [javascript, typescript]
|
|
2972
|
+
mode: taint
|
|
2973
|
+
metadata:
|
|
2974
|
+
cwe:
|
|
2975
|
+
- "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')"
|
|
2976
|
+
owasp:
|
|
2977
|
+
- "A03:2021 - Injection"
|
|
2978
|
+
category: security
|
|
2979
|
+
subcategory: [vuln]
|
|
2980
|
+
technology: [nodejs, javascript, typescript]
|
|
2981
|
+
likelihood: HIGH
|
|
2982
|
+
impact: HIGH
|
|
2983
|
+
confidence: MEDIUM
|
|
2984
|
+
vulnerability_class:
|
|
2985
|
+
- Command Injection
|
|
2986
|
+
pattern-sources:
|
|
2987
|
+
- pattern: req.query.$Q
|
|
2988
|
+
- pattern: req.params.$P
|
|
2989
|
+
- pattern: req.body.$B
|
|
2990
|
+
# AWS Lambda taint source, additional to the Express req source above.
|
|
2991
|
+
- pattern: event.queryStringParameters.$Q
|
|
2992
|
+
- pattern: event.pathParameters.$P
|
|
2993
|
+
- pattern: event.body
|
|
2994
|
+
- pattern: event.headers.$H
|
|
2995
|
+
pattern-sinks:
|
|
2996
|
+
- patterns:
|
|
2997
|
+
- pattern-either:
|
|
2998
|
+
- pattern: '$CP.spawn($SINK, ..., {..., shell: true, ...})'
|
|
2999
|
+
- pattern: '$CP.spawn($CMD, [..., $SINK, ...], {..., shell: true, ...})'
|
|
3000
|
+
- pattern: '$CP.execFile($SINK, ..., {..., shell: true, ...})'
|
|
3001
|
+
|
|
3002
|
+
# 3. spawn/spawnSync with shell: true, untainted broad companion to the tainted rule.
|
|
3003
|
+
- id: narvy.javascript.lang.security.spawn-shell-true.spawn-shell-true
|
|
3004
|
+
message: >-
|
|
3005
|
+
spawn / spawnSync called with `{shell: true}` runs the command
|
|
3006
|
+
through a shell (sh / cmd.exe), enabling command injection if any
|
|
3007
|
+
of the arguments are user-controlled. Drop `shell: true` and pass
|
|
3008
|
+
arguments as an array - execve() bypasses the shell entirely.
|
|
3009
|
+
severity: ERROR
|
|
3010
|
+
languages: [javascript, typescript]
|
|
3011
|
+
metadata:
|
|
3012
|
+
cwe:
|
|
3013
|
+
- "CWE-78: OS Command Injection"
|
|
3014
|
+
owasp: ["A03:2021 - Injection"]
|
|
3015
|
+
category: security
|
|
3016
|
+
subcategory: [vuln]
|
|
3017
|
+
technology: [javascript, typescript]
|
|
3018
|
+
likelihood: HIGH
|
|
3019
|
+
impact: HIGH
|
|
3020
|
+
confidence: HIGH
|
|
3021
|
+
vulnerability_class:
|
|
3022
|
+
- Command Injection
|
|
3023
|
+
patterns:
|
|
3024
|
+
- pattern-either:
|
|
3025
|
+
- pattern: |
|
|
3026
|
+
$CP.spawn($X, $Y, {..., shell: true, ...})
|
|
3027
|
+
- pattern: |
|
|
3028
|
+
$CP.spawnSync($X, $Y, {..., shell: true, ...})
|
|
3029
|
+
- pattern: |
|
|
3030
|
+
spawn($X, $Y, {..., shell: true, ...})
|
|
3031
|
+
- pattern: |
|
|
3032
|
+
spawnSync($X, $Y, {..., shell: true, ...})
|
|
3033
|
+
|
|
3034
|
+
# Dynamic code execution (eval / new Function / vm module / setTimeout with
|
|
3035
|
+
# a string) is covered separately in code_injection.yml in this same pack.
|
|
3036
|
+
|
|
3037
|
+
# CODE INJECTION
|
|
3038
|
+
|
|
3039
|
+
# Rule 1: eval() with non-literal argument
|
|
3040
|
+
- id: narvy.javascript.code-injection.eval-non-literal.eval-non-literal
|
|
3041
|
+
message: >-
|
|
3042
|
+
`eval()` called with a non-literal argument. eval() executes its
|
|
3043
|
+
argument as JavaScript with full privileges of the calling context.
|
|
3044
|
+
If the value is attacker-influenced this is remote code execution
|
|
3045
|
+
(CWE-94). Remove eval() and use JSON.parse for data, or a safe
|
|
3046
|
+
dispatch table for logic selection.
|
|
3047
|
+
severity: ERROR
|
|
3048
|
+
languages: [javascript, typescript]
|
|
3049
|
+
metadata:
|
|
3050
|
+
cwe:
|
|
3051
|
+
- "CWE-94: Improper Control of Generation of Code ('Code Injection')"
|
|
3052
|
+
owasp:
|
|
3053
|
+
- "A03:2021 - Injection"
|
|
3054
|
+
category: security
|
|
3055
|
+
subcategory: [vuln]
|
|
3056
|
+
technology: [javascript, typescript]
|
|
3057
|
+
likelihood: MEDIUM
|
|
3058
|
+
impact: HIGH
|
|
3059
|
+
confidence: MEDIUM
|
|
3060
|
+
vulnerability_class:
|
|
3061
|
+
- Code Injection
|
|
3062
|
+
patterns:
|
|
3063
|
+
- pattern: eval($X)
|
|
3064
|
+
- pattern-not: eval("...")
|
|
3065
|
+
- pattern-not: eval(`...`)
|
|
3066
|
+
|
|
3067
|
+
# Rule 2: new Function(...) with non-literal body
|
|
3068
|
+
- id: narvy.javascript.code-injection.dynamic-function-constructor.dynamic-function-constructor
|
|
3069
|
+
message: >-
|
|
3070
|
+
`new Function(...)` constructs and compiles a function from string
|
|
3071
|
+
arguments at runtime - functionally equivalent to eval(). If any
|
|
3072
|
+
argument (especially the final one, the function body) is
|
|
3073
|
+
attacker-influenced this is remote code execution (CWE-94). Avoid
|
|
3074
|
+
the Function constructor with dynamic input.
|
|
3075
|
+
severity: ERROR
|
|
3076
|
+
languages: [javascript, typescript]
|
|
3077
|
+
metadata:
|
|
3078
|
+
cwe:
|
|
3079
|
+
- "CWE-94: Improper Control of Generation of Code ('Code Injection')"
|
|
3080
|
+
owasp:
|
|
3081
|
+
- "A03:2021 - Injection"
|
|
3082
|
+
category: security
|
|
3083
|
+
subcategory: [vuln]
|
|
3084
|
+
technology: [javascript, typescript]
|
|
3085
|
+
likelihood: LOW
|
|
3086
|
+
impact: HIGH
|
|
3087
|
+
confidence: MEDIUM
|
|
3088
|
+
vulnerability_class:
|
|
3089
|
+
- Code Injection
|
|
3090
|
+
patterns:
|
|
3091
|
+
- pattern: new Function(...)
|
|
3092
|
+
- pattern-not: new Function("...")
|
|
3093
|
+
- pattern-not: new Function()
|
|
3094
|
+
|
|
3095
|
+
# Rule 3: vm module running non-literal script string
|
|
3096
|
+
- id: narvy.javascript.code-injection.vm-run-non-literal.vm-run-non-literal
|
|
3097
|
+
message: >-
|
|
3098
|
+
Node `vm` module executes a non-literal script string
|
|
3099
|
+
(runInNewContext / runInContext / runInThisContext). Although `vm`
|
|
3100
|
+
is sandboxed for global scope, it is not a real security boundary
|
|
3101
|
+
against a determined attacker and does not restrict CPU/memory. If
|
|
3102
|
+
the script text is attacker-influenced this is code execution
|
|
3103
|
+
(CWE-94). Never pass untrusted input to `vm.run*`.
|
|
3104
|
+
severity: ERROR
|
|
3105
|
+
languages: [javascript, typescript]
|
|
3106
|
+
metadata:
|
|
3107
|
+
cwe:
|
|
3108
|
+
- "CWE-94: Improper Control of Generation of Code ('Code Injection')"
|
|
3109
|
+
owasp:
|
|
3110
|
+
- "A03:2021 - Injection"
|
|
3111
|
+
category: security
|
|
3112
|
+
subcategory: [vuln]
|
|
3113
|
+
technology: [javascript, typescript, node-vm]
|
|
3114
|
+
likelihood: MEDIUM
|
|
3115
|
+
impact: HIGH
|
|
3116
|
+
confidence: HIGH
|
|
3117
|
+
vulnerability_class:
|
|
3118
|
+
- Code Injection
|
|
3119
|
+
patterns:
|
|
3120
|
+
- pattern-either:
|
|
3121
|
+
- pattern: vm.runInNewContext($X, ...)
|
|
3122
|
+
- pattern: vm.runInContext($X, ...)
|
|
3123
|
+
- pattern: vm.runInThisContext($X, ...)
|
|
3124
|
+
- pattern: $VM.runInNewContext($X, ...)
|
|
3125
|
+
- pattern-not: vm.runInNewContext("...", ...)
|
|
3126
|
+
- pattern-not: vm.runInContext("...", ...)
|
|
3127
|
+
- pattern-not: vm.runInThisContext("...", ...)
|
|
3128
|
+
|
|
3129
|
+
# Rule 4: setTimeout/setInterval with a string (implicit eval)
|
|
3130
|
+
- id: narvy.javascript.code-injection.settimeout-string-arg.settimeout-string-arg
|
|
3131
|
+
message: >-
|
|
3132
|
+
`setTimeout` / `setInterval` called with a STRING as the first
|
|
3133
|
+
argument instead of a function reference. The engine compiles and
|
|
3134
|
+
runs the string as code (implicit eval) when the timer fires. If the
|
|
3135
|
+
string is built from attacker-influenced data this is code injection
|
|
3136
|
+
(CWE-94). Pass a function/arrow-function reference instead.
|
|
3137
|
+
severity: WARNING
|
|
3138
|
+
languages: [javascript, typescript]
|
|
3139
|
+
metadata:
|
|
3140
|
+
cwe:
|
|
3141
|
+
- "CWE-94: Improper Control of Generation of Code ('Code Injection')"
|
|
3142
|
+
owasp:
|
|
3143
|
+
- "A03:2021 - Injection"
|
|
3144
|
+
category: security
|
|
3145
|
+
subcategory: [vuln]
|
|
3146
|
+
technology: [javascript, typescript]
|
|
3147
|
+
likelihood: LOW
|
|
3148
|
+
impact: MEDIUM
|
|
3149
|
+
confidence: MEDIUM
|
|
3150
|
+
vulnerability_class:
|
|
3151
|
+
- Code Injection
|
|
3152
|
+
patterns:
|
|
3153
|
+
- pattern-either:
|
|
3154
|
+
- pattern: setTimeout($X + ..., $MS)
|
|
3155
|
+
- pattern: setTimeout(`...${$Y}...`, $MS)
|
|
3156
|
+
- pattern: setInterval($X + ..., $MS)
|
|
3157
|
+
- pattern: setInterval(`...${$Y}...`, $MS)
|
|
3158
|
+
|
|
3159
|
+
# ADDITIONAL COVERAGE: JWT verification, token storage, headless-browser
|
|
3160
|
+
# SSRF, javascript: URIs, jQuery markup sinks, unsafe deserialization, ReDoS.
|
|
3161
|
+
|
|
3162
|
+
# jwt.decode() where jwt.verify() belongs: fires only when the token is request-derived and no .verify() appears in the enclosing function.
|
|
3163
|
+
- id: narvy.javascript.jwt.decode-without-verify.decode-without-verify
|
|
3164
|
+
message: >-
|
|
3165
|
+
A request-supplied JWT is parsed with `jwt.decode()` and no
|
|
3166
|
+
`jwt.verify()` runs in the same function. `decode` does NOT check the
|
|
3167
|
+
signature: an attacker can forge any claim (user id, role, expiry) by
|
|
3168
|
+
editing the token payload. Authentication bypass / privilege
|
|
3169
|
+
escalation (CWE-347). Use `jwt.verify(token, key, {algorithms: [...]})`
|
|
3170
|
+
and read the claims from its return value.
|
|
3171
|
+
severity: ERROR
|
|
3172
|
+
languages: [javascript, typescript]
|
|
3173
|
+
mode: taint
|
|
3174
|
+
metadata:
|
|
3175
|
+
cwe:
|
|
3176
|
+
- "CWE-347: Improper Verification of Cryptographic Signature"
|
|
3177
|
+
- "CWE-287: Improper Authentication"
|
|
3178
|
+
owasp:
|
|
3179
|
+
- "A07:2021 - Identification and Authentication Failures"
|
|
3180
|
+
- "A02:2021 - Cryptographic Failures"
|
|
3181
|
+
category: security
|
|
3182
|
+
subcategory: [vuln]
|
|
3183
|
+
technology: [jsonwebtoken, jwt, express, nodejs, javascript, typescript]
|
|
3184
|
+
likelihood: HIGH
|
|
3185
|
+
impact: HIGH
|
|
3186
|
+
confidence: HIGH
|
|
3187
|
+
vulnerability_class:
|
|
3188
|
+
- Improper Authentication
|
|
3189
|
+
references:
|
|
3190
|
+
- https://cwe.mitre.org/data/definitions/347.html
|
|
3191
|
+
pattern-sources:
|
|
3192
|
+
- patterns:
|
|
3193
|
+
- pattern-either:
|
|
3194
|
+
- pattern: $REQ.headers
|
|
3195
|
+
- pattern: $REQ.cookies
|
|
3196
|
+
- pattern: $REQ.body
|
|
3197
|
+
- pattern: $REQ.query
|
|
3198
|
+
- pattern: $REQ.get("...")
|
|
3199
|
+
- pattern: $REQ.header("...")
|
|
3200
|
+
- metavariable-regex:
|
|
3201
|
+
metavariable: $REQ
|
|
3202
|
+
regex: ^(req|request|_req|ctx|httpRequest|httpReq)$
|
|
3203
|
+
- patterns:
|
|
3204
|
+
- pattern: $REQ.$KIND.$X
|
|
3205
|
+
- metavariable-regex:
|
|
3206
|
+
metavariable: $KIND
|
|
3207
|
+
regex: ^(body|params|query|headers|cookies)$
|
|
3208
|
+
pattern-sinks:
|
|
3209
|
+
- patterns:
|
|
3210
|
+
- pattern: $JWT.decode($SINK, ...)
|
|
3211
|
+
- focus-metavariable: $SINK
|
|
3212
|
+
- metavariable-regex:
|
|
3213
|
+
metavariable: $JWT
|
|
3214
|
+
regex: ^(jwt|jsonwebtoken|jsonWebToken|JWT|jwtLib)$
|
|
3215
|
+
# A .verify() anywhere in the function clears the finding; free $V receiver also matches wrappers.
|
|
3216
|
+
- pattern-not-inside: |
|
|
3217
|
+
function $F(...) {
|
|
3218
|
+
...
|
|
3219
|
+
$V.verify(...)
|
|
3220
|
+
...
|
|
3221
|
+
}
|
|
3222
|
+
- pattern-not-inside: |
|
|
3223
|
+
(...) => {
|
|
3224
|
+
...
|
|
3225
|
+
$V.verify(...)
|
|
3226
|
+
...
|
|
3227
|
+
}
|
|
3228
|
+
- pattern-not-inside: |
|
|
3229
|
+
async (...) => {
|
|
3230
|
+
...
|
|
3231
|
+
$V.verify(...)
|
|
3232
|
+
...
|
|
3233
|
+
}
|
|
3234
|
+
- pattern-not-inside: |
|
|
3235
|
+
$F(...) {
|
|
3236
|
+
...
|
|
3237
|
+
$V.verify(...)
|
|
3238
|
+
...
|
|
3239
|
+
}
|
|
3240
|
+
- pattern-not-inside: |
|
|
3241
|
+
try {
|
|
3242
|
+
...
|
|
3243
|
+
$V.verify(...)
|
|
3244
|
+
...
|
|
3245
|
+
} catch($E) {
|
|
3246
|
+
...
|
|
3247
|
+
}
|
|
3248
|
+
paths:
|
|
3249
|
+
exclude:
|
|
3250
|
+
- "**/test/**"
|
|
3251
|
+
- "**/tests/**"
|
|
3252
|
+
- "**/__tests__/**"
|
|
3253
|
+
- "**/*.test.js"
|
|
3254
|
+
- "**/*.test.ts"
|
|
3255
|
+
- "**/*.spec.js"
|
|
3256
|
+
- "**/*.spec.ts"
|
|
3257
|
+
|
|
3258
|
+
# Bearer/refresh token in localStorage: readable by any script, no expiry/HttpOnly/SameSite; key-name allow-list separates credentials from UI state.
|
|
3259
|
+
- id: narvy.javascript.jwt.token-in-localstorage.token-in-localstorage
|
|
3260
|
+
message: >-
|
|
3261
|
+
An authentication token is written to `localStorage`. Anything stored
|
|
3262
|
+
there is readable by every script on the origin, survives the browser
|
|
3263
|
+
session, and cannot be protected with HttpOnly/SameSite - so a single
|
|
3264
|
+
XSS anywhere on the site steals a long-lived credential (CWE-522).
|
|
3265
|
+
Prefer an HttpOnly + Secure + SameSite cookie, or keep the access
|
|
3266
|
+
token in memory only.
|
|
3267
|
+
severity: WARNING
|
|
3268
|
+
languages: [javascript, typescript]
|
|
3269
|
+
metadata:
|
|
3270
|
+
cwe:
|
|
3271
|
+
- "CWE-522: Insufficiently Protected Credentials"
|
|
3272
|
+
- "CWE-1004: Sensitive Cookie Without 'HttpOnly' Flag"
|
|
3273
|
+
owasp:
|
|
3274
|
+
- "A07:2021 - Identification and Authentication Failures"
|
|
3275
|
+
category: security
|
|
3276
|
+
subcategory: [audit]
|
|
3277
|
+
technology: [browser, jwt, javascript, typescript]
|
|
3278
|
+
likelihood: MEDIUM
|
|
3279
|
+
impact: HIGH
|
|
3280
|
+
confidence: HIGH
|
|
3281
|
+
vulnerability_class:
|
|
3282
|
+
- Insecure Storage
|
|
3283
|
+
patterns:
|
|
3284
|
+
- pattern-either:
|
|
3285
|
+
- pattern: localStorage.setItem("$KEY", $V)
|
|
3286
|
+
- pattern: window.localStorage.setItem("$KEY", $V)
|
|
3287
|
+
- pattern: $S.localStorage.setItem("$KEY", $V)
|
|
3288
|
+
- pattern: localStorage["$KEY"] = $V
|
|
3289
|
+
- pattern: window.localStorage["$KEY"] = $V
|
|
3290
|
+
- metavariable-regex:
|
|
3291
|
+
metavariable: $KEY
|
|
3292
|
+
regex: '(?i)^[a-z0-9_.-]*(jwt|access[_-]?token|id[_-]?token|refresh[_-]?token|bearer|auth[_-]?token|session[_-]?token|api[_-]?key)[a-z0-9_.-]*$'
|
|
3293
|
+
paths:
|
|
3294
|
+
exclude:
|
|
3295
|
+
- "**/test/**"
|
|
3296
|
+
- "**/tests/**"
|
|
3297
|
+
- "**/__tests__/**"
|
|
3298
|
+
- "**/*.test.js"
|
|
3299
|
+
- "**/*.test.ts"
|
|
3300
|
+
- "**/*.test.tsx"
|
|
3301
|
+
- "**/*.spec.js"
|
|
3302
|
+
- "**/*.spec.ts"
|
|
3303
|
+
- "**/*.spec.tsx"
|
|
3304
|
+
|
|
3305
|
+
# Authorization decision on a client-side decoded (unverified) JWT claim; fires only when the claim drives a conditional and is authz-shaped.
|
|
3306
|
+
- id: narvy.javascript.jwt.unverified-claim-authorization.unverified-claim-authorization
|
|
3307
|
+
message: >-
|
|
3308
|
+
An authorization decision is made on a claim read from a
|
|
3309
|
+
client-side-DECODED JWT. `jwt-decode` / `decodeJwt` do not verify the
|
|
3310
|
+
signature, so the user can edit the claim in their own token and take
|
|
3311
|
+
the privileged branch (CWE-347). Client-side decoding is fine for
|
|
3312
|
+
display, but every authorization check must happen server-side on a
|
|
3313
|
+
signature-verified token.
|
|
3314
|
+
severity: ERROR
|
|
3315
|
+
languages: [javascript, typescript]
|
|
3316
|
+
metadata:
|
|
3317
|
+
cwe:
|
|
3318
|
+
- "CWE-347: Improper Verification of Cryptographic Signature"
|
|
3319
|
+
- "CWE-602: Client-Side Enforcement of Server-Side Security"
|
|
3320
|
+
owasp:
|
|
3321
|
+
- "A01:2021 - Broken Access Control"
|
|
3322
|
+
category: security
|
|
3323
|
+
subcategory: [vuln]
|
|
3324
|
+
technology: [jwt-decode, jose, browser, javascript, typescript]
|
|
3325
|
+
likelihood: MEDIUM
|
|
3326
|
+
impact: HIGH
|
|
3327
|
+
confidence: MEDIUM
|
|
3328
|
+
vulnerability_class:
|
|
3329
|
+
- Improper Authorization
|
|
3330
|
+
patterns:
|
|
3331
|
+
- pattern-either:
|
|
3332
|
+
- pattern: if (<... $DEC(...).$CLAIM ...>) { ... }
|
|
3333
|
+
- pattern: |
|
|
3334
|
+
$C = $DEC(...);
|
|
3335
|
+
...
|
|
3336
|
+
if (<... $C.$CLAIM ...>) { ... }
|
|
3337
|
+
- metavariable-regex:
|
|
3338
|
+
metavariable: $DEC
|
|
3339
|
+
regex: ^(jwtDecode|jwt_decode|jwtDecoder|decodeJwt|decodeToken|parseJwt)$
|
|
3340
|
+
- metavariable-regex:
|
|
3341
|
+
metavariable: $CLAIM
|
|
3342
|
+
regex: ^(role|roles|isAdmin|is_admin|admin|scope|scopes|permissions|perms|privileges|isStaff|is_staff|groups)$
|
|
3343
|
+
|
|
3344
|
+
# SSRF via headless browser .goto()/.setContent(): the server fetches the URL; sanitizers mirror request-url-from-user.
|
|
3345
|
+
- id: narvy.javascript.ssrf.headless-browser-tainted-url.headless-browser-tainted-url
|
|
3346
|
+
message: >-
|
|
3347
|
+
A request-derived value reaches a headless-browser navigation sink
|
|
3348
|
+
(puppeteer / playwright `page.goto` or `page.setContent`). The SERVER's
|
|
3349
|
+
browser fetches it, so an attacker can reach cloud metadata endpoints,
|
|
3350
|
+
localhost admin ports and other internal services - Server-Side
|
|
3351
|
+
Request Forgery (CWE-918). Validate the URL against an allow-list of
|
|
3352
|
+
hosts and schemes, and run the browser with egress restricted.
|
|
3353
|
+
severity: ERROR
|
|
3354
|
+
languages: [javascript, typescript]
|
|
3355
|
+
mode: taint
|
|
3356
|
+
metadata:
|
|
3357
|
+
cwe:
|
|
3358
|
+
- "CWE-918: Server-Side Request Forgery (SSRF)"
|
|
3359
|
+
owasp:
|
|
3360
|
+
- "A10:2021 - Server-Side Request Forgery (SSRF)"
|
|
3361
|
+
category: security
|
|
3362
|
+
subcategory: [vuln]
|
|
3363
|
+
technology: [puppeteer, playwright, nodejs, javascript, typescript]
|
|
3364
|
+
likelihood: MEDIUM
|
|
3365
|
+
impact: HIGH
|
|
3366
|
+
confidence: MEDIUM
|
|
3367
|
+
vulnerability_class:
|
|
3368
|
+
- Server-Side Request Forgery (SSRF)
|
|
3369
|
+
references:
|
|
3370
|
+
- https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/
|
|
3371
|
+
pattern-sources:
|
|
3372
|
+
- patterns:
|
|
3373
|
+
- pattern-either:
|
|
3374
|
+
- pattern: $REQ.body
|
|
3375
|
+
- pattern: $REQ.query
|
|
3376
|
+
- pattern: $REQ.params
|
|
3377
|
+
- metavariable-regex:
|
|
3378
|
+
metavariable: $REQ
|
|
3379
|
+
regex: ^(req|request|_req|ctx|httpRequest|httpReq)$
|
|
3380
|
+
- patterns:
|
|
3381
|
+
- pattern: $REQ.$KIND.$X
|
|
3382
|
+
- metavariable-regex:
|
|
3383
|
+
metavariable: $KIND
|
|
3384
|
+
regex: ^(body|params|query|headers|cookies)$
|
|
3385
|
+
pattern-sanitizers:
|
|
3386
|
+
- pattern: safeFetch(...)
|
|
3387
|
+
- pattern: isAllowedUrl(...)
|
|
3388
|
+
- pattern: isAllowedHost(...)
|
|
3389
|
+
- pattern: isSafeUrl(...)
|
|
3390
|
+
- pattern: validateUrl(...)
|
|
3391
|
+
- pattern: validateHost(...)
|
|
3392
|
+
- patterns:
|
|
3393
|
+
- pattern: $V
|
|
3394
|
+
- pattern-inside: |
|
|
3395
|
+
if (<... $ALLOW.includes(...) ...>) { ... }
|
|
3396
|
+
- patterns:
|
|
3397
|
+
- pattern: $V
|
|
3398
|
+
- pattern-inside: |
|
|
3399
|
+
if (!$GUARD($V)) { ... }
|
|
3400
|
+
...
|
|
3401
|
+
- patterns:
|
|
3402
|
+
- pattern: $V
|
|
3403
|
+
- pattern-inside: |
|
|
3404
|
+
if (!$GUARD($V)) return ...;
|
|
3405
|
+
...
|
|
3406
|
+
- patterns:
|
|
3407
|
+
- pattern: $V
|
|
3408
|
+
- pattern-inside: |
|
|
3409
|
+
if (!$ALLOW.includes($V)) { ... }
|
|
3410
|
+
...
|
|
3411
|
+
- patterns:
|
|
3412
|
+
- pattern: $V
|
|
3413
|
+
- pattern-inside: |
|
|
3414
|
+
if (!$ALLOW.includes($V)) return ...;
|
|
3415
|
+
...
|
|
3416
|
+
pattern-sinks:
|
|
3417
|
+
- patterns:
|
|
3418
|
+
- pattern-either:
|
|
3419
|
+
- pattern: $PAGE.goto($SINK, ...)
|
|
3420
|
+
- pattern: $PAGE.setContent($SINK, ...)
|
|
3421
|
+
- focus-metavariable: $SINK
|
|
3422
|
+
|
|
3423
|
+
# `javascript:` URI reaching an href/src sink; regex requires a dynamic part so void(0)/; literals are ignored.
|
|
3424
|
+
- id: narvy.javascript.xss.javascript-uri-href-sink.javascript-uri-href-sink
|
|
3425
|
+
message: >-
|
|
3426
|
+
A `javascript:` URI with an interpolated/concatenated value is
|
|
3427
|
+
assigned to an href/src sink. Whatever reaches the interpolation is
|
|
3428
|
+
executed as script when the link is followed, so any
|
|
3429
|
+
attacker-influenced value there is XSS (CWE-79). Build the handler in
|
|
3430
|
+
JS (`addEventListener`) instead of encoding it into a URI, and never
|
|
3431
|
+
assign a `javascript:` scheme from data.
|
|
3432
|
+
severity: ERROR
|
|
3433
|
+
languages: [javascript, typescript]
|
|
3434
|
+
metadata:
|
|
3435
|
+
cwe:
|
|
3436
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
3437
|
+
owasp:
|
|
3438
|
+
- "A03:2021 - Injection"
|
|
3439
|
+
category: security
|
|
3440
|
+
subcategory: [vuln]
|
|
3441
|
+
technology: [browser, react, javascript, typescript]
|
|
3442
|
+
likelihood: MEDIUM
|
|
3443
|
+
impact: HIGH
|
|
3444
|
+
confidence: MEDIUM
|
|
3445
|
+
vulnerability_class:
|
|
3446
|
+
- Cross-Site-Scripting (XSS)
|
|
3447
|
+
patterns:
|
|
3448
|
+
- pattern-either:
|
|
3449
|
+
- pattern: $EL.href = $X
|
|
3450
|
+
- pattern: $EL.src = $X
|
|
3451
|
+
- pattern: $EL.setAttribute("href", $X)
|
|
3452
|
+
- pattern: $EL.setAttribute("src", $X)
|
|
3453
|
+
- pattern: $EL.setAttribute("xlink:href", $X)
|
|
3454
|
+
- pattern: <$TAG href={$X} ...>...</$TAG>
|
|
3455
|
+
- pattern: <$TAG href={$X} ... />
|
|
3456
|
+
- pattern: <$TAG src={$X} ...>...</$TAG>
|
|
3457
|
+
- pattern: <$TAG src={$X} ... />
|
|
3458
|
+
- metavariable-regex:
|
|
3459
|
+
metavariable: $X
|
|
3460
|
+
regex: '(?s)^["''`]\s*javascript\s*:.*(\$\{|["''`]\s*\+).*'
|
|
3461
|
+
|
|
3462
|
+
# jQuery markup-insertion sinks beyond .html(); guards: string/template arg, contains markup, at least one raw-data operand.
|
|
3463
|
+
- id: narvy.javascript.xss.jquery-markup-insertion.jquery-markup-insertion
|
|
3464
|
+
message: >-
|
|
3465
|
+
A jQuery markup-insertion method is called with an HTML string built
|
|
3466
|
+
by concatenation/interpolation. Like `.html()`, these methods PARSE
|
|
3467
|
+
their string argument as markup, so any unescaped value in it is
|
|
3468
|
+
DOM-based or stored XSS (CWE-79). Insert text with `.text()`, build
|
|
3469
|
+
nodes with `$("<tag>").text(value)`, or sanitize with DOMPurify.
|
|
3470
|
+
severity: WARNING
|
|
3471
|
+
languages: [javascript, typescript]
|
|
3472
|
+
metadata:
|
|
3473
|
+
cwe:
|
|
3474
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
3475
|
+
owasp:
|
|
3476
|
+
- "A03:2021 - Injection"
|
|
3477
|
+
category: security
|
|
3478
|
+
subcategory: [vuln]
|
|
3479
|
+
technology: [jquery, browser, javascript, typescript]
|
|
3480
|
+
likelihood: MEDIUM
|
|
3481
|
+
impact: MEDIUM
|
|
3482
|
+
confidence: MEDIUM
|
|
3483
|
+
vulnerability_class:
|
|
3484
|
+
- Cross-Site-Scripting (XSS)
|
|
3485
|
+
patterns:
|
|
3486
|
+
- pattern: $EL.$M($ARG)
|
|
3487
|
+
- metavariable-regex:
|
|
3488
|
+
metavariable: $M
|
|
3489
|
+
regex: ^(append|prepend|after|before|replaceWith|wrap|wrapInner)$
|
|
3490
|
+
# Guard 1: a string/template expression, not a node or jQuery object.
|
|
3491
|
+
- metavariable-regex:
|
|
3492
|
+
metavariable: $ARG
|
|
3493
|
+
regex: '(?s)^["''`].*'
|
|
3494
|
+
# Guard 2: it actually contains markup.
|
|
3495
|
+
- metavariable-regex:
|
|
3496
|
+
metavariable: $ARG
|
|
3497
|
+
regex: '(?s).*<\s*[a-zA-Z/].*'
|
|
3498
|
+
# Guard 3: at least one raw-data operand, not only formatted/encoded call results.
|
|
3499
|
+
- metavariable-regex:
|
|
3500
|
+
metavariable: $ARG
|
|
3501
|
+
regex: '(?s).*(\+\s*[A-Za-z_$][A-Za-z0-9_$.\[\]]*\s*(\+|$)|\$\{\s*[A-Za-z_$][A-Za-z0-9_$.\[\]]*\s*\}).*'
|
|
3502
|
+
- pattern-not: $EL.$M($SAN.sanitize(...))
|
|
3503
|
+
- pattern-not: $EL.$M(DOMPurify.sanitize(...))
|
|
3504
|
+
|
|
3505
|
+
# jQuery selector from a URL fragment; only the sentinel-stripped form (slice/substring/replace/URLSearchParams) can parse as HTML.
|
|
3506
|
+
- id: narvy.javascript.xss.jquery-selector-from-url.jquery-selector-from-url
|
|
3507
|
+
message: >-
|
|
3508
|
+
A jQuery selector is built from a URL fragment/query value whose
|
|
3509
|
+
leading `#`/`?` sentinel has been stripped. jQuery parses a string
|
|
3510
|
+
starting with `<` as MARKUP rather than as a selector, so an attacker
|
|
3511
|
+
controlling the fragment gets DOM-based XSS (CWE-79). Look the element
|
|
3512
|
+
up explicitly (`document.getElementById(value)`) instead of passing
|
|
3513
|
+
URL data to `$()`.
|
|
3514
|
+
severity: ERROR
|
|
3515
|
+
languages: [javascript, typescript]
|
|
3516
|
+
metadata:
|
|
3517
|
+
cwe:
|
|
3518
|
+
- "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
|
|
3519
|
+
owasp:
|
|
3520
|
+
- "A03:2021 - Injection"
|
|
3521
|
+
category: security
|
|
3522
|
+
subcategory: [vuln]
|
|
3523
|
+
technology: [jquery, browser, javascript, typescript]
|
|
3524
|
+
likelihood: MEDIUM
|
|
3525
|
+
impact: HIGH
|
|
3526
|
+
confidence: MEDIUM
|
|
3527
|
+
vulnerability_class:
|
|
3528
|
+
- Cross-Site-Scripting (XSS)
|
|
3529
|
+
patterns:
|
|
3530
|
+
- pattern-either:
|
|
3531
|
+
- pattern: $($LOC.hash.$STRIP(...))
|
|
3532
|
+
- pattern: $($LOC.search.$STRIP(...))
|
|
3533
|
+
- pattern: $(decodeURIComponent($LOC.hash.$STRIP(...)))
|
|
3534
|
+
- pattern: $(decodeURIComponent($LOC.search.$STRIP(...)))
|
|
3535
|
+
- pattern: $(decodeURI($LOC.hash.$STRIP(...)))
|
|
3536
|
+
- pattern: |
|
|
3537
|
+
$V = $LOC.hash.$STRIP(...);
|
|
3538
|
+
...
|
|
3539
|
+
$($V)
|
|
3540
|
+
- pattern: |
|
|
3541
|
+
$V = decodeURIComponent($LOC.hash.$STRIP(...));
|
|
3542
|
+
...
|
|
3543
|
+
$($V)
|
|
3544
|
+
- metavariable-regex:
|
|
3545
|
+
metavariable: $STRIP
|
|
3546
|
+
regex: ^(slice|substring|substr|replace|split)$
|
|
3547
|
+
|
|
3548
|
+
# node-serialize / serialize-to-js .unserialize() eval-s embedded functions (RCE); scoped to receivers imported from those packages.
|
|
3549
|
+
- id: narvy.javascript.deserialization.node-serialize-unserialize.node-serialize-unserialize
|
|
3550
|
+
message: >-
|
|
3551
|
+
`unserialize()` from node-serialize / serialize-to-js is called on a
|
|
3552
|
+
non-literal value. These libraries restore FUNCTIONS by evaluating the
|
|
3553
|
+
serialized payload, so attacker-controlled input here is remote code
|
|
3554
|
+
execution (CWE-502). Use `JSON.parse` and rebuild behaviour from data,
|
|
3555
|
+
or a serializer that never evaluates code.
|
|
3556
|
+
severity: ERROR
|
|
3557
|
+
languages: [javascript, typescript]
|
|
3558
|
+
metadata:
|
|
3559
|
+
cwe:
|
|
3560
|
+
- "CWE-502: Deserialization of Untrusted Data"
|
|
3561
|
+
- "CWE-94: Improper Control of Generation of Code ('Code Injection')"
|
|
3562
|
+
owasp:
|
|
3563
|
+
- "A08:2021 - Software and Data Integrity Failures"
|
|
3564
|
+
category: security
|
|
3565
|
+
subcategory: [vuln]
|
|
3566
|
+
technology: [node-serialize, serialize-to-js, nodejs, javascript, typescript]
|
|
3567
|
+
likelihood: HIGH
|
|
3568
|
+
impact: HIGH
|
|
3569
|
+
confidence: HIGH
|
|
3570
|
+
vulnerability_class:
|
|
3571
|
+
- Insecure Deserialization
|
|
3572
|
+
references:
|
|
3573
|
+
- https://cwe.mitre.org/data/definitions/502.html
|
|
3574
|
+
pattern-either:
|
|
3575
|
+
- patterns:
|
|
3576
|
+
- pattern: $S.unserialize($X)
|
|
3577
|
+
- pattern-not: $S.unserialize("...")
|
|
3578
|
+
- pattern-either:
|
|
3579
|
+
- pattern-inside: |
|
|
3580
|
+
$S = require("node-serialize");
|
|
3581
|
+
...
|
|
3582
|
+
- pattern-inside: |
|
|
3583
|
+
$S = require("serialize-to-js");
|
|
3584
|
+
...
|
|
3585
|
+
- pattern-inside: |
|
|
3586
|
+
import $S from "node-serialize";
|
|
3587
|
+
...
|
|
3588
|
+
- pattern-inside: |
|
|
3589
|
+
import * as $S from "node-serialize";
|
|
3590
|
+
...
|
|
3591
|
+
- pattern-inside: |
|
|
3592
|
+
import $S from "serialize-to-js";
|
|
3593
|
+
...
|
|
3594
|
+
- pattern-inside: |
|
|
3595
|
+
import * as $S from "serialize-to-js";
|
|
3596
|
+
...
|
|
3597
|
+
- patterns:
|
|
3598
|
+
- pattern-either:
|
|
3599
|
+
- pattern: require("node-serialize").unserialize($X)
|
|
3600
|
+
- pattern: require("serialize-to-js").unserialize($X)
|
|
3601
|
+
- pattern-not: require("...").unserialize("...")
|
|
3602
|
+
|
|
3603
|
+
# Catastrophic-backtracking regex literal (static ReDoS): a quantified group holding a single quantified atom.
|
|
3604
|
+
- id: narvy.javascript.misc.redos-nested-quantifier-literal.redos-nested-quantifier-literal
|
|
3605
|
+
message: >-
|
|
3606
|
+
Regex literal with a nested quantifier over a single atom (e.g.
|
|
3607
|
+
`(a+)+`, `([a-z]+)*`). The engine can split the input between the
|
|
3608
|
+
inner and outer quantifier in exponentially many ways, so a crafted
|
|
3609
|
+
non-matching subject string hangs the event loop - catastrophic
|
|
3610
|
+
backtracking / ReDoS (CWE-1333). Drop the redundant outer quantifier,
|
|
3611
|
+
make the group unambiguous, or match with a linear-time engine (re2).
|
|
3612
|
+
severity: WARNING
|
|
3613
|
+
languages: [javascript, typescript]
|
|
3614
|
+
metadata:
|
|
3615
|
+
cwe:
|
|
3616
|
+
- "CWE-1333: Inefficient Regular Expression Complexity"
|
|
3617
|
+
- "CWE-400: Uncontrolled Resource Consumption"
|
|
3618
|
+
owasp:
|
|
3619
|
+
- "A05:2021 - Security Misconfiguration"
|
|
3620
|
+
category: security
|
|
3621
|
+
subcategory: [audit]
|
|
3622
|
+
technology: [javascript, typescript]
|
|
3623
|
+
likelihood: LOW
|
|
3624
|
+
impact: MEDIUM
|
|
3625
|
+
confidence: MEDIUM
|
|
3626
|
+
vulnerability_class:
|
|
3627
|
+
- Denial-of-Service (DoS)
|
|
3628
|
+
patterns:
|
|
3629
|
+
- pattern-either:
|
|
3630
|
+
- pattern: $RE.test(...)
|
|
3631
|
+
- pattern: $RE.exec(...)
|
|
3632
|
+
- pattern: $X.match($RE)
|
|
3633
|
+
- pattern: $X.matchAll($RE)
|
|
3634
|
+
- pattern: $X.replace($RE, ...)
|
|
3635
|
+
- pattern: $X.replaceAll($RE, ...)
|
|
3636
|
+
- pattern: $X.split($RE)
|
|
3637
|
+
- pattern: $X.search($RE)
|
|
3638
|
+
- pattern: new RegExp($RE)
|
|
3639
|
+
- pattern: $V = $RE
|
|
3640
|
+
- metavariable-regex:
|
|
3641
|
+
metavariable: $RE
|
|
3642
|
+
regex: '^/.*\((\?:)?(\[[^\]]+\]|\\[dwsSWD]|\.|[A-Za-z0-9])[+*]\)[+*][^+*?].*/[gimsuyd]*$'
|
|
3643
|
+
|
|
3644
|
+
# CORS allow-list regex with an unescaped dot matches attacker domains; regex-on-regex requires the dot followed by a letter, scoped to origin-shaped identifiers.
|
|
3645
|
+
- id: narvy.javascript.cors.allowlist-regex-unescaped-dot.allowlist-regex-unescaped-dot
|
|
3646
|
+
message: >-
|
|
3647
|
+
CORS origin allow-list regex contains an UNESCAPED dot. `.` matches
|
|
3648
|
+
any character, so `/^https://app.example.com$/` also accepts
|
|
3649
|
+
`https://appXexampleYcom` - a hostname an attacker can register - and
|
|
3650
|
+
the server will reflect it in Access-Control-Allow-Origin (CWE-942).
|
|
3651
|
+
Escape the dots (`app\.example\.com`) and anchor the pattern with
|
|
3652
|
+
`^`/`$`.
|
|
3653
|
+
severity: ERROR
|
|
3654
|
+
languages: [javascript, typescript]
|
|
3655
|
+
metadata:
|
|
3656
|
+
cwe:
|
|
3657
|
+
- "CWE-942: Permissive Cross-domain Policy with Untrusted Domains"
|
|
3658
|
+
- "CWE-346: Origin Validation Error"
|
|
3659
|
+
owasp:
|
|
3660
|
+
- "A05:2021 - Security Misconfiguration"
|
|
3661
|
+
category: security
|
|
3662
|
+
subcategory: [vuln]
|
|
3663
|
+
technology: [cors, express, nodejs, javascript, typescript]
|
|
3664
|
+
likelihood: MEDIUM
|
|
3665
|
+
impact: HIGH
|
|
3666
|
+
confidence: MEDIUM
|
|
3667
|
+
vulnerability_class:
|
|
3668
|
+
- Improper Access Control
|
|
3669
|
+
patterns:
|
|
3670
|
+
- pattern-either:
|
|
3671
|
+
- patterns:
|
|
3672
|
+
- pattern-either:
|
|
3673
|
+
- pattern: $RE.test($ORIGIN)
|
|
3674
|
+
- pattern: $ORIGIN.match($RE)
|
|
3675
|
+
- pattern: $RE.exec($ORIGIN)
|
|
3676
|
+
- metavariable-regex:
|
|
3677
|
+
metavariable: $ORIGIN
|
|
3678
|
+
regex: '(?i)^_?(origin|reqorigin|requestorigin|referer|referrer|allowedorigin|targetorigin)$'
|
|
3679
|
+
- pattern: "cors({..., origin: $RE, ...})"
|
|
3680
|
+
- pattern: "cors({..., origin: [..., $RE, ...], ...})"
|
|
3681
|
+
- metavariable-regex:
|
|
3682
|
+
metavariable: $RE
|
|
3683
|
+
regex: '^/.*[^\\]\.[a-zA-Z].*/[gimsuyd]*$'
|