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.VENDOR, "msbuild", "id", id, Confidence.MEDIUM);
189 child.addEvidence(EvidenceType.VERSION, "msbuild", "version", version, Confidence.HIGHEST);
190
191 if (id.indexOf('.') > 0) {
192 final String[] parts = id.split("\\.");
193
194
195 child.addEvidence(EvidenceType.VENDOR, "msbuild", "id", parts[0], Confidence.MEDIUM);
196 child.addEvidence(EvidenceType.PRODUCT, "msbuild", "id", parts[1], Confidence.MEDIUM);
197 child.addEvidence(EvidenceType.VENDOR, "msbuild", "id", parts[1], Confidence.LOW);
198
199 if (parts.length > 2) {
200 final String rest = id.substring(id.indexOf('.') + 1);
201 child.addEvidence(EvidenceType.PRODUCT, "msbuild", "id", rest, Confidence.MEDIUM);
202 child.addEvidence(EvidenceType.VENDOR, "msbuild", "id", rest, Confidence.LOW);
203 }
204 } else {
205
206 child.addEvidence(EvidenceType.VENDOR, "msbuild", "id", id, Confidence.LOW);
207 }
208
209 engine.addDependency(child);
210 }
211
212 } catch (Throwable e) {
213 throw new AnalysisException(e);
214 }
215 }
216
217
218
219
220
221
222
223
224
225 private Properties loadDirectoryBuildProps(File directory) throws MSBuildProjectParseException {
226 final Properties props = new Properties();
227 if (directory == null || !directory.isDirectory()) {
228 return props;
229 }
230
231 final File directoryProps = locateDirectoryBuildFile(DIRECTORY_BUILDPROPS, directory);
232 if (directoryProps != null) {
233 final Map<String, String> entries = readDirectoryBuildProps(directoryProps);
234
235 if (entries != null) {
236 for (Map.Entry<String, String> entry : entries.entrySet()) {
237 props.put(entry.getKey(), entry.getValue());
238 }
239 }
240 }
241 return props;
242 }
243
244
245
246
247
248
249
250
251 private File locateDirectoryBuildFile(String name, File directory) {
252 File search = directory;
253 while (search != null && search.isDirectory()) {
254 final File props = new File(search, name);
255 if (props.isFile()) {
256 return props;
257 }
258 search = search.getParentFile();
259 }
260 return null;
261 }
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279 private File getImport(String importStatement, File currentFile) {
280
281
282 if (importStatement == null || importStatement.isEmpty()) {
283 return null;
284 }
285 if (importStatement.startsWith("$")) {
286 final String compact = importStatement.replaceAll("\\s", "");
287 if (IMPORT_GET_PATH_OF_FILE.equalsIgnoreCase(compact)
288 || IMPORT_GET_DIRECTORY.equalsIgnoreCase(compact)) {
289 return locateDirectoryBuildFile("Directory.Build.props", currentFile.getParentFile().getParentFile());
290 } else if (importStatement.startsWith("$(MSBuildThisFileDirectory)")) {
291 final String path = importStatement.substring(27);
292 final File currentDirectory = currentFile.getParentFile();
293 final Path p = Paths.get(currentDirectory.getAbsolutePath(),
294 path.replace('\\', File.separatorChar).replace('/', File.separatorChar));
295 final File f = p.normalize().toFile();
296 if (f.isFile() && !f.equals(currentFile)) {
297 return f;
298 }
299 }
300 } else {
301 final File currentDirectory = currentFile.getParentFile();
302 final Path p = Paths.get(currentDirectory.getAbsolutePath(),
303 importStatement.replace('\\', File.separatorChar).replace('/', File.separatorChar));
304
305 final File f = p.normalize().toFile();
306
307 if (f.isFile() && !f.equals(currentFile)) {
308 return f;
309 }
310 }
311 LOGGER.warn("Unable to import Directory.Build.props import `{}` in `{}`", importStatement, currentFile);
312 return null;
313 }
314
315 private Map<String, String> readDirectoryBuildProps(File directoryProps) throws MSBuildProjectParseException {
316 Map<String, String> entries = null;
317 final Set<String> imports = new HashSet<>();
318 if (directoryProps != null && directoryProps.isFile()) {
319 final DirectoryBuildPropsParser parser = new DirectoryBuildPropsParser();
320 try (FileInputStream fis = new FileInputStream(directoryProps);
321 BOMInputStream bis = BOMInputStream.builder().setInputStream(fis).get()) {
322
323 bis.getBOM();
324 entries = parser.parse(bis);
325 imports.addAll(parser.getImports());
326 } catch (IOException ex) {
327 throw new MSBuildProjectParseException("Error reading Directory.Build.props", ex);
328 }
329
330 for (String importStatement : imports) {
331 final File parentBuildProps = getImport(importStatement, directoryProps);
332 if (parentBuildProps != null && !directoryProps.equals(parentBuildProps)) {
333 final Map<String, String> parentEntries = readDirectoryBuildProps(parentBuildProps);
334 if (parentEntries != null) {
335 parentEntries.putAll(entries);
336 entries = parentEntries;
337 }
338 }
339 }
340 return entries;
341 }
342 return null;
343 }
344
345 private Map<String, String> loadCentrallyManaged(File folder, Properties props) throws MSBuildProjectParseException {
346 final File packages = locateDirectoryBuildFile(DIRECTORY_PACKAGESPROPS, folder);
347 if (packages != null && packages.isFile()) {
348 final DirectoryPackagesPropsParser parser = new DirectoryPackagesPropsParser();
349 try (FileInputStream fis = new FileInputStream(packages);
350 BOMInputStream bis = BOMInputStream.builder().setInputStream(fis).get()) {
351
352 bis.getBOM();
353 return parser.parse(bis, props);
354 } catch (IOException ex) {
355 throw new MSBuildProjectParseException("Error reading Directory.Build.props", ex);
356 }
357 }
358 return new HashMap<>();
359 }
360
361 }