1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.agent;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.util.List;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25 import javax.annotation.concurrent.NotThreadSafe;
26 import org.owasp.dependencycheck.Engine;
27 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
28 import org.owasp.dependencycheck.data.update.exception.UpdateException;
29 import org.owasp.dependencycheck.dependency.Dependency;
30 import org.owasp.dependencycheck.dependency.Vulnerability;
31 import org.owasp.dependencycheck.dependency.naming.Identifier;
32 import org.owasp.dependencycheck.exception.ExceptionCollection;
33 import org.owasp.dependencycheck.exception.ReportException;
34 import org.owasp.dependencycheck.exception.ScanAgentException;
35 import org.owasp.dependencycheck.reporting.ReportGenerator;
36 import org.owasp.dependencycheck.utils.Settings;
37 import org.owasp.dependencycheck.utils.SeverityUtil;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 @SuppressWarnings("unused")
68 @NotThreadSafe
69 public class DependencyCheckScanAgent {
70
71
72
73
74
75 private static final String NEW_LINE = System.getProperty("line.separator", "\n").intern();
76
77
78
79 private static final Logger LOGGER = LoggerFactory.getLogger(DependencyCheckScanAgent.class);
80
81
82
83 private String applicationName = "Dependency-Check";
84
85
86
87 private List<Dependency> dependencies;
88
89
90
91 private String dataDirectory = null;
92
93
94
95
96 private String reportOutputDirectory;
97
98
99
100
101
102
103
104 private Double failBuildOnCVSS = 11.0;
105
106
107
108
109 private boolean autoUpdate = true;
110
111
112
113 private String nvdApiKey;
114
115
116
117
118
119 private boolean updateOnly = false;
120
121
122
123 private boolean generateReport = true;
124
125
126
127
128
129
130 private ReportGenerator.Format reportFormat = ReportGenerator.Format.HTML;
131
132
133
134 private String proxyServer;
135
136
137
138 private String proxyPort;
139
140
141
142 private String proxyUsername;
143
144
145
146 private String proxyPassword;
147
148
149
150 private String connectionTimeout;
151
152
153
154 private String readTimeout;
155
156
157
158 private String logFile = null;
159
160
161
162 private boolean showSummary = true;
163
164
165
166 private String suppressionFile;
167
168
169
170 private String databasePassword;
171
172
173
174
175 private String cpeStartsWithFilter;
176
177
178
179 private boolean centralAnalyzerEnabled = true;
180
181
182
183 private boolean failOnUnusedSuppressionRule = false;
184
185
186
187 private String centralUrl;
188
189
190
191 private boolean nexusAnalyzerEnabled = true;
192
193
194
195 private String nexusUrl;
196
197
198
199 private boolean nexusUsesProxy = true;
200
201
202
203 private String databaseDriverName;
204
205
206
207 private String databaseDriverPath;
208
209
210
211 private String connectionString;
212
213
214
215 private String databaseUser;
216
217
218
219
220 private String zipExtensions;
221
222
223
224 private String pathToCore;
225
226
227
228 private Settings settings;
229
230
231
232
233
234 private String propertiesFilePath;
235
236
237
238
239
240
241
242
243 public String getApplicationName() {
244 return applicationName;
245 }
246
247
248
249
250
251
252 public void setApplicationName(String applicationName) {
253 this.applicationName = applicationName;
254 }
255
256
257
258
259
260
261 public String getNvdApiKey() {
262 return nvdApiKey;
263 }
264
265
266
267
268
269
270 public void setNvdApiKey(String nvdApiKey) {
271 this.nvdApiKey = nvdApiKey;
272 }
273
274
275
276
277
278
279 public List<Dependency> getDependencies() {
280 return dependencies;
281 }
282
283
284
285
286
287
288 public void setDependencies(List<Dependency> dependencies) {
289 this.dependencies = dependencies;
290 }
291
292
293
294
295
296
297 public String getDataDirectory() {
298 return dataDirectory;
299 }
300
301
302
303
304
305
306 public void setDataDirectory(String dataDirectory) {
307 this.dataDirectory = dataDirectory;
308 }
309
310
311
312
313
314
315 public String getReportOutputDirectory() {
316 return reportOutputDirectory;
317 }
318
319
320
321
322
323
324 public void setReportOutputDirectory(String reportOutputDirectory) {
325 this.reportOutputDirectory = reportOutputDirectory;
326 }
327
328
329
330
331
332
333 public Double getFailBuildOnCVSS() {
334 return failBuildOnCVSS;
335 }
336
337
338
339
340
341
342 public void setFailBuildOnCVSS(Double failBuildOnCVSS) {
343 this.failBuildOnCVSS = failBuildOnCVSS;
344 }
345
346
347
348
349
350
351 public boolean isAutoUpdate() {
352 return autoUpdate;
353 }
354
355
356
357
358
359
360 public void setAutoUpdate(boolean autoUpdate) {
361 this.autoUpdate = autoUpdate;
362 }
363
364
365
366
367
368
369 public boolean isUpdateOnly() {
370 return updateOnly;
371 }
372
373
374
375
376
377
378 public void setUpdateOnly(boolean updateOnly) {
379 this.updateOnly = updateOnly;
380 }
381
382
383
384
385
386
387 public boolean isGenerateReport() {
388 return generateReport;
389 }
390
391
392
393
394
395
396 public void setGenerateReport(boolean generateReport) {
397 this.generateReport = generateReport;
398 }
399
400
401
402
403
404
405 public ReportGenerator.Format getReportFormat() {
406 return reportFormat;
407 }
408
409
410
411
412
413
414 public void setReportFormat(ReportGenerator.Format reportFormat) {
415 this.reportFormat = reportFormat;
416 }
417
418
419
420
421
422
423 public String getProxyServer() {
424 return proxyServer;
425 }
426
427
428
429
430
431
432 public void setProxyServer(String proxyServer) {
433 this.proxyServer = proxyServer;
434 }
435
436
437
438
439
440
441
442
443
444 @Deprecated
445 public String getProxyUrl() {
446 return proxyServer;
447 }
448
449
450
451
452
453
454
455
456 @Deprecated
457 public void setProxyUrl(String proxyUrl) {
458 this.proxyServer = proxyUrl;
459 }
460
461
462
463
464
465
466 public String getProxyPort() {
467 return proxyPort;
468 }
469
470
471
472
473
474
475 public void setProxyPort(String proxyPort) {
476 this.proxyPort = proxyPort;
477 }
478
479
480
481
482
483
484 public String getProxyUsername() {
485 return proxyUsername;
486 }
487
488
489
490
491
492
493 public void setProxyUsername(String proxyUsername) {
494 this.proxyUsername = proxyUsername;
495 }
496
497
498
499
500
501
502 public String getProxyPassword() {
503 return proxyPassword;
504 }
505
506
507
508
509
510
511 public void setProxyPassword(String proxyPassword) {
512 this.proxyPassword = proxyPassword;
513 }
514
515
516
517
518
519
520 public String getConnectionTimeout() {
521 return connectionTimeout;
522 }
523
524
525
526
527
528
529 public void setConnectionTimeout(String connectionTimeout) {
530 this.connectionTimeout = connectionTimeout;
531 }
532
533
534
535
536
537
538 public String getReadTimeout() {
539 return readTimeout;
540 }
541
542
543
544
545
546
547 public void setReadTimeout(String readTimeout) {
548 this.readTimeout = readTimeout;
549 }
550
551
552
553
554
555
556 public String getLogFile() {
557 return logFile;
558 }
559
560
561
562
563
564
565 public void setLogFile(String logFile) {
566 this.logFile = logFile;
567 }
568
569
570
571
572
573
574 public String getSuppressionFile() {
575 return suppressionFile;
576 }
577
578
579
580
581
582
583 public void setSuppressionFile(String suppressionFile) {
584 this.suppressionFile = suppressionFile;
585 }
586
587
588
589
590
591
592 public boolean isShowSummary() {
593 return showSummary;
594 }
595
596
597
598
599
600
601 public void setShowSummary(boolean showSummary) {
602 this.showSummary = showSummary;
603 }
604
605
606
607
608
609
610
611
612 public void setCpeStartsWithFilter(String cpeStartsWithFilter) {
613 this.cpeStartsWithFilter = cpeStartsWithFilter;
614 }
615
616
617
618
619
620
621
622 public String getCpeStartsWithFilter() {
623 return cpeStartsWithFilter;
624 }
625
626
627
628
629
630
631 public boolean isFailOnUnusedSuppressionRule() {
632 return failOnUnusedSuppressionRule;
633 }
634
635
636
637
638
639
640 public void setFailOnUnusedSuppressionRule(boolean failOnUnusedSuppressionRule) {
641 this.failOnUnusedSuppressionRule = failOnUnusedSuppressionRule;
642 }
643
644
645
646
647
648
649 public boolean isCentralAnalyzerEnabled() {
650 return centralAnalyzerEnabled;
651 }
652
653
654
655
656
657
658 public void setCentralAnalyzerEnabled(boolean centralAnalyzerEnabled) {
659 this.centralAnalyzerEnabled = centralAnalyzerEnabled;
660 }
661
662
663
664
665
666
667 public String getCentralUrl() {
668 return centralUrl;
669 }
670
671
672
673
674
675
676 public void setCentralUrl(String centralUrl) {
677 this.centralUrl = centralUrl;
678 }
679
680
681
682
683
684
685 public boolean isNexusAnalyzerEnabled() {
686 return nexusAnalyzerEnabled;
687 }
688
689
690
691
692
693
694 public void setNexusAnalyzerEnabled(boolean nexusAnalyzerEnabled) {
695 this.nexusAnalyzerEnabled = nexusAnalyzerEnabled;
696 }
697
698
699
700
701
702
703 public String getNexusUrl() {
704 return nexusUrl;
705 }
706
707
708
709
710
711
712 public void setNexusUrl(String nexusUrl) {
713 this.nexusUrl = nexusUrl;
714 }
715
716
717
718
719
720
721 public boolean isNexusUsesProxy() {
722 return nexusUsesProxy;
723 }
724
725
726
727
728
729
730 public void setNexusUsesProxy(boolean nexusUsesProxy) {
731 this.nexusUsesProxy = nexusUsesProxy;
732 }
733
734
735
736
737
738
739 public String getDatabaseDriverName() {
740 return databaseDriverName;
741 }
742
743
744
745
746
747
748 public void setDatabaseDriverName(String databaseDriverName) {
749 this.databaseDriverName = databaseDriverName;
750 }
751
752
753
754
755
756
757 public String getDatabaseDriverPath() {
758 return databaseDriverPath;
759 }
760
761
762
763
764
765
766 public void setDatabaseDriverPath(String databaseDriverPath) {
767 this.databaseDriverPath = databaseDriverPath;
768 }
769
770
771
772
773
774
775 public String getConnectionString() {
776 return connectionString;
777 }
778
779
780
781
782
783
784 public void setConnectionString(String connectionString) {
785 this.connectionString = connectionString;
786 }
787
788
789
790
791
792
793 public String getDatabaseUser() {
794 return databaseUser;
795 }
796
797
798
799
800
801
802 public void setDatabaseUser(String databaseUser) {
803 this.databaseUser = databaseUser;
804 }
805
806
807
808
809
810
811 public String getDatabasePassword() {
812 return databasePassword;
813 }
814
815
816
817
818
819
820 public void setDatabasePassword(String databasePassword) {
821 this.databasePassword = databasePassword;
822 }
823
824
825
826
827
828
829 public String getZipExtensions() {
830 return zipExtensions;
831 }
832
833
834
835
836
837
838 public void setZipExtensions(String zipExtensions) {
839 this.zipExtensions = zipExtensions;
840 }
841
842
843
844
845
846
847 public String getPathToDotnetCore() {
848 return pathToCore;
849 }
850
851
852
853
854
855
856 public void setPathToDotnetCore(String pathToCore) {
857 this.pathToCore = pathToCore;
858 }
859
860
861
862
863
864
865 public String getPropertiesFilePath() {
866 return propertiesFilePath;
867 }
868
869
870
871
872
873
874 public void setPropertiesFilePath(String propertiesFilePath) {
875 this.propertiesFilePath = propertiesFilePath;
876 }
877
878
879
880
881
882
883
884
885
886
887
888 @SuppressWarnings("squid:S2095")
889 private Engine executeDependencyCheck() throws ExceptionCollection {
890 populateSettings();
891 final Engine engine;
892 try {
893 engine = new Engine(settings);
894 } catch (DatabaseException ex) {
895 throw new ExceptionCollection(ex, true);
896 }
897 if (this.updateOnly) {
898 try {
899 engine.doUpdates();
900 } catch (UpdateException ex) {
901 throw new ExceptionCollection(ex);
902 } finally {
903 engine.close();
904 }
905 } else {
906 engine.setDependencies(this.dependencies);
907 engine.analyzeDependencies();
908 }
909 return engine;
910 }
911
912
913
914
915
916
917
918
919
920 private void generateExternalReports(Engine engine, File outDirectory) throws ScanAgentException {
921 try {
922 engine.writeReports(applicationName, outDirectory, this.reportFormat.name(), null);
923 } catch (ReportException ex) {
924 LOGGER.debug("Unexpected exception occurred during analysis; please see the verbose error log for more details.", ex);
925 throw new ScanAgentException("Error generating the report", ex);
926 }
927 }
928
929
930
931
932
933
934 private void populateSettings() {
935 settings = new Settings();
936 if (dataDirectory != null) {
937 settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
938 } else {
939 final File jarPath = new File(DependencyCheckScanAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath());
940 final File base = jarPath.getParentFile();
941 final String sub = settings.getString(Settings.KEYS.DATA_DIRECTORY);
942 final File dataDir = new File(base, sub);
943 settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
944 }
945 if (propertiesFilePath != null) {
946 try {
947 settings.mergeProperties(propertiesFilePath);
948 LOGGER.info("Successfully loaded user-defined properties");
949 } catch (IOException e) {
950 LOGGER.error("Unable to merge user-defined properties", e);
951 LOGGER.error("Continuing execution");
952 }
953 }
954
955 settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
956 settings.setStringIfNotEmpty(Settings.KEYS.PROXY_SERVER, proxyServer);
957 settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PORT, proxyPort);
958 settings.setStringIfNotEmpty(Settings.KEYS.PROXY_USERNAME, proxyUsername);
959 settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PASSWORD, proxyPassword);
960 settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
961 settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_READ_TIMEOUT, readTimeout);
962 settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
963 settings.setStringIfNotEmpty(Settings.KEYS.CVE_CPE_STARTS_WITH_FILTER, cpeStartsWithFilter);
964 settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, centralAnalyzerEnabled);
965 settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_CENTRAL_URL, centralUrl);
966 settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, nexusAnalyzerEnabled);
967 settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
968 settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY, nexusUsesProxy);
969 settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
970 settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
971 settings.setStringIfNotEmpty(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
972 settings.setStringIfNotEmpty(Settings.KEYS.DB_USER, databaseUser);
973 settings.setStringIfNotEmpty(Settings.KEYS.DB_PASSWORD, databasePassword);
974 settings.setStringIfNotEmpty(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, zipExtensions);
975 settings.setStringIfNotEmpty(Settings.KEYS.NVD_API_KEY, nvdApiKey);
976 settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ASSEMBLY_DOTNET_PATH, pathToCore);
977 settings.setBoolean(Settings.KEYS.FAIL_ON_UNUSED_SUPPRESSION_RULE, failOnUnusedSuppressionRule);
978 }
979
980
981
982
983
984
985
986
987 public Engine execute() throws ScanAgentException {
988 Engine engine = null;
989 try {
990 engine = executeDependencyCheck();
991 if (!this.updateOnly) {
992 if (this.generateReport) {
993 generateExternalReports(engine, new File(this.reportOutputDirectory));
994 }
995 if (this.showSummary) {
996 showSummary(engine.getDependencies());
997 }
998 if (this.failBuildOnCVSS <= 10.0) {
999 checkForFailure(engine.getDependencies());
1000 }
1001 }
1002 } catch (ExceptionCollection ex) {
1003 if (ex.isFatal()) {
1004 LOGGER.error("A fatal exception occurred during analysis; analysis has stopped. Please see the debug log for more details.");
1005 LOGGER.debug("", ex);
1006 }
1007 throw new ScanAgentException("One or more exceptions occurred during analysis; please see the debug log for more details.", ex);
1008 } finally {
1009 if (engine != null) {
1010 engine.close();
1011 }
1012 settings.cleanup(true);
1013 }
1014 return engine;
1015 }
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025 private void checkForFailure(Dependency[] dependencies) throws ScanAgentException {
1026 final StringBuilder ids = new StringBuilder();
1027 for (Dependency d : dependencies) {
1028 boolean addName = true;
1029 for (Vulnerability v : d.getVulnerabilities()) {
1030 final double cvssV2 = v.getCvssV2() != null && v.getCvssV2().getCvssData() != null
1031 && v.getCvssV2().getCvssData().getBaseScore() != null ? v.getCvssV2().getCvssData().getBaseScore() : -1;
1032 final double cvssV3 = v.getCvssV3() != null && v.getCvssV3().getCvssData() != null
1033 && v.getCvssV3().getCvssData().getBaseScore() != null ? v.getCvssV3().getCvssData().getBaseScore() : -1;
1034 final double cvssV4 = v.getCvssV4() != null && v.getCvssV4().getCvssData() != null
1035 && v.getCvssV4().getCvssData().getBaseScore() != null ? v.getCvssV4().getCvssData().getBaseScore() : -1;
1036 final boolean useUnscored = cvssV2 == -1 && cvssV3 == -1 && cvssV4 == -1;
1037 final double unscoredCvss = (useUnscored && v.getUnscoredSeverity() != null) ? SeverityUtil.estimateCvssV2(v.getUnscoredSeverity()) : -1;
1038 if (cvssV2 >= failBuildOnCVSS
1039 || cvssV3 >= failBuildOnCVSS
1040 || cvssV4 >= failBuildOnCVSS
1041 || unscoredCvss >= failBuildOnCVSS
1042
1043 || failBuildOnCVSS <= 0.0f
1044 ) {
1045 if (addName) {
1046 addName = false;
1047 ids.append(NEW_LINE).append(d.getFileName()).append(" (")
1048 .append(Stream.concat(d.getSoftwareIdentifiers().stream(), d.getVulnerableSoftwareIdentifiers().stream())
1049 .map(Identifier::getValue)
1050 .collect(Collectors.joining(", ")))
1051 .append("): ")
1052 .append(v.getName());
1053 } else {
1054 ids.append(", ").append(v.getName());
1055 }
1056 }
1057 }
1058 }
1059 if (ids.length() > 0) {
1060 final String msg;
1061 if (showSummary) {
1062 msg = String.format("%n%nDependency-Check Failure:%n"
1063 + "One or more dependencies were identified with vulnerabilities that have a CVSS score greater than or equal to '%.1f': %s%n"
1064 + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids);
1065 } else {
1066 msg = String.format("%n%nDependency-Check Failure:%n"
1067 + "One or more dependencies were identified with vulnerabilities.%n%n"
1068 + "See the dependency-check report for more details.%n%n");
1069 }
1070 throw new ScanAgentException(msg);
1071 }
1072 }
1073
1074
1075
1076
1077
1078
1079
1080 public static void showSummary(Dependency[] dependencies) {
1081 showSummary(null, dependencies);
1082 }
1083
1084
1085
1086
1087
1088
1089
1090
1091 public static void showSummary(String projectName, Dependency[] dependencies) {
1092 final StringBuilder summary = new StringBuilder();
1093 for (Dependency d : dependencies) {
1094 final String ids = d.getVulnerabilities(true).stream()
1095 .map(Vulnerability::getName)
1096 .collect(Collectors.joining(", "));
1097 if (ids.length() > 0) {
1098 summary.append(d.getFileName()).append(" (");
1099 summary.append(Stream.concat(d.getSoftwareIdentifiers().stream(), d.getVulnerableSoftwareIdentifiers().stream())
1100 .map(Identifier::getValue)
1101 .collect(Collectors.joining(", ")));
1102 summary.append(") : ").append(ids).append(NEW_LINE);
1103 }
1104 }
1105 if (summary.length() > 0) {
1106 if (projectName == null || projectName.isEmpty()) {
1107 LOGGER.warn("\n\nOne or more dependencies were identified with known vulnerabilities:\n\n{}\n\n"
1108 + "See the dependency-check report for more details.\n\n",
1109 summary);
1110 } else {
1111 LOGGER.warn("\n\nOne or more dependencies were identified with known vulnerabilities in {}:\n\n{}\n\n"
1112 + "See the dependency-check report for more details.\n\n",
1113 projectName,
1114 summary);
1115 }
1116 }
1117 }
1118 }