Vulnerable and Outdated Components are a top 10 OWASP security threat to applications. To mitigate this risk, there’s a commonly used OWASP dependency-check to scan software to identify the use of known vulnerable components.

In this blogpost we will outline a way to run the OWASP dependency-check for Maven projects on GitLab.com.

Local NVD copy

One important aspect to know about running dependency-check, is it’s reliance on a local copy of the National Vulnerability Database.

OWASP dependency-check maintains a local copy of the NVD CVE data hosted by NIST. By default, a local H2 database instance is used. As each instance maintains its own copy of the NVD the machine will need access to nvd.nist.gov in order to download the NVD data feeds. While the initial download of the NVD data feed is large, if after the initial download the tool is run at least once every seven days only two small XML files containing the recent modifications will need to be downloaded.
— Local NVD Database
https://jeremylong.github.io/DependencyCheck/data/index.html

And indeed, when running mvn org.owasp:dependency-check-maven:update-only for the first time, you will find it downloads vulnerabilities for a couple of minutes.

Listing 1. Checking for updates on initial run
[INFO] Checking for updates
[INFO] NVD CVE requires several updates; this could take a couple of minutes.
[INFO] Download Started for NVD CVE - 2002
[INFO] Download Complete for NVD CVE - 2002  (1697 ms)
[INFO] Processing Started for NVD CVE - 2002
[INFO] Processing Complete for NVD CVE - 2002  (3818 ms)
[INFO] Download Started for NVD CVE - 2003
[INFO] Download Complete for NVD CVE - 2003  (1062 ms)
[INFO] Processing Started for NVD CVE - 2003
[INFO] Processing Complete for NVD CVE - 2003  (807 ms)
...

This local copy is stored in ~/.m2/repository/org/owasp/dependency-check-data by default, which needs to be cached to prevent repeated downloads taking excessive time, and potentially leading to HTTP/429 Too many requests.

Notice how the local copy is stored in the local Maven repository. This will work fine for simple use cases, such as your own machine, or a long lived build server. But it might not be ideal for more fleeting CI environments, where you have to explicitly cache ~/.m2/repository, typically per project. At present the database takes up ~166MB, so any duplication per project quickly adds up to both storage needs, and cache transfer times per build.

There’s also the option to use a database server, but this requires infrastructure to maintain and keep available, for risk of breaking your builds. The documentation itself starts out with the following message, which is also not very encouraging.

WARNING : This discusses an advanced setup and you may run into issues.

NVD cached in Docker image

We propose to store the NVD data in a custom Docker image, updated on a weekly schedule, and used as CI image for the OWASP dependency check CI step.

To start, we create a Dockerfile based on Maven, and within that image populate the NVD copy.

Listing 2. Dockerfile for custom owasp-dependency-check container
FROM maven:3.8-eclipse-temurin-17-alpine (1)
RUN mvn org.owasp:dependency-check-maven:update-only \ (2)
  -DdataDirectory=/owasp \ (3)
  && rm -rf /root/.m2/ (4)
1 We extend from the Maven base image, to ensure mvn is available.
2 We run update-only to populate the NVD data copy.
3 We store the data in /owasp/, instead of in ~/.m2/repository/.
4 We remove the ~/.m2/ folder, to minimize the size of our container image.

Next we create a build pipeline to push this container image to the GitLab container registry.

Listing 3. .gitlab-ci.yml file for custom owasp-dependency-check container
# Adapted from:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Docker.gitlab-ci.yml
container_registry:
  image: docker:latest
  stage: build
  services:
    # Required for Docker; not available on RedHat OpenShift
    - docker:dind
  script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
    - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
    - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"

# Push to latest tag only from main branch
docker-push-latest:
  image: docker:latest
  stage: deploy
  script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
    - docker pull "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
    - docker tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" "$CI_REGISTRY_IMAGE"
    - docker push "$CI_REGISTRY_IMAGE"
  only:
    variables:
      - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Finally, we add a CI/CD Pipeline Schedule to run once a week, to stay within the seven day limit mentioned previously. That way we always have a mostly up to date NVD available, and individual scans will at most download two small XML files containing recent modifications. If you want you can schedule the pipeline once every four hours, during working hours, to make your scans work offline as much as possible.

You will likely want to configure a periodic clean up of image tags, which is available under Settings > Packages & Registries.

Dependency check projects through GitLab CI

With the custom owasp-dependency-check image built and periodically pushed to GitLab Container registry, you can now use it in your, hopefully shared, GitLab CI pipelines.

The below build step will use the custom image to perform a scan of your pom.xml, and report on any vulnerabilities.

Listing 4. .gitlab-ci.yml file for custom owasp-dependency-check container
variables:
  MAVEN_REPOSITORY: $CI_PROJECT_DIR/.m2/repository/

owasp_dependency_check:
  stage: .post (1)
  needs: [] (2)
  image: registry.gitlab.com/acme/containers/owasp-dependency-check:latest (3)
  script:
    - mvn org.owasp:dependency-check-maven:check -DdataDirectory=/owasp -DfailBuildOnCVSS=8 (4)
  artifacts:
    when: always
    paths:
      - target/dependency-check-report.html (5)
1 We run the check as part of the final .post stage, to not block the build.
2 An empty array for needs, starts the job as soon as the pipeline is created.
3 We refer to the latest tag of our custom owasp dependency check container.
4 We execute the owasp dependency check, with the same dataDirectory, and fail the build on any finding above an 8.
5 We make the HTML report available for further inspection.

Reading the reports

When running the above build step you might see something like the following messages in the logs.

Listing 5. Checking for updates on initial run
53157 [INFO] Writing report to: /builds/ACME/archiver/target/dependency-check-report.html
53549 [WARNING]
One or more dependencies were identified with known vulnerabilities in archiver:
log4j-api-2.17.2.jar (pkg:maven/org.apache.logging.log4j/log4j-api@2.17.2, cpe:2.3:a:apache:log4j:2.17.2:*:*:*:*:*:*:*) : CVE-2022-33915
spring-security-crypto-5.7.2.jar (pkg:maven/org.springframework.security/spring-security-crypto@5.7.2, cpe:2.3:a:pivotal_software:spring_security:5.7.2:*:*:*:*:*:*:*, cpe:2.3:a:vmware:spring_security:5.7.2:*:*:*:*:*:*:*) : CVE-2020-5408
tomcat-embed-core-9.0.64.jar (pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@9.0.64, cpe:2.3:a:apache:tomcat:9.0.64:*:*:*:*:*:*:*, cpe:2.3:a:apache_tomcat:apache_tomcat:9.0.64:*:*:*:*:*:*:*) : CVE-2022-34305
See the dependency-check report for more details.
53872 [INFO] ------------------------------------------------------------------------
53872 [INFO] BUILD SUCCESS
53872 [INFO] ------------------------------------------------------------------------

Notice how this check did identify known vulnerabilities, but none exceeded the failBuildOnCVSS value of 8. You can ofcoure set this to a suitable value for your organization and needs, as this is just an example.

The full report allows you to inspect the identified vulnerabilities, and potentially ignore false positives by passing a suppression file to the dependency check command.

dependency check report
Figure 1. Dependency check report example

Versions

Tested with

  • dependency-check-maven 7.1.1

  • Docker 20.10.12

  • GitLab 15.1

  • Java 17

  • Maven 3.8.6

shadow-left