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.File;
21 import java.io.FileFilter;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import org.apache.commons.io.IOUtils;
26 import org.apache.commons.io.output.NullOutputStream;
27 import org.owasp.dependencycheck.Engine;
28 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
29 import org.owasp.dependencycheck.dependency.Confidence;
30 import org.owasp.dependencycheck.dependency.Dependency;
31 import org.owasp.dependencycheck.dependency.Evidence;
32 import org.owasp.dependencycheck.utils.FileFilterBuilder;
33 import org.owasp.dependencycheck.utils.Settings;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.w3c.dom.Document;
37 import org.xml.sax.SAXException;
38
39 import javax.xml.parsers.DocumentBuilder;
40 import javax.xml.xpath.XPath;
41 import javax.xml.xpath.XPathExpressionException;
42 import javax.xml.xpath.XPathFactory;
43 import java.util.ArrayList;
44 import java.util.List;
45 import javax.xml.parsers.ParserConfigurationException;
46 import org.owasp.dependencycheck.exception.InitializationException;
47 import org.apache.commons.lang3.SystemUtils;
48 import org.owasp.dependencycheck.utils.XmlUtils;
49
50
51
52
53
54
55
56
57 public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
58
59
60
61
62 private static final String ANALYZER_NAME = "Assembly Analyzer";
63
64
65
66 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
67
68
69
70 private static final String[] SUPPORTED_EXTENSIONS = {"dll", "exe"};
71
72
73
74 private File grokAssemblyExe = null;
75
76
77
78 private static final Logger LOGGER = LoggerFactory.getLogger(AssemblyAnalyzer.class);
79
80
81
82
83
84
85 protected List<String> buildArgumentList() {
86
87 final List<String> args = new ArrayList<String>();
88 if (!SystemUtils.IS_OS_WINDOWS) {
89 if (Settings.getString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH) != null) {
90 args.add(Settings.getString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH));
91 } else if (isInPath("mono")) {
92 args.add("mono");
93 } else {
94 return null;
95 }
96 }
97 args.add(grokAssemblyExe.getPath());
98 return args;
99 }
100
101
102
103
104
105
106
107
108 @Override
109 public void analyzeDependency(Dependency dependency, Engine engine)
110 throws AnalysisException {
111 if (grokAssemblyExe == null) {
112 LOGGER.warn("GrokAssembly didn't get deployed");
113 return;
114 }
115
116 final List<String> args = buildArgumentList();
117 if (args == null) {
118 LOGGER.warn("Assembly Analyzer was unable to execute");
119 return;
120 }
121 args.add(dependency.getActualFilePath());
122 final ProcessBuilder pb = new ProcessBuilder(args);
123 Document doc = null;
124 try {
125 final Process proc = pb.start();
126 final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
127
128 doc = builder.parse(proc.getInputStream());
129
130
131 final String errorStream = IOUtils.toString(proc.getErrorStream(), "UTF-8");
132 if (null != errorStream && !errorStream.isEmpty()) {
133 LOGGER.warn("Error from GrokAssembly: {}", errorStream);
134 }
135
136 int rc = 0;
137 try {
138 rc = proc.waitFor();
139 } catch (InterruptedException ie) {
140 return;
141 }
142 if (rc == 3) {
143 LOGGER.debug("{} is not a .NET assembly or executable and as such cannot be analyzed by dependency-check",
144 dependency.getActualFilePath());
145 return;
146 } else if (rc != 0) {
147 LOGGER.warn("Return code {} from GrokAssembly", rc);
148 }
149
150 final XPath xpath = XPathFactory.newInstance().newXPath();
151
152
153 final String error = xpath.evaluate("/assembly/error", doc);
154 if (error != null && !error.isEmpty()) {
155 throw new AnalysisException(error);
156 }
157
158 final String version = xpath.evaluate("/assembly/version", doc);
159 if (version != null) {
160 dependency.getVersionEvidence().addEvidence(new Evidence("grokassembly", "version",
161 version, Confidence.HIGHEST));
162 }
163
164 final String vendor = xpath.evaluate("/assembly/company", doc);
165 if (vendor != null) {
166 dependency.getVendorEvidence().addEvidence(new Evidence("grokassembly", "vendor",
167 vendor, Confidence.HIGH));
168 }
169
170 final String product = xpath.evaluate("/assembly/product", doc);
171 if (product != null) {
172 dependency.getProductEvidence().addEvidence(new Evidence("grokassembly", "product",
173 product, Confidence.HIGH));
174 }
175
176 } catch (ParserConfigurationException pce) {
177 throw new AnalysisException("Error initializing the assembly analyzer", pce);
178 } catch (IOException ioe) {
179 throw new AnalysisException(ioe);
180 } catch (SAXException saxe) {
181 LOGGER.error("----------------------------------------------------");
182 LOGGER.error("Failed to read the Assembly Analyzer results. "
183 + "On some systems mono-runtime and mono-devel need to be installed.");
184 LOGGER.error("----------------------------------------------------");
185 throw new AnalysisException("Couldn't parse Assembly Analzyzer results (GrokAssembly)", saxe);
186 } catch (XPathExpressionException xpe) {
187
188 throw new AnalysisException(xpe);
189 }
190 }
191
192
193
194
195
196
197
198 @Override
199 public void initializeFileTypeAnalyzer() throws InitializationException {
200 final File tempFile;
201 try {
202 tempFile = File.createTempFile("GKA", ".exe", Settings.getTempDirectory());
203 } catch (IOException ex) {
204 setEnabled(false);
205 throw new InitializationException("Unable to create temporary file for the assembly analyzerr", ex);
206 }
207 FileOutputStream fos = null;
208 InputStream is = null;
209 try {
210 fos = new FileOutputStream(tempFile);
211 is = AssemblyAnalyzer.class.getClassLoader().getResourceAsStream("GrokAssembly.exe");
212 IOUtils.copy(is, fos);
213
214 grokAssemblyExe = tempFile;
215 LOGGER.debug("Extracted GrokAssembly.exe to {}", grokAssemblyExe.getPath());
216 } catch (IOException ioe) {
217 this.setEnabled(false);
218 LOGGER.warn("Could not extract GrokAssembly.exe: {}", ioe.getMessage());
219 throw new InitializationException("Could not extract GrokAssembly.exe", ioe);
220 } finally {
221 if (fos != null) {
222 try {
223 fos.close();
224 } catch (Throwable e) {
225 LOGGER.debug("Error closing output stream");
226 }
227 }
228 if (is != null) {
229 try {
230 is.close();
231 } catch (Throwable e) {
232 LOGGER.debug("Error closing input stream");
233 }
234 }
235 }
236
237
238 final List<String> args = buildArgumentList();
239
240
241
242
243
244
245
246 if (args == null) {
247 setEnabled(false);
248 LOGGER.error("----------------------------------------------------");
249 LOGGER.error(".NET Assembly Analyzer could not be initialized and at least one "
250 + "'exe' or 'dll' was scanned. The 'mono' executable could not be found on "
251 + "the path; either disable the Assembly Analyzer or configure the path mono. "
252 + "On some systems mono-runtime and mono-devel need to be installed.");
253 LOGGER.error("----------------------------------------------------");
254 return;
255 }
256 try {
257 final ProcessBuilder pb = new ProcessBuilder(args);
258 final Process p = pb.start();
259
260 IOUtils.copy(p.getErrorStream(), NullOutputStream.NULL_OUTPUT_STREAM);
261
262 final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
263 final Document doc = builder.parse(p.getInputStream());
264 final XPath xpath = XPathFactory.newInstance().newXPath();
265 final String error = xpath.evaluate("/assembly/error", doc);
266 if (p.waitFor() != 1 || error == null || error.isEmpty()) {
267 LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer, please see the log for more details.");
268 LOGGER.debug("GrokAssembly.exe is not working properly");
269 grokAssemblyExe = null;
270 setEnabled(false);
271 throw new InitializationException("Could not execute .NET AssemblyAnalyzer");
272 }
273 } catch (InitializationException e) {
274 setEnabled(false);
275 throw e;
276 } catch (Throwable e) {
277 LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer;\n"
278 + "this can be ignored unless you are scanning .NET DLLs. Please see the log for more details.");
279 LOGGER.debug("Could not execute GrokAssembly {}", e.getMessage());
280 setEnabled(false);
281 throw new InitializationException("An error occurred with the .NET AssemblyAnalyzer", e);
282 }
283 }
284
285
286
287
288
289
290 @Override
291 public void closeAnalyzer() throws Exception {
292 try {
293 if (grokAssemblyExe != null && !grokAssemblyExe.delete()) {
294 LOGGER.debug("Unable to delete temporary GrokAssembly.exe; attempting delete on exit");
295 grokAssemblyExe.deleteOnExit();
296 }
297 } catch (SecurityException se) {
298 LOGGER.debug("Can't delete temporary GrokAssembly.exe");
299 grokAssemblyExe.deleteOnExit();
300 }
301 }
302
303
304
305
306 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(
307 SUPPORTED_EXTENSIONS).build();
308
309 @Override
310 protected FileFilter getFileFilter() {
311 return FILTER;
312 }
313
314
315
316
317
318
319 @Override
320 public String getName() {
321 return ANALYZER_NAME;
322 }
323
324
325
326
327
328
329 @Override
330 public AnalysisPhase getAnalysisPhase() {
331 return ANALYSIS_PHASE;
332 }
333
334
335
336
337
338
339
340 @Override
341 protected String getAnalyzerEnabledSettingKey() {
342 return Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED;
343 }
344
345
346
347
348
349
350
351
352
353
354 private boolean isInPath(String file) {
355 final ProcessBuilder pb = new ProcessBuilder("which", file);
356 try {
357 final Process proc = pb.start();
358 final int retCode = proc.waitFor();
359 if (retCode == 0) {
360 return true;
361 }
362 } catch (IOException ex) {
363 LOGGER.debug("Path seach failed for " + file);
364 } catch (InterruptedException ex) {
365 LOGGER.debug("Path seach failed for " + file);
366 }
367 return false;
368 }
369 }