Introduction

You might not be aware, but you could be at risk of a security incident due to a third-party library or component within your codebase having a vulnerability.

This article will help you detect security vulnerabilities in third-party libraries using a free-to-use tool called Trivy.

I’ve created a repo to demonstrate scanning a few types of apps here https://github.com/jtbuk/CVEDetection, it has a .NET app, and an angular spa, both of which run in docker, Trivy will scan the docker images, npm packages, and NuGet packages and we use the output to send a formatted slack message containing the results.

A slack message showing vulnerabilities

Outcome

You will be made aware of any critical or high-risk vulnerabilities within third-party dependencies via a slack message.

Prerequisites

This article is assuming that you’re using slack, however, you could modify my code to send a message to your instant messaging app of choice.

Slack

Create a new slack app, which you can do here, and then click into the app, and configure a webhook to post a message to a channel of your choice, more info about webhooks can be found here.

Docker

You’ll need docker installed, it is possible to run Trivy natively using a CLI tool, however, I’ve gone for a platform-agnostic approach by using a docker image. You can download and install Docker here.

Code

Scanning the file system

The following PowerShell will run Trivy inside a docker container, using the fs command to scan the scan-me folder inside the container. The scan-me folder in the container is mounted as a volume to the ${pwd} variable, which is the current location in your terminal. This gives Trivy the ability to scan your current working directory. There’s a few other arguments worth mentioning:

  • --format json is used to give us a JSON object back, you can omit this if you wanted a more human-readable format, however, we’re processing the output so stick with JSON for this scenario.
  • --security-checks vuln tells Trivy to only look for CVEs, it’s possible to also look for secrets, and misconfigurations.
  • --severity HIGH,CRITICAL will limit the results to high, and critical vulnerabilities. You can omit this if you want to see all vulnerabilities.
 1$fileSystemJson = docker run --rm `
 2    -v ${pwd}/trivytmp:/root/.cache `
 3    -v /var/run/docker.sock:/var/run/docker.sock `
 4    -v ${pwd}/:/scan-me `
 5    aquasec/trivy `
 6    fs `
 7    --format json `
 8    --security-checks vuln `
 9    --severity HIGH,CRITICAL `
10    scan-me

Scanning docker images

The first step when scanning images is to build them, which you can see I’ve done on lines 1 and 2. Similar to the above we’re running Trivy inside a docker container, this time using the image command, and then instead of providing a path, we’re providing the docker images that we’d like to scan. You might have also noticed we don’t need to mount the volume to our working directory in this scenario as we’re only interested in what is inside the docker image.

 1docker build -t scan-me-console-app ./ConsoleApp
 2docker build -t scan-me-spa ./my-app
 3
 4#Scan the Console App Docker Image
 5$consoleDockerJson = docker run --rm `
 6    -v ${pwd}/trivytmp:/root/.cache `
 7    -v /var/run/docker.sock:/var/run/docker.sock `
 8    aquasec/trivy `
 9    image `
10    --format json `
11    --security-checks vuln `
12    --severity HIGH,CRITICAL `
13    scan-me-console-app;
14
15#Scan the Angular SPA on Nginx Docker Image
16$spaDockerJson = docker run --rm `
17    -v ${pwd}/trivytmp:/root/.cache `
18    -v /var/run/docker.sock:/var/run/docker.sock `
19    aquasec/trivy `
20    image `
21    --format json `
22    --security-checks vuln `
23    --severity HIGH,CRITICAL `
24    scan-me-spa;

Processing the results

Trivy gives us a JSON object to work with, the following PowerShell will iterate through the results, and then vulnerabilities collection to extract the type which could be the operating system, package manager, NuGet, npm, etc. The name of the component with the vulnerability, version is the version impacted. The severity of the vulnerability. The cve property is the identifier for the vulnerability which you can read about here. The url can be used to navigate to see more information about the vulnerability.

There’s a lot more information in the JSON that I haven’t included in my mapped object to send to slack, however, there might be fields that you find valuable, so I recommend having a look at the JSON object. For example, you might want to include a short description of the vulnerability in your slack message.

 1$jsonToScan = @( $spaDockerJson, $consoleDockerJson, $fileSystemJson );
 2
 3$jsonToScan | ForEach-Object {
 4    $_ | ConvertFrom-Json | ForEach-Object {
 5        $findings = $_;
 6        $findings.Results | Where-Object { $_.type -ne "dotnet-core" } | ForEach-Object {
 7            $result = $_;            
 8            $_.Vulnerabilities | ForEach-Object {
 9                $vulnerability = $_;
10                if($null -ne $vulnerability){
11                    $vulnerabilities += [pscustomobject]@{
12                        type = $result.type
13                        name = $vulnerability.pkgName
14                        version = $vulnerability.installedVersion
15                        severity = $vulnerability.severity
16                        cve = $vulnerability.vulnerabilityID
17                        url = $vulnerability.primaryURL
18                    };
19                }                
20            }
21        };
22    }    
23}

Sending the results to slack

Finally, we take the mapped objects, group them by type, order them by severity, and then build a formatted message string to send to slack. More information can be found here on how to apply formatting to slack messages.

 1    $formattedVulnerabilities = "";
 2
 3    $vulnerabilities | Group-Object -Property type | ForEach-Object {
 4        $type = $_.Name;
 5
 6        if($type -eq "debian" -or $type -eq "alpine"){
 7            $type = "🐧 ${type}";
 8        }
 9        if($type -eq "npm" -or $type -eq "yarn" -or $type -eq "nuget") {
10            $type = "📦 ${type}";
11        }
12
13        if($_.Count -gt 0) {
14            $formattedVulnerabilities += "*$type*`n";
15            
16            $_.Group | Sort-Object -Property severity -Descending | ForEach-Object {            
17                $name = $_.name;
18                $version = $_.version;
19                $severity = $_.severity;
20                $cve = $_.cve;
21                $url = $_.url;
22
23                if($severity -eq "critical"){
24                    $severity = "🔥 ${severity}";
25                }            
26
27                $severity = $severity.ToLower();
28
29                $formattedVulnerabilities += "- *$severity* - $name[$version] has the cve <$url|$cve>`n"
30            }
31        }
32    }
33
34    $body = [pscustomobject]@{
35        text = $formattedVulnerabilities
36    };
37
38    $bodyJson = $body | ConvertTo-Json;
39    
40    Invoke-WebRequest -Uri "$slackWebhook" `
41        -Method Post `
42        -Body $bodyJson `
43        -ContentType "application/json";

Outro

If you didn’t have any CVE detection in place, I strongly recommend putting something in place. There are plenty of tools out there, both free and paid / as a service.

Here are a few of the other options I’d recommend: