* fix(scanner/dpkg): fix dpkg-query and not remove src pkgs * refactor(gost): remove unnecesary field and fix typo * refactor(detector/debian): detect using only SrcPackage
295 lines
8.7 KiB
Go
295 lines
8.7 KiB
Go
//go:build !scanner
|
|
// +build !scanner
|
|
|
|
package gost
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
debver "github.com/knqyf263/go-deb-version"
|
|
"golang.org/x/exp/maps"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/future-architect/vuls/logging"
|
|
"github.com/future-architect/vuls/models"
|
|
"github.com/future-architect/vuls/util"
|
|
gostmodels "github.com/vulsio/gost/models"
|
|
)
|
|
|
|
// Debian is Gost client for Debian GNU/Linux
|
|
type Debian struct {
|
|
Base
|
|
}
|
|
|
|
func (deb Debian) supported(major string) bool {
|
|
_, ok := map[string]string{
|
|
"7": "wheezy",
|
|
"8": "jessie",
|
|
"9": "stretch",
|
|
"10": "buster",
|
|
"11": "bullseye",
|
|
// "12": "bookworm",
|
|
// "13": "trixie",
|
|
// "14": "forky",
|
|
}[major]
|
|
return ok
|
|
}
|
|
|
|
// DetectCVEs fills cve information that has in Gost
|
|
func (deb Debian) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
|
|
if !deb.supported(major(r.Release)) {
|
|
// only logging
|
|
logging.Log.Warnf("Debian %s is not supported yet", r.Release)
|
|
return 0, nil
|
|
}
|
|
|
|
if r.Container.ContainerID == "" {
|
|
if r.RunningKernel.Release == "" {
|
|
logging.Log.Warnf("Since the exact kernel release is not available, the vulnerability in the kernel package is not detected.")
|
|
}
|
|
}
|
|
|
|
fixedCVEs, err := deb.detectCVEsWithFixState(r, true)
|
|
if err != nil {
|
|
return 0, xerrors.Errorf("Failed to detect fixed CVEs. err: %w", err)
|
|
}
|
|
|
|
unfixedCVEs, err := deb.detectCVEsWithFixState(r, false)
|
|
if err != nil {
|
|
return 0, xerrors.Errorf("Failed to detect unfixed CVEs. err: %w", err)
|
|
}
|
|
|
|
return len(unique(append(fixedCVEs, unfixedCVEs...))), nil
|
|
}
|
|
|
|
func (deb Debian) detectCVEsWithFixState(r *models.ScanResult, fixed bool) ([]string, error) {
|
|
detects := map[string]cveContent{}
|
|
if deb.driver == nil {
|
|
urlPrefix, err := util.URLPathJoin(deb.baseURL, "debian", major(r.Release), "pkgs")
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("Failed to join URLPath. err: %w", err)
|
|
}
|
|
s := "fixed-cves"
|
|
if !fixed {
|
|
s = "unfixed-cves"
|
|
}
|
|
responses, err := getCvesWithFixStateViaHTTP(r, urlPrefix, s)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("Failed to get CVEs via HTTP. err: %w", err)
|
|
}
|
|
|
|
for _, res := range responses {
|
|
if !res.request.isSrcPack {
|
|
continue
|
|
}
|
|
|
|
n := strings.NewReplacer("linux-signed", "linux", "linux-latest", "linux", "-amd64", "", "-arm64", "", "-i386", "").Replace(res.request.packName)
|
|
|
|
if deb.isKernelSourcePackage(n) {
|
|
isRunning := false
|
|
for _, bn := range r.SrcPackages[res.request.packName].BinaryNames {
|
|
if bn == fmt.Sprintf("linux-image-%s", r.RunningKernel.Release) {
|
|
isRunning = true
|
|
break
|
|
}
|
|
}
|
|
// To detect vulnerabilities in running kernels only, skip if the kernel is not running.
|
|
if !isRunning {
|
|
continue
|
|
}
|
|
}
|
|
|
|
cs := map[string]gostmodels.DebianCVE{}
|
|
if err := json.Unmarshal([]byte(res.json), &cs); err != nil {
|
|
return nil, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
|
|
}
|
|
for _, content := range deb.detect(cs, models.SrcPackage{Name: res.request.packName, Version: r.SrcPackages[res.request.packName].Version, BinaryNames: r.SrcPackages[res.request.packName].BinaryNames}, models.Kernel{Release: r.RunningKernel.Release, Version: r.Packages[fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)].Version}) {
|
|
c, ok := detects[content.cveContent.CveID]
|
|
if ok {
|
|
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
|
}
|
|
detects[content.cveContent.CveID] = content
|
|
}
|
|
}
|
|
} else {
|
|
for _, p := range r.SrcPackages {
|
|
n := strings.NewReplacer("linux-signed", "linux", "linux-latest", "linux", "-amd64", "", "-arm64", "", "-i386", "").Replace(p.Name)
|
|
|
|
if deb.isKernelSourcePackage(n) {
|
|
isRunning := false
|
|
for _, bn := range p.BinaryNames {
|
|
if bn == fmt.Sprintf("linux-image-%s", r.RunningKernel.Release) {
|
|
isRunning = true
|
|
break
|
|
}
|
|
}
|
|
// To detect vulnerabilities in running kernels only, skip if the kernel is not running.
|
|
if !isRunning {
|
|
continue
|
|
}
|
|
}
|
|
|
|
var f func(string, string) (map[string]gostmodels.DebianCVE, error) = deb.driver.GetFixedCvesDebian
|
|
if !fixed {
|
|
f = deb.driver.GetUnfixedCvesDebian
|
|
}
|
|
cs, err := f(major(r.Release), n)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("Failed to get CVEs. release: %s, src package: %s, err: %w", major(r.Release), p.Name, err)
|
|
}
|
|
for _, content := range deb.detect(cs, p, models.Kernel{Release: r.RunningKernel.Release, Version: r.Packages[fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)].Version}) {
|
|
c, ok := detects[content.cveContent.CveID]
|
|
if ok {
|
|
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
|
}
|
|
detects[content.cveContent.CveID] = content
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, content := range detects {
|
|
v, ok := r.ScannedCves[content.cveContent.CveID]
|
|
if ok {
|
|
if v.CveContents == nil {
|
|
v.CveContents = models.NewCveContents(content.cveContent)
|
|
} else {
|
|
v.CveContents[models.DebianSecurityTracker] = []models.CveContent{content.cveContent}
|
|
}
|
|
v.Confidences.AppendIfMissing(models.DebianSecurityTrackerMatch)
|
|
} else {
|
|
v = models.VulnInfo{
|
|
CveID: content.cveContent.CveID,
|
|
CveContents: models.NewCveContents(content.cveContent),
|
|
Confidences: models.Confidences{models.DebianSecurityTrackerMatch},
|
|
}
|
|
}
|
|
|
|
for _, s := range content.fixStatuses {
|
|
v.AffectedPackages = v.AffectedPackages.Store(s)
|
|
}
|
|
r.ScannedCves[content.cveContent.CveID] = v
|
|
}
|
|
|
|
return maps.Keys(detects), nil
|
|
}
|
|
|
|
func (deb Debian) isKernelSourcePackage(pkgname string) bool {
|
|
switch ss := strings.Split(pkgname, "-"); len(ss) {
|
|
case 1:
|
|
return pkgname == "linux"
|
|
case 2:
|
|
if ss[0] != "liunx" {
|
|
return false
|
|
}
|
|
switch ss[1] {
|
|
case "grsec":
|
|
return true
|
|
default:
|
|
_, err := strconv.ParseFloat(ss[1], 64)
|
|
return err == nil
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (deb Debian) detect(cves map[string]gostmodels.DebianCVE, srcPkg models.SrcPackage, runningKernel models.Kernel) []cveContent {
|
|
n := strings.NewReplacer("linux-signed", "linux", "linux-latest", "linux", "-amd64", "", "-arm64", "", "-i386", "").Replace(srcPkg.Name)
|
|
|
|
var contents []cveContent
|
|
for _, cve := range cves {
|
|
c := cveContent{
|
|
cveContent: *(Debian{}).ConvertToModel(&cve),
|
|
}
|
|
|
|
for _, p := range cve.Package {
|
|
for _, r := range p.Release {
|
|
switch r.Status {
|
|
case "open", "undetermined":
|
|
for _, bn := range srcPkg.BinaryNames {
|
|
if deb.isKernelSourcePackage(n) && bn != fmt.Sprintf("linux-image-%s", runningKernel.Release) {
|
|
continue
|
|
}
|
|
c.fixStatuses = append(c.fixStatuses, models.PackageFixStatus{
|
|
Name: bn,
|
|
FixState: r.Status,
|
|
NotFixedYet: true,
|
|
})
|
|
}
|
|
case "resolved":
|
|
installedVersion := srcPkg.Version
|
|
patchedVersion := r.FixedVersion
|
|
|
|
if deb.isKernelSourcePackage(n) {
|
|
installedVersion = runningKernel.Version
|
|
}
|
|
|
|
affected, err := deb.isGostDefAffected(installedVersion, patchedVersion)
|
|
if err != nil {
|
|
logging.Log.Debugf("Failed to parse versions: %s, Ver: %s, Gost: %s", err, installedVersion, patchedVersion)
|
|
continue
|
|
}
|
|
|
|
if affected {
|
|
for _, bn := range srcPkg.BinaryNames {
|
|
if deb.isKernelSourcePackage(n) && bn != fmt.Sprintf("linux-image-%s", runningKernel.Release) {
|
|
continue
|
|
}
|
|
c.fixStatuses = append(c.fixStatuses, models.PackageFixStatus{
|
|
Name: bn,
|
|
FixedIn: patchedVersion,
|
|
})
|
|
}
|
|
}
|
|
default:
|
|
logging.Log.Debugf("Failed to check vulnerable CVE. err: unknown status: %s", r.Status)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(c.fixStatuses) > 0 {
|
|
contents = append(contents, c)
|
|
}
|
|
}
|
|
return contents
|
|
}
|
|
|
|
func (deb Debian) isGostDefAffected(versionRelease, gostVersion string) (affected bool, err error) {
|
|
vera, err := debver.NewVersion(versionRelease)
|
|
if err != nil {
|
|
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", versionRelease, err)
|
|
}
|
|
verb, err := debver.NewVersion(gostVersion)
|
|
if err != nil {
|
|
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", gostVersion, err)
|
|
}
|
|
return vera.LessThan(verb), nil
|
|
}
|
|
|
|
// ConvertToModel converts gost model to vuls model
|
|
func (deb Debian) ConvertToModel(cve *gostmodels.DebianCVE) *models.CveContent {
|
|
severity := ""
|
|
for _, p := range cve.Package {
|
|
for _, r := range p.Release {
|
|
severity = r.Urgency
|
|
break
|
|
}
|
|
}
|
|
var optinal map[string]string
|
|
if cve.Scope != "" {
|
|
optinal = map[string]string{"attack range": cve.Scope}
|
|
}
|
|
return &models.CveContent{
|
|
Type: models.DebianSecurityTracker,
|
|
CveID: cve.CveID,
|
|
Summary: cve.Description,
|
|
Cvss2Severity: severity,
|
|
Cvss3Severity: severity,
|
|
SourceLink: fmt.Sprintf("https://security-tracker.debian.org/tracker/%s", cve.CveID),
|
|
Optional: optinal,
|
|
}
|
|
}
|