1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.processing;
19
20 import com.github.packageurl.MalformedPackageURLException;
21 import com.github.packageurl.PackageURL;
22 import com.github.packageurl.PackageURLBuilder;
23 import io.github.jeremylong.openvulnerability.client.nvd.CvssV2;
24 import io.github.jeremylong.openvulnerability.client.nvd.CvssV2Data;
25 import org.owasp.dependencycheck.Engine;
26 import org.owasp.dependencycheck.data.nvdcve.CveDB;
27 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
28 import org.owasp.dependencycheck.dependency.Confidence;
29 import org.owasp.dependencycheck.dependency.Dependency;
30 import org.owasp.dependencycheck.dependency.EvidenceType;
31 import org.owasp.dependencycheck.dependency.Reference;
32 import org.owasp.dependencycheck.dependency.Vulnerability;
33 import org.owasp.dependencycheck.dependency.VulnerableSoftware;
34 import org.owasp.dependencycheck.dependency.VulnerableSoftwareBuilder;
35 import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
36 import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
37 import org.owasp.dependencycheck.utils.Checksum;
38 import org.owasp.dependencycheck.utils.processing.Processor;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import us.springett.parsers.cpe.exceptions.CpeValidationException;
42 import us.springett.parsers.cpe.values.Part;
43
44 import java.io.BufferedReader;
45 import java.io.File;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.InputStreamReader;
49 import java.nio.charset.StandardCharsets;
50 import java.util.HashMap;
51 import java.util.Map;
52
53 import static org.owasp.dependencycheck.analyzer.RubyBundleAuditAnalyzer.ADVISORY;
54 import static org.owasp.dependencycheck.analyzer.RubyBundleAuditAnalyzer.CRITICALITY;
55 import static org.owasp.dependencycheck.analyzer.RubyBundleAuditAnalyzer.CVE;
56 import static org.owasp.dependencycheck.analyzer.RubyBundleAuditAnalyzer.DEPENDENCY_ECOSYSTEM;
57 import static org.owasp.dependencycheck.analyzer.RubyBundleAuditAnalyzer.NAME;
58 import static org.owasp.dependencycheck.analyzer.RubyBundleAuditAnalyzer.VERSION;
59
60
61
62
63
64
65 public class BundlerAuditProcessor extends Processor<InputStream> {
66
67
68
69
70 private static final Logger LOGGER = LoggerFactory.getLogger(BundlerAuditProcessor.class);
71
72
73
74 private final Dependency gemDependency;
75
76
77
78 private final Engine engine;
79
80
81
82 private IOException ioException;
83
84
85
86 private CpeValidationException cpeException;
87
88
89
90
91
92
93
94 public BundlerAuditProcessor(Dependency gemDependency, Engine engine) {
95 this.gemDependency = gemDependency;
96 this.engine = engine;
97 }
98
99
100
101
102
103
104
105
106 @Override
107 public void close() throws IOException, CpeValidationException {
108 if (ioException != null) {
109 addSuppressedExceptions(ioException, cpeException);
110 throw ioException;
111 }
112 if (cpeException != null) {
113 throw cpeException;
114 }
115 }
116
117 @Override
118 public void run() {
119 final String parentName = gemDependency.getActualFile().getParentFile().getName();
120 final String fileName = gemDependency.getFileName();
121 final String filePath = gemDependency.getFilePath();
122 Dependency dependency = null;
123 Vulnerability vulnerability = null;
124 String gem = null;
125 final Map<String, Dependency> map = new HashMap<>();
126 boolean appendToDescription = false;
127
128 try (InputStreamReader ir = new InputStreamReader(getInput(), StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(ir)) {
129
130 String nextLine;
131 while ((nextLine = br.readLine()) != null) {
132 if (nextLine.startsWith(NAME)) {
133 appendToDescription = false;
134 gem = nextLine.substring(NAME.length());
135 if (!map.containsKey(gem)) {
136 map.put(gem, createDependencyForGem(engine, gemDependency.getActualFile(), parentName, fileName, filePath, gem));
137 }
138 dependency = map.get(gem);
139 LOGGER.debug("bundle-audit ({}): {}", parentName, nextLine);
140 } else if (nextLine.startsWith(VERSION)) {
141 vulnerability = createVulnerability(parentName, dependency, gem, nextLine);
142 } else if (nextLine.startsWith(ADVISORY) || nextLine.startsWith(CVE)) {
143 setVulnerabilityName(parentName, dependency, vulnerability, nextLine);
144 } else if (nextLine.startsWith(CRITICALITY)) {
145 addCriticalityToVulnerability(parentName, vulnerability, nextLine);
146 } else if (nextLine.startsWith("URL: ")) {
147 addReferenceToVulnerability(parentName, vulnerability, nextLine);
148 } else if (nextLine.startsWith("Description:") || nextLine.startsWith("Title:")) {
149 appendToDescription = true;
150 if (null != vulnerability) {
151 vulnerability.setDescription("*** Vulnerability obtained from bundle-audit verbose report. "
152 + "Title link may not work. CPE below is guessed. CVSS score is estimated (-1.0 "
153 + " indicates unknown). See link below for full details. *** ");
154 }
155 } else if (appendToDescription && null != vulnerability) {
156 vulnerability.setDescription(vulnerability.getDescription() + nextLine + "\n");
157 }
158 }
159 } catch (IOException ex) {
160 this.ioException = ex;
161 } catch (CpeValidationException ex) {
162 this.cpeException = ex;
163 }
164 }
165
166
167
168
169
170
171
172
173
174 private void setVulnerabilityName(String parentName, Dependency dependency, Vulnerability vulnerability, String nextLine) {
175 final String advisory;
176 if (nextLine.startsWith(CVE)) {
177 advisory = nextLine.substring(CVE.length());
178 } else {
179 advisory = nextLine.substring(ADVISORY.length());
180 }
181 if (null != vulnerability) {
182 vulnerability.setName(advisory);
183 }
184 if (null != dependency) {
185 dependency.addVulnerability(vulnerability);
186 }
187 LOGGER.debug("bundle-audit ({}): {}", parentName, nextLine);
188 }
189
190
191
192
193
194
195
196
197 private void addReferenceToVulnerability(String parentName, Vulnerability vulnerability, String nextLine) {
198 final String url = nextLine.substring("URL: ".length());
199 if (null != vulnerability) {
200 final Reference ref = new Reference();
201 ref.setName(vulnerability.getName());
202 ref.setSource("bundle-audit");
203 ref.setUrl(url);
204 vulnerability.getReferences().add(ref);
205 }
206 LOGGER.debug("bundle-audit ({}): {}", parentName, nextLine);
207 }
208
209
210
211
212
213
214
215
216 private void addCriticalityToVulnerability(String parentName, Vulnerability vulnerability, String nextLine) {
217 if (null != vulnerability) {
218 final String criticality = nextLine.substring(CRITICALITY.length()).trim();
219 Double score = -1.0;
220 Vulnerability v = null;
221 final CveDB cvedb = engine.getDatabase();
222 if (cvedb != null) {
223 try {
224 v = cvedb.getVulnerability(vulnerability.getName());
225 } catch (DatabaseException ex) {
226 LOGGER.debug("Unable to look up vulnerability {}", vulnerability.getName());
227 }
228 }
229 if (v != null && (v.getCvssV2() != null || v.getCvssV3() != null)) {
230 if (v.getCvssV2() != null) {
231 vulnerability.setCvssV2(v.getCvssV2());
232 }
233 if (v.getCvssV3() != null) {
234 vulnerability.setCvssV3(v.getCvssV3());
235 }
236 } else {
237 if ("High".equalsIgnoreCase(criticality)) {
238 score = 8.5;
239 } else if ("Medium".equalsIgnoreCase(criticality)) {
240 score = 5.5;
241 } else if ("Low".equalsIgnoreCase(criticality)) {
242 score = 2.0;
243 }
244 LOGGER.debug("bundle-audit vulnerability missing CVSS data: {}", vulnerability.getName());
245 final CvssV2Data cvssData = new CvssV2Data("2.0", null, null, null, null, null, null, null, score, criticality.toUpperCase(),
246 null, null, null, null, null, null, null, null, null, null);
247 final CvssV2 cvssV2 = new CvssV2(null, null, cvssData, criticality.toUpperCase(), null, null, null, null, null, null, null);
248 vulnerability.setCvssV2(cvssV2);
249 vulnerability.setUnscoredSeverity(null);
250 }
251 }
252 LOGGER.debug("bundle-audit ({}): {}", parentName, nextLine);
253 }
254
255
256
257
258
259
260
261
262
263
264
265
266 private Vulnerability createVulnerability(String parentName, Dependency dependency, String gem, String nextLine) throws CpeValidationException {
267 Vulnerability vulnerability = null;
268 if (null != dependency) {
269 final String version = nextLine.substring(VERSION.length());
270 dependency.addEvidence(EvidenceType.VERSION,
271 "bundler-audit",
272 "Version",
273 version,
274 Confidence.HIGHEST);
275 dependency.setVersion(version);
276 dependency.setName(gem);
277 try {
278 final PackageURL purl = PackageURLBuilder.aPackageURL().withType("gem").withName(dependency.getName())
279 .withVersion(dependency.getVersion()).build();
280 dependency.addSoftwareIdentifier(new PurlIdentifier(purl, Confidence.HIGHEST));
281 } catch (MalformedPackageURLException ex) {
282 LOGGER.debug("Unable to build package url for python", ex);
283 final GenericIdentifier id = new GenericIdentifier("gem:" + dependency.getName() + "@" + dependency.getVersion(),
284 Confidence.HIGHEST);
285 dependency.addSoftwareIdentifier(id);
286 }
287
288 vulnerability = new Vulnerability();
289 vulnerability.setSource(Vulnerability.Source.BUNDLEAUDIT);
290 final VulnerableSoftwareBuilder builder = new VulnerableSoftwareBuilder();
291 final VulnerableSoftware vs = builder.part(Part.APPLICATION)
292 .vendor(gem)
293 .product(String.format("%s_project", gem))
294 .version(version).build();
295 vulnerability.addVulnerableSoftware(vs);
296 vulnerability.setMatchedVulnerableSoftware(vs);
297 vulnerability.setUnscoredSeverity("UNKNOWN");
298 }
299 LOGGER.debug("bundle-audit ({}): {}", parentName, nextLine);
300 return vulnerability;
301 }
302
303
304
305
306
307
308
309
310
311
312
313
314 private Dependency createDependencyForGem(Engine engine, File gemFile, String parentName, String fileName, String filePath, String gem) throws IOException {
315 final String displayFileName = String.format("%s%c%s:%s", parentName, File.separatorChar, fileName, gem);
316 final Dependency dependency = new Dependency(gemFile, true);
317 dependency.setSha1sum(Checksum.getSHA1Checksum(displayFileName));
318 dependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
319 dependency.addEvidence(EvidenceType.PRODUCT, "bundler-audit", "Name", gem, Confidence.HIGHEST);
320
321
322 dependency.setDisplayFileName(displayFileName);
323 dependency.setFileName(fileName);
324 dependency.setFilePath(filePath);
325
326 dependency.setSha1sum(Checksum.getSHA1Checksum(displayFileName));
327 engine.addDependency(dependency);
328 return dependency;
329 }
330 }