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
315 private Dependency createDependencyForGem(Engine engine, File gemFile, String parentName, String fileName,
316 String filePath, String gem) throws IOException {
317 final String displayFileName = String.format("%s%c%s:%s", parentName, File.separatorChar, fileName, gem);
318 final Dependency dependency = new Dependency(gemFile, true);
319 dependency.setSha1sum(Checksum.getSHA1Checksum(displayFileName));
320 dependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
321 dependency.addEvidence(EvidenceType.PRODUCT, "bundler-audit", "Name", gem, Confidence.HIGHEST);
322
323
324 dependency.setDisplayFileName(displayFileName);
325 dependency.setFileName(fileName);
326 dependency.setFilePath(filePath);
327
328 dependency.setSha1sum(Checksum.getSHA1Checksum(displayFileName));
329 engine.addDependency(dependency);
330 return dependency;
331 }
332 }