1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.analyzer;
19
20 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
21 import java.io.FileFilter;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.ListIterator;
27 import java.util.Set;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 import javax.annotation.concurrent.ThreadSafe;
31 import org.owasp.dependencycheck.Engine;
32 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
33 import org.owasp.dependencycheck.dependency.Dependency;
34 import org.owasp.dependencycheck.dependency.Evidence;
35 import org.owasp.dependencycheck.dependency.EvidenceType;
36 import org.owasp.dependencycheck.dependency.naming.CpeIdentifier;
37 import org.owasp.dependencycheck.dependency.naming.Identifier;
38 import org.owasp.dependencycheck.utils.FileFilterBuilder;
39 import org.owasp.dependencycheck.utils.Settings;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import us.springett.parsers.cpe.Cpe;
43 import us.springett.parsers.cpe.CpeBuilder;
44 import us.springett.parsers.cpe.exceptions.CpeValidationException;
45 import us.springett.parsers.cpe.values.Part;
46
47
48
49
50
51
52
53 @ThreadSafe
54 public class FalsePositiveAnalyzer extends AbstractAnalyzer {
55
56
57
58
59 private static final Logger LOGGER = LoggerFactory.getLogger(FalsePositiveAnalyzer.class);
60
61
62
63 private static final FileFilter DLL_EXE_FILTER = FileFilterBuilder.newInstance().addExtensions("dll", "exe").build();
64
65
66
67
68 public static final Pattern CORE_JAVA = Pattern.compile("^cpe:/a:(sun|oracle|ibm):(j2[ems]e|"
69 + "java(_platform_micro_edition|_runtime_environment|_se|virtual_machine|se_development_kit|fx)?|"
70 + "jdk|jre|jsse)($|:.*)");
71
72
73
74 public static final Pattern CORE_JAVA_JSF = Pattern.compile("^cpe:/a:(sun|oracle|ibm):jsf($|:.*)");
75
76
77
78 public static final Pattern CORE_FILES = Pattern.compile("(^|/)((alt[-])?rt|jsse|jfxrt|jfr|jce|javaws|deploy|charsets)\\.jar$");
79
80
81
82
83 public static final Pattern CORE_JSF_FILES = Pattern.compile("(^|/)jsf[-][^/]*\\.jar$");
84
85
86
87
88
89 private static final String ANALYZER_NAME = "False Positive Analyzer";
90
91
92
93 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.POST_IDENTIFIER_ANALYSIS;
94
95
96
97
98
99
100 @Override
101 public String getName() {
102 return ANALYZER_NAME;
103 }
104
105
106
107
108
109
110 @Override
111 public AnalysisPhase getAnalysisPhase() {
112 return ANALYSIS_PHASE;
113 }
114
115
116
117
118
119
120
121 @Override
122 protected String getAnalyzerEnabledSettingKey() {
123 return Settings.KEYS.ANALYZER_FALSE_POSITIVE_ENABLED;
124 }
125
126
127
128
129
130
131
132
133
134
135
136 @Override
137 protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
138 removeJreEntries(dependency);
139 removeBadMatches(dependency);
140 removeWrongVersionMatches(dependency);
141 removeSpuriousCPE(dependency);
142 removeDuplicativeEntriesFromJar(dependency, engine);
143 addFalseNegativeCPEs(dependency);
144 }
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166 @SuppressWarnings("null")
167 @SuppressFBWarnings(justification = "null checks are working correctly to prevent NPE", value = {"NP_NULL_ON_SOME_PATH_MIGHT_BE_INFEASIBLE"})
168 private void removeSpuriousCPE(Dependency dependency) {
169 final List<Identifier> ids = new ArrayList<>(dependency.getVulnerableSoftwareIdentifiers());
170 Collections.sort(ids);
171 final ListIterator<Identifier> mainItr = ids.listIterator();
172 while (mainItr.hasNext()) {
173 final Identifier temp = mainItr.next();
174 if (temp instanceof CpeIdentifier) {
175 final CpeIdentifier currentId = (CpeIdentifier) temp;
176 final Cpe currentCpe = currentId.getCpe();
177 final ListIterator<Identifier> subItr = ids.listIterator(mainItr.nextIndex());
178 while (subItr.hasNext()) {
179 final Identifier nextId = subItr.next();
180 if (nextId instanceof CpeIdentifier) {
181 final CpeIdentifier nextCpeId = (CpeIdentifier) nextId;
182 final Cpe nextCpe = nextCpeId.getCpe();
183
184 if (currentCpe.getVendor().equals(nextCpe.getVendor())) {
185 if (currentCpe.getProduct().equals(nextCpe.getProduct())) {
186
187 final String currentVersion = currentCpe.getVersion();
188 final String nextVersion = nextCpe.getVersion();
189 if (currentVersion == null && nextVersion == null) {
190
191 LOGGER.debug("currentVersion and nextVersion are both null?");
192 } else if (currentVersion == null && nextVersion != null) {
193 dependency.removeVulnerableSoftwareIdentifier(currentId);
194 } else if (nextVersion == null && currentVersion != null) {
195 dependency.removeVulnerableSoftwareIdentifier(nextId);
196 } else if (currentVersion.length() < nextVersion.length()) {
197 if (nextVersion.startsWith(currentVersion) || "-".equals(currentVersion)) {
198 dependency.removeVulnerableSoftwareIdentifier(currentId);
199 }
200 } else if (currentVersion.startsWith(nextVersion) || "-".equals(nextVersion)) {
201 dependency.removeVulnerableSoftwareIdentifier(nextId);
202 }
203 }
204 }
205 }
206 }
207 }
208 }
209 }
210
211
212
213
214
215
216
217
218 private void removeJreEntries(Dependency dependency) {
219 final Set<Identifier> removalSet = new HashSet<>();
220 dependency.getVulnerableSoftwareIdentifiers().forEach(i -> {
221 final Matcher coreCPE = CORE_JAVA.matcher(i.getValue());
222 final Matcher coreFiles = CORE_FILES.matcher(dependency.getFileName());
223 final Matcher coreJsfCPE = CORE_JAVA_JSF.matcher(i.getValue());
224 final Matcher coreJsfFiles = CORE_JSF_FILES.matcher(dependency.getFileName());
225 if ((coreCPE.matches() && !coreFiles.matches())
226 || (coreJsfCPE.matches() && !coreJsfFiles.matches())) {
227 removalSet.add(i);
228 }
229
230 });
231 removalSet.forEach(dependency::removeVulnerableSoftwareIdentifier);
232 }
233
234
235
236
237
238
239
240
241 protected void removeBadMatches(Dependency dependency) {
242
243 final Set<Identifier> toRemove = new HashSet<>();
244
245
246
247
248
249
250
251 for (Identifier i : dependency.getVulnerableSoftwareIdentifiers()) {
252
253 if (i instanceof CpeIdentifier) {
254 final CpeIdentifier cpeId = (CpeIdentifier) i;
255 final Cpe cpe = cpeId.getCpe();
256 if ((cpe.getProduct().matches(".*c\\+\\+.*")
257 || ("file".equals(cpe.getVendor()) && "file".equals(cpe.getProduct()))
258 || ("mozilla".equals(cpe.getVendor()) && "mozilla".equals(cpe.getProduct()))
259 || ("cvs".equals(cpe.getVendor()) && "cvs".equals(cpe.getProduct()))
260 || ("ftp".equals(cpe.getVendor()) && "ftp".equals(cpe.getProduct()))
261 || ("tcp".equals(cpe.getVendor()) && "tcp".equals(cpe.getProduct()))
262 || ("ssh".equals(cpe.getVendor()) && "ssh".equals(cpe.getProduct()))
263 || ("lookup".equals(cpe.getVendor()) && "lookup".equals(cpe.getProduct())))
264 && (dependency.getFileName().toLowerCase().endsWith(".jar")
265 || dependency.getFileName().toLowerCase().endsWith("pom.xml")
266 || dependency.getFileName().toLowerCase().endsWith(".dll")
267 || dependency.getFileName().toLowerCase().endsWith(".exe")
268 || dependency.getFileName().toLowerCase().endsWith(".nuspec")
269 || dependency.getFileName().toLowerCase().endsWith(".zip")
270 || dependency.getFileName().toLowerCase().endsWith(".sar")
271 || dependency.getFileName().toLowerCase().endsWith(".apk")
272 || dependency.getFileName().toLowerCase().endsWith(".tar")
273 || dependency.getFileName().toLowerCase().endsWith(".gz")
274 || dependency.getFileName().toLowerCase().endsWith(".tgz")
275 || dependency.getFileName().toLowerCase().endsWith(".rpm")
276 || dependency.getFileName().toLowerCase().endsWith(".ear")
277 || dependency.getFileName().toLowerCase().endsWith(".war"))) {
278 toRemove.add(i);
279 } else if ((("jquery".equals(cpe.getVendor()) && "jquery".equals(cpe.getProduct()))
280 || ("prototypejs".equals(cpe.getVendor()) && "prototype".equals(cpe.getProduct()))
281 || ("yahoo".equals(cpe.getVendor()) && "yui".equals(cpe.getProduct())))
282 && (dependency.getFileName().toLowerCase().endsWith(".jar")
283 || dependency.getFileName().toLowerCase().endsWith("pom.xml")
284 || dependency.getFileName().toLowerCase().endsWith(".dll")
285 || dependency.getFileName().toLowerCase().endsWith(".exe"))) {
286 toRemove.add(i);
287 } else if ((("microsoft".equals(cpe.getVendor()) && "excel".equals(cpe.getProduct()))
288 || ("microsoft".equals(cpe.getVendor()) && "word".equals(cpe.getProduct()))
289 || ("microsoft".equals(cpe.getVendor()) && "visio".equals(cpe.getProduct()))
290 || ("microsoft".equals(cpe.getVendor()) && "powerpoint".equals(cpe.getProduct()))
291 || ("microsoft".equals(cpe.getVendor()) && "office".equals(cpe.getProduct()))
292 || ("core_ftp".equals(cpe.getVendor()) && "core_ftp".equals(cpe.getProduct())))
293 && (dependency.getFileName().toLowerCase().endsWith(".jar")
294 || dependency.getFileName().toLowerCase().endsWith(".ear")
295 || dependency.getFileName().toLowerCase().endsWith(".war")
296 || dependency.getFileName().toLowerCase().endsWith("pom.xml"))) {
297 toRemove.add(i);
298 } else if (("apache".equals(cpe.getVendor()) && "maven".equals(cpe.getProduct()))
299 && !dependency.getFileName().toLowerCase().matches("maven-core-[\\d.]+\\.jar")) {
300 toRemove.add(i);
301 } else if (("m-core".equals(cpe.getVendor()) && "m-core".equals(cpe.getProduct()))) {
302 boolean found = false;
303 for (Evidence e : dependency.getEvidence(EvidenceType.PRODUCT)) {
304 if ("m-core".equalsIgnoreCase(e.getValue())) {
305 found = true;
306 break;
307 }
308 }
309 if (!found) {
310 for (Evidence e : dependency.getEvidence(EvidenceType.VENDOR)) {
311 if ("m-core".equalsIgnoreCase(e.getValue())) {
312 found = true;
313 break;
314 }
315 }
316 }
317 if (!found) {
318 toRemove.add(i);
319 }
320 } else if (("jboss".equals(cpe.getVendor()) && "jboss".equals(cpe.getProduct()))
321 && !dependency.getFileName().toLowerCase().matches("jboss-?[\\d.-]+(GA)?\\.jar")) {
322 toRemove.add(i);
323 } else if ("java-websocket_project".equals(cpe.getVendor())
324 && "java-websocket".equals(cpe.getProduct())) {
325 boolean found = false;
326 for (Identifier si : dependency.getSoftwareIdentifiers()) {
327 if (si.getValue().toLowerCase().contains("org.java-websocket/java-websocket")) {
328 found = true;
329 break;
330 }
331 }
332 if (!found) {
333 toRemove.add(i);
334 }
335 }
336 }
337 }
338 toRemove.forEach(dependency::removeVulnerableSoftwareIdentifier);
339 }
340
341
342
343
344
345
346
347 private void removeWrongVersionMatches(Dependency dependency) {
348 final Set<Identifier> identifiersToRemove = new HashSet<>();
349 final String fileName = dependency.getFileName();
350 if (fileName != null && fileName.contains("axis2")) {
351 dependency.getVulnerableSoftwareIdentifiers().stream()
352 .filter((i) -> (i instanceof CpeIdentifier))
353 .map(i -> (CpeIdentifier) i)
354 .forEach((i) -> {
355 final Cpe cpe = i.getCpe();
356 if ("apache".equals(cpe.getVendor()) && "axis".equals(cpe.getProduct())) {
357 identifiersToRemove.add(i);
358 }
359 });
360 } else if (fileName != null && fileName.contains("axis")) {
361 dependency.getVulnerableSoftwareIdentifiers().stream()
362 .filter((i) -> (i instanceof CpeIdentifier))
363 .map(i -> (CpeIdentifier) i)
364 .forEach((i) -> {
365 final Cpe cpe = i.getCpe();
366 if ("apache".equals(cpe.getVendor()) && "axis2".equals(cpe.getProduct())) {
367 identifiersToRemove.add(i);
368 }
369 });
370 }
371 identifiersToRemove.forEach(dependency::removeVulnerableSoftwareIdentifier);
372 }
373
374
375
376
377
378
379
380
381
382 @SuppressWarnings("UnnecessaryParentheses")
383 private void addFalseNegativeCPEs(Dependency dependency) {
384 final CpeBuilder builder = new CpeBuilder();
385
386 final List<Identifier> identifiersToAdd = new ArrayList<>();
387 dependency.getVulnerableSoftwareIdentifiers().stream()
388 .filter((i) -> (i instanceof CpeIdentifier))
389 .map(i -> (CpeIdentifier) i)
390 .forEach((i) -> {
391 final Cpe cpe = i.getCpe();
392 if ((("oracle".equals(cpe.getVendor())
393 && ("opensso".equals(cpe.getProduct()) || "opensso_enterprise".equals(cpe.getProduct()))))
394 || ("sun".equals(cpe.getVendor())
395 && ("opensso".equals(cpe.getProduct()) || "opensso_enterprise".equals(cpe.getProduct())))) {
396
397 try {
398 final Cpe newCpe1 = builder.part(Part.APPLICATION).vendor("sun")
399 .product("opensso_enterprise").version(cpe.getVersion()).build();
400 final Cpe newCpe2 = builder.part(Part.APPLICATION).vendor("oracle")
401 .product("opensso_enterprise").version(cpe.getVersion()).build();
402 final Cpe newCpe3 = builder.part(Part.APPLICATION).vendor("sun")
403 .product("opensso").version(cpe.getVersion()).build();
404 final Cpe newCpe4 = builder.part(Part.APPLICATION).vendor("oracle")
405 .product("opensso").version(cpe.getVersion()).build();
406 final CpeIdentifier newCpeId1 = new CpeIdentifier(newCpe1, i.getConfidence());
407 final CpeIdentifier newCpeId2 = new CpeIdentifier(newCpe2, i.getConfidence());
408 final CpeIdentifier newCpeId3 = new CpeIdentifier(newCpe3, i.getConfidence());
409 final CpeIdentifier newCpeId4 = new CpeIdentifier(newCpe4, i.getConfidence());
410 identifiersToAdd.add(newCpeId1);
411 identifiersToAdd.add(newCpeId2);
412 identifiersToAdd.add(newCpeId3);
413 identifiersToAdd.add(newCpeId4);
414
415 } catch (CpeValidationException ex) {
416 LOGGER.warn("Unable to add oracle and sun CPEs", ex);
417 }
418 }
419 if ("apache".equals(cpe.getVendor()) && "santuario_xml_security_for_java".equals(cpe.getProduct())) {
420 try {
421 final Cpe newCpe1 = builder.part(Part.APPLICATION).vendor("apache")
422 .product("xml_security_for_java").version(cpe.getVersion()).build();
423 final CpeIdentifier newCpeId1 = new CpeIdentifier(newCpe1, i.getConfidence());
424 identifiersToAdd.add(newCpeId1);
425 } catch (CpeValidationException ex) {
426 LOGGER.warn("Unable to add apache xml_security_for_java CPE", ex);
427 }
428 }
429 });
430 identifiersToAdd.forEach(dependency::addVulnerableSoftwareIdentifier);
431 }
432
433
434
435
436
437
438
439
440
441 private synchronized void removeDuplicativeEntriesFromJar(Dependency dependency, Engine engine) {
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468 }
469
470
471
472
473
474
475
476
477
478 private Dependency findDependency(String dependencyPath, Dependency[] dependencies) {
479 for (Dependency d : dependencies) {
480 if (d.getFilePath().equalsIgnoreCase(dependencyPath)) {
481 return d;
482 }
483 }
484 return null;
485 }
486 }