From 8aeed1033e586c01b350f5fe11249112f47334b1 Mon Sep 17 00:00:00 2001 From: Ziya Genc Date: Tue, 20 Aug 2024 06:03:57 +0200 Subject: [PATCH 1/5] [ADD] Support scanning Flatpak apps --- models/packages.go | 11 +++++++ models/scanresults.go | 17 ++++++++++ reporter/util.go | 3 ++ scanner/base.go | 72 +++++++++++++++++++++++++++++++++++++++++++ scanner/scanner.go | 5 +++ 5 files changed, 108 insertions(+) diff --git a/models/packages.go b/models/packages.go index fa72f019a6..070f4272fb 100644 --- a/models/packages.go +++ b/models/packages.go @@ -85,6 +85,7 @@ type Package struct { NewRelease string `json:"newRelease"` Arch string `json:"arch"` Repository string `json:"repository"` + Type string `json:"type,omitempty"` ModularityLabel string `json:"modularitylabel"` Changelog *Changelog `json:"changelog,omitempty"` AffectedProcs []AffectedProcess `json:",omitempty"` @@ -456,3 +457,13 @@ func IsKernelSourcePackage(family, name string) bool { return false } } + +type Flatpaks map[string]Flatpak + +type Flatpak struct { + Application string `json:"application"` + Name string `json:"name"` + Arch string `json:"arch"` + Version string `json:"version"` + NewVersion string `json:"newVersion"` +} diff --git a/models/scanresults.go b/models/scanresults.go index 508b992577..9e0d675002 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -49,6 +49,7 @@ type ScanResult struct { RunningKernel Kernel `json:"runningKernel"` Packages Packages `json:"packages"` SrcPackages SrcPackages `json:",omitempty"` + Flatpaks Flatpaks `json:"flatpaks"` EnabledDnfModules []string `json:"enabledDnfModules,omitempty"` // for dnf modules WordPressPackages WordPressPackages `json:",omitempty"` GitHubManifests DependencyGraphManifests `json:"gitHubManifests,omitempty"` @@ -229,6 +230,22 @@ func (r ScanResult) FormatUpdatablePkgsSummary() string { nUpdatable) } +// FormatUpdatableFlatpakSummary returns a summary of updatable Flatpak packages +func (r ScanResult) FormatUpdatableFlatpakSummary() string { + nUpdatable := 0 + for _, f := range r.Flatpaks { + if f.NewVersion == "" { + continue + } + if f.Version != f.NewVersion { + nUpdatable++ + } + } + return fmt.Sprintf("Flatpak: %d apps, %d updatable", + len(r.Flatpaks), + nUpdatable) +} + // FormatExploitCveSummary returns a summary of exploit cve func (r ScanResult) FormatExploitCveSummary() string { nExploitCve := 0 diff --git a/reporter/util.go b/reporter/util.go index d9cfdaa93b..47adb258c6 100644 --- a/reporter/util.go +++ b/reporter/util.go @@ -164,6 +164,9 @@ func formatScanSummary(rs ...models.ScanResult) string { fmt.Sprintf("%s%s", r.Family, r.Release), r.FormatUpdatablePkgsSummary(), } + if 0 < len(r.Flatpaks) { + cols = append(cols, r.FormatUpdatableFlatpakSummary()) + } if 0 < len(r.WordPressPackages) { cols = append(cols, fmt.Sprintf("%d WordPress pkgs", len(r.WordPressPackages))) } diff --git a/scanner/base.go b/scanner/base.go index 8c07ec9717..539f684b0d 100644 --- a/scanner/base.go +++ b/scanner/base.go @@ -96,6 +96,9 @@ type osPackages struct { // installed source packages (Debian based only) SrcPackages models.SrcPackages + // Flatpaks + Flatpaks models.Flatpaks + // Detected Vulnerabilities Key: CVE-ID VulnInfos models.VulnInfos @@ -546,6 +549,7 @@ func (l *base) convertToModel() models.ScanResult { RunningKernel: l.Kernel, Packages: l.Packages, SrcPackages: l.SrcPackages, + Flatpaks: l.Flatpaks, WordPressPackages: l.WordPress, LibraryScanners: l.LibraryScanners, WindowsKB: l.windowsKB, @@ -1496,3 +1500,71 @@ func (l *base) pkgPs(getOwnerPkgs func([]string) ([]string, error)) error { } return nil } + +func (l *base) scanFlatpaks() error { + l.log.Info("Scanning Flatpak applications.") + cmd := util.PrependProxyEnv("flatpak list --app --columns=application,arch,version") + r := l.exec(cmd, noSudo) + + if !r.isSuccess() { + return xerrors.Errorf("Scanning flatpaks failed.") + } + + flatpaks := l.parseFlatpakList(r.Stdout) + + cmd = util.PrependProxyEnv("flatpak remote-ls --app --updates --columns=application,arch,version") + r = l.exec(cmd, noSudo) + + if !r.isSuccess() { + err := xerrors.Errorf("Failed to check flatpak updates.") + l.log.Warnf("err: %+v", err) + l.warns = append(l.warns, err) + // Only log this error. + } + + scanner := bufio.NewScanner(strings.NewReader(r.Stdout)) + + for scanner.Scan() { + line := scanner.Text() + ss := strings.Fields(line) + + if len(ss) != 3 { + continue + } + + updatableApp := flatpaks[ss[0]] + updatableApp.NewVersion = ss[2] + flatpaks[ss[0]] = updatableApp + } + + l.Flatpaks = flatpaks + + return nil +} + +func (l *base) parseFlatpakList(stdout string) models.Flatpaks { + flatpaks := models.Flatpaks{} + scanner := bufio.NewScanner(strings.NewReader(stdout)) + + for scanner.Scan() { + line := scanner.Text() + ss := strings.Fields(line) + + if len(ss) != 3 { + continue + } + + nameSegments := strings.Split(ss[0], ".") + name := nameSegments[len(nameSegments)-1] + name = strings.ToLower(name) + + flatpaks[ss[0]] = models.Flatpak{ + Application: ss[0], + Name: name, + Arch: ss[1], + Version: ss[2], + } + } + + return flatpaks +} diff --git a/scanner/scanner.go b/scanner/scanner.go index eb233a5624..5b6a037c27 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -62,6 +62,8 @@ type osTypeInterface interface { parseInstalledPackages(string) (models.Packages, models.SrcPackages, error) + scanFlatpaks() error + runningContainers() ([]config.Container, error) exitedContainers() ([]config.Container, error) allContainers() ([]config.Container, error) @@ -965,6 +967,9 @@ func (s Scanner) getScanResults(scannedAt time.Time) (results models.ScanResults if err = o.postScan(); err != nil { return err } + if err = o.scanFlatpaks(); err != nil { + return err + } } if o.getServerInfo().Module.IsScanPort() { if err = o.scanPorts(); err != nil { From 06bce43f1fe17040fde5fa5a76e0060fe0f281e2 Mon Sep 17 00:00:00 2001 From: Ziya Genc Date: Thu, 22 Aug 2024 01:07:08 +0200 Subject: [PATCH 2/5] [ADD] Support detecting vulnerable Flatpak apps This commit requires go-cve-dictionary package to provide GetCveIDsByProduct in DB interface. --- detector/detector.go | 82 ++++++++++++++++++++++++++++++++++++++++++++ scanner/base.go | 81 ++++++++++++++++++++++++++----------------- tui/tui.go | 8 +++++ 3 files changed, 139 insertions(+), 32 deletions(-) diff --git a/detector/detector.go b/detector/detector.go index a2f8aa3c24..21f8ecd111 100644 --- a/detector/detector.go +++ b/detector/detector.go @@ -22,6 +22,7 @@ import ( "github.com/future-architect/vuls/oval" "github.com/future-architect/vuls/reporter" "github.com/future-architect/vuls/util" + hver "github.com/hashicorp/go-version" cvemodels "github.com/vulsio/go-cve-dictionary/models" ) @@ -54,6 +55,10 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) { return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err) } + if err := DetectFlatpakCves(&r, config.Conf.CveDict, config.Conf.LogOpts); err != nil { + return nil, xerrors.Errorf("Failed to detect Flatpak CVE: %w", err) + } + cpeURIs, owaspDCXMLPath := []string{}, "" cpes := []Cpe{} if len(r.Container.ContainerID) == 0 { @@ -367,6 +372,83 @@ func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf c return nil } +func DetectFlatpakCves(r *models.ScanResult, cnf config.GoCveDictConf, logOpts logging.LogOpts) error { + detects := make(map[string]string) + client, err := newGoCveDictClient(&cnf, logOpts) + if err != nil { + return xerrors.Errorf("Failed to newGoCveDictClient. err: %w", err) + } + defer func() { + if err := client.closeDB(); err != nil { + logging.Log.Errorf("Failed to close DB. err: %+v", err) + } + }() + + for _, flatpak := range r.Flatpaks { + results, err := client.driver.GetCveIDsByProduct(flatpak.Name) + if err != nil { + return xerrors.Errorf("Failed to detectCveByCpeURI. err: %w", err) + } + + vera, err := hver.NewVersion(flatpak.Version) + if err != nil { + continue + } + + for _, row := range results { + if row.VersionEndExcluding != "" { + verb, err := hver.NewVersion(row.VersionEndExcluding) + if err != nil { + continue + } + if vera.LessThan(verb) { + detects[row.CveId] = flatpak.Name + } + } else if row.VersionEndIncluding != "" { + verb, err := hver.NewVersion(row.VersionEndIncluding) + if err != nil { + continue + } + if vera.LessThanOrEqual(verb) { + detects[row.CveId] = flatpak.Name + } + } + } + } + + for cveId, flatpak := range detects { + v, ok := r.ScannedCves[cveId] + if ok { + if v.CveContents == nil { + v.CveContents = models.NewCveContents(models.CveContent{ + Type: models.Nvd, + CveID: cveId, + }) + } else { + v.CveContents[models.Nvd] = []models.CveContent{models.CveContent{ + Type: models.Nvd, + CveID: cveId, + }} + } + v.Confidences.AppendIfMissing(models.NvdVendorProductMatch) + } else { + v = models.VulnInfo{ + CveID: cveId, + CveContents: models.NewCveContents(models.CveContent{ + Type: models.Nvd, + CveID: cveId, + }), + Confidences: models.Confidences{models.NvdVendorProductMatch}, + } + } + + v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{Name: flatpak}) + r.ScannedCves[cveId] = v + } + + return nil +} + // isPkgCvesDetactable checks whether CVEs is detactable with gost and oval from the result func isPkgCvesDetactable(r *models.ScanResult) bool { switch r.Family { diff --git a/scanner/base.go b/scanner/base.go index 539f684b0d..b88861bb05 100644 --- a/scanner/base.go +++ b/scanner/base.go @@ -1501,25 +1501,36 @@ func (l *base) pkgPs(getOwnerPkgs func([]string) ([]string, error)) error { return nil } +func (l *base) isFlatpakInstalled() bool { + r := l.exec("flatpak --version", noSudo) + return r.isSuccess() +} + func (l *base) scanFlatpaks() error { + if !l.isFlatpakInstalled() { + return nil + } + l.log.Info("Scanning Flatpak applications.") - cmd := util.PrependProxyEnv("flatpak list --app --columns=application,arch,version") - r := l.exec(cmd, noSudo) - if !r.isSuccess() { - return xerrors.Errorf("Scanning flatpaks failed.") + flatpaks, err := l.parseFlatpakList() + if err != nil { + return err } - flatpaks := l.parseFlatpakList(r.Stdout) + l.fillUpdatableFlatpaks(flatpaks) + l.Flatpaks = flatpaks + + return nil +} - cmd = util.PrependProxyEnv("flatpak remote-ls --app --updates --columns=application,arch,version") - r = l.exec(cmd, noSudo) +func (l *base) parseFlatpakList() (models.Flatpaks, error) { + flatpaks := models.Flatpaks{} + cmd := util.PrependProxyEnv("flatpak list --app --columns=application,arch,version") + r := l.exec(cmd, noSudo) if !r.isSuccess() { - err := xerrors.Errorf("Failed to check flatpak updates.") - l.log.Warnf("err: %+v", err) - l.warns = append(l.warns, err) - // Only log this error. + return nil, xerrors.Errorf("Failed to get list of flatpak applications.") } scanner := bufio.NewScanner(strings.NewReader(r.Stdout)) @@ -1532,19 +1543,34 @@ func (l *base) scanFlatpaks() error { continue } - updatableApp := flatpaks[ss[0]] - updatableApp.NewVersion = ss[2] - flatpaks[ss[0]] = updatableApp - } + nameSegments := strings.Split(ss[0], ".") + name := nameSegments[len(nameSegments)-1] + name = strings.ToLower(name) - l.Flatpaks = flatpaks + flatpaks[ss[0]] = models.Flatpak{ + Application: ss[0], + Name: name, + Arch: ss[1], + Version: ss[2], + } + } - return nil + return flatpaks, nil } -func (l *base) parseFlatpakList(stdout string) models.Flatpaks { - flatpaks := models.Flatpaks{} - scanner := bufio.NewScanner(strings.NewReader(stdout)) +func (l *base) fillUpdatableFlatpaks(flatpaks models.Flatpaks) { + cmd := util.PrependProxyEnv("flatpak remote-ls --app --updates --columns=application,arch,version") + r := l.exec(cmd, noSudo) + + if !r.isSuccess() { + err := xerrors.Errorf("Failed to check flatpak updates.") + l.log.Warnf("err: %+v", err) + l.warns = append(l.warns, err) + // Only log this error. + return + } + + scanner := bufio.NewScanner(strings.NewReader(r.Stdout)) for scanner.Scan() { line := scanner.Text() @@ -1554,17 +1580,8 @@ func (l *base) parseFlatpakList(stdout string) models.Flatpaks { continue } - nameSegments := strings.Split(ss[0], ".") - name := nameSegments[len(nameSegments)-1] - name = strings.ToLower(name) - - flatpaks[ss[0]] = models.Flatpak{ - Application: ss[0], - Name: name, - Arch: ss[1], - Version: ss[2], - } + updatableApp := flatpaks[ss[0]] + updatableApp.NewVersion = ss[2] + flatpaks[ss[0]] = updatableApp } - - return flatpaks } diff --git a/tui/tui.go b/tui/tui.go index 4407f5602c..e96924831d 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -737,6 +737,14 @@ func setChangelogLayout(g *gocui.Gui) error { lines = append(lines, fmt.Sprintf(" * PID: %s %s Port: %s", p.PID, p.Name, ports)) } } + + for _, flatpak := range currentScanResult.Flatpaks { + if flatpak.Name == affected.Name { + line := fmt.Sprintf("[flatpak] %s-%s", flatpak.Name, flatpak.Version) + lines = append(lines, line) + break + } + } } sort.Strings(vinfo.CpeURIs) for _, uri := range vinfo.CpeURIs { From 9e2cb23742be299f1f97fc33a8f67b8726de8ce1 Mon Sep 17 00:00:00 2001 From: Ziya Genc Date: Mon, 28 Oct 2024 01:51:02 +0100 Subject: [PATCH 3/5] Refactor flatpak functions --- scanner/base.go | 55 +++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/scanner/base.go b/scanner/base.go index b88861bb05..7154e0ec8d 100644 --- a/scanner/base.go +++ b/scanner/base.go @@ -1501,39 +1501,47 @@ func (l *base) pkgPs(getOwnerPkgs func([]string) ([]string, error)) error { return nil } -func (l *base) isFlatpakInstalled() bool { +func (l *base) scanFlatpaks() error { r := l.exec("flatpak --version", noSudo) - return r.isSuccess() -} -func (l *base) scanFlatpaks() error { - if !l.isFlatpakInstalled() { + if !r.isSuccess() { return nil } l.log.Info("Scanning Flatpak applications.") - flatpaks, err := l.parseFlatpakList() + cmd := util.PrependProxyEnv("flatpak list --app --columns=application,arch,version") + r = l.exec(cmd, noSudo) + + if !r.isSuccess() { + return xerrors.Errorf("Failed to get the list of flatpak applications.") + } + + flatpaks, err := l.parseFlatpakList(r.Stdout) + if err != nil { return err } - l.fillUpdatableFlatpaks(flatpaks) + cmd = util.PrependProxyEnv("flatpak remote-ls --app --updates --columns=application,arch,version") + r = l.exec(cmd, noSudo) + + if !r.isSuccess() { + // Only log this error. + err := xerrors.Errorf("Failed to check flatpak updates.") + l.log.Warnf("err: %+v", err) + l.warns = append(l.warns, err) + } + + l.fillUpdatableFlatpaks(flatpaks, r.Stdout) l.Flatpaks = flatpaks return nil } -func (l *base) parseFlatpakList() (models.Flatpaks, error) { +func (l *base) parseFlatpakList(consoleOutput string) (models.Flatpaks, error) { flatpaks := models.Flatpaks{} - cmd := util.PrependProxyEnv("flatpak list --app --columns=application,arch,version") - r := l.exec(cmd, noSudo) - - if !r.isSuccess() { - return nil, xerrors.Errorf("Failed to get list of flatpak applications.") - } - - scanner := bufio.NewScanner(strings.NewReader(r.Stdout)) + scanner := bufio.NewScanner(strings.NewReader(consoleOutput)) for scanner.Scan() { line := scanner.Text() @@ -1558,19 +1566,8 @@ func (l *base) parseFlatpakList() (models.Flatpaks, error) { return flatpaks, nil } -func (l *base) fillUpdatableFlatpaks(flatpaks models.Flatpaks) { - cmd := util.PrependProxyEnv("flatpak remote-ls --app --updates --columns=application,arch,version") - r := l.exec(cmd, noSudo) - - if !r.isSuccess() { - err := xerrors.Errorf("Failed to check flatpak updates.") - l.log.Warnf("err: %+v", err) - l.warns = append(l.warns, err) - // Only log this error. - return - } - - scanner := bufio.NewScanner(strings.NewReader(r.Stdout)) +func (l *base) fillUpdatableFlatpaks(flatpaks models.Flatpaks, consoleOutput string) { + scanner := bufio.NewScanner(strings.NewReader(consoleOutput)) for scanner.Scan() { line := scanner.Text() From a699afae0d0c784f67a9f98630bf906f1a49ff69 Mon Sep 17 00:00:00 2001 From: Ziya Genc Date: Sat, 2 Nov 2024 02:51:36 +0100 Subject: [PATCH 4/5] Fix CveID field name. --- detector/detector.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/detector/detector.go b/detector/detector.go index 21f8ecd111..c9561bf173 100644 --- a/detector/detector.go +++ b/detector/detector.go @@ -402,7 +402,7 @@ func DetectFlatpakCves(r *models.ScanResult, cnf config.GoCveDictConf, logOpts l continue } if vera.LessThan(verb) { - detects[row.CveId] = flatpak.Name + detects[row.CveID] = flatpak.Name } } else if row.VersionEndIncluding != "" { verb, err := hver.NewVersion(row.VersionEndIncluding) @@ -410,7 +410,7 @@ func DetectFlatpakCves(r *models.ScanResult, cnf config.GoCveDictConf, logOpts l continue } if vera.LessThanOrEqual(verb) { - detects[row.CveId] = flatpak.Name + detects[row.CveID] = flatpak.Name } } } @@ -425,7 +425,7 @@ func DetectFlatpakCves(r *models.ScanResult, cnf config.GoCveDictConf, logOpts l CveID: cveId, }) } else { - v.CveContents[models.Nvd] = []models.CveContent{models.CveContent{ + v.CveContents[models.Nvd] = []models.CveContent{{ Type: models.Nvd, CveID: cveId, }} From 3d1f256f3991245c96f77f0d1c25b493bc09e661 Mon Sep 17 00:00:00 2001 From: Ziya Genc Date: Sat, 2 Nov 2024 04:27:26 +0100 Subject: [PATCH 5/5] Add tests for flatpak --- scanner/base.go | 7 ++--- scanner/base_test.go | 63 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/scanner/base.go b/scanner/base.go index 7154e0ec8d..92713b9eaa 100644 --- a/scanner/base.go +++ b/scanner/base.go @@ -1533,8 +1533,7 @@ func (l *base) scanFlatpaks() error { l.warns = append(l.warns, err) } - l.fillUpdatableFlatpaks(flatpaks, r.Stdout) - l.Flatpaks = flatpaks + l.Flatpaks = l.fillUpdatableFlatpaks(flatpaks, r.Stdout) return nil } @@ -1566,7 +1565,7 @@ func (l *base) parseFlatpakList(consoleOutput string) (models.Flatpaks, error) { return flatpaks, nil } -func (l *base) fillUpdatableFlatpaks(flatpaks models.Flatpaks, consoleOutput string) { +func (l *base) fillUpdatableFlatpaks(flatpaks models.Flatpaks, consoleOutput string) models.Flatpaks { scanner := bufio.NewScanner(strings.NewReader(consoleOutput)) for scanner.Scan() { @@ -1581,4 +1580,6 @@ func (l *base) fillUpdatableFlatpaks(flatpaks models.Flatpaks, consoleOutput str updatableApp.NewVersion = ss[2] flatpaks[ss[0]] = updatableApp } + + return flatpaks } diff --git a/scanner/base_test.go b/scanner/base_test.go index 4f7418fe89..d9f1179aa6 100644 --- a/scanner/base_test.go +++ b/scanner/base_test.go @@ -542,3 +542,66 @@ func Test_findPortScanSuccessOn(t *testing.T) { }) } } + +func TestParseFlatpakList(t *testing.T) { + + var tests = struct { + in string + expected models.Flatpaks + }{ + `Application ID Arch Version +com.github.d4nj1.tlpui x86_64 1.7.1 +com.github.finefindus.eyedropper x86_64 2.0.1 +com.github.taiko2k.avvie x86_64 2.4`, + models.Flatpaks{ + "com.github.d4nj1.tlpui": models.Flatpak{Application: "com.github.d4nj1.tlpui", Name: "tlpui", Arch: "x86_64", Version: "1.7.1"}, + "com.github.finefindus.eyedropper": models.Flatpak{Application: "com.github.finefindus.eyedropper", Name: "eyedropper", Arch: "x86_64", Version: "2.0.1"}, + "com.github.taiko2k.avvie": models.Flatpak{Application: "com.github.taiko2k.avvie", Name: "avvie", Arch: "x86_64", Version: "2.4"}, + }, + } + + l := base{} + actual, err := l.parseFlatpakList(tests.in) + if err != nil { + t.Errorf("Error occurred. in: %s, err: %+v", tests.in, err) + return + } + + for _, e := range tests.expected { + if !reflect.DeepEqual(e, actual[e.Application]) { + t.Errorf("expected %v, actual %v", e, actual[e.Application]) + } + } +} + +func TestFillUpdatableFlatpaks(t *testing.T) { + + type args struct { + flatpaks models.Flatpaks + consoleOutput string + } + + var test = struct { + args args + expected models.Flatpaks + }{ + args{ + models.Flatpaks{ + "org.videolan.VLC": models.Flatpak{Application: "org.videolan.VLC", Name: "vlc", Arch: "x86_64", Version: "3.0.19"}, + }, + `Application ID Arch Version +org.videolan.VLC x86_64 3.0.21`}, + models.Flatpaks{ + "org.videolan.VLC": models.Flatpak{Application: "org.videolan.VLC", Name: "vlc", Arch: "x86_64", Version: "3.0.19", NewVersion: "3.0.21"}, + }, + } + + l := base{} + actual := l.fillUpdatableFlatpaks(test.args.flatpaks, test.args.consoleOutput) + + for _, e := range test.expected { + if !reflect.DeepEqual(e, actual[e.Application]) { + t.Errorf("expected %v, actual %v", e, actual[e.Application]) + } + } +}