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 com.github.packageurl.MalformedPackageURLException;
21 import com.github.packageurl.PackageURL;
22 import com.github.packageurl.PackageURLBuilder;
23 import java.io.File;
24 import org.owasp.dependencycheck.Engine;
25 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
26 import org.owasp.dependencycheck.data.nuget.MSBuildProjectParseException;
27 import org.owasp.dependencycheck.data.nuget.NugetPackageReference;
28 import org.owasp.dependencycheck.data.nuget.XPathMSBuildProjectParser;
29 import org.owasp.dependencycheck.dependency.Confidence;
30 import org.owasp.dependencycheck.dependency.Dependency;
31 import org.owasp.dependencycheck.dependency.EvidenceType;
32 import org.owasp.dependencycheck.exception.InitializationException;
33 import org.owasp.dependencycheck.utils.FileFilterBuilder;
34 import org.owasp.dependencycheck.utils.Settings;
35 import org.owasp.dependencycheck.utils.Checksum;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import javax.annotation.concurrent.ThreadSafe;
40 import java.io.FileFilter;
41 import java.io.FileInputStream;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.nio.file.Path;
45 import java.nio.file.Paths;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Properties;
51 import java.util.Set;
52 import org.apache.commons.io.input.BOMInputStream;
53
54 import static org.owasp.dependencycheck.analyzer.NuspecAnalyzer.DEPENDENCY_ECOSYSTEM;
55 import org.owasp.dependencycheck.data.nuget.DirectoryBuildPropsParser;
56 import org.owasp.dependencycheck.data.nuget.DirectoryPackagesPropsParser;
57 import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
58 import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
59
60
61
62
63
64
65 @ThreadSafe
66 public class MSBuildProjectAnalyzer extends AbstractFileTypeAnalyzer {
67
68
69
70
71 private static final Logger LOGGER = LoggerFactory.getLogger(NuspecAnalyzer.class);
72
73
74
75
76 private static final String ANALYZER_NAME = "MSBuild Project Analyzer";
77
78
79
80
81 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
82
83
84
85
86 private static final String[] SUPPORTED_EXTENSIONS = new String[]{"csproj", "vbproj"};
87
88
89
90
91 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(SUPPORTED_EXTENSIONS).build();
92
93
94
95 private static final String IMPORT_GET_DIRECTORY = "$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..,"
96 + "Directory.Build.props))\\Directory.Build.props";
97
98
99
100 private static final String IMPORT_GET_PATH_OF_FILE = "$([MSBuild]::GetPathOfFileAbove('Directory.Build.props','"
101 + "$(MSBuildThisFileDirectory)../'))";
102
103
104
105 private static final String DIRECTORY_BUILDPROPS = "Directory.Build.props";
106
107
108
109 private static final String DIRECTORY_PACKAGESPROPS = "Directory.Packages.props";
110
111 @Override
112 public String getName() {
113 return ANALYZER_NAME;
114 }
115
116 @Override
117 public AnalysisPhase getAnalysisPhase() {
118 return ANALYSIS_PHASE;
119 }
120
121 @Override
122 protected FileFilter getFileFilter() {
123 return FILTER;
124 }
125
126 @Override
127 protected String getAnalyzerEnabledSettingKey() {
128 return Settings.KEYS.ANALYZER_MSBUILD_PROJECT_ENABLED;
129 }
130
131 @Override
132 protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
133
134 }
135
136 @Override
137 @SuppressWarnings("StringSplitter")
138 protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
139 final File parent = dependency.getActualFile().getParentFile();
140
141 try {
142
143 final Properties props = loadDirectoryBuildProps(parent);
144
145 final Map<String, String> centrallyManaged = loadCentrallyManaged(parent, props);
146
147 LOGGER.debug("Checking MSBuild project file {}", dependency);
148
149 final XPathMSBuildProjectParser parser = new XPathMSBuildProjectParser();
150 final List<NugetPackageReference> packages;
151
152 try (FileInputStream fis = new FileInputStream(dependency.getActualFilePath());
153 BOMInputStream bis = BOMInputStream.builder().setInputStream(fis).get()) {
154
155 bis.getBOM();
156 packages = parser.parse(bis, props, centrallyManaged);
157 } catch (MSBuildProjectParseException | FileNotFoundException ex) {
158 throw new AnalysisException(ex);
159 }
160
161 if (packages == null || packages.isEmpty()) {
162 return;
163 }
164
165 for (NugetPackageReference npr : packages) {
166 final Dependency child = new Dependency(dependency.getActualFile(), true);
167
168 final String id = npr.getId();
169 final String version = npr.getVersion();
170
171 child.setEcosystem(DEPENDENCY_ECOSYSTEM);
172 child.setName(id);
173 child.setVersion(version);
174 try {
175 final PackageURL purl = PackageURLBuilder.aPackageURL().withType("nuget").withName(id).withVersion(version).build();
176 child.addSoftwareIdentifier(new PurlIdentifier(purl, Confidence.HIGHEST));
177 } catch (MalformedPackageURLException ex) {
178 LOGGER.debug("Unable to build package url for msbuild", ex);
179 final GenericIdentifier gid = new GenericIdentifier("msbuild:" + id + "@" + version, Confidence.HIGHEST);
180 child.addSoftwareIdentifier(gid);
181 }
182 child.setPackagePath(String.format("%s:%s", id, version));
183 child.setSha1sum(Checksum.getSHA1Checksum(String.format("%s:%s", id, version)));
184 child.setSha256sum(Checksum.getSHA256Checksum(String.format("%s:%s", id, version)));
185 child.setMd5sum(Checksum.getMD5Checksum(String.format("%s:%s", id, version)));
186
187 child.addEvidence(EvidenceType.PRODUCT, "msbuild", "id", id, Confidence.HIGHEST);
188 child.addEvidence(EvidenceType.VERSION, "msbuild", "version", version, Confidence.HIGHEST);
189
190 if (id.indexOf('.') > 0) {
191 final String[] parts = id.split("\\.");
192
193
194 child.addEvidence(EvidenceType.VENDOR, "msbuild", "id", parts[0], Confidence.MEDIUM);
195 child.addEvidence(EvidenceType.PRODUCT, "msbuild", "id", parts[1], Confidence.MEDIUM);
196
197 if (parts.length > 2) {
198 final String rest = id.substring(id.indexOf('.') + 1);
199 child.addEvidence(EvidenceType.PRODUCT, "msbuild", "id", rest, Confidence.MEDIUM);
200 }
201 } else {
202
203 child.addEvidence(EvidenceType.VENDOR, "msbuild", "id", id, Confidence.LOW);
204 }
205
206 engine.addDependency(child);
207 }
208
209 } catch (Throwable e) {
210 throw new AnalysisException(e);
211 }
212 }
213
214
215
216
217
218
219
220
221
222 private Properties loadDirectoryBuildProps(File directory) throws MSBuildProjectParseException {
223 final Properties props = new Properties();
224 if (directory == null || !directory.isDirectory()) {
225 return props;
226 }
227
228 final File directoryProps = locateDirectoryBuildFile(DIRECTORY_BUILDPROPS, directory);
229 if (directoryProps != null) {
230 final Map<String, String> entries = readDirectoryBuildProps(directoryProps);
231
232 if (entries != null) {
233 for (Map.Entry<String, String> entry : entries.entrySet()) {
234 props.put(entry.getKey(), entry.getValue());
235 }
236 }
237 }
238 return props;
239 }
240
241
242
243
244
245
246
247
248 private File locateDirectoryBuildFile(String name, File directory) {
249 File search = directory;
250 while (search != null && search.isDirectory()) {
251 final File props = new File(search, name);
252 if (props.isFile()) {
253 return props;
254 }
255 search = search.getParentFile();
256 }
257 return null;
258 }
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276 private File getImport(String importStatement, File currentFile) {
277
278
279 if (importStatement == null || importStatement.isEmpty()) {
280 return null;
281 }
282 if (importStatement.startsWith("$")) {
283 final String compact = importStatement.replaceAll("\\s", "");
284 if (IMPORT_GET_PATH_OF_FILE.equalsIgnoreCase(compact)
285 || IMPORT_GET_DIRECTORY.equalsIgnoreCase(compact)) {
286 return locateDirectoryBuildFile("Directory.Build.props", currentFile.getParentFile().getParentFile());
287 } else if (importStatement.startsWith("$(MSBuildThisFileDirectory)")) {
288 final String path = importStatement.substring(27);
289 final File currentDirectory = currentFile.getParentFile();
290 final Path p = Paths.get(currentDirectory.getAbsolutePath(),
291 path.replace('\\', File.separatorChar).replace('/', File.separatorChar));
292 final File f = p.normalize().toFile();
293 if (f.isFile() && !f.equals(currentFile)) {
294 return f;
295 }
296 }
297 } else {
298 final File currentDirectory = currentFile.getParentFile();
299 final Path p = Paths.get(currentDirectory.getAbsolutePath(),
300 importStatement.replace('\\', File.separatorChar).replace('/', File.separatorChar));
301
302 final File f = p.normalize().toFile();
303
304 if (f.isFile() && !f.equals(currentFile)) {
305 return f;
306 }
307 }
308 LOGGER.warn("Unable to import Directory.Build.props import `{}` in `{}`", importStatement, currentFile);
309 return null;
310 }
311
312 private Map<String, String> readDirectoryBuildProps(File directoryProps) throws MSBuildProjectParseException {
313 Map<String, String> entries = null;
314 final Set<String> imports = new HashSet<>();
315 if (directoryProps != null && directoryProps.isFile()) {
316 final DirectoryBuildPropsParser parser = new DirectoryBuildPropsParser();
317 try (FileInputStream fis = new FileInputStream(directoryProps);
318 BOMInputStream bis = BOMInputStream.builder().setInputStream(fis).get()) {
319
320 bis.getBOM();
321 entries = parser.parse(bis);
322 imports.addAll(parser.getImports());
323 } catch (IOException ex) {
324 throw new MSBuildProjectParseException("Error reading Directory.Build.props", ex);
325 }
326
327 for (String importStatement : imports) {
328 final File parentBuildProps = getImport(importStatement, directoryProps);
329 if (parentBuildProps != null && !directoryProps.equals(parentBuildProps)) {
330 final Map<String, String> parentEntries = readDirectoryBuildProps(parentBuildProps);
331 if (parentEntries != null) {
332 parentEntries.putAll(entries);
333 entries = parentEntries;
334 }
335 }
336 }
337 return entries;
338 }
339 return null;
340 }
341
342 private Map<String, String> loadCentrallyManaged(File folder, Properties props) throws MSBuildProjectParseException {
343 final File packages = locateDirectoryBuildFile(DIRECTORY_PACKAGESPROPS, folder);
344 if (packages != null && packages.isFile()) {
345 final DirectoryPackagesPropsParser parser = new DirectoryPackagesPropsParser();
346 try (FileInputStream fis = new FileInputStream(packages);
347 BOMInputStream bis = BOMInputStream.builder().setInputStream(fis).get()) {
348
349 bis.getBOM();
350 return parser.parse(bis, props);
351 } catch (IOException ex) {
352 throw new MSBuildProjectParseException("Error reading Directory.Build.props", ex);
353 }
354 }
355 return new HashMap<>();
356 }
357
358 }