Fix OVAL detection on Debian and Ubuntu (#509)
* Add filter options to tui subcommand (#508) * Capture version of source packages on Debian based linux * Change makefile, gofmt -s * Refactoring * Implement OVAL detection of source packages for Debian, Ubuntu
This commit is contained in:
@@ -313,6 +313,7 @@ func (l *base) convertToModel() models.ScanResult {
|
||||
ScannedCves: l.VulnInfos,
|
||||
RunningKernel: l.Kernel,
|
||||
Packages: l.Packages,
|
||||
SrcPackages: l.SrcPackages,
|
||||
Optional: l.ServerInfo.Optional,
|
||||
Errors: errs,
|
||||
}
|
||||
|
||||
@@ -176,7 +176,6 @@ func (o *debian) checkDependencies() error {
|
||||
}
|
||||
|
||||
for _, name := range packNames {
|
||||
//TODO --show-format
|
||||
cmd := "dpkg-query -W " + name
|
||||
if r := o.exec(cmd, noSudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("%s is not installed", name)
|
||||
@@ -206,12 +205,13 @@ func (o *debian) scanPackages() error {
|
||||
RebootRequired: rebootRequired,
|
||||
}
|
||||
|
||||
installed, updatable, err := o.scanInstalledPackages()
|
||||
installed, updatable, srcPacks, err := o.scanInstalledPackages()
|
||||
if err != nil {
|
||||
o.log.Errorf("Failed to scan installed packages: %s", err)
|
||||
return err
|
||||
}
|
||||
o.Packages = installed
|
||||
o.SrcPackages = srcPacks
|
||||
|
||||
if config.Conf.Deep || o.Distro.Family == config.Raspbian {
|
||||
unsecures, err := o.scanUnsecurePackages(updatable)
|
||||
@@ -238,20 +238,26 @@ func (o *debian) rebootRequired() (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, error) {
|
||||
installed, updatable := models.Packages{}, models.Packages{}
|
||||
r := o.exec(`dpkg-query -W -f='${binary:Package}\t${db:Status-Abbrev}\t${Version}\n'`, noSudo)
|
||||
func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, models.SrcPackages, error) {
|
||||
installed, updatable, srcPacks := models.Packages{}, models.Packages{}, models.SrcPackages{}
|
||||
r := o.exec(`dpkg-query -W -f='${binary:Package},${db:Status-Abbrev},${Version},${Source},${source:Version}\n'`, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
return nil, nil, nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
|
||||
// e.g.
|
||||
// curl ii 7.47.0-1ubuntu2.2
|
||||
// openssh-server rc 1:7.2p2-4ubuntu2.2
|
||||
// e.g.
|
||||
// curl,ii ,7.38.0-4+deb8u2,,7.38.0-4+deb8u2
|
||||
// openssh-server,ii ,1:6.7p1-5+deb8u3,openssh,1:6.7p1-5+deb8u3
|
||||
// tar,ii ,1.27.1-2+b1,tar (1.27.1-2),1.27.1-2
|
||||
lines := strings.Split(r.Stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
|
||||
name, status, version, err := o.parseScannedPackagesLine(trimmed)
|
||||
name, status, version, srcName, srcVersion, err := o.parseScannedPackagesLine(trimmed)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf(
|
||||
"Debian: Failed to parse package line: %s", line)
|
||||
}
|
||||
|
||||
packageStatus := status[1]
|
||||
// Package status:
|
||||
// n = Not-installed
|
||||
@@ -266,20 +272,38 @@ func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, erro
|
||||
o.log.Debugf("%s package status is '%c', ignoring", name, packageStatus)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"Debian: Failed to parse package line: %s", line)
|
||||
}
|
||||
installed[name] = models.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
}
|
||||
|
||||
if srcName != "" && srcName != name {
|
||||
if pack, ok := srcPacks[srcName]; ok {
|
||||
pack.AddBinaryName(name)
|
||||
srcPacks[srcName] = pack
|
||||
} else {
|
||||
srcPacks[srcName] = models.SrcPackage{
|
||||
Name: srcName,
|
||||
Version: srcVersion,
|
||||
BinaryNames: []string{name},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove "linux"
|
||||
// kernel-related packages are showed "linux" as source package name
|
||||
// If "linux" is left, oval detection will cause trouble, so delete.
|
||||
delete(srcPacks, "linux")
|
||||
// Remove duplicate
|
||||
for name := range installed {
|
||||
delete(srcPacks, name)
|
||||
}
|
||||
|
||||
updatableNames, err := o.getUpdatablePackNames()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
for _, name := range updatableNames {
|
||||
for _, pack := range installed {
|
||||
@@ -293,29 +317,30 @@ func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, erro
|
||||
// Fill the candidate versions of upgradable packages
|
||||
err = o.fillCandidateVersion(updatable)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
|
||||
return nil, nil, nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
|
||||
}
|
||||
installed.MergeNewVersion(updatable)
|
||||
|
||||
return installed, updatable, nil
|
||||
return installed, updatable, srcPacks, nil
|
||||
}
|
||||
|
||||
var packageLinePattern = regexp.MustCompile(`^([^\t']+)\t(.+)\t(.+)$`)
|
||||
|
||||
func (o *debian) parseScannedPackagesLine(line string) (name, status, version string, err error) {
|
||||
result := packageLinePattern.FindStringSubmatch(line)
|
||||
if len(result) == 4 {
|
||||
func (o *debian) parseScannedPackagesLine(line string) (name, status, version, srcName, srcVersion string, err error) {
|
||||
ss := strings.Split(line, ",")
|
||||
if len(ss) == 5 {
|
||||
// remove :amd64, i386...
|
||||
name = result[1]
|
||||
name = ss[0]
|
||||
if i := strings.IndexRune(name, ':'); i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
status = result[2]
|
||||
version = result[3]
|
||||
status = ss[1]
|
||||
version = ss[2]
|
||||
// remove version. ex: tar (1.27.1-2)
|
||||
srcName = strings.Split(ss[3], " ")[0]
|
||||
srcVersion = ss[4]
|
||||
return
|
||||
}
|
||||
|
||||
return "", "", "", fmt.Errorf("Unknown format: %s", line)
|
||||
return "", "", "", "", "", fmt.Errorf("Unknown format: %s", line)
|
||||
}
|
||||
|
||||
func (o *debian) aptGetUpdate() error {
|
||||
|
||||
@@ -29,39 +29,6 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func TestParseScannedPackagesLineDebian(t *testing.T) {
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
name string
|
||||
status string
|
||||
version string
|
||||
}{
|
||||
{"base-passwd ii 3.5.33", "base-passwd", "ii ", "3.5.33"},
|
||||
{"bzip2 ii 1.0.6-5", "bzip2", "ii ", "1.0.6-5"},
|
||||
{"adduser ii 3.113+nmu3ubuntu3", "adduser", "ii ", "3.113+nmu3ubuntu3"},
|
||||
{"bash ii 4.3-7ubuntu1.5", "bash", "ii ", "4.3-7ubuntu1.5"},
|
||||
{"bsdutils ii 1:2.20.1-5.1ubuntu20.4", "bsdutils", "ii ", "1:2.20.1-5.1ubuntu20.4"},
|
||||
{"ca-certificates ii 20141019ubuntu0.14.04.1", "ca-certificates", "ii ", "20141019ubuntu0.14.04.1"},
|
||||
{"apt rc 1.0.1ubuntu2.8", "apt", "rc ", "1.0.1ubuntu2.8"},
|
||||
}
|
||||
|
||||
d := newDebian(config.ServerInfo{})
|
||||
for _, tt := range packagetests {
|
||||
n, s, v, _ := d.parseScannedPackagesLine(tt.in)
|
||||
if n != tt.name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.name, n)
|
||||
}
|
||||
if s != tt.status {
|
||||
t.Errorf("status: expected %s, actual %s", tt.status, s)
|
||||
}
|
||||
if v != tt.version {
|
||||
t.Errorf("version: expected %s, actual %s", tt.version, v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetCveIDsFromChangelog(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
|
||||
@@ -385,7 +385,7 @@ func (o *redhat) parseUpdatablePacksLine(line string) (models.Package, error) {
|
||||
ver = fmt.Sprintf("%s:%s", epoch, fields[2])
|
||||
}
|
||||
|
||||
repos := strings.Join(fields[4:len(fields)], " ")
|
||||
repos := strings.Join(fields[4:], " ")
|
||||
|
||||
p := models.Package{
|
||||
Name: fields[0],
|
||||
@@ -816,7 +816,7 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
|
||||
inDesctiption, inCves = true, false
|
||||
ss := strings.Split(line, " : ")
|
||||
advisory.Description += fmt.Sprintf("%s\n",
|
||||
strings.Join(ss[1:len(ss)], " : "))
|
||||
strings.Join(ss[1:], " : "))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -830,7 +830,7 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
|
||||
if inDesctiption {
|
||||
if ss := strings.Split(line, ": "); 1 < len(ss) {
|
||||
advisory.Description += fmt.Sprintf("%s\n",
|
||||
strings.Join(ss[1:len(ss)], ": "))
|
||||
strings.Join(ss[1:], ": "))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -838,7 +838,7 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
|
||||
if found := o.isCvesHeaderLine(line); found {
|
||||
inCves = true
|
||||
ss := strings.Split(line, "CVEs : ")
|
||||
line = strings.Join(ss[1:len(ss)], " ")
|
||||
line = strings.Join(ss[1:], " ")
|
||||
cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
|
||||
for _, cveID := range cveIDs {
|
||||
cveIDsSetInThisSection[cveID] = true
|
||||
|
||||
@@ -61,6 +61,9 @@ type osPackages struct {
|
||||
// installed packages
|
||||
Packages models.Packages
|
||||
|
||||
// installed source packages (Debian based only)
|
||||
SrcPackages models.SrcPackages
|
||||
|
||||
// unsecure packages
|
||||
VulnInfos models.VulnInfos
|
||||
|
||||
|
||||
Reference in New Issue
Block a user