Add: source code
This commit is contained in:
655
scan/debian.go
Normal file
655
scan/debian.go
Normal file
@@ -0,0 +1,655 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
// inherit OsTypeInterface
|
||||
type debian struct {
|
||||
linux
|
||||
}
|
||||
|
||||
// NewDebian is constructor
|
||||
func newDebian(c config.ServerInfo) *debian {
|
||||
d := &debian{}
|
||||
d.log = util.NewCustomLogger(c)
|
||||
return d
|
||||
}
|
||||
|
||||
// Ubuntu, Debian
|
||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
|
||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
|
||||
|
||||
deb = newDebian(c)
|
||||
|
||||
// set sudo option flag
|
||||
c.SudoOpt = config.SudoOption{ExecBySudo: true}
|
||||
deb.setServerInfo(c)
|
||||
|
||||
if r := sshExec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
|
||||
Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return false, deb
|
||||
}
|
||||
|
||||
if r := sshExec(c, "lsb_release -ir", noSudo); r.isSuccess() {
|
||||
// e.g.
|
||||
// root@fa3ec524be43:/# lsb_release -ir
|
||||
// Distributor ID: Ubuntu
|
||||
// Release: 14.04
|
||||
re, _ := regexp.Compile(
|
||||
`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
|
||||
result := re.FindStringSubmatch(trim(r.Stdout))
|
||||
|
||||
if len(result) == 0 {
|
||||
deb.setDistributionInfo("debian/ubuntu", "unknown")
|
||||
Log.Warnf(
|
||||
"Unknown Debian/Ubuntu version. lsb_release -ir: %s, Host: %s:%s",
|
||||
r.Stdout, c.Host, c.Port)
|
||||
} else {
|
||||
distro := strings.ToLower(trim(result[1]))
|
||||
deb.setDistributionInfo(distro, trim(result[2]))
|
||||
}
|
||||
return true, deb
|
||||
}
|
||||
|
||||
if r := sshExec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
|
||||
// e.g.
|
||||
// DISTRIB_ID=Ubuntu
|
||||
// DISTRIB_RELEASE=14.04
|
||||
// DISTRIB_CODENAME=trusty
|
||||
// DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS"
|
||||
re, _ := regexp.Compile(
|
||||
`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
|
||||
result := re.FindStringSubmatch(trim(r.Stdout))
|
||||
if len(result) == 0 {
|
||||
Log.Warnf(
|
||||
"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s, Host: %s:%s",
|
||||
r.Stdout, c.Host, c.Port)
|
||||
deb.setDistributionInfo("debian/ubuntu", "unknown")
|
||||
} else {
|
||||
distro := strings.ToLower(trim(result[1]))
|
||||
deb.setDistributionInfo(distro, trim(result[2]))
|
||||
}
|
||||
return true, deb
|
||||
}
|
||||
|
||||
// Debian
|
||||
cmd := "cat /etc/debian_version"
|
||||
if r := sshExec(c, cmd, noSudo); r.isSuccess() {
|
||||
deb.setDistributionInfo("debian", trim(r.Stdout))
|
||||
return true, deb
|
||||
}
|
||||
|
||||
Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return false, deb
|
||||
}
|
||||
|
||||
func trim(str string) string {
|
||||
return strings.TrimSpace(str)
|
||||
}
|
||||
|
||||
func (o *debian) install() error {
|
||||
|
||||
// apt-get update
|
||||
o.log.Infof("apt-get update...")
|
||||
cmd := util.PrependProxyEnv("apt-get update")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
if o.Family == "debian" {
|
||||
// install aptitude
|
||||
cmd = util.PrependProxyEnv("apt-get install --force-yes -y aptitude")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
o.log.Infof("Installed: aptitude")
|
||||
}
|
||||
|
||||
// install unattended-upgrades
|
||||
if !config.Conf.UseUnattendedUpgrades {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r := o.ssh("type unattended-upgrade", noSudo); r.isSuccess() {
|
||||
o.log.Infof(
|
||||
"Ignored: unattended-upgrade already installed")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd = util.PrependProxyEnv(
|
||||
"apt-get install --force-yes -y unattended-upgrades")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
o.log.Infof("Installed: unattended-upgrades")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *debian) scanPackages() error {
|
||||
var err error
|
||||
var packs []models.PackageInfo
|
||||
if packs, err = o.scanInstalledPackages(); err != nil {
|
||||
o.log.Errorf("Failed to scan installed packages")
|
||||
return err
|
||||
}
|
||||
o.setPackages(packs)
|
||||
|
||||
var unsecurePacks []CvePacksInfo
|
||||
if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
|
||||
o.log.Errorf("Failed to scan valnerable packages")
|
||||
return err
|
||||
}
|
||||
o.setUnsecurePackages(unsecurePacks)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error) {
|
||||
r := o.ssh("dpkg-query -W", noSudo)
|
||||
if !r.isSuccess() {
|
||||
return packs, fmt.Errorf(
|
||||
"Failed to scan packages. status: %d, stdout:%s, stderr: %s",
|
||||
r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
// e.g.
|
||||
// curl 7.19.7-40.el6_6.4
|
||||
// openldap 2.4.39-8.el6
|
||||
lines := strings.Split(r.Stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
|
||||
name, version, err := o.parseScanedPackagesLine(trimmed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Debian: Failed to parse package line: %s", line)
|
||||
}
|
||||
packs = append(packs, models.PackageInfo{
|
||||
Name: name,
|
||||
Version: version,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *debian) parseScanedPackagesLine(line string) (name, version string, err error) {
|
||||
re, _ := regexp.Compile(`^([^\t']+)\t(.+)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) == 3 {
|
||||
// remove :amd64, i386...
|
||||
name = regexp.MustCompile(":.+").ReplaceAllString(result[1], "")
|
||||
version = result[2]
|
||||
return
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("Unknown format: %s", line)
|
||||
}
|
||||
|
||||
// unattended-upgrade command need to check security upgrades).
|
||||
func (o *debian) checkRequiredPackagesInstalled() error {
|
||||
|
||||
if o.Family == "debian" {
|
||||
if r := o.ssh("test -f /usr/bin/aptitude", sudo); !r.isSuccess() {
|
||||
msg := "aptitude is not installed"
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
}
|
||||
|
||||
if !config.Conf.UseUnattendedUpgrades {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r := o.ssh("type unattended-upgrade", noSudo); !r.isSuccess() {
|
||||
msg := "unattended-upgrade is not installed"
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//TODO return whether already expired.
|
||||
func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
|
||||
// cmd := prependProxyEnv(conf.HTTPProxy, "apt-get update | cat; echo 1")
|
||||
cmd := util.PrependProxyEnv("apt-get update")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr,
|
||||
)
|
||||
}
|
||||
|
||||
var upgradablePackNames []string
|
||||
var err error
|
||||
if config.Conf.UseUnattendedUpgrades {
|
||||
upgradablePackNames, err = o.GetUnsecurePackNamesUsingUnattendedUpgrades()
|
||||
if err != nil {
|
||||
return []CvePacksInfo{}, err
|
||||
}
|
||||
} else {
|
||||
upgradablePackNames, err = o.GetUpgradablePackNames()
|
||||
if err != nil {
|
||||
return []CvePacksInfo{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Convert package name to PackageInfo struct
|
||||
var unsecurePacks []models.PackageInfo
|
||||
for _, name := range upgradablePackNames {
|
||||
for _, pack := range packs {
|
||||
if pack.Name == name {
|
||||
unsecurePacks = append(unsecurePacks, pack)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Collect CVE information of upgradable packages
|
||||
cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
|
||||
}
|
||||
|
||||
return cvePacksInfos, nil
|
||||
}
|
||||
|
||||
func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.PackageInfo, error) {
|
||||
reqChan := make(chan models.PackageInfo, len(packs))
|
||||
resChan := make(chan models.PackageInfo, len(packs))
|
||||
errChan := make(chan error, len(packs))
|
||||
defer close(resChan)
|
||||
defer close(errChan)
|
||||
defer close(reqChan)
|
||||
|
||||
go func() {
|
||||
for _, pack := range packs {
|
||||
reqChan <- pack
|
||||
}
|
||||
}()
|
||||
|
||||
timeout := time.After(5 * 60 * time.Second)
|
||||
concurrency := 5
|
||||
tasks := util.GenWorkers(concurrency)
|
||||
for range packs {
|
||||
tasks <- func() {
|
||||
select {
|
||||
case pack := <-reqChan:
|
||||
func(p models.PackageInfo) {
|
||||
cmd := fmt.Sprintf("apt-cache policy %s", p.Name)
|
||||
r := o.ssh(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
errChan <- fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
return
|
||||
}
|
||||
ver, err := o.parseAptCachePolicy(r.Stdout, p.Name)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("Failed to parse %s", err)
|
||||
}
|
||||
p.NewVersion = ver.Candidate
|
||||
resChan <- p
|
||||
}(pack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := []models.PackageInfo{}
|
||||
for i := 0; i < len(packs); i++ {
|
||||
select {
|
||||
case pack := <-resChan:
|
||||
result = append(result, pack)
|
||||
o.log.Infof("(%d/%d) Upgradable: %s-%s -> %s",
|
||||
i+1, len(packs), pack.Name, pack.Version, pack.NewVersion)
|
||||
case err := <-errChan:
|
||||
return nil, err
|
||||
case <-timeout:
|
||||
return nil, fmt.Errorf("Timeout fillCandidateVersion.")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (o *debian) GetUnsecurePackNamesUsingUnattendedUpgrades() (packNames []string, err error) {
|
||||
cmd := util.PrependProxyEnv("unattended-upgrades --dry-run -d 2>&1 ")
|
||||
release, err := strconv.ParseFloat(o.Release, 64)
|
||||
if err != nil {
|
||||
return packNames, fmt.Errorf(
|
||||
"OS Release Version is invalid, %s, %s", o.Family, o.Release)
|
||||
}
|
||||
switch {
|
||||
case release < 12:
|
||||
return packNames, fmt.Errorf(
|
||||
"Support expired. %s, %s", o.Family, o.Release)
|
||||
|
||||
case 12 < release && release < 14:
|
||||
cmd += `| grep 'pkgs that look like they should be upgraded:' |
|
||||
sed -e 's/pkgs that look like they should be upgraded://g'`
|
||||
|
||||
case 14 < release:
|
||||
cmd += `| grep 'Packages that will be upgraded:' |
|
||||
sed -e 's/Packages that will be upgraded://g'`
|
||||
|
||||
default:
|
||||
return packNames, fmt.Errorf(
|
||||
"Not supported yet. %s, %s", o.Family, o.Release)
|
||||
}
|
||||
|
||||
r := o.ssh(cmd, sudo)
|
||||
if r.isSuccess(0, 1) {
|
||||
packNames = strings.Split(strings.TrimSpace(r.Stdout), " ")
|
||||
return packNames, nil
|
||||
}
|
||||
|
||||
return packNames, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
|
||||
cmd := util.PrependProxyEnv("apt-get upgrade --dry-run")
|
||||
r := o.ssh(cmd, sudo)
|
||||
if r.isSuccess(0, 1) {
|
||||
return o.parseAptGetUpgrade(r.Stdout)
|
||||
}
|
||||
return packNames, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, err error) {
|
||||
startRe, _ := regexp.Compile(`The following packages will be upgraded:`)
|
||||
stopRe, _ := regexp.Compile(`^(\d+) upgraded.*`)
|
||||
startLineFound, stopLineFound := false, false
|
||||
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if !startLineFound {
|
||||
if matche := startRe.MatchString(line); matche {
|
||||
startLineFound = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
result := stopRe.FindStringSubmatch(line)
|
||||
if len(result) == 2 {
|
||||
numUpgradablePacks, err := strconv.Atoi(result[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to scan upgradable packages number. line: %s", line)
|
||||
}
|
||||
if numUpgradablePacks != len(upgradableNames) {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to scan upgradable packages, expected: %s, detected: %d",
|
||||
result[1], len(upgradableNames))
|
||||
}
|
||||
stopLineFound = true
|
||||
o.log.Debugf("Found the stop line. line: %s", line)
|
||||
break
|
||||
}
|
||||
upgradableNames = append(upgradableNames, strings.Fields(line)...)
|
||||
}
|
||||
if !startLineFound {
|
||||
// no upgrades
|
||||
return
|
||||
}
|
||||
if !stopLineFound {
|
||||
// There are upgrades, but not found the stop line.
|
||||
return nil, fmt.Errorf("Failed to scan upgradable packages")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
|
||||
|
||||
// { CVE ID: [packageInfo] }
|
||||
cvePackages := make(map[string][]models.PackageInfo)
|
||||
|
||||
type strarray []string
|
||||
resChan := make(chan struct {
|
||||
models.PackageInfo
|
||||
strarray
|
||||
}, len(unsecurePacks))
|
||||
errChan := make(chan error, len(unsecurePacks))
|
||||
reqChan := make(chan models.PackageInfo, len(unsecurePacks))
|
||||
defer close(resChan)
|
||||
defer close(errChan)
|
||||
defer close(reqChan)
|
||||
|
||||
go func() {
|
||||
for _, pack := range unsecurePacks {
|
||||
reqChan <- pack
|
||||
}
|
||||
}()
|
||||
|
||||
timeout := time.After(30 * 60 * time.Second)
|
||||
|
||||
concurrency := 10
|
||||
tasks := util.GenWorkers(concurrency)
|
||||
for range unsecurePacks {
|
||||
tasks <- func() {
|
||||
select {
|
||||
case pack := <-reqChan:
|
||||
func(p models.PackageInfo) {
|
||||
if cveIds, err := o.scanPackageCveIds(p); err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
resChan <- struct {
|
||||
models.PackageInfo
|
||||
strarray
|
||||
}{p, cveIds}
|
||||
}
|
||||
}(pack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(unsecurePacks); i++ {
|
||||
select {
|
||||
case pair := <-resChan:
|
||||
pack := pair.PackageInfo
|
||||
cveIds := pair.strarray
|
||||
for _, cveID := range cveIds {
|
||||
cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
|
||||
}
|
||||
o.log.Infof("(%d/%d) Scanned %s-%s : %s",
|
||||
i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIds)
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case <-timeout:
|
||||
return nil, fmt.Errorf("Timeout scanPackageCveIds.")
|
||||
}
|
||||
}
|
||||
|
||||
var cveIds []string
|
||||
for k := range cvePackages {
|
||||
cveIds = append(cveIds, k)
|
||||
}
|
||||
|
||||
o.log.Debugf("%d Cves are found. cves: %v", len(cveIds), cveIds)
|
||||
|
||||
o.log.Info("Fetching CVE details...")
|
||||
cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.log.Info("Done")
|
||||
|
||||
for _, detail := range cveDetails {
|
||||
cvePacksList = append(cvePacksList, CvePacksInfo{
|
||||
CveID: detail.CveID,
|
||||
CveDetail: detail,
|
||||
Packs: cvePackages[detail.CveID],
|
||||
// CvssScore: cinfo.CvssScore(conf.Lang),
|
||||
})
|
||||
}
|
||||
sort.Sort(CvePacksList(cvePacksList))
|
||||
return
|
||||
}
|
||||
|
||||
func (o *debian) scanPackageCveIds(pack models.PackageInfo) (cveIds []string, err error) {
|
||||
cmd := ""
|
||||
switch o.Family {
|
||||
case "ubuntu":
|
||||
cmd = fmt.Sprintf(`apt-get changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
|
||||
case "debian":
|
||||
cmd = fmt.Sprintf(`aptitude changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
|
||||
}
|
||||
cmd = util.PrependProxyEnv(cmd)
|
||||
|
||||
r := o.ssh(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
o.log.Warnf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
// Ignore this Error.
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
cveIds, err = o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
|
||||
if err != nil {
|
||||
trimUbuntu := strings.Split(pack.Version, "ubuntu")[0]
|
||||
return o.getCveIDParsingChangelog(r.Stdout, pack.Name, trimUbuntu)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *debian) getCveIDParsingChangelog(changelog string,
|
||||
packName string, versionOrLater string) (cveIDs []string, err error) {
|
||||
|
||||
cveIDs, err = o.parseChangelog(changelog, packName, versionOrLater)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ver := strings.Split(versionOrLater, "ubuntu")[0]
|
||||
cveIDs, err = o.parseChangelog(changelog, packName, ver)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
splittedByColon := strings.Split(versionOrLater, ":")
|
||||
if 1 < len(splittedByColon) {
|
||||
ver = splittedByColon[1]
|
||||
}
|
||||
cveIDs, err = o.parseChangelog(changelog, packName, ver)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO report as unable to parse changelog.
|
||||
o.log.Warn(err)
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// Collect CVE-IDs included in the changelog.
|
||||
// The version which specified in argument(versionOrLater) is excluded.
|
||||
func (o *debian) parseChangelog(changelog string,
|
||||
packName string, versionOrLater string) (cveIDs []string, err error) {
|
||||
|
||||
cveRe, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
|
||||
stopRe, _ := regexp.Compile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(versionOrLater)))
|
||||
stopLineFound := false
|
||||
lines := strings.Split(changelog, "\n")
|
||||
for _, line := range lines {
|
||||
if matche := stopRe.MatchString(line); matche {
|
||||
o.log.Debugf("Found the stop line. line: %s", line)
|
||||
stopLineFound = true
|
||||
break
|
||||
} else if matches := cveRe.FindAllString(line, -1); len(matches) > 0 {
|
||||
for _, m := range matches {
|
||||
cveIDs = util.AppendIfMissing(cveIDs, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !stopLineFound {
|
||||
return []string{}, fmt.Errorf(
|
||||
"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
|
||||
packName,
|
||||
versionOrLater,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type packCandidateVer struct {
|
||||
Name string
|
||||
Installed string
|
||||
Candidate string
|
||||
}
|
||||
|
||||
// parseAptCachePolicy the stdout of parse pat-get cache policy
|
||||
func (o *debian) parseAptCachePolicy(stdout, name string) (packCandidateVer, error) {
|
||||
ver := packCandidateVer{Name: name}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 2 {
|
||||
continue
|
||||
}
|
||||
switch fields[0] {
|
||||
case "Installed:":
|
||||
ver.Installed = fields[1]
|
||||
case "Candidate:":
|
||||
ver.Candidate = fields[1]
|
||||
return ver, nil
|
||||
default:
|
||||
// nop
|
||||
}
|
||||
}
|
||||
return ver, fmt.Errorf("Unknown Format: %s", stdout)
|
||||
}
|
||||
|
||||
func appendPackIfMissing(slice []models.PackageInfo, s models.PackageInfo) []models.PackageInfo {
|
||||
for _, ele := range slice {
|
||||
if ele.Name == s.Name &&
|
||||
ele.Version == s.Version &&
|
||||
ele.Release == s.Release {
|
||||
return slice
|
||||
}
|
||||
}
|
||||
return append(slice, s)
|
||||
}
|
||||
601
scan/debian_test.go
Normal file
601
scan/debian_test.go
Normal file
@@ -0,0 +1,601 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
func TestParseScanedPackagesLineDebian(t *testing.T) {
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
name string
|
||||
version string
|
||||
}{
|
||||
{"base-passwd 3.5.33", "base-passwd", "3.5.33"},
|
||||
{"bzip2 1.0.6-5", "bzip2", "1.0.6-5"},
|
||||
{"adduser 3.113+nmu3ubuntu3", "adduser", "3.113+nmu3ubuntu3"},
|
||||
{"bash 4.3-7ubuntu1.5", "bash", "4.3-7ubuntu1.5"},
|
||||
{"bsdutils 1:2.20.1-5.1ubuntu20.4", "bsdutils", "1:2.20.1-5.1ubuntu20.4"},
|
||||
{"ca-certificates 20141019ubuntu0.14.04.1", "ca-certificates", "20141019ubuntu0.14.04.1"},
|
||||
{"apt 1.0.1ubuntu2.8", "apt", "1.0.1ubuntu2.8"},
|
||||
}
|
||||
|
||||
d := newDebian(config.ServerInfo{})
|
||||
for _, tt := range packagetests {
|
||||
n, v, _ := d.parseScanedPackagesLine(tt.in)
|
||||
if n != tt.name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.name, n)
|
||||
}
|
||||
if v != tt.version {
|
||||
t.Errorf("version: expected %s, actual %s", tt.version, v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestgetCveIDParsingChangelog(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
in []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
// verubuntu1
|
||||
[]string{
|
||||
"systemd",
|
||||
"228-4ubuntu1",
|
||||
`systemd (229-2) unstable; urgency=medium
|
||||
systemd (229-1) unstable; urgency=medium
|
||||
systemd (228-6) unstable; urgency=medium
|
||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
|
||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
|
||||
systemd (228-5) unstable; urgency=medium
|
||||
systemd (228-4) unstable; urgency=medium
|
||||
systemd (228-3) unstable; urgency=medium
|
||||
systemd (228-2) unstable; urgency=medium
|
||||
systemd (228-1) unstable; urgency=medium
|
||||
systemd (227-3) unstable; urgency=medium
|
||||
systemd (227-2) unstable; urgency=medium
|
||||
systemd (227-1) unstable; urgency=medium`,
|
||||
},
|
||||
[]string{
|
||||
"CVE-2015-2325",
|
||||
"CVE-2015-2326",
|
||||
"CVE-2015-3210",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
// ver
|
||||
[]string{
|
||||
"libpcre3",
|
||||
"2:8.38-1ubuntu1",
|
||||
`pcre3 (2:8.38-2) unstable; urgency=low
|
||||
pcre3 (2:8.38-1) unstable; urgency=low
|
||||
pcre3 (2:8.35-8) unstable; urgency=low
|
||||
pcre3 (2:8.35-7.4) unstable; urgency=medium
|
||||
pcre3 (2:8.35-7.3) unstable; urgency=medium
|
||||
pcre3 (2:8.35-7.2) unstable; urgency=low
|
||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
|
||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
|
||||
pcre3 (2:8.35-7.1) unstable; urgency=medium
|
||||
pcre3 (2:8.35-7) unstable; urgency=medium`,
|
||||
},
|
||||
[]string{
|
||||
"CVE-2015-2325",
|
||||
"CVE-2015-2326",
|
||||
"CVE-2015-3210",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
// ver-ubuntu3
|
||||
[]string{
|
||||
"sysvinit",
|
||||
"2.88dsf-59.2ubuntu3",
|
||||
`sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
|
||||
sysvinit (2.88dsf-59.3) unstable; urgency=medium
|
||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
|
||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
|
||||
sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
|
||||
sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
|
||||
sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
|
||||
CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
sysvinit (2.88dsf-59.2) unstable; urgency=medium
|
||||
sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
|
||||
CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
|
||||
sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
|
||||
sysvinit (2.88dsf-59.1) unstable; urgency=medium
|
||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
sysvinit (2.88dsf-59) unstable; urgency=medium
|
||||
sysvinit (2.88dsf-58) unstable; urgency=low
|
||||
sysvinit (2.88dsf-57) unstable; urgency=low`,
|
||||
},
|
||||
[]string{
|
||||
"CVE-2015-2325",
|
||||
"CVE-2015-2326",
|
||||
"CVE-2015-3210",
|
||||
},
|
||||
},
|
||||
{
|
||||
// 1:ver-ubuntu3
|
||||
[]string{
|
||||
"bsdutils",
|
||||
"1:2.27.1-1ubuntu3",
|
||||
` util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
|
||||
util-linux (2.27.1-3) unstable; urgency=medium
|
||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
|
||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
|
||||
util-linux (2.27.1-2) unstable; urgency=medium
|
||||
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
|
||||
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
|
||||
util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
|
||||
util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
|
||||
util-linux (2.27.1-1) unstable; urgency=medium
|
||||
util-linux (2.27-3ubuntu1) xenial; urgency=medium
|
||||
util-linux (2.27-3) unstable; urgency=medium
|
||||
util-linux (2.27-2) unstable; urgency=medium
|
||||
util-linux (2.27-1) unstable; urgency=medium
|
||||
util-linux (2.27~rc2-2) experimental; urgency=medium
|
||||
util-linux (2.27~rc2-1) experimental; urgency=medium
|
||||
util-linux (2.27~rc1-1) experimental; urgency=medium
|
||||
util-linux (2.26.2-9) unstable; urgency=medium
|
||||
util-linux (2.26.2-8) experimental; urgency=medium
|
||||
util-linux (2.26.2-7) experimental; urgency=medium
|
||||
util-linux (2.26.2-6ubuntu3) wily; urgency=medium
|
||||
CVE-2015-2329: heap buffer overflow in compile_branch(). (Closes: #781795)
|
||||
util-linux (2.26.2-6ubuntu2) wily; urgency=medium
|
||||
util-linux (2.26.2-6ubuntu1) wily; urgency=medium
|
||||
util-linux (2.26.2-6) unstable; urgency=medium`,
|
||||
},
|
||||
[]string{
|
||||
"CVE-2015-2325",
|
||||
"CVE-2015-2326",
|
||||
"CVE-2015-3210",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d := newDebian(config.ServerInfo{})
|
||||
for _, tt := range tests {
|
||||
actual, _ := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], tt.in[1])
|
||||
if len(actual) != len(tt.expected) {
|
||||
t.Errorf("Len of return array are'nt same. expected %#v, actual %#v", tt.expected, actual)
|
||||
continue
|
||||
}
|
||||
for i := range tt.expected {
|
||||
if actual[i] != tt.expected[i] {
|
||||
t.Errorf("expected %s, actual %s", tt.expected[i], actual[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
_, err := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], "version number do'nt match case")
|
||||
if err != nil {
|
||||
t.Errorf("Returning error is unexpected.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUpdatablePackNames(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
expected []string
|
||||
}{
|
||||
{ // Ubuntu 12.04
|
||||
`Reading package lists... Done
|
||||
Building dependency tree
|
||||
Reading state information... Done
|
||||
The following packages will be upgraded:
|
||||
apt ca-certificates cpio dpkg e2fslibs e2fsprogs gnupg gpgv libc-bin libc6 libcomerr2 libpcre3
|
||||
libpng12-0 libss2 libssl1.0.0 libudev0 multiarch-support openssl tzdata udev upstart
|
||||
21 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
|
||||
Inst dpkg [1.16.1.2ubuntu7.5] (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf dpkg (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst upstart [1.5-0ubuntu7.2] (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libc-bin [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
|
||||
Conf libc-bin (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
|
||||
Inst libc6 [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libc6 (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libudev0 [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst tzdata [2015a-0ubuntu0.12.04] (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
|
||||
Conf tzdata (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
|
||||
Inst e2fslibs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
|
||||
Conf e2fslibs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 ]
|
||||
Inst e2fsprogs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf e2fsprogs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst gpgv [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf gpgv (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst gnupg [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf gnupg (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst apt [0.8.16~exp12ubuntu10.22] (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf apt (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libcomerr2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libcomerr2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libss2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libss2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libssl1.0.0 [1.0.1-4ubuntu5.21] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libssl1.0.0 (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libpcre3 [8.12-4] (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libpng12-0 [1.2.46-3ubuntu4] (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst multiarch-support [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf multiarch-support (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst cpio [2.11-7ubuntu3.1] (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst udev [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst openssl [1.0.1-4ubuntu5.33] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst ca-certificates [20141019ubuntu0.12.04.1] (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])
|
||||
Conf libudev0 (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf upstart (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libpcre3 (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libpng12-0 (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf cpio (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf udev (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf openssl (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf ca-certificates (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])`,
|
||||
[]string{
|
||||
"apt",
|
||||
"ca-certificates",
|
||||
"cpio",
|
||||
"dpkg",
|
||||
"e2fslibs",
|
||||
"e2fsprogs",
|
||||
"gnupg",
|
||||
"gpgv",
|
||||
"libc-bin",
|
||||
"libc6",
|
||||
"libcomerr2",
|
||||
"libpcre3",
|
||||
"libpng12-0",
|
||||
"libss2",
|
||||
"libssl1.0.0",
|
||||
"libudev0",
|
||||
"multiarch-support",
|
||||
"openssl",
|
||||
"tzdata",
|
||||
"udev",
|
||||
"upstart",
|
||||
},
|
||||
},
|
||||
{ // Ubuntu 14.04
|
||||
`Reading package lists... Done
|
||||
Building dependency tree
|
||||
Reading state information... Done
|
||||
Calculating upgrade... Done
|
||||
The following packages will be upgraded:
|
||||
apt apt-utils base-files bsdutils coreutils cpio dh-python dpkg e2fslibs
|
||||
e2fsprogs gcc-4.8-base gcc-4.9-base gnupg gpgv ifupdown initscripts iproute2
|
||||
isc-dhcp-client isc-dhcp-common libapt-inst1.5 libapt-pkg4.12 libblkid1
|
||||
libc-bin libc6 libcgmanager0 libcomerr2 libdrm2 libexpat1 libffi6 libgcc1
|
||||
libgcrypt11 libgnutls-openssl27 libgnutls26 libmount1 libpcre3 libpng12-0
|
||||
libpython3.4-minimal libpython3.4-stdlib libsqlite3-0 libss2 libssl1.0.0
|
||||
libstdc++6 libtasn1-6 libudev1 libuuid1 login mount multiarch-support
|
||||
ntpdate passwd python3.4 python3.4-minimal rsyslog sudo sysv-rc
|
||||
sysvinit-utils tzdata udev util-linux
|
||||
59 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
|
||||
Inst base-files [7.2ubuntu5.2] (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf base-files (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst coreutils [8.21-1ubuntu5.1] (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf coreutils (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst dpkg [1.17.5ubuntu5.3] (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf dpkg (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libc-bin [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libc6 [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libgcc1 [1:4.9.1-0ubuntu1] (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst gcc-4.9-base [4.9.1-0ubuntu1] (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf gcc-4.9-base (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libgcc1 (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libc6 (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libc-bin (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst e2fslibs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
|
||||
Conf e2fslibs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 ]
|
||||
Inst e2fsprogs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf e2fsprogs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst login [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf login (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst mount [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf mount (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst tzdata [2015a-0ubuntu0.14.04] (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
|
||||
Conf tzdata (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
|
||||
Inst sysvinit-utils [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst sysv-rc [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
|
||||
Conf sysv-rc (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
|
||||
Conf sysvinit-utils (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst util-linux [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf util-linux (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst gcc-4.8-base [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
|
||||
Conf gcc-4.8-base (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
|
||||
Inst libstdc++6 [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libstdc++6 (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libapt-pkg4.12 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libapt-pkg4.12 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst gpgv [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf gpgv (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst gnupg [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf gnupg (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst apt [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf apt (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst bsdutils [1:2.20.1-5.1ubuntu20.4] (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf bsdutils (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst passwd [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf passwd (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libuuid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libuuid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libblkid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libblkid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libcomerr2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libcomerr2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libmount1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libmount1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libpcre3 [1:8.31-2ubuntu2] (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libpcre3 (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libss2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libss2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libapt-inst1.5 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libexpat1 [2.1.0-4ubuntu1] (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libffi6 [3.1~rc1+r3.0.13-12] (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libgcrypt11 [1.5.3-2ubuntu4.1] (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libtasn1-6 [3.4-3ubuntu0.1] (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libgnutls-openssl27 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst libgnutls26 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libsqlite3-0 [3.8.2-1ubuntu2] (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst python3.4 [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst libpython3.4-stdlib [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst python3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst libssl1.0.0 [1.0.1f-1ubuntu2.8] (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst libpython3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst ntpdate [1:4.2.6.p5+dfsg-3ubuntu2.14.04.2] (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libdrm2 [2.4.56-1~ubuntu2] (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libpng12-0 [1.2.50-1ubuntu2] (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst initscripts [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libcgmanager0 [0.24-0ubuntu7.3] (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst udev [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst libudev1 [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst multiarch-support [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf multiarch-support (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst apt-utils [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst dh-python [1.20140128-1ubuntu8] (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
|
||||
Inst iproute2 [3.12.0-2] (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst ifupdown [0.7.47.2ubuntu4.1] (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst isc-dhcp-client [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst isc-dhcp-common [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst rsyslog [7.4.4-1ubuntu2.5] (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst sudo [1.8.9p5-1ubuntu1] (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst cpio [2.11+dfsg-1ubuntu1.1] (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libapt-inst1.5 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libexpat1 (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libffi6 (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libgcrypt11 (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libtasn1-6 (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libgnutls26 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libgnutls-openssl27 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libsqlite3-0 (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libssl1.0.0 (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libpython3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf python3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libpython3.4-stdlib (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf python3.4 (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf ntpdate (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libdrm2 (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libpng12-0 (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf initscripts (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libcgmanager0 (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libudev1 (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf udev (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf apt-utils (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf dh-python (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
|
||||
Conf iproute2 (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf ifupdown (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf isc-dhcp-common (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf isc-dhcp-client (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf rsyslog (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf sudo (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf cpio (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
`,
|
||||
[]string{
|
||||
"apt",
|
||||
"apt-utils",
|
||||
"base-files",
|
||||
"bsdutils",
|
||||
"coreutils",
|
||||
"cpio",
|
||||
"dh-python",
|
||||
"dpkg",
|
||||
"e2fslibs",
|
||||
"e2fsprogs",
|
||||
"gcc-4.8-base",
|
||||
"gcc-4.9-base",
|
||||
"gnupg",
|
||||
"gpgv",
|
||||
"ifupdown",
|
||||
"initscripts",
|
||||
"iproute2",
|
||||
"isc-dhcp-client",
|
||||
"isc-dhcp-common",
|
||||
"libapt-inst1.5",
|
||||
"libapt-pkg4.12",
|
||||
"libblkid1",
|
||||
"libc-bin",
|
||||
"libc6",
|
||||
"libcgmanager0",
|
||||
"libcomerr2",
|
||||
"libdrm2",
|
||||
"libexpat1",
|
||||
"libffi6",
|
||||
"libgcc1",
|
||||
"libgcrypt11",
|
||||
"libgnutls-openssl27",
|
||||
"libgnutls26",
|
||||
"libmount1",
|
||||
"libpcre3",
|
||||
"libpng12-0",
|
||||
"libpython3.4-minimal",
|
||||
"libpython3.4-stdlib",
|
||||
"libsqlite3-0",
|
||||
"libss2",
|
||||
"libssl1.0.0",
|
||||
"libstdc++6",
|
||||
"libtasn1-6",
|
||||
"libudev1",
|
||||
"libuuid1",
|
||||
"login",
|
||||
"mount",
|
||||
"multiarch-support",
|
||||
"ntpdate",
|
||||
"passwd",
|
||||
"python3.4",
|
||||
"python3.4-minimal",
|
||||
"rsyslog",
|
||||
"sudo",
|
||||
"sysv-rc",
|
||||
"sysvinit-utils",
|
||||
"tzdata",
|
||||
"udev",
|
||||
"util-linux",
|
||||
},
|
||||
},
|
||||
{
|
||||
//Ubuntu12.04
|
||||
`Reading package lists... Done
|
||||
Building dependency tree
|
||||
Reading state information... Done
|
||||
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
//Ubuntu14.04
|
||||
`Reading package lists... Done
|
||||
Building dependency tree
|
||||
Reading state information... Done
|
||||
Calculating upgrade... Done
|
||||
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
|
||||
[]string{},
|
||||
},
|
||||
}
|
||||
|
||||
d := newDebian(config.ServerInfo{})
|
||||
for _, tt := range tests {
|
||||
actual, err := d.parseAptGetUpgrade(tt.in)
|
||||
if err != nil {
|
||||
t.Errorf("Returning error is unexpected.")
|
||||
}
|
||||
if len(tt.expected) != len(actual) {
|
||||
t.Errorf("Result length is not as same as expected. expected: %d, actual: %d", len(tt.expected), len(actual))
|
||||
pp.Println(tt.expected)
|
||||
pp.Println(actual)
|
||||
return
|
||||
}
|
||||
for i := range tt.expected {
|
||||
if tt.expected[i] != actual[i] {
|
||||
t.Errorf("[%d] expected %s, actual %s", i, tt.expected[i], actual[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAptCachePolicy(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
stdout string
|
||||
name string
|
||||
expected packCandidateVer
|
||||
}{
|
||||
{
|
||||
// Ubuntu 16.04
|
||||
`openssl:
|
||||
Installed: 1.0.2f-2ubuntu1
|
||||
Candidate: 1.0.2g-1ubuntu2
|
||||
Version table:
|
||||
1.0.2g-1ubuntu2 500
|
||||
500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages
|
||||
*** 1.0.2f-2ubuntu1 100
|
||||
100 /var/lib/dpkg/status`,
|
||||
"openssl",
|
||||
packCandidateVer{
|
||||
Name: "openssl",
|
||||
Installed: "1.0.2f-2ubuntu1",
|
||||
Candidate: "1.0.2g-1ubuntu2",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Ubuntu 14.04
|
||||
`openssl:
|
||||
Installed: 1.0.1f-1ubuntu2.16
|
||||
Candidate: 1.0.1f-1ubuntu2.17
|
||||
Version table:
|
||||
1.0.1f-1ubuntu2.17 0
|
||||
500 http://archive.ubuntu.com/ubuntu/ trusty-updates/main amd64 Packages
|
||||
500 http://archive.ubuntu.com/ubuntu/ trusty-security/main amd64 Packages
|
||||
*** 1.0.1f-1ubuntu2.16 0
|
||||
100 /var/lib/dpkg/status
|
||||
1.0.1f-1ubuntu2 0
|
||||
500 http://archive.ubuntu.com/ubuntu/ trusty/main amd64 Packages`,
|
||||
"openssl",
|
||||
packCandidateVer{
|
||||
Name: "openssl",
|
||||
Installed: "1.0.1f-1ubuntu2.16",
|
||||
Candidate: "1.0.1f-1ubuntu2.17",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Ubuntu 12.04
|
||||
`openssl:
|
||||
Installed: 1.0.1-4ubuntu5.33
|
||||
Candidate: 1.0.1-4ubuntu5.34
|
||||
Version table:
|
||||
1.0.1-4ubuntu5.34 0
|
||||
500 http://archive.ubuntu.com/ubuntu/ precise-updates/main amd64 Packages
|
||||
500 http://archive.ubuntu.com/ubuntu/ precise-security/main amd64 Packages
|
||||
*** 1.0.1-4ubuntu5.33 0
|
||||
100 /var/lib/dpkg/status
|
||||
1.0.1-4ubuntu3 0
|
||||
500 http://archive.ubuntu.com/ubuntu/ precise/main amd64 Packages`,
|
||||
"openssl",
|
||||
packCandidateVer{
|
||||
Name: "openssl",
|
||||
Installed: "1.0.1-4ubuntu5.33",
|
||||
Candidate: "1.0.1-4ubuntu5.34",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d := newDebian(config.ServerInfo{})
|
||||
for _, tt := range tests {
|
||||
actual, err := d.parseAptCachePolicy(tt.stdout, tt.name)
|
||||
if err != nil {
|
||||
t.Errorf("Error has occurred: %s, actual: %#v", err, actual)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.expected, actual) {
|
||||
e := pp.Sprintf("%v", tt.expected)
|
||||
a := pp.Sprintf("%v", actual)
|
||||
t.Errorf("expected %s, actual %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
129
scan/linux.go
Normal file
129
scan/linux.go
Normal file
@@ -0,0 +1,129 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
type linux struct {
|
||||
ServerInfo config.ServerInfo
|
||||
|
||||
Family string
|
||||
Release string
|
||||
osPackages
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
func (l *linux) ssh(cmd string, sudo bool) sshResult {
|
||||
return sshExec(l.ServerInfo, cmd, sudo, l.log)
|
||||
}
|
||||
|
||||
func (l *linux) setServerInfo(c config.ServerInfo) {
|
||||
l.ServerInfo = c
|
||||
}
|
||||
|
||||
func (l *linux) getServerInfo() config.ServerInfo {
|
||||
return l.ServerInfo
|
||||
}
|
||||
|
||||
func (l *linux) setDistributionInfo(fam, rel string) {
|
||||
l.Family = fam
|
||||
l.Release = rel
|
||||
}
|
||||
|
||||
func (l *linux) convertToModel() (models.ScanResult, error) {
|
||||
var cves, unknownScoreCves []models.CveInfo
|
||||
for _, p := range l.UnsecurePackages {
|
||||
if p.CveDetail.CvssScore(config.Conf.Lang) < 0 {
|
||||
unknownScoreCves = append(unknownScoreCves, models.CveInfo{
|
||||
CveDetail: p.CveDetail,
|
||||
Packages: p.Packs,
|
||||
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
cpenames := []models.CpeName{}
|
||||
for _, cpename := range p.CpeNames {
|
||||
cpenames = append(cpenames,
|
||||
models.CpeName{Name: cpename})
|
||||
}
|
||||
|
||||
cve := models.CveInfo{
|
||||
CveDetail: p.CveDetail,
|
||||
Packages: p.Packs,
|
||||
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
|
||||
CpeNames: cpenames,
|
||||
}
|
||||
cves = append(cves, cve)
|
||||
}
|
||||
|
||||
return models.ScanResult{
|
||||
ServerName: l.ServerInfo.ServerName,
|
||||
Family: l.Family,
|
||||
Release: l.Release,
|
||||
KnownCves: cves,
|
||||
UnknownCves: unknownScoreCves,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
|
||||
func (l *linux) scanVulnByCpeName() error {
|
||||
unsecurePacks := CvePacksList{}
|
||||
|
||||
serverInfo := l.getServerInfo()
|
||||
cpeNames := serverInfo.CpeNames
|
||||
|
||||
// remove duplicate
|
||||
set := map[string]CvePacksInfo{}
|
||||
|
||||
for _, name := range cpeNames {
|
||||
details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, detail := range details {
|
||||
if val, ok := set[detail.CveID]; ok {
|
||||
names := val.CpeNames
|
||||
names = append(names, name)
|
||||
val.CpeNames = names
|
||||
set[detail.CveID] = val
|
||||
} else {
|
||||
set[detail.CveID] = CvePacksInfo{
|
||||
CveID: detail.CveID,
|
||||
CveDetail: detail,
|
||||
CpeNames: []string{name},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key := range set {
|
||||
unsecurePacks = append(unsecurePacks, set[key])
|
||||
}
|
||||
unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
|
||||
sort.Sort(CvePacksList(unsecurePacks))
|
||||
l.setUnsecurePackages(unsecurePacks)
|
||||
return nil
|
||||
}
|
||||
18
scan/linux_test.go
Normal file
18
scan/linux_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
861
scan/redhat.go
Normal file
861
scan/redhat.go
Normal file
@@ -0,0 +1,861 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
// inherit OsTypeInterface
|
||||
type redhat struct {
|
||||
linux
|
||||
}
|
||||
|
||||
// NewRedhat is constructor
|
||||
func newRedhat(c config.ServerInfo) *redhat {
|
||||
r := &redhat{}
|
||||
r.log = util.NewCustomLogger(c)
|
||||
return r
|
||||
}
|
||||
|
||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb
|
||||
func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
|
||||
red = newRedhat(c)
|
||||
|
||||
// set sudo option flag
|
||||
c.SudoOpt = config.SudoOption{ExecBySudoSh: true}
|
||||
red.setServerInfo(c)
|
||||
|
||||
if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
|
||||
red.setDistributionInfo("fedora", "unknown")
|
||||
Log.Warn("Fedora not tested yet. Host: %s:%s", c.Host, c.Port)
|
||||
return true, red
|
||||
}
|
||||
|
||||
if r := sshExec(c, "ls /etc/redhat-release", noSudo); r.isSuccess() {
|
||||
// https://www.rackaid.com/blog/how-to-determine-centos-or-red-hat-version/
|
||||
// e.g.
|
||||
// $ cat /etc/redhat-release
|
||||
// CentOS release 6.5 (Final)
|
||||
if r := sshExec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() {
|
||||
re, _ := regexp.Compile(`(.*) release (\d[\d.]*)`)
|
||||
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
|
||||
if len(result) != 3 {
|
||||
Log.Warn(
|
||||
"Failed to parse RedHat/CentOS version. stdout: %s, Host: %s:%s",
|
||||
r.Stdout, c.Host, c.Port)
|
||||
return true, red
|
||||
}
|
||||
|
||||
release := result[2]
|
||||
switch strings.ToLower(result[1]) {
|
||||
case "centos", "centos linux":
|
||||
red.setDistributionInfo("centos", release)
|
||||
default:
|
||||
red.setDistributionInfo("rhel", release)
|
||||
}
|
||||
return true, red
|
||||
}
|
||||
return true, red
|
||||
}
|
||||
|
||||
if r := sshExec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
|
||||
family := "amazon"
|
||||
release := "unknown"
|
||||
if r := sshExec(c, "cat /etc/system-release", noSudo); r.isSuccess() {
|
||||
fields := strings.Fields(r.Stdout)
|
||||
if len(fields) == 5 {
|
||||
release = fields[4]
|
||||
}
|
||||
}
|
||||
red.setDistributionInfo(family, release)
|
||||
return true, red
|
||||
}
|
||||
|
||||
Log.Debugf("Not RedHat like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return false, red
|
||||
}
|
||||
|
||||
// CentOS 5 ... yum-plugin-security, yum-changelog
|
||||
// CentOS 6 ... yum-plugin-security, yum-plugin-changelog
|
||||
// CentOS 7 ... yum-plugin-security, yum-plugin-changelog
|
||||
// RHEL, Amazon ... no additinal packages needed
|
||||
func (o *redhat) install() error {
|
||||
|
||||
switch o.Family {
|
||||
case "rhel", "amazon":
|
||||
o.log.Infof("Nothing to do")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := o.installYumPluginSecurity(); err != nil {
|
||||
return err
|
||||
}
|
||||
return o.installYumChangelog()
|
||||
}
|
||||
|
||||
func (o *redhat) installYumPluginSecurity() error {
|
||||
|
||||
if r := o.ssh("rpm -q yum-plugin-security", noSudo); r.isSuccess() {
|
||||
o.log.Infof("Ignored: yum-plugin-security already installed")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := util.PrependProxyEnv("yum install -y yum-plugin-security")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
return fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) installYumChangelog() error {
|
||||
o.log.Info("Installing yum-plugin-security...")
|
||||
|
||||
if o.Family == "centos" {
|
||||
var majorVersion int
|
||||
if 0 < len(o.Release) {
|
||||
majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
|
||||
} else {
|
||||
return fmt.Errorf(
|
||||
"Not implemented yet. family: %s, release: %s",
|
||||
o.Family, o.Release)
|
||||
}
|
||||
|
||||
var packName = ""
|
||||
if majorVersion < 6 {
|
||||
packName = "yum-changelog"
|
||||
} else {
|
||||
packName = "yum-plugin-changelog"
|
||||
}
|
||||
|
||||
cmd := "rpm -q " + packName
|
||||
if r := o.ssh(cmd, noSudo); r.isSuccess() {
|
||||
o.log.Infof("Ignored: %s already installed.", packName)
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd = util.PrependProxyEnv("yum install -y " + packName)
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
return fmt.Errorf(
|
||||
"Failed to install %s. status: %d, stdout: %s, stderr: %s",
|
||||
packName, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
o.log.Infof("Installed: %s.", packName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) checkRequiredPackagesInstalled() error {
|
||||
if config.Conf.UseYumPluginSecurity {
|
||||
// check if yum-plugin-security is installed.
|
||||
// Amazon Linux, REHL can execute 'yum updateinfo --security updates' without yum-plugin-security
|
||||
cmd := "rpm -q yum-plugin-security"
|
||||
if o.Family == "centos" {
|
||||
if r := o.ssh(cmd, noSudo); !r.isSuccess() {
|
||||
msg := "yum-plugin-security is not installed"
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if o.Family == "centos" {
|
||||
var majorVersion int
|
||||
if 0 < len(o.Release) {
|
||||
majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
|
||||
} else {
|
||||
msg := fmt.Sprintf("Not implemented yet. family: %s, release: %s", o.Family, o.Release)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
var packName = ""
|
||||
if majorVersion < 6 {
|
||||
packName = "yum-changelog"
|
||||
} else {
|
||||
packName = "yum-plugin-changelog"
|
||||
}
|
||||
|
||||
cmd := "rpm -q " + packName
|
||||
if r := o.ssh(cmd, noSudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("%s is not installed", packName)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) scanPackages() error {
|
||||
var err error
|
||||
var packs []models.PackageInfo
|
||||
if packs, err = o.scanInstalledPackages(); err != nil {
|
||||
o.log.Errorf("Failed to scan installed packages")
|
||||
return err
|
||||
}
|
||||
o.setPackages(packs)
|
||||
|
||||
var unsecurePacks []CvePacksInfo
|
||||
if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
|
||||
o.log.Errorf("Failed to scan valnerable packages")
|
||||
return err
|
||||
}
|
||||
o.setUnsecurePackages(unsecurePacks)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoList, err error) {
|
||||
cmd := "rpm -qa --queryformat '%{NAME}\t%{VERSION}\t%{RELEASE}\n'"
|
||||
r := o.ssh(cmd, noSudo)
|
||||
if r.isSuccess() {
|
||||
// e.g.
|
||||
// openssl 1.0.1e 30.el6.11
|
||||
lines := strings.Split(r.Stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if trimed := strings.TrimSpace(line); len(trimed) != 0 {
|
||||
var packinfo models.PackageInfo
|
||||
if packinfo, err = o.parseScanedPackagesLine(line); err != nil {
|
||||
return
|
||||
}
|
||||
installedPackages = append(installedPackages, packinfo)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return installedPackages, fmt.Errorf(
|
||||
"Scan packages failed. status: %d, stdout: %s, stderr: %s",
|
||||
r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
func (o *redhat) parseScanedPackagesLine(line string) (pack models.PackageInfo, err error) {
|
||||
re, _ := regexp.Compile(`^([^\t']+)\t([^\t]+)\t(.+)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) == 4 {
|
||||
pack.Name = result[1]
|
||||
pack.Version = result[2]
|
||||
pack.Release = strings.TrimSpace(result[3])
|
||||
} else {
|
||||
err = fmt.Errorf("redhat: Failed to parse package line: %s", line)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
|
||||
if o.Family != "centos" || config.Conf.UseYumPluginSecurity {
|
||||
// Amazon, RHEL has yum updateinfo as default
|
||||
// yum updateinfo can collenct vendor advisory information.
|
||||
return o.scanUnsecurePackagesUsingYumPluginSecurity()
|
||||
}
|
||||
// CentOS does not have security channel...
|
||||
// So, yum check-update then parse chnagelog.
|
||||
return o.scanUnsecurePackagesUsingYumCheckUpdate()
|
||||
}
|
||||
|
||||
//TODO return whether already expired.
|
||||
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) {
|
||||
|
||||
cmd := "yum check-update"
|
||||
r := o.ssh(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess(0, 100) {
|
||||
//returns an exit code of 100 if there are available updates.
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
// get Updateble package name, installed, candidate version.
|
||||
packInfoList, err := o.parseYumCheckUpdateLines(r.Stdout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
|
||||
}
|
||||
o.log.Debugf("%s", pp.Sprintf("%s", packInfoList))
|
||||
|
||||
// Collect CVE-IDs in changelog
|
||||
type PackInfoCveIDs struct {
|
||||
PackInfo models.PackageInfo
|
||||
CveIDs []string
|
||||
}
|
||||
var results []PackInfoCveIDs
|
||||
for i, packInfo := range packInfoList {
|
||||
changelog, err := o.getChangelog(packInfo.Name)
|
||||
if err != nil {
|
||||
o.log.Errorf("Failed to collect CVE. err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Collect unique set of CVE-ID in each changelog
|
||||
uniqueCveIDMap := make(map[string]bool)
|
||||
lines := strings.Split(changelog, "\n")
|
||||
for _, line := range lines {
|
||||
cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
|
||||
for _, c := range cveIDs {
|
||||
uniqueCveIDMap[c] = true
|
||||
}
|
||||
}
|
||||
|
||||
// keys
|
||||
var cveIDs []string
|
||||
for k := range uniqueCveIDMap {
|
||||
cveIDs = append(cveIDs, k)
|
||||
}
|
||||
p := PackInfoCveIDs{
|
||||
PackInfo: packInfo,
|
||||
CveIDs: cveIDs,
|
||||
}
|
||||
results = append(results, p)
|
||||
|
||||
o.log.Infof("(%d/%d) Scanned %s-%s-%s -> %s-%s : %s",
|
||||
i+1,
|
||||
len(packInfoList),
|
||||
p.PackInfo.Name,
|
||||
p.PackInfo.Version,
|
||||
p.PackInfo.Release,
|
||||
p.PackInfo.NewVersion,
|
||||
p.PackInfo.NewRelease,
|
||||
p.CveIDs)
|
||||
}
|
||||
|
||||
// transform datastructure
|
||||
// - From
|
||||
// [
|
||||
// {
|
||||
// PackInfo: models.PackageInfo,
|
||||
// CveIDs: []string,
|
||||
// },
|
||||
// ]
|
||||
// - To
|
||||
// map {
|
||||
// CveID: []models.PackageInfo
|
||||
// }
|
||||
cveIDPackInfoMap := make(map[string][]models.PackageInfo)
|
||||
for _, res := range results {
|
||||
for _, cveID := range res.CveIDs {
|
||||
// packInfo, found := o.Packages.FindByName(res.Packname)
|
||||
// if !found {
|
||||
// return CvePacksList{}, fmt.Errorf(
|
||||
// "Faild to transform data structure: %v", res.Packname)
|
||||
// }
|
||||
cveIDPackInfoMap[cveID] = append(cveIDPackInfoMap[cveID], res.PackInfo)
|
||||
}
|
||||
}
|
||||
|
||||
var uniqueCveIDs []string
|
||||
for cveID := range cveIDPackInfoMap {
|
||||
uniqueCveIDs = append(uniqueCveIDs, cveID)
|
||||
}
|
||||
|
||||
// cveIDs => []cve.CveInfo
|
||||
o.log.Info("Fetching CVE details...")
|
||||
cveDetails, err := cveapi.CveClient.FetchCveDetails(uniqueCveIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.log.Info("Done")
|
||||
|
||||
cvePacksList := []CvePacksInfo{}
|
||||
for _, detail := range cveDetails {
|
||||
// Amazon, RHEL do not use this method, so VendorAdvisory do not set.
|
||||
cvePacksList = append(cvePacksList, CvePacksInfo{
|
||||
CveID: detail.CveID,
|
||||
CveDetail: detail,
|
||||
Packs: cveIDPackInfoMap[detail.CveID],
|
||||
// CvssScore: cinfo.CvssScore(conf.Lang),
|
||||
})
|
||||
}
|
||||
sort.Sort(CvePacksList(cvePacksList))
|
||||
return cvePacksList, nil
|
||||
}
|
||||
|
||||
// parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
|
||||
func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.PackageInfoList, err error) {
|
||||
needToParse := false
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
// update information of packages begin after blank line.
|
||||
if trimed := strings.TrimSpace(line); len(trimed) == 0 {
|
||||
needToParse = true
|
||||
continue
|
||||
}
|
||||
if needToParse {
|
||||
candidate, err := o.parseYumCheckUpdateLine(line)
|
||||
if err != nil {
|
||||
return models.PackageInfoList{}, err
|
||||
}
|
||||
|
||||
installed, found := o.Packages.FindByName(candidate.Name)
|
||||
if !found {
|
||||
return models.PackageInfoList{}, fmt.Errorf(
|
||||
"Failed to parse yum check update line: %s-%s-%s",
|
||||
candidate.Name, candidate.Version, candidate.Release)
|
||||
}
|
||||
installed.NewVersion = candidate.NewVersion
|
||||
installed.NewRelease = candidate.NewRelease
|
||||
results = append(results, installed)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 3 {
|
||||
return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
|
||||
}
|
||||
splitted := strings.Split(fields[0], ".")
|
||||
packName := ""
|
||||
if len(splitted) == 1 {
|
||||
packName = fields[0]
|
||||
} else {
|
||||
packName = strings.Join(strings.Split(fields[0], ".")[0:(len(splitted)-1)], ".")
|
||||
}
|
||||
|
||||
fields = strings.Split(fields[1], "-")
|
||||
if len(fields) != 2 {
|
||||
return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
|
||||
}
|
||||
version := fields[0]
|
||||
release := fields[1]
|
||||
return models.PackageInfo{
|
||||
Name: packName,
|
||||
NewVersion: version,
|
||||
NewRelease: release,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *redhat) getChangelog(packageNames string) (stdout string, err error) {
|
||||
command := "echo N | "
|
||||
if 0 < len(config.Conf.HTTPProxy) {
|
||||
command += util.ProxyEnv()
|
||||
}
|
||||
command += fmt.Sprintf(" yum update --changelog %s | grep CVE", packageNames)
|
||||
|
||||
r := o.ssh(command, sudo)
|
||||
if !r.isSuccess(0, 1) {
|
||||
return "", fmt.Errorf(
|
||||
"Failed to get changelog. status: %d, stdout: %s, stderr: %s",
|
||||
r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
|
||||
type distroAdvisoryCveIDs struct {
|
||||
DistroAdvisory models.DistroAdvisory
|
||||
CveIDs []string
|
||||
}
|
||||
|
||||
// Scaning unsecure packages using yum-plugin-security.
|
||||
//TODO return whether already expired.
|
||||
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) {
|
||||
if o.Family == "centos" {
|
||||
// CentOS has no security channel.
|
||||
// So use yum check-update && parse changelog
|
||||
return CvePacksList{}, fmt.Errorf(
|
||||
"yum updateinfo is not suppported on CentOS")
|
||||
}
|
||||
|
||||
cmd := "yum repolist"
|
||||
r := o.ssh(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
// get advisoryID(RHSA, ALAS) - package name,version
|
||||
cmd = "yum updateinfo list available --security"
|
||||
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout)
|
||||
|
||||
// get package name, version, rel to be upgrade.
|
||||
cmd = "yum check-update --security"
|
||||
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess(0, 100) {
|
||||
//returns an exit code of 100 if there are available updates.
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
vulnerablePackInfoList, err := o.parseYumCheckUpdateLines(r.Stdout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
|
||||
}
|
||||
o.log.Debugf("%s", pp.Sprintf("%s", vulnerablePackInfoList))
|
||||
for i, packInfo := range vulnerablePackInfoList {
|
||||
installedPack, found := o.Packages.FindByName(packInfo.Name)
|
||||
if !found {
|
||||
return nil, fmt.Errorf(
|
||||
"Parsed package not found. packInfo: %#v", packInfo)
|
||||
}
|
||||
vulnerablePackInfoList[i].Version = installedPack.Version
|
||||
vulnerablePackInfoList[i].Release = installedPack.Release
|
||||
}
|
||||
|
||||
dict := map[string][]models.PackageInfo{}
|
||||
for _, advIDPackNames := range advIDPackNamesList {
|
||||
packInfoList := models.PackageInfoList{}
|
||||
for _, packName := range advIDPackNames.PackNames {
|
||||
packInfo, found := vulnerablePackInfoList.FindByName(packName)
|
||||
if !found {
|
||||
return nil, fmt.Errorf(
|
||||
"PackInfo not found. packInfo: %#v", packName)
|
||||
}
|
||||
packInfoList = append(packInfoList, packInfo)
|
||||
continue
|
||||
}
|
||||
dict[advIDPackNames.AdvisoryID] = packInfoList
|
||||
}
|
||||
|
||||
// get advisoryID(RHSA, ALAS) - CVE IDs
|
||||
cmd = "yum updateinfo --security update"
|
||||
r = o.ssh(util.PrependProxyEnv(cmd), noSudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
|
||||
if err != nil {
|
||||
return CvePacksList{}, err
|
||||
}
|
||||
// pp.Println(advisoryCveIDsList)
|
||||
|
||||
// All information collected.
|
||||
// Convert to CvePacksList.
|
||||
o.log.Info("Fetching CVE details...")
|
||||
result := CvePacksList{}
|
||||
for _, advIDCveIDs := range advisoryCveIDsList {
|
||||
cveDetails, err :=
|
||||
cveapi.CveClient.FetchCveDetails(advIDCveIDs.CveIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, cveDetail := range cveDetails {
|
||||
found := false
|
||||
for i, p := range result {
|
||||
if cveDetail.CveID == p.CveID {
|
||||
advAppended := append(p.DistroAdvisories, advIDCveIDs.DistroAdvisory)
|
||||
result[i].DistroAdvisories = advAppended
|
||||
|
||||
packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID]
|
||||
result[i].Packs = append(result[i].Packs, packs...)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
cpinfo := CvePacksInfo{
|
||||
CveID: cveDetail.CveID,
|
||||
CveDetail: cveDetail,
|
||||
DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory},
|
||||
Packs: dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
|
||||
}
|
||||
result = append(result, cpinfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
o.log.Info("Done")
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveIDs, err error) {
|
||||
sectionState := Outside
|
||||
lines := strings.Split(stdout, "\n")
|
||||
lines = append(lines, "=============")
|
||||
|
||||
// Amazon Linux AMI Security Information
|
||||
advisory := models.DistroAdvisory{}
|
||||
|
||||
cveIDsSetInThisSection := make(map[string]bool)
|
||||
|
||||
// use this flag to Collect CVE IDs in CVEs field.
|
||||
var inDesctiption = false
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
// find the new section pattern
|
||||
if match, _ := o.isHorizontalRule(line); match {
|
||||
|
||||
// set previous section's result to return-variable
|
||||
if sectionState == Content {
|
||||
|
||||
foundCveIDs := []string{}
|
||||
for cveID := range cveIDsSetInThisSection {
|
||||
foundCveIDs = append(foundCveIDs, cveID)
|
||||
}
|
||||
sort.Strings(foundCveIDs)
|
||||
result = append(result, distroAdvisoryCveIDs{
|
||||
DistroAdvisory: advisory,
|
||||
CveIDs: foundCveIDs,
|
||||
})
|
||||
|
||||
// reset for next section.
|
||||
cveIDsSetInThisSection = make(map[string]bool)
|
||||
inDesctiption = false
|
||||
}
|
||||
|
||||
// Go to next section
|
||||
sectionState = o.changeSectionState(sectionState)
|
||||
continue
|
||||
}
|
||||
|
||||
switch sectionState {
|
||||
case Header:
|
||||
switch o.Family {
|
||||
case "centos":
|
||||
// CentOS has no security channel.
|
||||
// So use yum check-update && parse changelog
|
||||
return result, fmt.Errorf(
|
||||
"yum updateinfo is not suppported on CentOS")
|
||||
case "rhel", "amazon":
|
||||
// nop
|
||||
}
|
||||
|
||||
case Content:
|
||||
if found := o.isDescriptionLine(line); found {
|
||||
inDesctiption = true
|
||||
}
|
||||
|
||||
// severity
|
||||
severity, found := o.parseYumUpdateinfoToGetSeverity(line)
|
||||
if found {
|
||||
advisory.Severity = severity
|
||||
}
|
||||
|
||||
// No need to parse in description except severity
|
||||
if inDesctiption {
|
||||
continue
|
||||
}
|
||||
|
||||
cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
|
||||
for _, cveID := range cveIDs {
|
||||
cveIDsSetInThisSection[cveID] = true
|
||||
}
|
||||
|
||||
advisoryID, found := o.parseYumUpdateinfoToGetAdvisoryID(line)
|
||||
if found {
|
||||
advisory.AdvisoryID = advisoryID
|
||||
}
|
||||
|
||||
issued, found := o.parseYumUpdateinfoLineToGetIssued(line)
|
||||
if found {
|
||||
advisory.Issued = issued
|
||||
}
|
||||
|
||||
updated, found := o.parseYumUpdateinfoLineToGetUpdated(line)
|
||||
if found {
|
||||
advisory.Updated = updated
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// state
|
||||
const (
|
||||
Outside = iota
|
||||
Header = iota
|
||||
Content = iota
|
||||
)
|
||||
|
||||
func (o *redhat) changeSectionState(state int) (newState int) {
|
||||
switch state {
|
||||
case Outside, Content:
|
||||
newState = Header
|
||||
case Header:
|
||||
newState = Content
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
||||
func (o *redhat) isHorizontalRule(line string) (bool, error) {
|
||||
return regexp.MatchString("^=+$", line)
|
||||
}
|
||||
|
||||
// see test case
|
||||
func (o *redhat) parseYumUpdateinfoHeaderCentOS(line string) (packs []models.PackageInfo, err error) {
|
||||
pkgs := strings.Split(strings.TrimSpace(line), ",")
|
||||
for _, pkg := range pkgs {
|
||||
packs = append(packs, models.PackageInfo{})
|
||||
s := strings.Split(pkg, "-")
|
||||
if len(s) == 3 {
|
||||
packs[len(packs)-1].Name = s[0]
|
||||
packs[len(packs)-1].Version = s[1]
|
||||
packs[len(packs)-1].Release = s[2]
|
||||
} else {
|
||||
return packs, fmt.Errorf("CentOS: Unknown Header format: %s", line)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoHeaderAmazon(line string) (a models.DistroAdvisory, names []string, err error) {
|
||||
re, _ := regexp.Compile(`(ALAS-.+): (.+) priority package update for (.+)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) == 4 {
|
||||
a.AdvisoryID = result[1]
|
||||
a.Severity = result[2]
|
||||
spaceSeparatedPacknames := result[3]
|
||||
names = strings.Fields(spaceSeparatedPacknames)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("Amazon Linux: Unknown Header Format. %s", line)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetCveIDs(line string) []string {
|
||||
re, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
|
||||
return re.FindAllString(line, -1)
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoToGetAdvisoryID(line string) (advisoryID string, found bool) {
|
||||
re, _ := regexp.Compile(`^ *Update ID : (.*)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) != 2 {
|
||||
return "", false
|
||||
}
|
||||
return strings.TrimSpace(result[1]), true
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetIssued(line string) (date time.Time, found bool) {
|
||||
return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Issued : (\d{4}-\d{2}-\d{2})`)
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetUpdated(line string) (date time.Time, found bool) {
|
||||
return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Updated : (\d{4}-\d{2}-\d{2})`)
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetDate(line, regexpFormat string) (date time.Time, found bool) {
|
||||
re, _ := regexp.Compile(regexpFormat)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) != 2 {
|
||||
return date, false
|
||||
}
|
||||
t, err := time.Parse("2006-01-02", result[1])
|
||||
if err != nil {
|
||||
return date, false
|
||||
}
|
||||
return t, true
|
||||
}
|
||||
|
||||
func (o *redhat) isDescriptionLine(line string) bool {
|
||||
re, _ := regexp.Compile(`^\s*Description : `)
|
||||
return re.MatchString(line)
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoToGetSeverity(line string) (severity string, found bool) {
|
||||
re, _ := regexp.Compile(`^ *Severity : (.*)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) != 2 {
|
||||
return "", false
|
||||
}
|
||||
return strings.TrimSpace(result[1]), true
|
||||
}
|
||||
|
||||
type advisoryIDPacks struct {
|
||||
AdvisoryID string
|
||||
PackNames []string
|
||||
}
|
||||
|
||||
type advisoryIDPacksList []advisoryIDPacks
|
||||
|
||||
func (list advisoryIDPacksList) find(advisoryID string) (advisoryIDPacks, bool) {
|
||||
for _, a := range list {
|
||||
if a.AdvisoryID == advisoryID {
|
||||
return a, true
|
||||
}
|
||||
}
|
||||
return advisoryIDPacks{}, false
|
||||
}
|
||||
func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string) {
|
||||
fields := strings.Split(nameVerRel, ".")
|
||||
archTrimed := strings.Join(fields[0:len(fields)-1], ".")
|
||||
|
||||
fields = strings.Split(archTrimed, "-")
|
||||
rel = fields[len(fields)-1]
|
||||
ver = fields[len(fields)-2]
|
||||
name = strings.Join(fields[0:(len(fields)-2)], "-")
|
||||
return
|
||||
}
|
||||
|
||||
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages
|
||||
func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
|
||||
|
||||
result := []advisoryIDPacks{}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
|
||||
if !(strings.HasPrefix(line, "RHSA") ||
|
||||
strings.HasPrefix(line, "ALAS")) {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 3 {
|
||||
return []advisoryIDPacks{}, fmt.Errorf(
|
||||
"Unknown format. line: %s", line)
|
||||
}
|
||||
|
||||
// extract fields
|
||||
advisoryID := fields[0]
|
||||
packVersion := fields[2]
|
||||
packName, _, _ := o.extractPackNameVerRel(packVersion)
|
||||
|
||||
found := false
|
||||
for i, s := range result {
|
||||
if s.AdvisoryID == advisoryID {
|
||||
names := s.PackNames
|
||||
names = append(names, packName)
|
||||
result[i].PackNames = names
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
result = append(result, advisoryIDPacks{
|
||||
AdvisoryID: advisoryID,
|
||||
PackNames: []string{packName},
|
||||
})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
843
scan/redhat_test.go
Normal file
843
scan/redhat_test.go
Normal file
@@ -0,0 +1,843 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
// func unixtimeNoerr(s string) time.Time {
|
||||
// t, _ := unixtime(s)
|
||||
// return t
|
||||
// }
|
||||
|
||||
func TestParseScanedPackagesLineRedhat(t *testing.T) {
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
pack models.PackageInfo
|
||||
}{
|
||||
{
|
||||
"openssl 1.0.1e 30.el6.11",
|
||||
models.PackageInfo{
|
||||
Name: "openssl",
|
||||
Version: "1.0.1e",
|
||||
Release: "30.el6.11",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range packagetests {
|
||||
p, _ := r.parseScanedPackagesLine(tt.in)
|
||||
if p.Name != tt.pack.Name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
|
||||
}
|
||||
if p.Version != tt.pack.Version {
|
||||
t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
|
||||
}
|
||||
if p.Release != tt.pack.Release {
|
||||
t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestChangeSectionState(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
oldState int
|
||||
newState int
|
||||
}{
|
||||
{Outside, Header},
|
||||
{Header, Content},
|
||||
{Content, Header},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if n := r.changeSectionState(tt.oldState); n != tt.newState {
|
||||
t.Errorf("expected %d, actual %d", tt.newState, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoHeader(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []models.PackageInfo
|
||||
}{
|
||||
{
|
||||
" nodejs-0.10.36-3.el6,libuv-0.10.34-1.el6,v8-3.14.5.10-17.el6 ",
|
||||
[]models.PackageInfo{
|
||||
{
|
||||
Name: "nodejs",
|
||||
Version: "0.10.36",
|
||||
Release: "3.el6",
|
||||
},
|
||||
{
|
||||
Name: "libuv",
|
||||
Version: "0.10.34",
|
||||
Release: "1.el6",
|
||||
},
|
||||
{
|
||||
Name: "v8",
|
||||
Version: "3.14.5.10",
|
||||
Release: "17.el6",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if a, err := r.parseYumUpdateinfoHeaderCentOS(tt.in); err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
} else {
|
||||
if !reflect.DeepEqual(a, tt.out) {
|
||||
e := pp.Sprintf("%#v", tt.out)
|
||||
a := pp.Sprintf("%#v", a)
|
||||
t.Errorf("expected %s, actual %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoLineToGetCveIDs(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{
|
||||
"Bugs : 1194651 - CVE-2015-0278 libuv:",
|
||||
[]string{"CVE-2015-0278"},
|
||||
},
|
||||
{
|
||||
": 1195457 - nodejs-0.10.35 causes undefined symbolsCVE-2015-0278, CVE-2015-0278, CVE-2015-0277",
|
||||
[]string{
|
||||
"CVE-2015-0278",
|
||||
"CVE-2015-0278",
|
||||
"CVE-2015-0277",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
act := r.parseYumUpdateinfoLineToGetCveIDs(tt.in)
|
||||
for i, s := range act {
|
||||
if s != tt.out[i] {
|
||||
t.Errorf("expected %s, actual %s", tt.out[i], s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoToGetAdvisoryID(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out string
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"Update ID : RHSA-2015:2315",
|
||||
"RHSA-2015:2315",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Update ID : ALAS-2015-620",
|
||||
"ALAS-2015-620",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Issued : 2015-11-19 00:00:00",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
advisoryID, found := r.parseYumUpdateinfoToGetAdvisoryID(tt.in)
|
||||
if tt.out != advisoryID {
|
||||
t.Errorf("expected %s, actual %s", tt.out, advisoryID)
|
||||
}
|
||||
if tt.found != found {
|
||||
t.Errorf("expected %t, actual %t", tt.found, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoLineToGetIssued(t *testing.T) {
|
||||
|
||||
date, _ := time.Parse("2006-01-02", "2015-12-15")
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out time.Time
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"Issued : 2015-12-15 00:00:00",
|
||||
date,
|
||||
true,
|
||||
},
|
||||
{
|
||||
" Issued : 2015-12-15 00:00:00 ",
|
||||
date,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Type : security",
|
||||
time.Time{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
d, found := r.parseYumUpdateinfoLineToGetIssued(tt.in)
|
||||
if tt.found != found {
|
||||
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
|
||||
}
|
||||
if tt.out != d {
|
||||
t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoLineToGetUpdated(t *testing.T) {
|
||||
|
||||
date, _ := time.Parse("2006-01-02", "2015-12-15")
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out time.Time
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"Updated : 2015-12-15 00:00:00 Bugs : 1286966 - CVE-2015-8370 grub2: buffer overflow when checking password entered during bootup",
|
||||
date,
|
||||
true,
|
||||
},
|
||||
{
|
||||
|
||||
"Updated : 2015-12-15 14:16 CVEs : CVE-2015-7981",
|
||||
date,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Type : security",
|
||||
time.Time{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
d, found := r.parseYumUpdateinfoLineToGetUpdated(tt.in)
|
||||
if tt.found != found {
|
||||
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
|
||||
}
|
||||
if tt.out != d {
|
||||
t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDescriptionLine(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"Description : Package updates are available for Amazon Linux AMI that fix the",
|
||||
true,
|
||||
},
|
||||
{
|
||||
" Description : Package updates are available for Amazon Linux AMI that fix the",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Status : final",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
found := r.isDescriptionLine(tt.in)
|
||||
if tt.found != found {
|
||||
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoToGetSeverity(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out string
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"Severity : Moderate",
|
||||
"Moderate",
|
||||
true,
|
||||
},
|
||||
{
|
||||
" Severity : medium",
|
||||
"medium",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Status : final",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
out, found := r.parseYumUpdateinfoToGetSeverity(tt.in)
|
||||
if tt.found != found {
|
||||
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
|
||||
}
|
||||
if tt.out != out {
|
||||
t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoRHEL(t *testing.T) {
|
||||
|
||||
stdout := `===============================================================================
|
||||
Important: bind security update
|
||||
===============================================================================
|
||||
Update ID : RHSA-2015:1705
|
||||
Release :
|
||||
Type : security
|
||||
Status : final
|
||||
Issued : 2015-09-03 00:00:00
|
||||
Bugs : 1259087 - CVE-2015-5722 bind: malformed DNSSEC key failed assertion denial of service
|
||||
CVEs : CVE-2015-5722
|
||||
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
|
||||
: the Domain Name System (DNS) protocols. BIND
|
||||
: includes a DNS server (named); a resolver library
|
||||
: (routines for applications to use when interfacing
|
||||
: with DNS); and tools for verifying that the DNS
|
||||
: server is operating correctly.
|
||||
:
|
||||
Severity : Important
|
||||
|
||||
===============================================================================
|
||||
Important: bind security update
|
||||
===============================================================================
|
||||
Update ID : RHSA-2015:2655
|
||||
Release :
|
||||
Type : security
|
||||
Status : final
|
||||
Issued : 2015-09-03 01:00:00
|
||||
Updated : 2015-09-04 00:00:00
|
||||
Bugs : 1291176 - CVE-2015-8000 bind: responses with a malformed class attribute can trigger an assertion failure in db.c
|
||||
CVEs : CVE-2015-8000
|
||||
: CVE-2015-8001
|
||||
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
|
||||
: the Domain Name System (DNS) protocols. BIND
|
||||
: includes a DNS server (named); a resolver library
|
||||
: (routines for applications to use when interfacing
|
||||
: with DNS); and tools for verifying that the DNS
|
||||
: server is operating correctly.
|
||||
:
|
||||
Severity : Low
|
||||
|
||||
===============================================================================
|
||||
Moderate: bind security update
|
||||
===============================================================================
|
||||
Update ID : RHSA-2016:0073
|
||||
Release :
|
||||
Type : security
|
||||
Status : final
|
||||
Issued : 2015-09-03 02:00:00
|
||||
Bugs : 1299364 - CVE-2015-8704 bind: specific APL data could trigger an INSIST in apl_42.c CVEs : CVE-2015-8704
|
||||
: CVE-2015-8705
|
||||
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
|
||||
: the Domain Name System (DNS) protocols. BIND
|
||||
: includes a DNS server (named); a resolver library
|
||||
: (routines for applications to use when interfacing
|
||||
: with DNS); and tools for verifying that the DNS
|
||||
: server is operating correctly.
|
||||
:
|
||||
Severity : Moderate
|
||||
|
||||
`
|
||||
issued, _ := time.Parse("2006-01-02", "2015-09-03")
|
||||
updated, _ := time.Parse("2006-01-02", "2015-09-04")
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r.Family = "redhat"
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []distroAdvisoryCveIDs
|
||||
}{
|
||||
{
|
||||
stdout,
|
||||
[]distroAdvisoryCveIDs{
|
||||
{
|
||||
DistroAdvisory: models.DistroAdvisory{
|
||||
AdvisoryID: "RHSA-2015:1705",
|
||||
Severity: "Important",
|
||||
Issued: issued,
|
||||
},
|
||||
CveIDs: []string{"CVE-2015-5722"},
|
||||
},
|
||||
{
|
||||
DistroAdvisory: models.DistroAdvisory{
|
||||
AdvisoryID: "RHSA-2015:2655",
|
||||
Severity: "Low",
|
||||
Issued: issued,
|
||||
Updated: updated,
|
||||
},
|
||||
CveIDs: []string{
|
||||
"CVE-2015-8000",
|
||||
"CVE-2015-8001",
|
||||
},
|
||||
},
|
||||
{
|
||||
DistroAdvisory: models.DistroAdvisory{
|
||||
AdvisoryID: "RHSA-2016:0073",
|
||||
Severity: "Moderate",
|
||||
Issued: issued,
|
||||
Updated: updated,
|
||||
},
|
||||
CveIDs: []string{
|
||||
"CVE-2015-8704",
|
||||
"CVE-2015-8705",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
actual, _ := r.parseYumUpdateinfo(tt.in)
|
||||
for i, advisoryCveIDs := range actual {
|
||||
if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) {
|
||||
e := pp.Sprintf("%v", tt.out[i])
|
||||
a := pp.Sprintf("%v", advisoryCveIDs)
|
||||
t.Errorf("[%d] Alas is not same. \nexpected: %s\nactual: %s",
|
||||
i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoAmazon(t *testing.T) {
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r.Family = "amazon"
|
||||
|
||||
issued, _ := time.Parse("2006-01-02", "2015-12-15")
|
||||
updated, _ := time.Parse("2006-01-02", "2015-12-16")
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []distroAdvisoryCveIDs
|
||||
}{
|
||||
{
|
||||
`===============================================================================
|
||||
Amazon Linux AMI 2014.03 - ALAS-2016-644: medium priority package update for python-rsa
|
||||
===============================================================================
|
||||
Update ID : ALAS-2016-644
|
||||
Release :
|
||||
Type : security
|
||||
Status : final
|
||||
Issued : 2015-12-15 13:30
|
||||
CVEs : CVE-2016-1494
|
||||
Description : Package updates are available for Amazon Linux AMI that fix the
|
||||
: following vulnerabilities: CVE-2016-1494:
|
||||
: 1295869:
|
||||
: CVE-2016-1494 python-rsa: Signature forgery using
|
||||
: Bleichenbacher'06 attack
|
||||
Severity : medium
|
||||
|
||||
===============================================================================
|
||||
Amazon Linux AMI 2014.03 - ALAS-2015-614: medium priority package update for openssl
|
||||
===============================================================================
|
||||
Update ID : ALAS-2015-614
|
||||
Release :
|
||||
Type : security
|
||||
Status : final
|
||||
Issued : 2015-12-15 10:00
|
||||
Updated : 2015-12-16 14:15 CVEs : CVE-2015-3194
|
||||
: CVE-2015-3195
|
||||
: CVE-2015-3196
|
||||
Description : Package updates are available for Amazon Linux AMI that fix the
|
||||
: following vulnerabilities: CVE-2015-3196:
|
||||
: 1288326:
|
||||
: CVE-2015-3196 OpenSSL: Race condition handling PSK
|
||||
: identify hint A race condition flaw, leading to a
|
||||
: double free, was found in the way OpenSSL handled
|
||||
: pre-shared keys (PSKs). A remote attacker could
|
||||
: use this flaw to crash a multi-threaded SSL/TLS
|
||||
: client.
|
||||
:
|
||||
Severity : medium`,
|
||||
|
||||
[]distroAdvisoryCveIDs{
|
||||
{
|
||||
DistroAdvisory: models.DistroAdvisory{
|
||||
AdvisoryID: "ALAS-2016-644",
|
||||
Severity: "medium",
|
||||
Issued: issued,
|
||||
},
|
||||
CveIDs: []string{"CVE-2016-1494"},
|
||||
},
|
||||
{
|
||||
DistroAdvisory: models.DistroAdvisory{
|
||||
AdvisoryID: "ALAS-2015-614",
|
||||
Severity: "medium",
|
||||
Issued: issued,
|
||||
Updated: updated,
|
||||
},
|
||||
CveIDs: []string{
|
||||
"CVE-2015-3194",
|
||||
"CVE-2015-3195",
|
||||
"CVE-2015-3196",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
actual, _ := r.parseYumUpdateinfo(tt.in)
|
||||
for i, advisoryCveIDs := range actual {
|
||||
if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) {
|
||||
e := pp.Sprintf("%v", tt.out[i])
|
||||
a := pp.Sprintf("%v", advisoryCveIDs)
|
||||
t.Errorf("[%d] Alas is not same. expected %s, actual %s",
|
||||
i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumCheckUpdateLines(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r.Family = "centos"
|
||||
stdout := `Loaded plugins: changelog, fastestmirror, keys, protect-packages, protectbase, security
|
||||
Loading mirror speeds from cached hostfile
|
||||
* base: mirror.fairway.ne.jp
|
||||
* epel: epel.mirror.srv.co.ge
|
||||
* extras: mirror.fairway.ne.jp
|
||||
* updates: mirror.fairway.ne.jp
|
||||
0 packages excluded due to repository protections
|
||||
|
||||
audit-libs.x86_64 2.3.7-5.el6 base
|
||||
bash.x86_64 4.1.2-33.el6_7.1 updates
|
||||
`
|
||||
r.Packages = []models.PackageInfo{
|
||||
{
|
||||
Name: "audit-libs",
|
||||
Version: "2.3.6",
|
||||
Release: "4.el6",
|
||||
},
|
||||
{
|
||||
Name: "bash",
|
||||
Version: "4.1.1",
|
||||
Release: "33",
|
||||
},
|
||||
}
|
||||
var tests = []struct {
|
||||
in string
|
||||
out models.PackageInfoList
|
||||
}{
|
||||
{
|
||||
stdout,
|
||||
models.PackageInfoList{
|
||||
{
|
||||
Name: "audit-libs",
|
||||
Version: "2.3.6",
|
||||
Release: "4.el6",
|
||||
NewVersion: "2.3.7",
|
||||
NewRelease: "5.el6",
|
||||
},
|
||||
{
|
||||
Name: "bash",
|
||||
Version: "4.1.1",
|
||||
Release: "33",
|
||||
NewVersion: "4.1.2",
|
||||
NewRelease: "33.el6_7.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
packInfoList, err := r.parseYumCheckUpdateLines(tt.in)
|
||||
if err != nil {
|
||||
t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
|
||||
return
|
||||
}
|
||||
for i, ePackInfo := range tt.out {
|
||||
if !reflect.DeepEqual(ePackInfo, packInfoList[i]) {
|
||||
e := pp.Sprintf("%v", ePackInfo)
|
||||
a := pp.Sprintf("%v", packInfoList[i])
|
||||
t.Errorf("[%d] expected %s, actual %s", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r.Family = "amzon"
|
||||
stdout := `Loaded plugins: priorities, update-motd, upgrade-helper
|
||||
34 package(s) needed for security, out of 71 available
|
||||
|
||||
bind-libs.x86_64 32:9.8.2-0.37.rc1.45.amzn1 amzn-main
|
||||
java-1.7.0-openjdk.x86_64 1:1.7.0.95-2.6.4.0.65.amzn1 amzn-main
|
||||
if-not-architecture 100-200 amzn-main
|
||||
`
|
||||
r.Packages = []models.PackageInfo{
|
||||
{
|
||||
Name: "bind-libs",
|
||||
Version: "32:9.8.0",
|
||||
Release: "0.33.rc1.45.amzn1",
|
||||
},
|
||||
{
|
||||
Name: "java-1.7.0-openjdk",
|
||||
Version: "1:1.7.0.0",
|
||||
Release: "2.6.4.0.0.amzn1",
|
||||
},
|
||||
{
|
||||
Name: "if-not-architecture",
|
||||
Version: "10",
|
||||
Release: "20",
|
||||
},
|
||||
}
|
||||
var tests = []struct {
|
||||
in string
|
||||
out models.PackageInfoList
|
||||
}{
|
||||
{
|
||||
stdout,
|
||||
models.PackageInfoList{
|
||||
{
|
||||
Name: "bind-libs",
|
||||
Version: "32:9.8.0",
|
||||
Release: "0.33.rc1.45.amzn1",
|
||||
NewVersion: "32:9.8.2",
|
||||
NewRelease: "0.37.rc1.45.amzn1",
|
||||
},
|
||||
{
|
||||
Name: "java-1.7.0-openjdk",
|
||||
Version: "1:1.7.0.0",
|
||||
Release: "2.6.4.0.0.amzn1",
|
||||
NewVersion: "1:1.7.0.95",
|
||||
NewRelease: "2.6.4.0.65.amzn1",
|
||||
},
|
||||
{
|
||||
Name: "if-not-architecture",
|
||||
Version: "10",
|
||||
Release: "20",
|
||||
NewVersion: "100",
|
||||
NewRelease: "200",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
packInfoList, err := r.parseYumCheckUpdateLines(tt.in)
|
||||
if err != nil {
|
||||
t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
|
||||
return
|
||||
}
|
||||
for i, ePackInfo := range tt.out {
|
||||
if !reflect.DeepEqual(ePackInfo, packInfoList[i]) {
|
||||
e := pp.Sprintf("%v", ePackInfo)
|
||||
a := pp.Sprintf("%v", packInfoList[i])
|
||||
t.Errorf("[%d] expected %s, actual %s", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoAmazonLinuxHeader(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out models.DistroAdvisory
|
||||
}{
|
||||
{
|
||||
"Amazon Linux AMI 2014.03 - ALAS-2015-598: low priority package update for grep",
|
||||
models.DistroAdvisory{
|
||||
AdvisoryID: "ALAS-2015-598",
|
||||
Severity: "low",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
a, _, _ := r.parseYumUpdateinfoHeaderAmazon(tt.in)
|
||||
if !reflect.DeepEqual(a, tt.out) {
|
||||
e := pp.Sprintf("%v", tt.out)
|
||||
a := pp.Sprintf("%v", a)
|
||||
t.Errorf("expected %s, actual %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoListAvailable(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
rhelStdout := `RHSA-2015:2315 Moderate/Sec. NetworkManager-1:1.0.6-27.el7.x86_64
|
||||
RHSA-2015:2315 Moderate/Sec. NetworkManager-config-server-1:1.0.6-27.el7.x86_64
|
||||
RHSA-2015:1705 Important/Sec. bind-libs-lite-32:9.9.4-18.el7_1.5.x86_64
|
||||
RHSA-2016:0176 Critical/Sec. glibc-2.17-106.el7_2.4.x86_64
|
||||
RHSA-2015:2401 Low/Sec. grub2-1:2.02-0.29.el7.x86_64
|
||||
RHSA-2015:2401 Low/Sec. grub2-tools-1:2.02-0.29.el7.x86_64
|
||||
updateinfo list done`
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []advisoryIDPacks
|
||||
}{
|
||||
{
|
||||
rhelStdout,
|
||||
[]advisoryIDPacks{
|
||||
{
|
||||
AdvisoryID: "RHSA-2015:2315",
|
||||
PackNames: []string{
|
||||
"NetworkManager",
|
||||
"NetworkManager-config-server",
|
||||
},
|
||||
},
|
||||
{
|
||||
AdvisoryID: "RHSA-2015:1705",
|
||||
PackNames: []string{
|
||||
"bind-libs-lite",
|
||||
},
|
||||
},
|
||||
{
|
||||
AdvisoryID: "RHSA-2016:0176",
|
||||
PackNames: []string{
|
||||
"glibc",
|
||||
},
|
||||
},
|
||||
{
|
||||
AdvisoryID: "RHSA-2015:2401",
|
||||
PackNames: []string{
|
||||
"grub2",
|
||||
"grub2-tools",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
actual, err := r.parseYumUpdateinfoListAvailable(tt.in)
|
||||
if err != nil {
|
||||
t.Errorf("Error has occurred: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := range actual {
|
||||
if !reflect.DeepEqual(actual[i], tt.out[i]) {
|
||||
e := pp.Sprintf("%v", tt.out)
|
||||
a := pp.Sprintf("%v", actual)
|
||||
t.Errorf("[%d] expected: %s\nactual: %s", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoToGetUpdateID(t *testing.T) {
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
pack models.PackageInfo
|
||||
}{
|
||||
{
|
||||
"openssl 1.0.1e 30.el6.11",
|
||||
models.PackageInfo{
|
||||
Name: "openssl",
|
||||
Version: "1.0.1e",
|
||||
Release: "30.el6.11",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range packagetests {
|
||||
p, _ := r.parseScanedPackagesLine(tt.in)
|
||||
if p.Name != tt.pack.Name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
|
||||
}
|
||||
if p.Version != tt.pack.Version {
|
||||
t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
|
||||
}
|
||||
if p.Release != tt.pack.Release {
|
||||
t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestExtractPackNameVerRel(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{
|
||||
"openssh-server-6.2p2-8.45.amzn1.x86_64",
|
||||
[]string{"openssh-server", "6.2p2", "8.45.amzn1"},
|
||||
},
|
||||
{
|
||||
"bind-libs-lite-32:9.9.4-29.el7_2.1.x86_64",
|
||||
[]string{"bind-libs-lite", "32:9.9.4", "29.el7_2.1"},
|
||||
},
|
||||
{
|
||||
"glibc-2.17-106.el7_2.1.x86_64",
|
||||
[]string{"glibc", "2.17", "106.el7_2.1"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
name, ver, rel := r.extractPackNameVerRel(tt.in)
|
||||
if tt.out[0] != name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.out[0], name)
|
||||
}
|
||||
if tt.out[1] != ver {
|
||||
t.Errorf("ver: expected %s, actual %s", tt.out[1], ver)
|
||||
}
|
||||
if tt.out[2] != rel {
|
||||
t.Errorf("ver: expected %s, actual %s", tt.out[2], rel)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
209
scan/serverapi.go
Normal file
209
scan/serverapi.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/k0kubun/pp"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
// Log for localhsot
|
||||
var Log *logrus.Entry
|
||||
|
||||
var servers []osTypeInterface
|
||||
|
||||
// Base Interface of redhat, debian
|
||||
type osTypeInterface interface {
|
||||
setServerInfo(config.ServerInfo)
|
||||
getServerInfo() config.ServerInfo
|
||||
setDistributionInfo(string, string)
|
||||
checkRequiredPackagesInstalled() error
|
||||
scanPackages() error
|
||||
scanVulnByCpeName() error
|
||||
install() error
|
||||
convertToModel() (models.ScanResult, error)
|
||||
}
|
||||
|
||||
// osPackages included by linux struct
|
||||
type osPackages struct {
|
||||
// installed packages
|
||||
Packages models.PackageInfoList
|
||||
|
||||
// unsecure packages
|
||||
UnsecurePackages CvePacksList
|
||||
}
|
||||
|
||||
func (p *osPackages) setPackages(pi models.PackageInfoList) {
|
||||
p.Packages = pi
|
||||
}
|
||||
|
||||
func (p *osPackages) setUnsecurePackages(pi []CvePacksInfo) {
|
||||
p.UnsecurePackages = pi
|
||||
}
|
||||
|
||||
// CvePacksList have CvePacksInfo list, getter/setter, sortable methods.
|
||||
type CvePacksList []CvePacksInfo
|
||||
|
||||
// CvePacksInfo hold the CVE information.
|
||||
type CvePacksInfo struct {
|
||||
CveID string
|
||||
CveDetail cve.CveDetail
|
||||
Packs []models.PackageInfo
|
||||
DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL
|
||||
CpeNames []string
|
||||
// CvssScore float64
|
||||
}
|
||||
|
||||
// FindByCveID find by CVEID
|
||||
func (s CvePacksList) FindByCveID(cveID string) (pi CvePacksInfo, found bool) {
|
||||
for _, p := range s {
|
||||
if cveID == p.CveID {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
return CvePacksInfo{CveID: cveID}, false
|
||||
}
|
||||
|
||||
// immutable
|
||||
func (s CvePacksList) set(cveID string, cvePacksInfo CvePacksInfo) CvePacksList {
|
||||
for i, p := range s {
|
||||
if cveID == p.CveID {
|
||||
s[i] = cvePacksInfo
|
||||
return s
|
||||
}
|
||||
}
|
||||
return append(s, cvePacksInfo)
|
||||
}
|
||||
|
||||
// Len implement Sort Interface
|
||||
func (s CvePacksList) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap implement Sort Interface
|
||||
func (s CvePacksList) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less implement Sort Interface
|
||||
func (s CvePacksList) Less(i, j int) bool {
|
||||
return s[i].CveDetail.CvssScore("en") > s[j].CveDetail.CvssScore("en")
|
||||
}
|
||||
|
||||
func detectOs(c config.ServerInfo) (osType osTypeInterface) {
|
||||
var itsMe bool
|
||||
itsMe, osType = detectDebian(c)
|
||||
if itsMe {
|
||||
return
|
||||
}
|
||||
itsMe, osType = detectRedhat(c)
|
||||
return
|
||||
}
|
||||
|
||||
// InitServers detect the kind of OS distribution of target servers
|
||||
func InitServers(localLogger *logrus.Entry) (err error) {
|
||||
Log = localLogger
|
||||
if servers, err = detectServersOS(); err != nil {
|
||||
err = fmt.Errorf("Failed to detect OS")
|
||||
} else {
|
||||
Log.Debugf("%s", pp.Sprintf("%s", servers))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func detectServersOS() (osi []osTypeInterface, err error) {
|
||||
osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers))
|
||||
defer close(osTypeChan)
|
||||
for _, s := range config.Conf.Servers {
|
||||
go func(s config.ServerInfo) {
|
||||
osTypeChan <- detectOs(s)
|
||||
}(s)
|
||||
}
|
||||
|
||||
timeout := time.After(60 * time.Second)
|
||||
for i := 0; i < len(config.Conf.Servers); i++ {
|
||||
select {
|
||||
case res := <-osTypeChan:
|
||||
osi = append(osi, res)
|
||||
case <-timeout:
|
||||
Log.Error("Timeout Occured while detecting OS.")
|
||||
err = fmt.Errorf("Timeout!")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare installs requred packages to scan vulnerabilities.
|
||||
func Prepare() []error {
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
if err := o.install(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Scan scan
|
||||
func Scan() []error {
|
||||
if len(servers) == 0 {
|
||||
return []error{fmt.Errorf("Not initialize yet.")}
|
||||
}
|
||||
|
||||
Log.Info("Check required packages for scanning...")
|
||||
if errs := checkRequiredPackagesInstalled(); errs != nil {
|
||||
Log.Error("Please execute with [prepare] subcommand to install required packages before scanning")
|
||||
return errs
|
||||
}
|
||||
|
||||
Log.Info("Scanning vuluneable OS packages...")
|
||||
if errs := scanPackages(); errs != nil {
|
||||
return errs
|
||||
}
|
||||
|
||||
Log.Info("Scanning vulnerable software specified in CPE...")
|
||||
if errs := scanVulnByCpeName(); errs != nil {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRequiredPackagesInstalled() []error {
|
||||
timeoutSec := 30 * 60
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
return o.checkRequiredPackagesInstalled()
|
||||
}, timeoutSec)
|
||||
}
|
||||
|
||||
func scanPackages() []error {
|
||||
timeoutSec := 30 * 60
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
return o.scanPackages()
|
||||
}, timeoutSec)
|
||||
|
||||
}
|
||||
|
||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
|
||||
func scanVulnByCpeName() []error {
|
||||
timeoutSec := 30 * 60
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
return o.scanVulnByCpeName()
|
||||
}, timeoutSec)
|
||||
|
||||
}
|
||||
|
||||
// GetScanResults returns Scan Resutls
|
||||
func GetScanResults() (results models.ScanResults, err error) {
|
||||
for _, s := range servers {
|
||||
r, err := s.convertToModel()
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("Failed converting to model: %s.", err)
|
||||
}
|
||||
results = append(results, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
47
scan/serverapi_test.go
Normal file
47
scan/serverapi_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package scan
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPackageCveInfosSetGet(t *testing.T) {
|
||||
var test = struct {
|
||||
in []string
|
||||
out []string
|
||||
}{
|
||||
[]string{
|
||||
"CVE1",
|
||||
"CVE2",
|
||||
"CVE3",
|
||||
"CVE1",
|
||||
"CVE1",
|
||||
"CVE2",
|
||||
"CVE3",
|
||||
},
|
||||
[]string{
|
||||
"CVE1",
|
||||
"CVE2",
|
||||
"CVE3",
|
||||
},
|
||||
}
|
||||
|
||||
// var ps packageCveInfos
|
||||
var ps CvePacksList
|
||||
for _, cid := range test.in {
|
||||
ps = ps.set(cid, CvePacksInfo{CveID: cid})
|
||||
}
|
||||
|
||||
if len(test.out) != len(ps) {
|
||||
t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
|
||||
}
|
||||
|
||||
for i, expectedCid := range test.out {
|
||||
if expectedCid != ps[i].CveID {
|
||||
t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
|
||||
}
|
||||
}
|
||||
for _, cid := range test.in {
|
||||
p, _ := ps.FindByCveID(cid)
|
||||
if p.CveID != cid {
|
||||
t.Errorf("expected %s, actual %s", cid, p.CveID)
|
||||
}
|
||||
}
|
||||
}
|
||||
311
scan/sshutil.go
Normal file
311
scan/sshutil.go
Normal file
@@ -0,0 +1,311 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
conf "github.com/future-architect/vuls/config"
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
type sshResult struct {
|
||||
Host string
|
||||
Port string
|
||||
Stdout string
|
||||
Stderr string
|
||||
ExitStatus int
|
||||
}
|
||||
|
||||
func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
|
||||
if len(expectedStatusCodes) == 0 {
|
||||
return s.ExitStatus == 0
|
||||
}
|
||||
for _, code := range expectedStatusCodes {
|
||||
if code == s.ExitStatus {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Sudo is Const value for sudo mode
|
||||
const sudo = true
|
||||
|
||||
// NoSudo is Const value for normal user mode
|
||||
const noSudo = false
|
||||
|
||||
func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []error) {
|
||||
errChan := make(chan error, len(servers))
|
||||
defer close(errChan)
|
||||
for _, s := range servers {
|
||||
go func(s osTypeInterface) {
|
||||
if err := fn(s); err != nil {
|
||||
errChan <- fmt.Errorf("%s@%s:%s: %s",
|
||||
s.getServerInfo().User,
|
||||
s.getServerInfo().Host,
|
||||
s.getServerInfo().Port,
|
||||
err,
|
||||
)
|
||||
} else {
|
||||
errChan <- nil
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
var timeout int
|
||||
if len(timeoutSec) == 0 {
|
||||
timeout = 10 * 60
|
||||
} else {
|
||||
timeout = timeoutSec[0]
|
||||
}
|
||||
|
||||
for i := 0; i < len(servers); i++ {
|
||||
select {
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
logrus.Debug("Parallel SSH Success.")
|
||||
}
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
logrus.Errorf("Parallel SSH Timeout.")
|
||||
errs = append(errs, fmt.Errorf("Timed out!"))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
|
||||
// Setup Logger
|
||||
var logger *logrus.Entry
|
||||
if len(log) == 0 {
|
||||
level := logrus.InfoLevel
|
||||
if conf.Conf.Debug == true {
|
||||
level = logrus.DebugLevel
|
||||
}
|
||||
l := &logrus.Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(logrus.TextFormatter),
|
||||
Hooks: make(logrus.LevelHooks),
|
||||
Level: level,
|
||||
}
|
||||
logger = logrus.NewEntry(l)
|
||||
} else {
|
||||
logger = log[0]
|
||||
}
|
||||
|
||||
var err error
|
||||
if sudo && c.User != "root" {
|
||||
switch {
|
||||
case c.SudoOpt.ExecBySudo:
|
||||
cmd = fmt.Sprintf("echo %s | sudo -S %s", c.Password, cmd)
|
||||
case c.SudoOpt.ExecBySudoSh:
|
||||
cmd = fmt.Sprintf("echo %s | sudo sh -c '%s'", c.Password, cmd)
|
||||
default:
|
||||
logger.Panicf("sudoOpt is invalid. SudoOpt: %v", c.SudoOpt)
|
||||
}
|
||||
}
|
||||
// set pipefail option.
|
||||
// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
|
||||
cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
|
||||
logger.Debugf("Command: %s", strings.Replace(cmd, "\n", "", -1))
|
||||
|
||||
var client *ssh.Client
|
||||
client, err = sshConnect(c)
|
||||
defer client.Close()
|
||||
|
||||
var session *ssh.Session
|
||||
if session, err = client.NewSession(); err != nil {
|
||||
logger.Errorf("Failed to new session. err: %s, c: %s",
|
||||
err,
|
||||
pp.Sprintf("%v", c))
|
||||
result.ExitStatus = 999
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// http://blog.ralch.com/tutorial/golang-ssh-connection/
|
||||
modes := ssh.TerminalModes{
|
||||
ssh.ECHO: 0, // disable echoing
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
if err = session.RequestPty("xterm", 400, 120, modes); err != nil {
|
||||
logger.Errorf("Failed to request for pseudo terminal. err: %s, c: %s",
|
||||
err,
|
||||
pp.Sprintf("%v", c))
|
||||
|
||||
result.ExitStatus = 999
|
||||
return
|
||||
}
|
||||
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
session.Stdout = &stdoutBuf
|
||||
session.Stderr = &stderrBuf
|
||||
|
||||
if err := session.Run(cmd); err != nil {
|
||||
if exitErr, ok := err.(*ssh.ExitError); ok {
|
||||
result.ExitStatus = exitErr.ExitStatus()
|
||||
} else {
|
||||
result.ExitStatus = 999
|
||||
}
|
||||
} else {
|
||||
result.ExitStatus = 0
|
||||
}
|
||||
|
||||
result.Stdout = stdoutBuf.String()
|
||||
result.Stderr = stderrBuf.String()
|
||||
result.Host = c.Host
|
||||
result.Port = c.Port
|
||||
|
||||
logger.Debugf(
|
||||
"SSH executed. cmd: %s, status: %#v\nstdout: \n%s\nstderr: \n%s",
|
||||
cmd, err, result.Stdout, result.Stderr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
|
||||
if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 {
|
||||
if agconn, err := net.Dial("unix", sock); err == nil {
|
||||
ag := agent.NewClient(agconn)
|
||||
auth = ssh.PublicKeysCallback(ag.Signers)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func tryAgentConnect(c conf.ServerInfo) *ssh.Client {
|
||||
if auth, ok := getAgentAuth(); ok {
|
||||
config := &ssh.ClientConfig{
|
||||
User: c.User,
|
||||
Auth: []ssh.AuthMethod{auth},
|
||||
}
|
||||
client, _ := ssh.Dial("tcp", c.Host+":"+c.Port, config)
|
||||
return client
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
|
||||
|
||||
if client = tryAgentConnect(c); client != nil {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
var auths = []ssh.AuthMethod{}
|
||||
if auths, err = addKeyAuth(auths, c.KeyPath, c.KeyPassword); err != nil {
|
||||
logrus.Fatalf("Faild to add keyAuth. err: %s", err)
|
||||
}
|
||||
|
||||
if c.Password != "" {
|
||||
auths = append(auths, ssh.Password(c.Password))
|
||||
}
|
||||
|
||||
// http://blog.ralch.com/tutorial/golang-ssh-connection/
|
||||
config := &ssh.ClientConfig{
|
||||
User: c.User,
|
||||
Auth: auths,
|
||||
}
|
||||
// log.Debugf("config: %s", pp.Sprintf("%v", config))
|
||||
|
||||
notifyFunc := func(e error, t time.Duration) {
|
||||
logrus.Warnf("Faild to ssh %s@%s:%s. err: %s, Retrying in %s...",
|
||||
c.User, c.Host, c.Port, e, t)
|
||||
logrus.Debugf("sshConInfo: %s", pp.Sprintf("%v", c))
|
||||
}
|
||||
err = backoff.RetryNotify(func() error {
|
||||
if client, err = ssh.Dial("tcp", c.Host+":"+c.Port, config); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, backoff.NewExponentialBackOff(), notifyFunc)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// https://github.com/rapidloop/rtop/blob/ba5b35e964135d50e0babedf0bd69b2fcb5dbcb4/src/sshhelper.go#L100
|
||||
func addKeyAuth(auths []ssh.AuthMethod, keypath string, keypassword string) ([]ssh.AuthMethod, error) {
|
||||
if len(keypath) == 0 {
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
// read the file
|
||||
pemBytes, err := ioutil.ReadFile(keypath)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
|
||||
// get first pem block
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return auths, fmt.Errorf("no key found in %s", keypath)
|
||||
}
|
||||
|
||||
// handle plain and encrypted keyfiles
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
block.Bytes, err = x509.DecryptPEMBlock(block, []byte(keypassword))
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
key, err := parsePemBlock(block)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(key)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
return append(auths, ssh.PublicKeys(signer)), nil
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey(pemBytes)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
return append(auths, ssh.PublicKeys(signer)), nil
|
||||
}
|
||||
|
||||
// ref golang.org/x/crypto/ssh/keys.go#ParseRawPrivateKey.
|
||||
func parsePemBlock(block *pem.Block) (interface{}, error) {
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(block.Bytes)
|
||||
case "DSA PRIVATE KEY":
|
||||
return ssh.ParseDSAPrivateKey(block.Bytes)
|
||||
default:
|
||||
return nil, fmt.Errorf("rtop: unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user