google / osv-scanner

Vulnerability scanner written in Go which uses the data provided by https://osv.dev
https://google.github.io/osv-scanner/
Apache License 2.0
6.23k stars 359 forks source link

suppressing duplicate CVE with osv-scanner 1.5.0 & 1.9.1 #1367

Open krysopath opened 2 days ago

krysopath commented 2 days ago

I have problems creating suppression entries for CVE's:

my latest version is

osv-scanner version: 1.9.1
commit: b13f37e1a1e4cb98556c1d34cd3256a876929be1
built at: 2024-10-30T23:38:00Z

my gradle.lockfile contains this line:

io.undertow:undertow-core:2.3.16.Final=productionRuntimeClasspath

my suppression.toml contains:

[[IgnoredVulns]]
id = "CVE-2024-7885"
ignoreUntil = 2024-11-15
reason = "https://domain.domain.net/browse/CVE-127"

the related osv db entry is: https://osv.dev/vulnerability/GHSA-9623-mqmm-5rcf

I run the command like so:

$ ./osv-scanner_linux_amd64 scan --lockfile gradle.lockfile
Scanned gradle.lockfile file and found 217 packages
╭─────────────────────────────────────┬──────┬───────────┬────────────────────────────────────┬──────────────┬─────────────────╮
│ OSV URL                             │ CVSS │ ECOSYSTEM │ PACKAGE                            │ VERSION      │ SOURCE          │
├─────────────────────────────────────┼──────┼───────────┼────────────────────────────────────┼──────────────┼─────────────────┤
│ https://osv.dev/GHSA-9623-mqmm-5rcf │ 8.7  │ Maven     │ io.undertow:undertow-core          │ 2.3.16.Final │ gradle.lockfile │
│ https://osv.dev/GHSA-4gc7-5j7h-4qph │ 5.3  │ Maven     │ org.springframework:spring-context │ 6.1.13       │ gradle.lockfile │
╰─────────────────────────────────────┴──────┴───────────┴────────────────────────────────────┴──────────────┴─────────────────╯

$ ./osv-scanner_linux_amd64 scan --lockfile gradle.lockfile --config suppressions.toml 
Scanned gradle.lockfile file and found 217 packages
CVE-2024-38820 and 1 alias have been filtered out because: https://domain.domain.net/browse/CVE-127
Filtered 1 vulnerability from output
╭─────────────────────────────────────┬──────┬───────────┬───────────────────────────┬──────────────┬─────────────────╮
│ OSV URL                             │ CVSS │ ECOSYSTEM │ PACKAGE                   │ VERSION      │ SOURCE          │
├─────────────────────────────────────┼──────┼───────────┼───────────────────────────┼──────────────┼─────────────────┤
│ https://osv.dev/GHSA-9623-mqmm-5rcf │ 8.7  │ Maven     │ io.undertow:undertow-core │ 2.3.16.Final │ gradle.lockfile │
╰─────────────────────────────────────┴──────┴───────────┴───────────────────────────┴──────────────┴─────────────────╯

The content of suppressions.toml:

[[IgnoredVulns]]
id = "CVE-2024-38821"
ignoreUntil = 2024-11-15
reason = "https://domain.domain.net/browse/CVE-127"

[[IgnoredVulns]]
id = "CVE-2024-7885"
ignoreUntil = 2024-11-15
reason = "https://domain.domain.net/browse/CVE-127"

[[IgnoredVulns]]
id = "CVE-2024-38820"
ignoreUntil = 2024-11-15
reason = "https://domain.domain.net/browse/CVE-127"
krysopath commented 2 days ago

After more investigation I found the reason was duplicate cve id, where he first one was expired. I did not post full files for security reason..

I had expected that osv-scanner checks for uniqueness. I found that by creating this code that lints the toml file:

package main

import (
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "time"

    "github.com/BurntSushi/toml"
)

type SuppressedVulnerability struct {
    ID          string    `toml:"id"`
    Reason      string    `toml:"reason"`
    IgnoreUntil time.Time `toml:"ignoreUntil"`
}

var (
    suppressedVulns map[string][]SuppressedVulnerability
    filePath        *string
)

func validateSuppressedVulns(sv map[string][]SuppressedVulnerability) (error, []string) {
    var errors []string // Slice to hold error messages
    now := time.Now()
    seenIDs := make(map[string]bool) // Track IDs we've encountered

    for key, vulnerabilities := range sv {
        for _, v := range vulnerabilities {
            if v.ID == "" {
                errors = append(errors, fmt.Sprintf("empty ID for %s", key))
            }
            if v.Reason == "" {
                errors = append(errors, fmt.Sprintf("empty Reason for %s", v.ID))
            }
            if !v.IgnoreUntil.IsZero() && v.IgnoreUntil.Before(now) {
                errors = append(errors, fmt.Sprintf("IgnoreUntil %s is in the past for %s", v.IgnoreUntil, v.ID))
            }
            if seenIDs[v.ID] {
                errors = append(errors, fmt.Sprintf("duplicate ID %s found", v.ID)) // Collect duplicate ID error
            }
            seenIDs[v.ID] = true // Mark this ID as seen
        }
    }

    if len(errors) > 0 {
        return fmt.Errorf("validation failed with %d errors", len(errors)), errors
    }

    return nil, nil
}

func init() {
    filePath = flag.String("file", "", "Path to the suppression file")
    flag.Parse()
}

func main() {
    if *filePath == "" {
        log.Fatal("Please provide a file path using the -file flag")
    }
    data, err := ioutil.ReadFile(*filePath)
    if err != nil {
        log.Fatalf("Error reading suppression file: %v", err)
    }
    err = toml.Unmarshal(data, &suppressedVulns)
    if err != nil {
        log.Fatalf("Error unmarshalling suppression file: %v", err)
    }
    if err, all := validateSuppressedVulns(suppressedVulns); err != nil {
        for _, e := range all {
            log.Printf("Error: %v\n", e)
        }
        log.Fatalf("Error Validating suppressions: %v", err)
    }
    fmt.Println("OK")
}

Maybe this still counts as a input validation bug for osv-scanner. We discussed internally, we all had expected an error in he case of duplicate CVE id.

So leave it open for you to decide if it is user error. <3

another-rex commented 1 day ago

@G-Rath Can you take a look at adding validation for duplicate config entries when you have time?