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.parsers.DocumentBuilderFactory;
41 import javax.xml.xpath.XPath;
42 import javax.xml.xpath.XPathExpressionException;
43 import javax.xml.xpath.XPathFactory;
44 import java.util.ArrayList;
45 import java.util.List;
46 import javax.xml.parsers.ParserConfigurationException;
47 import org.owasp.dependencycheck.exception.InitializationException;
48 import org.apache.commons.lang3.SystemUtils;
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 analyzeFileType(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
127 final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
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 throw new AnalysisException("Couldn't parse GrokAssembly result", saxe);
182 } catch (XPathExpressionException xpe) {
183
184 throw new AnalysisException(xpe);
185 }
186 }
187
188
189
190
191
192
193
194 @Override
195 public void initializeFileTypeAnalyzer() throws InitializationException {
196 final File tempFile;
197 try {
198 tempFile = File.createTempFile("GKA", ".exe", Settings.getTempDirectory());
199 } catch (IOException ex) {
200 setEnabled(false);
201 throw new InitializationException("Unable to create temporary file for the assembly analyzerr", ex);
202 }
203 FileOutputStream fos = null;
204 InputStream is = null;
205 try {
206 fos = new FileOutputStream(tempFile);
207 is = AssemblyAnalyzer.class.getClassLoader().getResourceAsStream("GrokAssembly.exe");
208 IOUtils.copy(is, fos);
209
210 grokAssemblyExe = tempFile;
211 LOGGER.debug("Extracted GrokAssembly.exe to {}", grokAssemblyExe.getPath());
212 } catch (IOException ioe) {
213 this.setEnabled(false);
214 LOGGER.warn("Could not extract GrokAssembly.exe: {}", ioe.getMessage());
215 throw new InitializationException("Could not extract GrokAssembly.exe", ioe);
216 } finally {
217 if (fos != null) {
218 try {
219 fos.close();
220 } catch (Throwable e) {
221 LOGGER.debug("Error closing output stream");
222 }
223 }
224 if (is != null) {
225 try {
226 is.close();
227 } catch (Throwable e) {
228 LOGGER.debug("Error closing input stream");
229 }
230 }
231 }
232
233
234 final List<String> args = buildArgumentList();
235
236
237
238
239
240
241
242 if (args == null) {
243 setEnabled(false);
244 LOGGER.error("----------------------------------------------------");
245 LOGGER.error(".NET Assembly Analyzer could not be initialized and at least one "
246 + "'exe' or 'dll' was scanned. The 'mono' executable could not be found on "
247 + "the path; either disable the Assembly Analyzer or configure the path mono.");
248 LOGGER.error("----------------------------------------------------");
249 return;
250 }
251 try {
252 final ProcessBuilder pb = new ProcessBuilder(args);
253 final Process p = pb.start();
254
255 IOUtils.copy(p.getErrorStream(), NullOutputStream.NULL_OUTPUT_STREAM);
256
257 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
258 factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
259 final DocumentBuilder builder = factory.newDocumentBuilder();
260 final Document doc = builder.parse(p.getInputStream());
261 final XPath xpath = XPathFactory.newInstance().newXPath();
262 final String error = xpath.evaluate("/assembly/error", doc);
263 if (p.waitFor() != 1 || error == null || error.isEmpty()) {
264 LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer, please see the log for more details.");
265 LOGGER.debug("GrokAssembly.exe is not working properly");
266 grokAssemblyExe = null;
267 setEnabled(false);
268 throw new InitializationException("Could not execute .NET AssemblyAnalyzer");
269 }
270 } catch (InitializationException e) {
271 setEnabled(false);
272 throw e;
273 } catch (Throwable e) {
274 LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer;\n"
275 + "this can be ignored unless you are scanning .NET DLLs. Please see the log for more details.");
276 LOGGER.debug("Could not execute GrokAssembly {}", e.getMessage());
277 setEnabled(false);
278 throw new InitializationException("An error occurred with the .NET AssemblyAnalyzer", e);
279 }
280 }
281
282
283
284
285
286
287 @Override
288 public void close() throws Exception {
289 super.close();
290 try {
291 if (grokAssemblyExe != null && !grokAssemblyExe.delete()) {
292 LOGGER.debug("Unable to delete temporary GrokAssembly.exe; attempting delete on exit");
293 grokAssemblyExe.deleteOnExit();
294 }
295 } catch (SecurityException se) {
296 LOGGER.debug("Can't delete temporary GrokAssembly.exe");
297 grokAssemblyExe.deleteOnExit();
298 }
299 }
300
301
302
303
304 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(
305 SUPPORTED_EXTENSIONS).build();
306
307 @Override
308 protected FileFilter getFileFilter() {
309 return FILTER;
310 }
311
312
313
314
315
316
317 @Override
318 public String getName() {
319 return ANALYZER_NAME;
320 }
321
322
323
324
325
326
327 @Override
328 public AnalysisPhase getAnalysisPhase() {
329 return ANALYSIS_PHASE;
330 }
331
332
333
334
335
336
337
338 @Override
339 protected String getAnalyzerEnabledSettingKey() {
340 return Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED;
341 }
342
343
344
345
346
347
348
349
350
351
352 private boolean isInPath(String file) {
353 final ProcessBuilder pb = new ProcessBuilder("which", file);
354 try {
355 final Process proc = pb.start();
356 final int retCode = proc.waitFor();
357 if (retCode == 0) {
358 return true;
359 }
360 } catch (IOException ex) {
361 LOGGER.debug("Path seach failed for " + file);
362 } catch (InterruptedException ex) {
363 LOGGER.debug("Path seach failed for " + file);
364 }
365 return false;
366 }
367 }