One of the items on the 2013 OWASP Top Ten is “Using Components with Known Vulnerabilities.” It is new on this year’s list, debuting at number 9. OWASP lists at as being widespread and difficult to detect.
The issue is that modern software is made up of dozens, if not hundreds, of third-party components. Even if the code we write is secure, the other components we are using may not be. We might be using an out-of-date version of a library that has a known vulnerability. Or we might be using a component that depends on another component that has a known vulnerability. It can be hard to keep track of all the dependencies in our code, much less keep up-to-date with all the versions and the potential problems with those versions.
This problem is a really good reason to use a dependency manager for our builds, such as Apache Ivy or Apache Maven. They can handle a lot of the heavy lifting for dependencies in our code, including a way to explicitly track and document the third-party libraries our code may depend on.
I am going to describe how three different Maven plugins can help manage our Java dependency versions so that we know what components we are using, which have newer available versions, and which have published vulnerabilities.
Maven Dependency Plugin
The first plugin is the Maven Dependency Plugin has a number of goals, but the ones we are most interested in here are concerned with simply listing the dependencies in our project.
In the <build>
section of our pom.xml
file, we add:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.8</version> </plugin>
There are two goals I generally rely on. The first is dependency:tree
. It shows a list of the dependencies that our project depends on, along with a list of the transitive dependencies which are the dependencies for our dependencies.
The other is dependency:analyze
, which lists dependencies that are used and declared, used and undeclared, and unused and declared. It has some caveats, in that by default it examines the bytecode, and so it may identify dependencies as unused if they are only used at test time (e.g., junit) or runtime (e.g., logging library plugins). But it does give a short list of files to double check.
mvn dependency:tree dependency:analyze ... [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ client --- [INFO] com.coveros:sample-maven:jar:0.0.1-SNAPSHOT [INFO] +- junit:junit:jar:4.11:test [INFO] | \- org.hamcrest:hamcrest-core:jar:1.3:test [INFO] +- org.slf4j:slf4j-api:jar:1.7.5:compile [INFO] \- ch.qos.logback:logback-classic:jar:1.0.13:test [INFO] \- ch.qos.logback:logback-core:jar:1.0.13:test ... [INFO] --- maven-dependency-plugin:2.8:analyze (default-cli) @ client --- [WARNING] Unused declared dependencies found: [WARNING] junit:junit:jar:4.11:test [WARNING] ch.qos.logback:logback-classic:jar:1.0.13:test ...
In this case, we can see that the project depends on JUnit (for unit testing), the API for SLF4J (a logging library), and Logback Classic (a plugin for the logging). JUnit in turn depends on the Hamcrest library and Logback Classic depends on Logback Core. JUnit is only used in the test code, and Logback Classic is only used indirectly via the API so there is no bytecode referencing it, so they are listed as unused declared dependencies.
Using the Maven Dependency Plugin can be a good way to make sure we are using what we thought, to find out where a library came from (like Hamcrest in this example), and to find out if there are any libraries that we are no longer using.
The plugin also has a number of other goals that can be used to copy dependencies, prune our local repository cache, and even prepare our system for going offline by resolving all the dependencies it will need.
In the <reporting>
section of our pom.xml
, we can add the Maven Project Info Reports Plugin which will add some similar dependency reports to the Maven generated site (mvn site
).
Versions Maven Plugin
The next plugin is the Versions Maven Plugin and it can tell us if any of our plugins or dependencies have newer versions available.
In the <build>
section of our pom.xml
file, we add:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.1</version> </plugin>
There are two goals I use from this plugin: versions:display-plugin-updates
and versions:display-dependency-updates
. The first displays any updates to Maven plugins in our pom.xml
, the second displays updates to any declared dependencies.
The plugin has goals that will automatically update our pom.xml
file as well, but I prefer to handle those explicitly by hand and ensure that my code is ready for each update. I can change any that I might be wary of by itself, run all the tests, and make sure that they pass without having to untangle the effects of a bunch of changes at once.
mvn versions:display-plugin-updates versions:display-dependency-updates ... [INFO] --- versions-maven-plugin:2.1:display-plugin-updates (default-cli) @ sample-maven --- [INFO] [INFO] The following plugin updates are available: [INFO] maven-deploy-plugin ...................................... 2.7 -> 2.8 [INFO] [INFO] All plugins have a version specified. [INFO] [INFO] Project defines minimum Maven version as: 3.0 [INFO] Plugins require minimum Maven version of: 3.0 [INFO] [INFO] No plugins require a newer version of Maven than specified by the pom. [INFO] [INFO] [INFO] --- versions-maven-plugin:2.1:display-dependency-updates (default-cli) @ sample-maven --- [INFO] The following dependencies in Dependencies have newer versions: [INFO] junit:junit ............................................. 4.10 -> 4.11 ...
In this case the Maven Deploy Plugin and JUnit each have updates available.
In addition, the display-plugin-updates
goal points out that this project claims it needs at least Maven version 3.0, via the <prerequisites>
section), which agrees with what the plugins actually require.
If they were in conflict (e.g., specifying at least Maven 2.2.1 but really needing Maven 3.0), an error would be displayed.
[ERROR] Project requires an incorrect minimum version of Maven. [ERROR] Either change plugin versions to those compatible with 2.2.1 [ERROR] or update the pom.xml to contain [ERROR] <prerequisites> [ERROR] <maven>3.0</maven> [ERROR] </prerequisites>
Adding the following to the <reporting>
section of our pom.xml
will add similar reports to the Maven generated site (mvn site
).
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>versions-maven-plugin</artifactId> <version>2.1</version> <reportSets> <reportSet> <reports> <report>plugin-updates-report</report> <report>dependency-updates-report</report> </reports> </reportSet> </reportSets> </plugin>
A third report option, property-updates-report
, can show which properties defined in our pom.xml
file are used to set the versions on plugins and dependencies. Rather than putting explicit version numbers in each <plugin>
block, I define a property in the properties block at the top of my pom.xml
with the version numbers, and use variable substitution in the other sections. It is much easier to scan the versions being used, to update the version numbers when I need, and to keep dependency versions consistent (e.g., multiple Spring Framework dependencies that should always use the same version). Using the report can be a good check for finding versions that are not controlled via property.
<properties> ... <junit.version>4.10</junit.version> ... </properties> ... <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> ...
Be aware that the Versions Maven Plugin isn’t always 100% accurate. Not that it makes mistakes, but it assumes the latest version is the version with the largest number. In almost every case this is what you want. Every now and then, however, a version gets into Maven Central or another repository that uses a different numbering scheme.
For example, the latest version of the Apache Commons Collections package, commons-collections
, is 3.2.1 at the time of this writing. Years ago someone deployed a version numbered 20040616 to Maven Central. Since 20040616 > 3.2.1, commons-collections will always show that version as an update to 3.2.1.
In other cases, package maintainers change the group or artifact ID, the identifiers by which a dependency can be found in a Maven repository. For example, another Apache Commons package, Lang, used to have group ID commons-lang
and artifact ID commons-lang
for its 2.6 version. The package is currently at version 3.1 with group ID org.apache.commons
and artifact ID commons-lang3
. Someone using version 2.6 would not see 3.1 as an available update, since to Maven they appear to be different packages.
Nonetheless, the Versions Maven Plugin is helpful in making sure we are up-to-date with plugin and dependency versions. Whether for security reasons or just when returning to a project after some period of time, it is useful to know when the versions can be updated.
Dependency Check Plugin
While the first two plugins will help keep us organized and up-to-date, the OWASP Dependency Check Plugin is the key to finding out if we are using components with published vulnerabilities. Aside from the Maven plugin, it comes in Ant and command-line versions as well.
In the <build>
section of our pom.xml
file, we add:
<plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>1.0.4</version> </plugin>
Running the dependency-check:check
goal the first time will take 10-20 minutes while the plugin downloads vulnerability data from the National Vulnerability Database. Future runs will be much quicker as only changes are downloaded.
The goal will generate an HTML report (in target/DependencyCheck-Report.html
) with a list of the dependencies we are using that have published vulnerabilities, cross referenced with the applicable CVE number (Common Vulnerabilities and Exposures), CWE number (Common Weakness Enumeration), and CVSS score (Common Vulnerability Scoring System) which is a standard rating for the relative severity of the vulnerability.
By changing the plugin declaration to:
<plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>1.0.4</version> <configuration> <failBuildOnCVSS>7</failBuildOnCVSS> </configuration> </plugin>
we can automatically fail the build if any component has a published vulnerability with a CVSS score of 7 or higher.
[ERROR] Failed to execute goal org.owasp:dependency-check-maven:1.0.4:check (default-cli) on project sample-maven: [ERROR] [ERROR] Dependency-Check Failure: [ERROR] One or more dependencies were identified with vulnerabilities that have a CVSS score greater then '7.0': CVE-2011-5034, CVE-2007-1157 [ERROR] See the dependency-check report for more details.
Like the other two plugins, this one has a reporting mode as well. Adding the following to the <reporting>
section of our pom.xml
will add the report to the Maven generated site (mvn site
).
<plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>1.0.4</version> </plugin>
This plugin can be a very valuable tool for identifying components with known vulnerabilities quickly during the normal development cycle. By using the <failBuildOnCVSS>
configuration option and regularly reviewing the reports it generates, we’ll know right away when a dependency gets added that may compromise the security of our application or when a dependency that we have been using is discovered to have a vulnerability.
Explicitly Declaring Plugins
Some Maven plugins can be used without explicitly declaring them in the pom.xml
file. However, then we would lose the advantage of documenting what the project really uses and being able to tell when updates are available. Also, if a new version was released with different functionality, something could change in our build process without us being aware. It is best practice to declare any plugins we are relying on and to explicitly specify the version we are using.
Summary
Maven is a very good tool for managing dependencies in your Java code. Using these plugins can make it even better, making it easy to see what your code depends on while helping keep your code dependencies up-to-date and helping you identify known security vulnerabilities in the libraries you depend on (and even the libraries your dependencies depend on).