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