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