Compare commits
	
		
			24 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					aeaf308679 | ||
| 
						 | 
					f5e47bea40 | ||
| 
						 | 
					50cf13a7f2 | ||
| 
						 | 
					abd8041772 | ||
| 
						 | 
					847c6438e7 | ||
| 
						 | 
					ef8309df27 | ||
| 
						 | 
					0dff6cf983 | ||
| 
						 | 
					4c04acbd9e | ||
| 
						 | 
					1c4f231572 | ||
| 
						 | 
					51b8e169d2 | ||
| 
						 | 
					b4611ae9b7 | ||
| 
						 | 
					cd6722017b | ||
| 
						 | 
					290edffccf | ||
| 
						 | 
					64a6222bf9 | ||
| 
						 | 
					adb686b7c9 | ||
| 
						 | 
					d4af341b0f | ||
| 
						 | 
					fea7e93c8d | ||
| 
						 | 
					8b6b8d0f2e | ||
| 
						 | 
					4dcbd865cc | ||
| 
						 | 
					39b19444fe | ||
| 
						 | 
					644d5a5462 | ||
| 
						 | 
					8e18451e3f | ||
| 
						 | 
					3dbdd01f97 | ||
| 
						 | 
					a89079c005 | 
@@ -29,7 +29,7 @@ func main() {
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if *v {
 | 
			
		||||
		fmt.Printf("vuls %s %s\n", config.Version, config.Revision)
 | 
			
		||||
		fmt.Printf("vuls-%s-%s\n", config.Version, config.Revision)
 | 
			
		||||
		os.Exit(int(subcommands.ExitSuccess))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -83,6 +83,8 @@ type Config struct {
 | 
			
		||||
	FormatFullText    bool `json:"formatFullText,omitempty"`
 | 
			
		||||
	FormatCsvList     bool `json:"formatCsvList,omitempty"`
 | 
			
		||||
	GZIP              bool `json:"gzip,omitempty"`
 | 
			
		||||
	DiffPlus          bool `json:"diffPlus,omitempty"`
 | 
			
		||||
	DiffMinus         bool `json:"diffMinus,omitempty"`
 | 
			
		||||
	Diff              bool `json:"diff,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -339,7 +341,7 @@ type AzureConf struct {
 | 
			
		||||
 | 
			
		||||
// WpScanConf is wpscan.com config
 | 
			
		||||
type WpScanConf struct {
 | 
			
		||||
	Token          string `toml:"Token,omitempty" json:"-"`
 | 
			
		||||
	Token          string `toml:"token,omitempty" json:"-"`
 | 
			
		||||
	DetectInactive bool   `toml:"detectInactive,omitempty" json:"detectInactive,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
@@ -63,7 +64,7 @@ func (cnf *ExploitConf) CheckHTTPHealth() error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.URL)
 | 
			
		||||
	resp, _, errs := gorequest.New().Get(url).End()
 | 
			
		||||
	resp, _, errs := gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
@@ -63,7 +64,7 @@ func (cnf *GoCveDictConf) CheckHTTPHealth() error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.URL)
 | 
			
		||||
	resp, _, errs := gorequest.New().SetDebug(Conf.Debug).Get(url).End()
 | 
			
		||||
	resp, _, errs := gorequest.New().Timeout(10 * time.Second).SetDebug(Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return xerrors.Errorf("Failed to request to CVE server. url: %s, errs: %s",
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
@@ -63,7 +64,7 @@ func (cnf *GostConf) CheckHTTPHealth() error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.URL)
 | 
			
		||||
	resp, _, errs := gorequest.New().Get(url).End()
 | 
			
		||||
	resp, _, errs := gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
@@ -64,7 +65,7 @@ func (cnf *GovalDictConf) CheckHTTPHealth() error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.URL)
 | 
			
		||||
	resp, _, errs := gorequest.New().Get(url).End()
 | 
			
		||||
	resp, _, errs := gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
 
 | 
			
		||||
@@ -57,12 +57,24 @@ func Parse(vulnJSON []byte, scanResult *models.ScanResult) (result *models.ScanR
 | 
			
		||||
				return references[i].Link < references[j].Link
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			var published time.Time
 | 
			
		||||
			if vuln.PublishedDate != nil {
 | 
			
		||||
				published = *vuln.PublishedDate
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var lastModified time.Time
 | 
			
		||||
			if vuln.LastModifiedDate != nil {
 | 
			
		||||
				lastModified = *vuln.LastModifiedDate
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vulnInfo.CveContents = models.CveContents{
 | 
			
		||||
				models.Trivy: models.CveContent{
 | 
			
		||||
					Cvss3Severity: vuln.Severity,
 | 
			
		||||
					References:    references,
 | 
			
		||||
					Title:         vuln.Title,
 | 
			
		||||
					Summary:       vuln.Description,
 | 
			
		||||
					Published:     published,
 | 
			
		||||
					LastModified:  lastModified,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			// do only if image type is Vuln
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,9 @@ var (
 | 
			
		||||
 | 
			
		||||
	// ErrFailedToAccessWpScan is error of wpscan.com api access
 | 
			
		||||
	ErrFailedToAccessWpScan ErrorCode = "ErrFailedToAccessWpScan"
 | 
			
		||||
 | 
			
		||||
	// ErrWpScanAPILimitExceeded is error of wpscan.com api limit exceeded
 | 
			
		||||
	ErrWpScanAPILimitExceeded ErrorCode = "ErrWpScanAPILimitExceeded"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// New :
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
 | 
			
		||||
	count, retryMax := 0, 3
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string)
 | 
			
		||||
	src := oauth2.StaticTokenSource(
 | 
			
		||||
		&oauth2.Token{AccessToken: token},
 | 
			
		||||
	)
 | 
			
		||||
	//TODO Proxy
 | 
			
		||||
	httpClient := oauth2.NewClient(context.Background(), src)
 | 
			
		||||
 | 
			
		||||
	// TODO Use `https://github.com/shurcooL/githubv4` if the tool supports vulnerabilityAlerts Endpoint
 | 
			
		||||
@@ -32,10 +33,12 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string)
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		jsonStr := fmt.Sprintf(jsonfmt, owner, repo, 100, after)
 | 
			
		||||
		req, err := http.NewRequest("POST",
 | 
			
		||||
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
		req, err := http.NewRequestWithContext(ctx, http.MethodPost,
 | 
			
		||||
			"https://api.github.com/graphql",
 | 
			
		||||
			bytes.NewBuffer([]byte(jsonStr)),
 | 
			
		||||
		)
 | 
			
		||||
		defer cancel()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -70,11 +70,10 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveCon
 | 
			
		||||
 | 
			
		||||
	option := map[string]string{}
 | 
			
		||||
	if 0 < len(cve.ExploitStatus) {
 | 
			
		||||
		// TODO: CVE-2020-0739
 | 
			
		||||
		// "exploit_status": "Publicly Disclosed:No;Exploited:No;Latest Software Release:Exploitation Less Likely;Older Software Release:Exploitation Less Likely;DOS:N/A",
 | 
			
		||||
		option["exploit"] = cve.ExploitStatus
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(cve.Workaround) {
 | 
			
		||||
		option["workaround"] = cve.Workaround
 | 
			
		||||
	}
 | 
			
		||||
	kbids := []string{}
 | 
			
		||||
	for _, kbid := range cve.KBIDs {
 | 
			
		||||
		kbids = append(kbids, kbid.KBID)
 | 
			
		||||
@@ -86,13 +85,18 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveCon
 | 
			
		||||
	vendorURL := "https://msrc.microsoft.com/update-guide/vulnerability/" + cve.CveID
 | 
			
		||||
	mitigations := []models.Mitigation{}
 | 
			
		||||
	if cve.Mitigation != "" {
 | 
			
		||||
		mitigations = []models.Mitigation{
 | 
			
		||||
			{
 | 
			
		||||
				CveContentType: models.Microsoft,
 | 
			
		||||
				Mitigation:     cve.Mitigation,
 | 
			
		||||
				URL:            vendorURL,
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		mitigations = append(mitigations, models.Mitigation{
 | 
			
		||||
			CveContentType: models.Microsoft,
 | 
			
		||||
			Mitigation:     cve.Mitigation,
 | 
			
		||||
			URL:            vendorURL,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	if cve.Workaround != "" {
 | 
			
		||||
		mitigations = append(mitigations, models.Mitigation{
 | 
			
		||||
			CveContentType: models.Microsoft,
 | 
			
		||||
			Mitigation:     cve.Workaround,
 | 
			
		||||
			URL:            vendorURL,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
 
 | 
			
		||||
@@ -154,7 +154,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
 | 
			
		||||
	count, retryMax := 0, 3
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
 
 | 
			
		||||
@@ -63,13 +63,13 @@ func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByFQPN search a package by Fully-Qualified-Package-Name
 | 
			
		||||
func (ps Packages) FindByFQPN(nameVerRelArc string) (*Package, error) {
 | 
			
		||||
func (ps Packages) FindByFQPN(nameVerRel string) (*Package, error) {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if nameVerRelArc == p.FQPN() {
 | 
			
		||||
		if nameVerRel == p.FQPN() {
 | 
			
		||||
			return &p, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil, xerrors.Errorf("Failed to find the package: %s", nameVerRelArc)
 | 
			
		||||
	return nil, xerrors.Errorf("Failed to find the package: %s", nameVerRel)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Package has installed binary packages.
 | 
			
		||||
@@ -96,9 +96,6 @@ func (p Package) FQPN() string {
 | 
			
		||||
	if p.Release != "" {
 | 
			
		||||
		fqpn += fmt.Sprintf("-%s", p.Release)
 | 
			
		||||
	}
 | 
			
		||||
	if p.Arch != "" {
 | 
			
		||||
		fqpn += fmt.Sprintf(".%s", p.Arch)
 | 
			
		||||
	}
 | 
			
		||||
	return fqpn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -352,7 +352,7 @@ func (r ScanResult) FormatTextReportHeader() string {
 | 
			
		||||
		pkgs = fmt.Sprintf("%s, %d libs", pkgs, r.LibraryScanners.Total())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s, %s, %s, %s, %s\n%s\n",
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s\n%s, %s, %s, %s\n%s\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.ScannedCves.FormatCveSummary(),
 | 
			
		||||
 
 | 
			
		||||
@@ -78,16 +78,22 @@ func (v VulnInfos) CountGroupBySeverity() map[string]int {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatCveSummary summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (v VulnInfos) FormatCveSummary() string {
 | 
			
		||||
func (v VulnInfos) FormatCveSummary() (line string) {
 | 
			
		||||
	m := v.CountGroupBySeverity()
 | 
			
		||||
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		return fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d)",
 | 
			
		||||
		line = fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d)",
 | 
			
		||||
			m["High"]+m["Medium"]+m["Low"], m["Critical"], m["High"], m["Medium"], m["Low"])
 | 
			
		||||
	} else {
 | 
			
		||||
		line = fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d ?:%d)",
 | 
			
		||||
			m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
 | 
			
		||||
			m["Critical"], m["High"], m["Medium"], m["Low"], m["Unknown"])
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d ?:%d)",
 | 
			
		||||
		m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
 | 
			
		||||
		m["Critical"], m["High"], m["Medium"], m["Low"], m["Unknown"])
 | 
			
		||||
 | 
			
		||||
	if config.Conf.DiffMinus || config.Conf.DiffPlus {
 | 
			
		||||
		nPlus, nMinus := v.CountDiff()
 | 
			
		||||
		line = fmt.Sprintf("%s +%d -%d", line, nPlus, nMinus)
 | 
			
		||||
	}
 | 
			
		||||
	return line
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatFixedStatus summarize the number of cves are fixed.
 | 
			
		||||
@@ -105,6 +111,18 @@ func (v VulnInfos) FormatFixedStatus(packs Packages) string {
 | 
			
		||||
	return fmt.Sprintf("%d/%d Fixed", fixed, total)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CountDiff counts the number of added/removed CVE-ID
 | 
			
		||||
func (v VulnInfos) CountDiff() (nPlus int, nMinus int) {
 | 
			
		||||
	for _, vInfo := range v {
 | 
			
		||||
		if vInfo.DiffStatus == DiffPlus {
 | 
			
		||||
			nPlus++
 | 
			
		||||
		} else if vInfo.DiffStatus == DiffMinus {
 | 
			
		||||
			nMinus++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageFixStatuses is a list of PackageStatus
 | 
			
		||||
type PackageFixStatuses []PackageFixStatus
 | 
			
		||||
 | 
			
		||||
@@ -159,8 +177,8 @@ type VulnInfo struct {
 | 
			
		||||
	GitHubSecurityAlerts GitHubSecurityAlerts `json:"gitHubSecurityAlerts,omitempty"`
 | 
			
		||||
	WpPackageFixStats    WpPackageFixStats    `json:"wpPackageFixStats,omitempty"`
 | 
			
		||||
	LibraryFixedIns      LibraryFixedIns      `json:"libraryFixedIns,omitempty"`
 | 
			
		||||
 | 
			
		||||
	VulnType string `json:"vulnType,omitempty"`
 | 
			
		||||
	VulnType             string               `json:"vulnType,omitempty"`
 | 
			
		||||
	DiffStatus           DiffStatus           `json:"diffStatus,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Alert has CERT alert information
 | 
			
		||||
@@ -236,6 +254,25 @@ func (g WpPackages) Add(pkg WpPackage) WpPackages {
 | 
			
		||||
	return append(g, pkg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DiffStatus keeps a comparison with the previous detection results for this CVE
 | 
			
		||||
type DiffStatus string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// DiffPlus is newly detected CVE
 | 
			
		||||
	DiffPlus = DiffStatus("+")
 | 
			
		||||
 | 
			
		||||
	// DiffMinus is resolved CVE
 | 
			
		||||
	DiffMinus = DiffStatus("-")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveIDDiffFormat format CVE-ID for diff mode
 | 
			
		||||
func (v VulnInfo) CveIDDiffFormat(isDiffMode bool) string {
 | 
			
		||||
	if isDiffMode {
 | 
			
		||||
		return fmt.Sprintf("%s %s", v.DiffStatus, v.CveID)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s", v.CveID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Titles returns title (TUI)
 | 
			
		||||
func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
@@ -530,6 +567,7 @@ func (c Cvss) Format() string {
 | 
			
		||||
	return fmt.Sprintf("%3.1f/%s %s", c.Score, c.Vector, c.Severity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SeverityToCvssScoreRange returns CVSS score range
 | 
			
		||||
func (c Cvss) SeverityToCvssScoreRange() string {
 | 
			
		||||
	return severityToCvssScoreRange(c.Severity)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ func (b Base) CheckIfOvalFetched(driver db.DB, osFamily, release string) (fetche
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url, _ := util.URLPathJoin(cnf.Conf.OvalDict.URL, "count", osFamily, release)
 | 
			
		||||
	resp, body, errs := gorequest.New().Get(url).End()
 | 
			
		||||
	resp, body, errs := gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
 | 
			
		||||
	}
 | 
			
		||||
@@ -57,7 +57,7 @@ func (b Base) CheckIfOvalFresh(driver db.DB, osFamily, release string) (ok bool,
 | 
			
		||||
		lastModified = driver.GetLastModified(osFamily, release)
 | 
			
		||||
	} else {
 | 
			
		||||
		url, _ := util.URLPathJoin(cnf.Conf.OvalDict.URL, "lastmodified", osFamily, release)
 | 
			
		||||
		resp, body, errs := gorequest.New().Get(url).End()
 | 
			
		||||
		resp, body, errs := gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -189,7 +189,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
 | 
			
		||||
	count, retryMax := 0, 3
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
@@ -399,7 +399,7 @@ func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error
 | 
			
		||||
	case config.RedHat,
 | 
			
		||||
		config.CentOS:
 | 
			
		||||
		vera := rpmver.NewVersion(centOSVersionToRHEL(newVer))
 | 
			
		||||
		verb := rpmver.NewVersion(packInOVAL.Version)
 | 
			
		||||
		verb := rpmver.NewVersion(centOSVersionToRHEL(packInOVAL.Version))
 | 
			
		||||
		return vera.LessThan(verb), nil
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
 
 | 
			
		||||
@@ -1191,6 +1191,13 @@ func Test_centOSVersionToRHEL(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			want: "grub2-tools-2.02-0.80.el7.x86_64",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "remove minor",
 | 
			
		||||
			args: args{
 | 
			
		||||
				ver: "sudo-1.8.23-10.el7_9.1",
 | 
			
		||||
			},
 | 
			
		||||
			want: "sudo-1.8.23-10.el7.1",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
@@ -1200,3 +1207,77 @@ func Test_centOSVersionToRHEL(t *testing.T) {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_lessThan(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		family        string
 | 
			
		||||
		newVer        string
 | 
			
		||||
		AffectedPacks ovalmodels.Package
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "newVer and ovalmodels.Package both have underscoreMinorversion.",
 | 
			
		||||
			args: args{
 | 
			
		||||
				family: "centos",
 | 
			
		||||
				newVer: "1.8.23-10.el7_9.1",
 | 
			
		||||
				AffectedPacks: ovalmodels.Package{
 | 
			
		||||
					Name:        "sudo",
 | 
			
		||||
					Version:     "1.8.23-10.el7_9.1",
 | 
			
		||||
					NotFixedYet: false,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "only newVer has underscoreMinorversion.",
 | 
			
		||||
			args: args{
 | 
			
		||||
				family: "centos",
 | 
			
		||||
				newVer: "1.8.23-10.el7_9.1",
 | 
			
		||||
				AffectedPacks: ovalmodels.Package{
 | 
			
		||||
					Name:        "sudo",
 | 
			
		||||
					Version:     "1.8.23-10.el7.1",
 | 
			
		||||
					NotFixedYet: false,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "only ovalmodels.Package has underscoreMinorversion.",
 | 
			
		||||
			args: args{
 | 
			
		||||
				family: "centos",
 | 
			
		||||
				newVer: "1.8.23-10.el7.1",
 | 
			
		||||
				AffectedPacks: ovalmodels.Package{
 | 
			
		||||
					Name:        "sudo",
 | 
			
		||||
					Version:     "1.8.23-10.el7_9.1",
 | 
			
		||||
					NotFixedYet: false,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "neither newVer nor ovalmodels.Package have underscoreMinorversion.",
 | 
			
		||||
			args: args{
 | 
			
		||||
				family: "centos",
 | 
			
		||||
				newVer: "1.8.23-10.el7.1",
 | 
			
		||||
				AffectedPacks: ovalmodels.Package{
 | 
			
		||||
					Name:        "sudo",
 | 
			
		||||
					Version:     "1.8.23-10.el7.1",
 | 
			
		||||
					NotFixedYet: false,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			got, _ := lessThan(tt.args.family, tt.args.newVer, tt.args.AffectedPacks)
 | 
			
		||||
			if got != tt.want {
 | 
			
		||||
				t.Errorf("lessThan() = %t, want %t", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,17 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ChatWorkWriter send report to ChatWork
 | 
			
		||||
@@ -48,26 +51,25 @@ func (w ChatWorkWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
func chatWorkpostMessage(room, token, message string) error {
 | 
			
		||||
	uri := fmt.Sprintf("https://api.chatwork.com/v2/rooms/%s/messages=%s", room, token)
 | 
			
		||||
	payload := url.Values{"body": {message}}
 | 
			
		||||
 | 
			
		||||
	payload := url.Values{
 | 
			
		||||
		"body": {message},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reqs, err := http.NewRequest("POST", uri, strings.NewReader(payload.Encode()))
 | 
			
		||||
 | 
			
		||||
	reqs.Header.Add("X-ChatWorkToken", token)
 | 
			
		||||
	reqs.Header.Add("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, strings.NewReader(payload.Encode()))
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	client := &http.Client{}
 | 
			
		||||
	req.Header.Add("X-ChatWorkToken", token)
 | 
			
		||||
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
	resp, err := client.Do(reqs)
 | 
			
		||||
	client, err := util.GetHTTPClient(config.Conf.HTTPProxy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -114,7 +114,7 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return xerrors.Errorf("HTTP GET Error, url: %s, resp: %v, err: %s",
 | 
			
		||||
				url, resp, errs)
 | 
			
		||||
@@ -162,7 +162,7 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		//  req := gorequest.New().SetDebug(config.Conf.Debug).Post(url)
 | 
			
		||||
		req := gorequest.New().Post(url)
 | 
			
		||||
		req := gorequest.New().Timeout(10 * time.Second).Post(url)
 | 
			
		||||
		for key := range query {
 | 
			
		||||
			req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,13 +31,10 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
		path := filepath.Join(w.CurrentDir, r.ReportFileName())
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatJSON {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
			p := path + ".json"
 | 
			
		||||
			if c.Conf.DiffPlus || c.Conf.DiffMinus {
 | 
			
		||||
				p = path + "_diff.json"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + ".json"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = json.MarshalIndent(r, "", "    "); err != nil {
 | 
			
		||||
				return xerrors.Errorf("Failed to Marshal to JSON: %w", err)
 | 
			
		||||
@@ -48,13 +45,10 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatList {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
			p := path + "_short.txt"
 | 
			
		||||
			if c.Conf.DiffPlus || c.Conf.DiffMinus {
 | 
			
		||||
				p = path + "_short_diff.txt"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + "_short.txt"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := writeFile(
 | 
			
		||||
				p, []byte(formatList(r)), 0600); err != nil {
 | 
			
		||||
				return xerrors.Errorf(
 | 
			
		||||
@@ -63,11 +57,9 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatFullText {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
			p := path + "_full.txt"
 | 
			
		||||
			if c.Conf.DiffPlus || c.Conf.DiffMinus {
 | 
			
		||||
				p = path + "_full_diff.txt"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + "_full.txt"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := writeFile(
 | 
			
		||||
@@ -78,9 +70,9 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatCsvList {
 | 
			
		||||
			p := path + "_short.csv"
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
				p = path + "_short_diff.csv"
 | 
			
		||||
			p := path + ".csv"
 | 
			
		||||
			if c.Conf.DiffPlus || c.Conf.DiffMinus {
 | 
			
		||||
				p = path + "_diff.csv"
 | 
			
		||||
			}
 | 
			
		||||
			if err := formatCsvList(r, p); err != nil {
 | 
			
		||||
				return xerrors.Errorf("Failed to write CSV: %s, %w", p, err)
 | 
			
		||||
 
 | 
			
		||||
@@ -121,22 +121,12 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
	if c.Conf.DiffPlus || c.Conf.DiffMinus {
 | 
			
		||||
		prevs, err := loadPrevious(rs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		diff, err := diff(rs, prevs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		for i, r := range diff {
 | 
			
		||||
			if err := fillCvesWithNvdJvn(dbclient.CveDB, &r); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			rs[i] = r
 | 
			
		||||
		}
 | 
			
		||||
		rs = diff(rs, prevs, c.Conf.DiffPlus, c.Conf.DiffMinus)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, r := range rs {
 | 
			
		||||
@@ -227,7 +217,7 @@ func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]c.GitHubConf)
 | 
			
		||||
 | 
			
		||||
// DetectWordPressCves detects CVEs of WordPress
 | 
			
		||||
func DetectWordPressCves(r *models.ScanResult, wpCnf *c.WpScanConf) error {
 | 
			
		||||
	if len(r.Packages) == 0 {
 | 
			
		||||
	if len(r.WordPressPackages) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("Detect WordPress CVE. pkgs: %d ", len(r.WordPressPackages))
 | 
			
		||||
 
 | 
			
		||||
@@ -136,7 +136,7 @@ func send(msg message) error {
 | 
			
		||||
	jsonBody := string(bytes)
 | 
			
		||||
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).Send(string(jsonBody)).End()
 | 
			
		||||
		resp, body, errs := gorequest.New().Timeout(10 * time.Second).Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).Send(string(jsonBody)).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
@@ -206,7 +206,7 @@ func toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a := slack.Attachment{
 | 
			
		||||
			Title:      vinfo.CveID,
 | 
			
		||||
			Title:      vinfo.CveIDDiffFormat(config.Conf.DiffMinus || config.Conf.DiffPlus),
 | 
			
		||||
			TitleLink:  "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID,
 | 
			
		||||
			Text:       attachmentText(vinfo, r.Family, r.CweDict, r.Packages),
 | 
			
		||||
			MarkdownIn: []string{"text", "pretext"},
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,16 @@ package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -55,15 +58,21 @@ func (w TelegramWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
func sendMessage(chatID, token, message string) error {
 | 
			
		||||
	uri := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", token)
 | 
			
		||||
	payload := `{"text": "` + strings.Replace(message, `"`, `\"`, -1) + `", "chat_id": "` + chatID + `", "parse_mode": "Markdown" }`
 | 
			
		||||
	req, err := http.NewRequest("POST", uri, bytes.NewBuffer([]byte(payload)))
 | 
			
		||||
	req.Header.Add("Content-Type", "application/json")
 | 
			
		||||
 | 
			
		||||
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, bytes.NewBuffer([]byte(payload)))
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Add("Content-Type", "application/json")
 | 
			
		||||
 | 
			
		||||
	client, err := util.GetHTTPClient(config.Conf.HTTPProxy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	client := &http.Client{}
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if checkResponse(resp) != nil && err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 
 | 
			
		||||
@@ -633,6 +633,7 @@ func summaryLines(r models.ScanResult) string {
 | 
			
		||||
		var cols []string
 | 
			
		||||
		cols = []string{
 | 
			
		||||
			fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
			string(vinfo.DiffStatus),
 | 
			
		||||
			vinfo.CveID,
 | 
			
		||||
			cvssScore + " |",
 | 
			
		||||
			fmt.Sprintf("%-6s |", av),
 | 
			
		||||
 
 | 
			
		||||
@@ -149,7 +149,7 @@ No CVE-IDs are found in updatable packages.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		data = append(data, []string{
 | 
			
		||||
			vinfo.CveID,
 | 
			
		||||
			vinfo.CveIDDiffFormat(config.Conf.DiffMinus || config.Conf.DiffPlus),
 | 
			
		||||
			fmt.Sprintf("%4.1f", max),
 | 
			
		||||
			fmt.Sprintf("%5s", vinfo.AttackVector()),
 | 
			
		||||
			// fmt.Sprintf("%4.1f", v2max),
 | 
			
		||||
@@ -373,7 +373,7 @@ No CVE-IDs are found in updatable packages.
 | 
			
		||||
		table.SetColWidth(80)
 | 
			
		||||
		table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
 | 
			
		||||
		table.SetHeader([]string{
 | 
			
		||||
			vuln.CveID,
 | 
			
		||||
			vuln.CveIDDiffFormat(config.Conf.DiffMinus || config.Conf.DiffPlus),
 | 
			
		||||
			vuln.PatchStatus(r.Packages),
 | 
			
		||||
		})
 | 
			
		||||
		table.SetBorder(true)
 | 
			
		||||
@@ -477,15 +477,18 @@ func needToRefreshCve(r models.ScanResult) bool {
 | 
			
		||||
 | 
			
		||||
func overwriteJSONFile(dir string, r models.ScanResult) error {
 | 
			
		||||
	before := config.Conf.FormatJSON
 | 
			
		||||
	beforeDiff := config.Conf.Diff
 | 
			
		||||
	beforePlusDiff := config.Conf.DiffPlus
 | 
			
		||||
	beforeMinusDiff := config.Conf.DiffMinus
 | 
			
		||||
	config.Conf.FormatJSON = true
 | 
			
		||||
	config.Conf.Diff = false
 | 
			
		||||
	config.Conf.DiffPlus = false
 | 
			
		||||
	config.Conf.DiffMinus = false
 | 
			
		||||
	w := LocalFileWriter{CurrentDir: dir}
 | 
			
		||||
	if err := w.Write(r); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to write summary report: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	config.Conf.FormatJSON = before
 | 
			
		||||
	config.Conf.Diff = beforeDiff
 | 
			
		||||
	config.Conf.DiffPlus = beforePlusDiff
 | 
			
		||||
	config.Conf.DiffMinus = beforeMinusDiff
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -504,7 +507,7 @@ func loadPrevious(currs models.ScanResults) (prevs models.ScanResults, err error
 | 
			
		||||
			path := filepath.Join(dir, filename)
 | 
			
		||||
			r, err := loadOneServerScanResult(path)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("%+v", err)
 | 
			
		||||
				util.Log.Debugf("%+v", err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if r.Family == result.Family && r.Release == result.Release {
 | 
			
		||||
@@ -520,7 +523,7 @@ func loadPrevious(currs models.ScanResults) (prevs models.ScanResults, err error
 | 
			
		||||
	return prevs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func diff(curResults, preResults models.ScanResults) (diffed models.ScanResults, err error) {
 | 
			
		||||
func diff(curResults, preResults models.ScanResults, isPlus, isMinus bool) (diffed models.ScanResults) {
 | 
			
		||||
	for _, current := range curResults {
 | 
			
		||||
		found := false
 | 
			
		||||
		var previous models.ScanResult
 | 
			
		||||
@@ -532,24 +535,46 @@ func diff(curResults, preResults models.ScanResults) (diffed models.ScanResults,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if found {
 | 
			
		||||
			current.ScannedCves = getDiffCves(previous, current)
 | 
			
		||||
			packages := models.Packages{}
 | 
			
		||||
			for _, s := range current.ScannedCves {
 | 
			
		||||
				for _, affected := range s.AffectedPackages {
 | 
			
		||||
					p := current.Packages[affected.Name]
 | 
			
		||||
					packages[affected.Name] = p
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			current.Packages = packages
 | 
			
		||||
		if !found {
 | 
			
		||||
			diffed = append(diffed, current)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cves := models.VulnInfos{}
 | 
			
		||||
		if isPlus {
 | 
			
		||||
			cves = getPlusDiffCves(previous, current)
 | 
			
		||||
		}
 | 
			
		||||
		if isMinus {
 | 
			
		||||
			minus := getMinusDiffCves(previous, current)
 | 
			
		||||
			if len(cves) == 0 {
 | 
			
		||||
				cves = minus
 | 
			
		||||
			} else {
 | 
			
		||||
				for k, v := range minus {
 | 
			
		||||
					cves[k] = v
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		packages := models.Packages{}
 | 
			
		||||
		for _, s := range cves {
 | 
			
		||||
			for _, affected := range s.AffectedPackages {
 | 
			
		||||
				var p models.Package
 | 
			
		||||
				if s.DiffStatus == models.DiffPlus {
 | 
			
		||||
					p = current.Packages[affected.Name]
 | 
			
		||||
				} else {
 | 
			
		||||
					p = previous.Packages[affected.Name]
 | 
			
		||||
				}
 | 
			
		||||
				packages[affected.Name] = p
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		current.ScannedCves = cves
 | 
			
		||||
		current.Packages = packages
 | 
			
		||||
		diffed = append(diffed, current)
 | 
			
		||||
	}
 | 
			
		||||
	return diffed, err
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
func getPlusDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
	previousCveIDsSet := map[string]bool{}
 | 
			
		||||
	for _, previousVulnInfo := range previous.ScannedCves {
 | 
			
		||||
		previousCveIDsSet[previousVulnInfo.CveID] = true
 | 
			
		||||
@@ -560,6 +585,7 @@ func getDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
	for _, v := range current.ScannedCves {
 | 
			
		||||
		if previousCveIDsSet[v.CveID] {
 | 
			
		||||
			if isCveInfoUpdated(v.CveID, previous, current) {
 | 
			
		||||
				v.DiffStatus = models.DiffPlus
 | 
			
		||||
				updated[v.CveID] = v
 | 
			
		||||
				util.Log.Debugf("updated: %s", v.CveID)
 | 
			
		||||
 | 
			
		||||
@@ -575,11 +601,12 @@ func getDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("new: %s", v.CveID)
 | 
			
		||||
			v.DiffStatus = models.DiffPlus
 | 
			
		||||
			new[v.CveID] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(updated) == 0 {
 | 
			
		||||
	if len(updated) == 0 && len(new) == 0 {
 | 
			
		||||
		util.Log.Infof("%s: There are %d vulnerabilities, but no difference between current result and previous one.", current.FormatServerName(), len(current.ScannedCves))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -589,6 +616,27 @@ func getDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
	return updated
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getMinusDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
	currentCveIDsSet := map[string]bool{}
 | 
			
		||||
	for _, currentVulnInfo := range current.ScannedCves {
 | 
			
		||||
		currentCveIDsSet[currentVulnInfo.CveID] = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clear := models.VulnInfos{}
 | 
			
		||||
	for _, v := range previous.ScannedCves {
 | 
			
		||||
		if !currentCveIDsSet[v.CveID] {
 | 
			
		||||
			v.DiffStatus = models.DiffMinus
 | 
			
		||||
			clear[v.CveID] = v
 | 
			
		||||
			util.Log.Debugf("clear: %s", v.CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(clear) == 0 {
 | 
			
		||||
		util.Log.Infof("%s: There are %d vulnerabilities, but no difference between current result and previous one.", current.FormatServerName(), len(current.ScannedCves))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return clear
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isCveFixed(current models.VulnInfo, previous models.ScanResult) bool {
 | 
			
		||||
	preVinfo, _ := previous.ScannedCves[current.CveID]
 | 
			
		||||
	pre := map[string]bool{}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import (
 | 
			
		||||
 | 
			
		||||
func TestMain(m *testing.M) {
 | 
			
		||||
	util.Log = util.NewCustomLogger(config.ServerInfo{})
 | 
			
		||||
	pp.ColoringEnabled = false
 | 
			
		||||
	code := m.Run()
 | 
			
		||||
	os.Exit(code)
 | 
			
		||||
}
 | 
			
		||||
@@ -174,7 +175,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDiff(t *testing.T) {
 | 
			
		||||
func TestPlusMinusDiff(t *testing.T) {
 | 
			
		||||
	atCurrent, _ := time.Parse("2006-01-02", "2014-12-31")
 | 
			
		||||
	atPrevious, _ := time.Parse("2006-01-02", "2014-11-31")
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
@@ -182,81 +183,56 @@ func TestDiff(t *testing.T) {
 | 
			
		||||
		inPrevious models.ScanResults
 | 
			
		||||
		out        models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		//same
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					Family:     "ubuntu",
 | 
			
		||||
					Release:    "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libexpat1"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeURIs:          []string{},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libc-bin"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeURIs:          []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{},
 | 
			
		||||
					Errors:   []string{},
 | 
			
		||||
					Optional: map[string]interface{}{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atPrevious,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					Family:     "ubuntu",
 | 
			
		||||
					Release:    "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libexpat1"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeURIs:          []string{},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libc-bin"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeURIs:          []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{},
 | 
			
		||||
					Errors:   []string{},
 | 
			
		||||
					Optional: map[string]interface{}{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:   atCurrent,
 | 
			
		||||
				ServerName:  "u16",
 | 
			
		||||
				Family:      "ubuntu",
 | 
			
		||||
				Release:     "16.04",
 | 
			
		||||
				Packages:    models.Packages{},
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
				Errors:      []string{},
 | 
			
		||||
				Optional:    map[string]interface{}{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		//plus, minus
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					Family:     "ubuntu",
 | 
			
		||||
					Release:    "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2016-6662": {
 | 
			
		||||
							CveID:            "CVE-2016-6662",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeURIs:          []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{
 | 
			
		||||
@@ -267,34 +243,45 @@ func TestDiff(t *testing.T) {
 | 
			
		||||
							NewVersion: "5.1.73",
 | 
			
		||||
							NewRelease: "8.el6_8",
 | 
			
		||||
							Repository: "",
 | 
			
		||||
							Changelog: &models.Changelog{
 | 
			
		||||
								Contents: "",
 | 
			
		||||
								Method:   "",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:   atPrevious,
 | 
			
		||||
					ServerName:  "u16",
 | 
			
		||||
					Family:      "ubuntu",
 | 
			
		||||
					Release:     "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{},
 | 
			
		||||
					ScannedAt:  atPrevious,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2020-6662": {
 | 
			
		||||
							CveID:            "CVE-2020-6662",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "bind"}},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{
 | 
			
		||||
						"bind": {
 | 
			
		||||
							Name:       "bind",
 | 
			
		||||
							Version:    "5.1.73",
 | 
			
		||||
							Release:    "7.el6",
 | 
			
		||||
							NewVersion: "5.1.73",
 | 
			
		||||
							NewRelease: "8.el6_8",
 | 
			
		||||
							Repository: "",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:  atCurrent,
 | 
			
		||||
				ServerName: "u16",
 | 
			
		||||
				Family:     "ubuntu",
 | 
			
		||||
				Release:    "16.04",
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2016-6662": {
 | 
			
		||||
						CveID:            "CVE-2016-6662",
 | 
			
		||||
						AffectedPackages: models.PackageFixStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
						DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
						CpeURIs:          []string{},
 | 
			
		||||
						DiffStatus:       "+",
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2020-6662": {
 | 
			
		||||
						CveID:            "CVE-2020-6662",
 | 
			
		||||
						AffectedPackages: models.PackageFixStatuses{{Name: "bind"}},
 | 
			
		||||
						DiffStatus:       "-",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Packages: models.Packages{
 | 
			
		||||
@@ -305,10 +292,14 @@ func TestDiff(t *testing.T) {
 | 
			
		||||
						NewVersion: "5.1.73",
 | 
			
		||||
						NewRelease: "8.el6_8",
 | 
			
		||||
						Repository: "",
 | 
			
		||||
						Changelog: &models.Changelog{
 | 
			
		||||
							Contents: "",
 | 
			
		||||
							Method:   "",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					"bind": {
 | 
			
		||||
						Name:       "bind",
 | 
			
		||||
						Version:    "5.1.73",
 | 
			
		||||
						Release:    "7.el6",
 | 
			
		||||
						NewVersion: "5.1.73",
 | 
			
		||||
						NewRelease: "8.el6_8",
 | 
			
		||||
						Repository: "",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
@@ -316,7 +307,312 @@ func TestDiff(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		diff, _ := diff(tt.inCurrent, tt.inPrevious)
 | 
			
		||||
		diff := diff(tt.inCurrent, tt.inPrevious, true, true)
 | 
			
		||||
		for _, actual := range diff {
 | 
			
		||||
			if !reflect.DeepEqual(actual.ScannedCves, tt.out.ScannedCves) {
 | 
			
		||||
				h := pp.Sprint(actual.ScannedCves)
 | 
			
		||||
				x := pp.Sprint(tt.out.ScannedCves)
 | 
			
		||||
				t.Errorf("[%d] cves actual: \n %s \n expected: \n %s", i, h, x)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for j := range tt.out.Packages {
 | 
			
		||||
				if !reflect.DeepEqual(tt.out.Packages[j], actual.Packages[j]) {
 | 
			
		||||
					h := pp.Sprint(tt.out.Packages[j])
 | 
			
		||||
					x := pp.Sprint(actual.Packages[j])
 | 
			
		||||
					t.Errorf("[%d] packages actual: \n %s \n expected: \n %s", i, x, h)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPlusDiff(t *testing.T) {
 | 
			
		||||
	atCurrent, _ := time.Parse("2006-01-02", "2014-12-31")
 | 
			
		||||
	atPrevious, _ := time.Parse("2006-01-02", "2014-11-31")
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		inCurrent  models.ScanResults
 | 
			
		||||
		inPrevious models.ScanResults
 | 
			
		||||
		out        models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			// same
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libexpat1"}},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libc-bin"}},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atPrevious,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libexpat1"}},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libc-bin"}},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:   atCurrent,
 | 
			
		||||
				ServerName:  "u16",
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// plus
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2016-6662": {
 | 
			
		||||
							CveID:            "CVE-2016-6662",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{
 | 
			
		||||
						"mysql-libs": {
 | 
			
		||||
							Name:       "mysql-libs",
 | 
			
		||||
							Version:    "5.1.73",
 | 
			
		||||
							Release:    "7.el6",
 | 
			
		||||
							NewVersion: "5.1.73",
 | 
			
		||||
							NewRelease: "8.el6_8",
 | 
			
		||||
							Repository: "",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atPrevious,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:  atCurrent,
 | 
			
		||||
				ServerName: "u16",
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2016-6662": {
 | 
			
		||||
						CveID:            "CVE-2016-6662",
 | 
			
		||||
						AffectedPackages: models.PackageFixStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
						DiffStatus:       "+",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Packages: models.Packages{
 | 
			
		||||
					"mysql-libs": {
 | 
			
		||||
						Name:       "mysql-libs",
 | 
			
		||||
						Version:    "5.1.73",
 | 
			
		||||
						Release:    "7.el6",
 | 
			
		||||
						NewVersion: "5.1.73",
 | 
			
		||||
						NewRelease: "8.el6_8",
 | 
			
		||||
						Repository: "",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// minus
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID: "CVE-2012-6702",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atPrevious,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID: "CVE-2012-6702",
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID: "CVE-2014-9761",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:   atCurrent,
 | 
			
		||||
				ServerName:  "u16",
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		diff := diff(tt.inCurrent, tt.inPrevious, true, false)
 | 
			
		||||
		for _, actual := range diff {
 | 
			
		||||
			if !reflect.DeepEqual(actual.ScannedCves, tt.out.ScannedCves) {
 | 
			
		||||
				h := pp.Sprint(actual.ScannedCves)
 | 
			
		||||
				x := pp.Sprint(tt.out.ScannedCves)
 | 
			
		||||
				t.Errorf("[%d] cves actual: \n %s \n expected: \n %s", i, h, x)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for j := range tt.out.Packages {
 | 
			
		||||
				if !reflect.DeepEqual(tt.out.Packages[j], actual.Packages[j]) {
 | 
			
		||||
					h := pp.Sprint(tt.out.Packages[j])
 | 
			
		||||
					x := pp.Sprint(actual.Packages[j])
 | 
			
		||||
					t.Errorf("[%d] packages actual: \n %s \n expected: \n %s", i, x, h)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMinusDiff(t *testing.T) {
 | 
			
		||||
	atCurrent, _ := time.Parse("2006-01-02", "2014-12-31")
 | 
			
		||||
	atPrevious, _ := time.Parse("2006-01-02", "2014-11-31")
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		inCurrent  models.ScanResults
 | 
			
		||||
		inPrevious models.ScanResults
 | 
			
		||||
		out        models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		// same
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libexpat1"}},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libc-bin"}},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atPrevious,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libexpat1"}},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "libc-bin"}},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:   atCurrent,
 | 
			
		||||
				ServerName:  "u16",
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// minus
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atPrevious,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					Packages:   models.Packages{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2016-6662": {
 | 
			
		||||
							CveID:            "CVE-2016-6662",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{
 | 
			
		||||
						"mysql-libs": {
 | 
			
		||||
							Name:       "mysql-libs",
 | 
			
		||||
							Version:    "5.1.73",
 | 
			
		||||
							Release:    "7.el6",
 | 
			
		||||
							NewVersion: "5.1.73",
 | 
			
		||||
							NewRelease: "8.el6_8",
 | 
			
		||||
							Repository: "",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:  atCurrent,
 | 
			
		||||
				ServerName: "u16",
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2016-6662": {
 | 
			
		||||
						CveID:            "CVE-2016-6662",
 | 
			
		||||
						AffectedPackages: models.PackageFixStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
						DiffStatus:       "-",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Packages: models.Packages{
 | 
			
		||||
					"mysql-libs": {
 | 
			
		||||
						Name:       "mysql-libs",
 | 
			
		||||
						Version:    "5.1.73",
 | 
			
		||||
						Release:    "7.el6",
 | 
			
		||||
						NewVersion: "5.1.73",
 | 
			
		||||
						NewRelease: "8.el6_8",
 | 
			
		||||
						Repository: "",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// plus
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atPrevious,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2016-6662": {
 | 
			
		||||
							CveID:            "CVE-2016-6662",
 | 
			
		||||
							AffectedPackages: models.PackageFixStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:   atCurrent,
 | 
			
		||||
					ServerName:  "u16",
 | 
			
		||||
					ScannedCves: models.VulnInfos{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:   atCurrent,
 | 
			
		||||
				ServerName:  "u16",
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		diff := diff(tt.inCurrent, tt.inPrevious, false, true)
 | 
			
		||||
		for _, actual := range diff {
 | 
			
		||||
			if !reflect.DeepEqual(actual.ScannedCves, tt.out.ScannedCves) {
 | 
			
		||||
				h := pp.Sprint(actual.ScannedCves)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								saas/saas.go
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								saas/saas.go
									
									
									
									
									
								
							@@ -2,20 +2,22 @@ package saas
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/credentials"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/session"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/service/s3"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/service/sts"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
@@ -41,8 +43,7 @@ type payload struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UploadSaas : UploadSaas
 | 
			
		||||
func (w Writer) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	// dir string, configPath string, config *c.Config
 | 
			
		||||
func (w Writer) Write(rs ...models.ScanResult) error {
 | 
			
		||||
	if len(rs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
@@ -60,34 +61,25 @@ func (w Writer) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
		ScannedIPv4s: strings.Join(ipv4s, ", "),
 | 
			
		||||
		ScannedIPv6s: strings.Join(ipv6s, ", "),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var body []byte
 | 
			
		||||
	if body, err = json.Marshal(payload); err != nil {
 | 
			
		||||
	body, err := json.Marshal(payload)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to Marshal to JSON: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var req *http.Request
 | 
			
		||||
	if req, err = http.NewRequest("POST", c.Conf.Saas.URL, bytes.NewBuffer(body)); err != nil {
 | 
			
		||||
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.Conf.Saas.URL, bytes.NewBuffer(body))
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	req.Header.Set("Accept", "application/json")
 | 
			
		||||
 | 
			
		||||
	proxy := c.Conf.HTTPProxy
 | 
			
		||||
	var client http.Client
 | 
			
		||||
	if proxy != "" {
 | 
			
		||||
		proxyURL, _ := url.Parse(proxy)
 | 
			
		||||
		client = http.Client{
 | 
			
		||||
			Transport: &http.Transport{
 | 
			
		||||
				Proxy: http.ProxyURL(proxyURL),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		client = http.Client{}
 | 
			
		||||
	client, err := util.GetHTTPClient(config.Conf.HTTPProxy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	if resp, err = client.Do(req); err != nil {
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
@@ -95,44 +87,40 @@ func (w Writer) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
		return xerrors.Errorf("Failed to get Credential. Request JSON : %s,", string(body))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var t []byte
 | 
			
		||||
	if t, err = ioutil.ReadAll(resp.Body); err != nil {
 | 
			
		||||
	t, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tempCredential TempCredential
 | 
			
		||||
	if err = json.Unmarshal(t, &tempCredential); err != nil {
 | 
			
		||||
	if err := json.Unmarshal(t, &tempCredential); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to unmarshal saas credential file. err : %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	credential := credentials.NewStaticCredentialsFromCreds(credentials.Value{
 | 
			
		||||
		AccessKeyID:     *tempCredential.Credential.AccessKeyId,
 | 
			
		||||
		SecretAccessKey: *tempCredential.Credential.SecretAccessKey,
 | 
			
		||||
		SessionToken:    *tempCredential.Credential.SessionToken,
 | 
			
		||||
	sess, err := session.NewSession(&aws.Config{
 | 
			
		||||
		Credentials: credentials.NewStaticCredentialsFromCreds(credentials.Value{
 | 
			
		||||
			AccessKeyID:     *tempCredential.Credential.AccessKeyId,
 | 
			
		||||
			SecretAccessKey: *tempCredential.Credential.SecretAccessKey,
 | 
			
		||||
			SessionToken:    *tempCredential.Credential.SessionToken,
 | 
			
		||||
		}),
 | 
			
		||||
		Region: aws.String("ap-northeast-1"),
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	var sess *session.Session
 | 
			
		||||
	if sess, err = session.NewSession(&aws.Config{
 | 
			
		||||
		Credentials: credential,
 | 
			
		||||
		Region:      aws.String("ap-northeast-1"),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to new aws session. err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	svc := s3.New(sess)
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		s3Key := renameKeyName(r.ServerUUID, r.Container)
 | 
			
		||||
		var b []byte
 | 
			
		||||
		if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
		b, err := json.Marshal(r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to Marshal to JSON: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		util.Log.Infof("Uploading...: ServerName: %s, ", r.ServerName)
 | 
			
		||||
		s3Key := renameKeyName(r.ServerUUID, r.Container)
 | 
			
		||||
		putObjectInput := &s3.PutObjectInput{
 | 
			
		||||
			Bucket: aws.String(tempCredential.S3Bucket),
 | 
			
		||||
			Key:    aws.String(path.Join(tempCredential.S3ResultsDir, s3Key)),
 | 
			
		||||
			Body:   bytes.NewReader(b),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := svc.PutObject(putObjectInput); err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to upload data to %s/%s, err: %w",
 | 
			
		||||
				tempCredential.S3Bucket, s3Key, err)
 | 
			
		||||
 
 | 
			
		||||
@@ -32,19 +32,16 @@ func newAlpine(c config.ServerInfo) *alpine {
 | 
			
		||||
 | 
			
		||||
// Alpine
 | 
			
		||||
// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/alpine.rb
 | 
			
		||||
func detectAlpine(c config.ServerInfo) (itsMe bool, os osTypeInterface) {
 | 
			
		||||
	os = newAlpine(c)
 | 
			
		||||
 | 
			
		||||
func detectAlpine(c config.ServerInfo) (bool, osTypeInterface) {
 | 
			
		||||
	if r := exec(c, "ls /etc/alpine-release", noSudo); !r.isSuccess() {
 | 
			
		||||
		return false, os
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "cat /etc/alpine-release", noSudo); r.isSuccess() {
 | 
			
		||||
		os := newAlpine(c)
 | 
			
		||||
		os.setDistro(config.Alpine, strings.TrimSpace(r.Stdout))
 | 
			
		||||
		return true, os
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false, os
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) checkScanMode() error {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										105
									
								
								scan/base.go
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								scan/base.go
									
									
									
									
									
								
							@@ -322,7 +322,7 @@ func (l *base) detectPlatform() {
 | 
			
		||||
 | 
			
		||||
var dsFingerPrintPrefix = "AgentStatus.agentCertHash: "
 | 
			
		||||
 | 
			
		||||
func (l *base) detectDeepSecurity() (fingerprint string, err error) {
 | 
			
		||||
func (l *base) detectDeepSecurity() (string, error) {
 | 
			
		||||
	// only work root user
 | 
			
		||||
	if l.getServerInfo().Mode.IsFastRoot() {
 | 
			
		||||
		if r := l.exec("test -f /opt/ds_agent/dsa_query", sudo); r.isSuccess() {
 | 
			
		||||
@@ -621,7 +621,7 @@ func (d *DummyFileInfo) IsDir() bool { return false }
 | 
			
		||||
//Sys is
 | 
			
		||||
func (d *DummyFileInfo) Sys() interface{} { return nil }
 | 
			
		||||
 | 
			
		||||
func (l *base) scanWordPress() (err error) {
 | 
			
		||||
func (l *base) scanWordPress() error {
 | 
			
		||||
	if l.ServerInfo.WordPress.IsZero() || l.ServerInfo.Type == config.ServerTypePseudo {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
@@ -835,7 +835,7 @@ func (l *base) findPortTestSuccessOn(listenIPPorts []string, searchListenPort mo
 | 
			
		||||
	return addrs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) ps() (stdout string, err error) {
 | 
			
		||||
func (l *base) ps() (string, error) {
 | 
			
		||||
	cmd := `LANGUAGE=en_US.UTF-8 ps --no-headers --ppid 2 -p 2 --deselect -o pid,comm`
 | 
			
		||||
	r := l.exec(util.PrependProxyEnv(cmd), noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
@@ -858,7 +858,7 @@ func (l *base) parsePs(stdout string) map[string]string {
 | 
			
		||||
	return pidNames
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) lsProcExe(pid string) (stdout string, err error) {
 | 
			
		||||
func (l *base) lsProcExe(pid string) (string, error) {
 | 
			
		||||
	cmd := fmt.Sprintf("ls -l /proc/%s/exe", pid)
 | 
			
		||||
	r := l.exec(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
@@ -875,7 +875,7 @@ func (l *base) parseLsProcExe(stdout string) (string, error) {
 | 
			
		||||
	return ss[10], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) grepProcMap(pid string) (stdout string, err error) {
 | 
			
		||||
func (l *base) grepProcMap(pid string) (string, error) {
 | 
			
		||||
	cmd := fmt.Sprintf(`cat /proc/%s/maps 2>/dev/null | grep -v " 00:00 " | awk '{print $6}' | sort -n | uniq`, pid)
 | 
			
		||||
	r := l.exec(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
@@ -888,15 +888,16 @@ func (l *base) parseGrepProcMap(stdout string) (soPaths []string) {
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := strings.TrimSpace(scanner.Text())
 | 
			
		||||
		line = strings.Split(line, ";")[0]
 | 
			
		||||
		soPaths = append(soPaths, line)
 | 
			
		||||
	}
 | 
			
		||||
	return soPaths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) lsOfListen() (stdout string, err error) {
 | 
			
		||||
	cmd := `lsof -i -P -n | grep LISTEN`
 | 
			
		||||
func (l *base) lsOfListen() (string, error) {
 | 
			
		||||
	cmd := `lsof -i -P -n`
 | 
			
		||||
	r := l.exec(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return "", xerrors.Errorf("Failed to lsof: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return r.Stdout, nil
 | 
			
		||||
@@ -906,7 +907,11 @@ func (l *base) parseLsOf(stdout string) map[string][]string {
 | 
			
		||||
	portPids := map[string][]string{}
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		ss := strings.Fields(scanner.Text())
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		if !strings.Contains(line, "LISTEN") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ss := strings.Fields(line)
 | 
			
		||||
		if len(ss) < 10 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
@@ -915,3 +920,85 @@ func (l *base) parseLsOf(stdout string) map[string][]string {
 | 
			
		||||
	}
 | 
			
		||||
	return portPids
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) pkgPs(getOwnerPkgs func([]string) ([]string, error)) error {
 | 
			
		||||
	stdout, err := l.ps()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to pkgPs: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	pidNames := l.parsePs(stdout)
 | 
			
		||||
	pidLoadedFiles := map[string][]string{}
 | 
			
		||||
	for pid := range pidNames {
 | 
			
		||||
		stdout := ""
 | 
			
		||||
		stdout, err = l.lsProcExe(pid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			l.log.Debugf("Failed to exec ls -l /proc/%s/exe err: %s", pid, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		s, err := l.parseLsProcExe(stdout)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			l.log.Debugf("Failed to parse /proc/%s/exe: %s", pid, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pidLoadedFiles[pid] = append(pidLoadedFiles[pid], s)
 | 
			
		||||
 | 
			
		||||
		stdout, err = l.grepProcMap(pid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			l.log.Debugf("Failed to exec /proc/%s/maps: %s", pid, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ss := l.parseGrepProcMap(stdout)
 | 
			
		||||
		pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pidListenPorts := map[string][]models.PortStat{}
 | 
			
		||||
	stdout, err = l.lsOfListen()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// warning only, continue scanning
 | 
			
		||||
		l.log.Warnf("Failed to lsof: %+v", err)
 | 
			
		||||
	}
 | 
			
		||||
	portPids := l.parseLsOf(stdout)
 | 
			
		||||
	for ipPort, pids := range portPids {
 | 
			
		||||
		for _, pid := range pids {
 | 
			
		||||
			portStat, err := models.NewPortStat(ipPort)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				l.log.Warnf("Failed to parse ip:port: %s, err: %+v", ipPort, err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			pidListenPorts[pid] = append(pidListenPorts[pid], *portStat)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for pid, loadedFiles := range pidLoadedFiles {
 | 
			
		||||
		pkgNames, err := getOwnerPkgs(loadedFiles)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			l.log.Warnf("Failed to get owner pkgs of: %s", loadedFiles)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		uniq := map[string]struct{}{}
 | 
			
		||||
		for _, name := range pkgNames {
 | 
			
		||||
			uniq[name] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		procName := ""
 | 
			
		||||
		if _, ok := pidNames[pid]; ok {
 | 
			
		||||
			procName = pidNames[pid]
 | 
			
		||||
		}
 | 
			
		||||
		proc := models.AffectedProcess{
 | 
			
		||||
			PID:             pid,
 | 
			
		||||
			Name:            procName,
 | 
			
		||||
			ListenPortStats: pidListenPorts[pid],
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for name := range uniq {
 | 
			
		||||
			p, ok := l.Packages[name]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				l.log.Warnf("Failed to find a running pkg: %s", name)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			p.AffectedProcs = append(p.AffectedProcs, proc)
 | 
			
		||||
			l.Packages[p.Name] = p
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -218,12 +218,14 @@ func Test_base_parseGrepProcMap(t *testing.T) {
 | 
			
		||||
			args: args{
 | 
			
		||||
				`/etc/selinux/targeted/contexts/files/file_contexts.bin
 | 
			
		||||
/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin
 | 
			
		||||
/usr/lib64/libdl-2.28.so`,
 | 
			
		||||
/usr/lib64/libdl-2.28.so
 | 
			
		||||
				/usr/lib64/libnss_files-2.17.so;601ccbf3`,
 | 
			
		||||
			},
 | 
			
		||||
			wantSoPaths: []string{
 | 
			
		||||
				"/etc/selinux/targeted/contexts/files/file_contexts.bin",
 | 
			
		||||
				"/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin",
 | 
			
		||||
				"/usr/lib64/libdl-2.28.so",
 | 
			
		||||
				`/usr/lib64/libnss_files-2.17.so`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -255,6 +257,7 @@ sshd        644            root    4u  IPv6  16716      0t0  TCP *:22 (LISTEN)
 | 
			
		||||
squid       959           proxy   11u  IPv6  16351      0t0  TCP *:3128 (LISTEN)
 | 
			
		||||
node       1498          ubuntu   21u  IPv6  20132      0t0  TCP *:35401 (LISTEN)
 | 
			
		||||
node       1498          ubuntu   22u  IPv6  20133      0t0  TCP *:44801 (LISTEN)
 | 
			
		||||
rpcbind   568    rpc    7u  IPv6  15149      0t0  UDP *:111
 | 
			
		||||
docker-pr  9135            root    4u  IPv6 297133      0t0  TCP *:6379 (LISTEN)`,
 | 
			
		||||
			},
 | 
			
		||||
			wantPortPid: map[string][]string{
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										106
									
								
								scan/debian.go
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								scan/debian.go
									
									
									
									
									
								
							@@ -40,18 +40,16 @@ func newDebian(c config.ServerInfo) *debian {
 | 
			
		||||
 | 
			
		||||
// Ubuntu, Debian, Raspbian
 | 
			
		||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
 | 
			
		||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err error) {
 | 
			
		||||
	deb = newDebian(c)
 | 
			
		||||
 | 
			
		||||
func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
 | 
			
		||||
	if r := exec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
 | 
			
		||||
		if r.Error != nil {
 | 
			
		||||
			return false, deb, nil
 | 
			
		||||
			return false, nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		if r.ExitStatus == 255 {
 | 
			
		||||
			return false, deb, xerrors.Errorf("Unable to connect via SSH. Scan with -vvv option to print SSH debugging messages and check SSH settings. If you have never SSH to the host to be scanned, SSH to the host before scanning in order to add the HostKey. %s@%s port: %s\n%s", c.User, c.Host, c.Port, r)
 | 
			
		||||
			return false, nil, xerrors.Errorf("Unable to connect via SSH. Scan with -vvv option to print SSH debugging messages and check SSH settings. If you have never SSH to the host to be scanned, SSH to the host before scanning in order to add the HostKey. %s@%s port: %s\n%s", c.User, c.Host, c.Port, r)
 | 
			
		||||
		}
 | 
			
		||||
		util.Log.Debugf("Not Debian like Linux. %s", r)
 | 
			
		||||
		return false, deb, nil
 | 
			
		||||
		return false, nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Raspbian
 | 
			
		||||
@@ -62,8 +60,8 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err
 | 
			
		||||
		//  Raspbian GNU/Linux 7 \n \l
 | 
			
		||||
		result := strings.Fields(r.Stdout)
 | 
			
		||||
		if len(result) > 2 && result[0] == config.Raspbian {
 | 
			
		||||
			distro := strings.ToLower(trim(result[0]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
			deb := newDebian(c)
 | 
			
		||||
			deb.setDistro(strings.ToLower(trim(result[0])), trim(result[2]))
 | 
			
		||||
			return true, deb, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -76,10 +74,10 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err
 | 
			
		||||
		re := regexp.MustCompile(`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
 | 
			
		||||
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
			
		||||
 | 
			
		||||
		deb := newDebian(c)
 | 
			
		||||
		if len(result) == 0 {
 | 
			
		||||
			deb.setDistro("debian/ubuntu", "unknown")
 | 
			
		||||
			util.Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu version. lsb_release -ir: %s", r)
 | 
			
		||||
			util.Log.Warnf("Unknown Debian/Ubuntu version. lsb_release -ir: %s", r)
 | 
			
		||||
		} else {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
@@ -95,6 +93,7 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err
 | 
			
		||||
		//  DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS"
 | 
			
		||||
		re := regexp.MustCompile(`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
 | 
			
		||||
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
			
		||||
		deb := newDebian(c)
 | 
			
		||||
		if len(result) == 0 {
 | 
			
		||||
			util.Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s", r)
 | 
			
		||||
@@ -109,12 +108,13 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err
 | 
			
		||||
	// Debian
 | 
			
		||||
	cmd := "cat /etc/debian_version"
 | 
			
		||||
	if r := exec(c, cmd, noSudo); r.isSuccess() {
 | 
			
		||||
		deb := newDebian(c)
 | 
			
		||||
		deb.setDistro(config.Debian, trim(r.Stdout))
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Not Debian like Linux: %s", c.ServerName)
 | 
			
		||||
	return false, deb, nil
 | 
			
		||||
	return false, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func trim(str string) string {
 | 
			
		||||
@@ -251,15 +251,13 @@ func (o *debian) preCure() error {
 | 
			
		||||
 | 
			
		||||
func (o *debian) postScan() error {
 | 
			
		||||
	if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() {
 | 
			
		||||
		if err := o.dpkgPs(); err != nil {
 | 
			
		||||
		if err := o.pkgPs(o.getOwnerPkgs); err != nil {
 | 
			
		||||
			err = xerrors.Errorf("Failed to dpkg-ps: %w", err)
 | 
			
		||||
			o.log.Warnf("err: %+v", err)
 | 
			
		||||
			o.warns = append(o.warns, err)
 | 
			
		||||
			// Only warning this error
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() {
 | 
			
		||||
		if err := o.checkrestart(); err != nil {
 | 
			
		||||
			err = xerrors.Errorf("Failed to scan need-restarting processes: %w", err)
 | 
			
		||||
			o.log.Warnf("err: %+v", err)
 | 
			
		||||
@@ -1263,85 +1261,7 @@ func (o *debian) parseCheckRestart(stdout string) (models.Packages, []string) {
 | 
			
		||||
	return packs, unknownServices
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) dpkgPs() error {
 | 
			
		||||
	stdout, err := o.ps()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to ps: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	pidNames := o.parsePs(stdout)
 | 
			
		||||
	pidLoadedFiles := map[string][]string{}
 | 
			
		||||
	// for pid, name := range pidNames {
 | 
			
		||||
	for pid := range pidNames {
 | 
			
		||||
		stdout := ""
 | 
			
		||||
		stdout, err = o.lsProcExe(pid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to exec /proc/%s/exe err: %s", pid, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		s, err := o.parseLsProcExe(stdout)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to parse /proc/%s/exe: %s", pid, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pidLoadedFiles[pid] = append(pidLoadedFiles[pid], s)
 | 
			
		||||
 | 
			
		||||
		stdout, err = o.grepProcMap(pid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to exec /proc/%s/maps: %s", pid, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ss := o.parseGrepProcMap(stdout)
 | 
			
		||||
		pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pidListenPorts := map[string][]models.PortStat{}
 | 
			
		||||
	stdout, err = o.lsOfListen()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to ls of: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	portPids := o.parseLsOf(stdout)
 | 
			
		||||
	for ipPort, pids := range portPids {
 | 
			
		||||
		for _, pid := range pids {
 | 
			
		||||
			portStat, err := models.NewPortStat(ipPort)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				o.log.Warnf("Failed to parse ip:port: %s, err: %+v", ipPort, err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			pidListenPorts[pid] = append(pidListenPorts[pid], *portStat)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for pid, loadedFiles := range pidLoadedFiles {
 | 
			
		||||
		o.log.Debugf("dpkg -S %#v", loadedFiles)
 | 
			
		||||
		pkgNames, err := o.getPkgName(loadedFiles)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to get package name by file path: %s, err: %s", pkgNames, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		procName := ""
 | 
			
		||||
		if _, ok := pidNames[pid]; ok {
 | 
			
		||||
			procName = pidNames[pid]
 | 
			
		||||
		}
 | 
			
		||||
		proc := models.AffectedProcess{
 | 
			
		||||
			PID:             pid,
 | 
			
		||||
			Name:            procName,
 | 
			
		||||
			ListenPortStats: pidListenPorts[pid],
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, n := range pkgNames {
 | 
			
		||||
			p, ok := o.Packages[n]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return xerrors.Errorf("pkg not found %s", n)
 | 
			
		||||
			}
 | 
			
		||||
			p.AffectedProcs = append(p.AffectedProcs, proc)
 | 
			
		||||
			o.Packages[p.Name] = p
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) getPkgName(paths []string) (pkgNames []string, err error) {
 | 
			
		||||
func (o *debian) getOwnerPkgs(paths []string) (pkgNames []string, err error) {
 | 
			
		||||
	cmd := "dpkg -S " + strings.Join(paths, " ")
 | 
			
		||||
	r := o.exec(util.PrependProxyEnv(cmd), noSudo)
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,15 +31,14 @@ func newBsd(c config.ServerInfo) *bsd {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb
 | 
			
		||||
func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
 | 
			
		||||
	bsd = newBsd(c)
 | 
			
		||||
 | 
			
		||||
func detectFreebsd(c config.ServerInfo) (bool, osTypeInterface) {
 | 
			
		||||
	// Prevent from adding `set -o pipefail` option
 | 
			
		||||
	c.Distro = config.Distro{Family: config.FreeBSD}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "uname", noSudo); r.isSuccess() {
 | 
			
		||||
		if strings.Contains(strings.ToLower(r.Stdout), config.FreeBSD) == true {
 | 
			
		||||
			if b := exec(c, "freebsd-version", noSudo); b.isSuccess() {
 | 
			
		||||
				bsd := newBsd(c)
 | 
			
		||||
				rel := strings.TrimSpace(b.Stdout)
 | 
			
		||||
				bsd.setDistro(config.FreeBSD, rel)
 | 
			
		||||
				return true, bsd
 | 
			
		||||
@@ -47,7 +46,7 @@ func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName)
 | 
			
		||||
	return false, bsd
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkScanMode() error {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,12 @@ type pseudo struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectPseudo(c config.ServerInfo) (itsMe bool, pseudo osTypeInterface, err error) {
 | 
			
		||||
	p := newPseudo(c)
 | 
			
		||||
	p.setDistro(config.ServerTypePseudo, "")
 | 
			
		||||
	return c.Type == config.ServerTypePseudo, p, nil
 | 
			
		||||
	if c.Type == config.ServerTypePseudo {
 | 
			
		||||
		p := newPseudo(c)
 | 
			
		||||
		p.setDistro(config.ServerTypePseudo, "")
 | 
			
		||||
		return true, p, nil
 | 
			
		||||
	}
 | 
			
		||||
	return false, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newPseudo(c config.ServerInfo) *pseudo {
 | 
			
		||||
 
 | 
			
		||||
@@ -173,7 +173,7 @@ func (o *redhatBase) preCure() error {
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) postScan() error {
 | 
			
		||||
	if o.isExecYumPS() {
 | 
			
		||||
		if err := o.yumPs(); err != nil {
 | 
			
		||||
		if err := o.pkgPs(o.getOwnerPkgs); err != nil {
 | 
			
		||||
			err = xerrors.Errorf("Failed to execute yum-ps: %w", err)
 | 
			
		||||
			o.log.Warnf("err: %+v", err)
 | 
			
		||||
			o.warns = append(o.warns, err)
 | 
			
		||||
@@ -278,46 +278,43 @@ func (o *redhatBase) parseInstalledPackages(stdout string) (models.Packages, mod
 | 
			
		||||
	// openssl 0 1.0.1e	30.el6.11 x86_64
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
 | 
			
		||||
			pack, err := o.parseInstalledPackagesLine(line)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// `Kernel` and `kernel-devel` package may be installed multiple versions.
 | 
			
		||||
			// From the viewpoint of vulnerability detection,
 | 
			
		||||
			// pay attention only to the running kernel
 | 
			
		||||
			isKernel, running := isRunningKernel(pack, o.Distro.Family, o.Kernel)
 | 
			
		||||
			if isKernel {
 | 
			
		||||
				if o.Kernel.Release == "" {
 | 
			
		||||
					// When the running kernel release is unknown,
 | 
			
		||||
					// use the latest release among the installed release
 | 
			
		||||
					kernelRelease := ver.NewVersion(fmt.Sprintf("%s-%s", pack.Version, pack.Release))
 | 
			
		||||
					if kernelRelease.LessThan(latestKernelRelease) {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
					latestKernelRelease = kernelRelease
 | 
			
		||||
				} else if !running {
 | 
			
		||||
					o.log.Debugf("Not a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
 | 
			
		||||
					continue
 | 
			
		||||
				} else {
 | 
			
		||||
					o.log.Debugf("Found a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			installed[pack.Name] = pack
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); trimmed == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pack, err := o.parseInstalledPackagesLine(line)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// `Kernel` and `kernel-devel` package may be installed multiple versions.
 | 
			
		||||
		// From the viewpoint of vulnerability detection,
 | 
			
		||||
		// pay attention only to the running kernel
 | 
			
		||||
		isKernel, running := isRunningKernel(*pack, o.Distro.Family, o.Kernel)
 | 
			
		||||
		if isKernel {
 | 
			
		||||
			if o.Kernel.Release == "" {
 | 
			
		||||
				// When the running kernel release is unknown,
 | 
			
		||||
				// use the latest release among the installed release
 | 
			
		||||
				kernelRelease := ver.NewVersion(fmt.Sprintf("%s-%s", pack.Version, pack.Release))
 | 
			
		||||
				if kernelRelease.LessThan(latestKernelRelease) {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				latestKernelRelease = kernelRelease
 | 
			
		||||
			} else if !running {
 | 
			
		||||
				o.log.Debugf("Not a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
 | 
			
		||||
				continue
 | 
			
		||||
			} else {
 | 
			
		||||
				o.log.Debugf("Found a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		installed[pack.Name] = *pack
 | 
			
		||||
	}
 | 
			
		||||
	return installed, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) parseInstalledPackagesLine(line string) (models.Package, error) {
 | 
			
		||||
func (o *redhatBase) parseInstalledPackagesLine(line string) (*models.Package, error) {
 | 
			
		||||
	fields := strings.Fields(line)
 | 
			
		||||
	if len(fields) != 5 {
 | 
			
		||||
		return models.Package{},
 | 
			
		||||
			xerrors.Errorf("Failed to parse package line: %s", line)
 | 
			
		||||
	}
 | 
			
		||||
	if strings.HasSuffix(line, "Permission denied") {
 | 
			
		||||
		return models.Package{},
 | 
			
		||||
		return nil,
 | 
			
		||||
			xerrors.Errorf("Failed to parse package line: %s", line)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -329,7 +326,7 @@ func (o *redhatBase) parseInstalledPackagesLine(line string) (models.Package, er
 | 
			
		||||
		ver = fmt.Sprintf("%s:%s", epoch, fields[2])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return models.Package{
 | 
			
		||||
	return &models.Package{
 | 
			
		||||
		Name:    fields[0],
 | 
			
		||||
		Version: ver,
 | 
			
		||||
		Release: fields[3],
 | 
			
		||||
@@ -337,6 +334,20 @@ func (o *redhatBase) parseInstalledPackagesLine(line string) (models.Package, er
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) parseRpmQfLine(line string) (pkg *models.Package, ignored bool, err error) {
 | 
			
		||||
	for _, suffix := range []string{
 | 
			
		||||
		"Permission denied",
 | 
			
		||||
		"is not owned by any package",
 | 
			
		||||
		"No such file or directory",
 | 
			
		||||
	} {
 | 
			
		||||
		if strings.HasSuffix(line, suffix) {
 | 
			
		||||
			return nil, true, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	pkg, err = o.parseInstalledPackagesLine(line)
 | 
			
		||||
	return pkg, false, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) yumMakeCache() error {
 | 
			
		||||
	cmd := `yum makecache --assumeyes`
 | 
			
		||||
	r := o.exec(util.PrependProxyEnv(cmd), o.sudo.yumMakeCache())
 | 
			
		||||
@@ -458,89 +469,6 @@ func (o *redhatBase) isExecNeedsRestarting() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) yumPs() error {
 | 
			
		||||
	stdout, err := o.ps()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to yum ps: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pidNames := o.parsePs(stdout)
 | 
			
		||||
	pidLoadedFiles := map[string][]string{}
 | 
			
		||||
	for pid := range pidNames {
 | 
			
		||||
		stdout := ""
 | 
			
		||||
		stdout, err = o.lsProcExe(pid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to exec /proc/%s/exe err: %s", pid, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		s, err := o.parseLsProcExe(stdout)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to parse /proc/%s/exe: %s", pid, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pidLoadedFiles[pid] = append(pidLoadedFiles[pid], s)
 | 
			
		||||
 | 
			
		||||
		stdout, err = o.grepProcMap(pid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to exec /proc/%s/maps: %s", pid, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ss := o.parseGrepProcMap(stdout)
 | 
			
		||||
		pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pidListenPorts := map[string][]models.PortStat{}
 | 
			
		||||
	stdout, err = o.lsOfListen()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to lsof: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	portPids := o.parseLsOf(stdout)
 | 
			
		||||
	for ipPort, pids := range portPids {
 | 
			
		||||
		for _, pid := range pids {
 | 
			
		||||
			portStat, err := models.NewPortStat(ipPort)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				o.log.Warnf("Failed to parse ip:port: %s, err: %+v", ipPort, err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			pidListenPorts[pid] = append(pidListenPorts[pid], *portStat)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for pid, loadedFiles := range pidLoadedFiles {
 | 
			
		||||
		o.log.Debugf("rpm -qf: %#v", loadedFiles)
 | 
			
		||||
		pkgNames, err := o.getPkgName(loadedFiles)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to get package name by file path: %s, err: %s", pkgNames, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		uniq := map[string]struct{}{}
 | 
			
		||||
		for _, name := range pkgNames {
 | 
			
		||||
			uniq[name] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		procName := ""
 | 
			
		||||
		if _, ok := pidNames[pid]; ok {
 | 
			
		||||
			procName = pidNames[pid]
 | 
			
		||||
		}
 | 
			
		||||
		proc := models.AffectedProcess{
 | 
			
		||||
			PID:             pid,
 | 
			
		||||
			Name:            procName,
 | 
			
		||||
			ListenPortStats: pidListenPorts[pid],
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for fqpn := range uniq {
 | 
			
		||||
			p, err := o.Packages.FindByFQPN(fqpn)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			p.AffectedProcs = append(p.AffectedProcs, proc)
 | 
			
		||||
			o.Packages[p.Name] = *p
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) needsRestarting() error {
 | 
			
		||||
	initName, err := o.detectInitSystem()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -555,6 +483,7 @@ func (o *redhatBase) needsRestarting() error {
 | 
			
		||||
	}
 | 
			
		||||
	procs := o.parseNeedsRestarting(r.Stdout)
 | 
			
		||||
	for _, proc := range procs {
 | 
			
		||||
		//TODO refactor
 | 
			
		||||
		fqpn, err := o.procPathToFQPN(proc.Path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Warnf("Failed to detect a package name of need restarting process from the command path: %s, %s",
 | 
			
		||||
@@ -604,7 +533,7 @@ func (o *redhatBase) parseNeedsRestarting(stdout string) (procs []models.NeedRes
 | 
			
		||||
			cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 which %s", path)
 | 
			
		||||
			r := o.exec(cmd, sudo)
 | 
			
		||||
			if !r.isSuccess() {
 | 
			
		||||
				o.log.Warnf("Failed to exec which %s: %s", path, r)
 | 
			
		||||
				o.log.Debugf("Failed to exec which %s: %s", path, r)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			path = strings.TrimSpace(r.Stdout)
 | 
			
		||||
@@ -619,11 +548,12 @@ func (o *redhatBase) parseNeedsRestarting(stdout string) (procs []models.NeedRes
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO refactor
 | 
			
		||||
// procPathToFQPN returns Fully-Qualified-Package-Name from the command
 | 
			
		||||
func (o *redhatBase) procPathToFQPN(execCommand string) (string, error) {
 | 
			
		||||
	execCommand = strings.Replace(execCommand, "\x00", " ", -1) // for CentOS6.9
 | 
			
		||||
	path := strings.Fields(execCommand)[0]
 | 
			
		||||
	cmd := `LANGUAGE=en_US.UTF-8 rpm -qf --queryformat "%{NAME}-%{EPOCH}:%{VERSION}-%{RELEASE}.%{ARCH}\n" ` + path
 | 
			
		||||
	cmd := `LANGUAGE=en_US.UTF-8 rpm -qf --queryformat "%{NAME}-%{EPOCH}:%{VERSION}-%{RELEASE}\n" ` + path
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return "", xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
@@ -632,24 +562,32 @@ func (o *redhatBase) procPathToFQPN(execCommand string) (string, error) {
 | 
			
		||||
	return strings.Replace(fqpn, "-(none):", "-", -1), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) getPkgName(paths []string) (pkgNames []string, err error) {
 | 
			
		||||
func (o *redhatBase) getOwnerPkgs(paths []string) (names []string, _ error) {
 | 
			
		||||
	cmd := o.rpmQf() + strings.Join(paths, " ")
 | 
			
		||||
	r := o.exec(util.PrependProxyEnv(cmd), noSudo)
 | 
			
		||||
	if !r.isSuccess(0, 2, 4, 8) {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to rpm -qf: %s, cmd: %s", r, cmd)
 | 
			
		||||
	}
 | 
			
		||||
	// rpm exit code means `the number` of errors.
 | 
			
		||||
	// https://listman.redhat.com/archives/rpm-list/2005-July/msg00071.html
 | 
			
		||||
	// If we treat non-zero exit codes of `rpm` as errors,
 | 
			
		||||
	// we will be missing a partial package list we can get.
 | 
			
		||||
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(r.Stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		pack, err := o.parseInstalledPackagesLine(line)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to parse rpm -qf line: %s, r: %s. continue", line, r)
 | 
			
		||||
		pack, ignored, err := o.parseRpmQfLine(line)
 | 
			
		||||
		if ignored {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pkgNames = append(pkgNames, pack.FQPN())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to parse rpm -qf line: %s, err: %+v", line, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if _, ok := o.Packages[pack.Name]; !ok {
 | 
			
		||||
			o.log.Debugf("Failed to rpm -qf. pkg: %+v not found, line: %s", pack, line)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		names = append(names, pack.Name)
 | 
			
		||||
	}
 | 
			
		||||
	return pkgNames, nil
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) rpmQa() string {
 | 
			
		||||
 
 | 
			
		||||
@@ -163,11 +163,6 @@ func TestParseInstalledPackagesLine(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"error: file /run/log/journal/346a500b7fb944199748954baca56086/system.journal: Permission denied",
 | 
			
		||||
			models.Package{},
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range packagetests {
 | 
			
		||||
@@ -438,3 +433,86 @@ Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled`,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_redhatBase_parseRpmQfLine(t *testing.T) {
 | 
			
		||||
	type fields struct {
 | 
			
		||||
		base base
 | 
			
		||||
		sudo rootPriv
 | 
			
		||||
	}
 | 
			
		||||
	type args struct {
 | 
			
		||||
		line string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		fields      fields
 | 
			
		||||
		args        args
 | 
			
		||||
		wantPkg     *models.Package
 | 
			
		||||
		wantIgnored bool
 | 
			
		||||
		wantErr     bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:        "permission denied will be ignored",
 | 
			
		||||
			fields:      fields{base: base{}},
 | 
			
		||||
			args:        args{line: "/tmp/hogehoge Permission denied"},
 | 
			
		||||
			wantPkg:     nil,
 | 
			
		||||
			wantIgnored: true,
 | 
			
		||||
			wantErr:     false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:        "is not owned by any package",
 | 
			
		||||
			fields:      fields{base: base{}},
 | 
			
		||||
			args:        args{line: "/tmp/hogehoge is not owned by any package"},
 | 
			
		||||
			wantPkg:     nil,
 | 
			
		||||
			wantIgnored: true,
 | 
			
		||||
			wantErr:     false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:        "No such file or directory will be ignored",
 | 
			
		||||
			fields:      fields{base: base{}},
 | 
			
		||||
			args:        args{line: "/tmp/hogehoge No such file or directory"},
 | 
			
		||||
			wantPkg:     nil,
 | 
			
		||||
			wantIgnored: true,
 | 
			
		||||
			wantErr:     false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "valid line",
 | 
			
		||||
			fields: fields{base: base{}},
 | 
			
		||||
			args: args{line: "Percona-Server-shared-56	1	5.6.19	rel67.0.el6 x86_64"},
 | 
			
		||||
			wantPkg: &models.Package{
 | 
			
		||||
				Name:    "Percona-Server-shared-56",
 | 
			
		||||
				Version: "1:5.6.19",
 | 
			
		||||
				Release: "rel67.0.el6",
 | 
			
		||||
				Arch:    "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			wantIgnored: false,
 | 
			
		||||
			wantErr:     false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:        "err",
 | 
			
		||||
			fields:      fields{base: base{}},
 | 
			
		||||
			args:        args{line: "/tmp/hogehoge something unknown format"},
 | 
			
		||||
			wantPkg:     nil,
 | 
			
		||||
			wantIgnored: false,
 | 
			
		||||
			wantErr:     true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &redhatBase{
 | 
			
		||||
				base: tt.fields.base,
 | 
			
		||||
				sudo: tt.fields.sudo,
 | 
			
		||||
			}
 | 
			
		||||
			gotPkg, gotIgnored, err := o.parseRpmQfLine(tt.args.line)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("redhatBase.parseRpmQfLine() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(gotPkg, tt.wantPkg) {
 | 
			
		||||
				t.Errorf("redhatBase.parseRpmQfLine() gotPkg = %v, want %v", gotPkg, tt.wantPkg)
 | 
			
		||||
			}
 | 
			
		||||
			if gotIgnored != tt.wantIgnored {
 | 
			
		||||
				t.Errorf("redhatBase.parseRpmQfLine() gotIgnored = %v, want %v", gotIgnored, tt.wantIgnored)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -36,10 +36,10 @@ func newSUSE(c config.ServerInfo) *suse {
 | 
			
		||||
 | 
			
		||||
// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/suse.rb
 | 
			
		||||
func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
 | 
			
		||||
	s := newSUSE(c)
 | 
			
		||||
	if r := exec(c, "ls /etc/os-release", noSudo); r.isSuccess() {
 | 
			
		||||
		if r := exec(c, "zypper -V", noSudo); r.isSuccess() {
 | 
			
		||||
			if r := exec(c, "cat /etc/os-release", noSudo); r.isSuccess() {
 | 
			
		||||
				s := newSUSE(c)
 | 
			
		||||
				name, ver := s.parseOSRelease(r.Stdout)
 | 
			
		||||
				s.setDistro(name, ver)
 | 
			
		||||
				return true, s
 | 
			
		||||
@@ -52,6 +52,7 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
 | 
			
		||||
				result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
				if len(result) == 2 {
 | 
			
		||||
					//TODO check opensuse or opensuse.leap
 | 
			
		||||
					s := newSUSE(c)
 | 
			
		||||
					s.setDistro(config.OpenSUSE, result[1])
 | 
			
		||||
					return true, s
 | 
			
		||||
				}
 | 
			
		||||
@@ -63,18 +64,19 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
 | 
			
		||||
					re = regexp.MustCompile(`PATCHLEVEL = (\d+)`)
 | 
			
		||||
					result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
					if len(result) == 2 {
 | 
			
		||||
						s := newSUSE(c)
 | 
			
		||||
						s.setDistro(config.SUSEEnterpriseServer,
 | 
			
		||||
							fmt.Sprintf("%s.%s", version, result[1]))
 | 
			
		||||
						return true, s
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				util.Log.Warnf("Failed to parse SUSE Linux version: %s", r)
 | 
			
		||||
				return true, s
 | 
			
		||||
				return true, newSUSE(c)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("Not SUSE Linux. servername: %s", c.ServerName)
 | 
			
		||||
	return false, s
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *suse) parseOSRelease(content string) (name string, ver string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,8 @@ func (*ReportCmd) Usage() string {
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-diff]
 | 
			
		||||
		[-diff-minus]
 | 
			
		||||
		[-diff-plus]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-ignore-github-dismissed]
 | 
			
		||||
@@ -95,8 +97,14 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.Float64Var(&c.Conf.CvssScoreOver, "cvss-over", 0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.DiffMinus, "diff-minus", false,
 | 
			
		||||
		"Minus Difference between previous result and current result")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.DiffPlus, "diff-plus", false,
 | 
			
		||||
		"Plus Difference between previous result and current result")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.Diff, "diff", false,
 | 
			
		||||
		"Difference between previous result and current result ")
 | 
			
		||||
		"Plus & Minus Difference between previous result and current result")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
@@ -151,9 +159,14 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
 | 
			
		||||
	}
 | 
			
		||||
	c.Conf.HTTP.Init(p.httpConf)
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
		c.Conf.DiffPlus = true
 | 
			
		||||
		c.Conf.DiffMinus = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var dir string
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
	if c.Conf.DiffPlus || c.Conf.DiffMinus {
 | 
			
		||||
		dir, err = report.JSONDir([]string{})
 | 
			
		||||
	} else {
 | 
			
		||||
		dir, err = report.JSONDir(f.Args())
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,8 @@ func (*TuiCmd) Usage() string {
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-diff]
 | 
			
		||||
		[-diff-minus]
 | 
			
		||||
		[-diff-plus]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
@@ -75,7 +77,13 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.Diff, "diff", false,
 | 
			
		||||
		"Difference between previous result and current result ")
 | 
			
		||||
		"Plus Difference between previous result and current result")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.DiffPlus, "diff-plus", false,
 | 
			
		||||
		"Plus Difference between previous result and current result")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.DiffMinus, "diff-minus", false,
 | 
			
		||||
		"Minus Difference between previous result and current result")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&c.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false,
 | 
			
		||||
@@ -100,9 +108,13 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = "en"
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
		c.Conf.DiffPlus = true
 | 
			
		||||
		c.Conf.DiffMinus = true
 | 
			
		||||
	}
 | 
			
		||||
	var dir string
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
	if c.Conf.DiffPlus || c.Conf.DiffMinus {
 | 
			
		||||
		dir, err = report.JSONDir([]string{})
 | 
			
		||||
	} else {
 | 
			
		||||
		dir, err = report.JSONDir(f.Args())
 | 
			
		||||
 
 | 
			
		||||
@@ -86,8 +86,9 @@ func NewCustomLogger(server config.ServerInfo) *logrus.Entry {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fields := logrus.Fields{"prefix": whereami}
 | 
			
		||||
	return log.WithFields(fields)
 | 
			
		||||
	entry := log.WithFields(logrus.Fields{"prefix": whereami})
 | 
			
		||||
	entry.Infof("vuls-%s-%s", config.Version, config.Revision)
 | 
			
		||||
	return entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDefaultLogDir returns default log directory
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								util/util.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								util/util.go
									
									
									
									
									
								
							@@ -3,6 +3,7 @@ package util
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
@@ -178,3 +179,19 @@ func Major(version string) string {
 | 
			
		||||
	}
 | 
			
		||||
	return ver[0:strings.Index(ver, ".")]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetHTTPClient return http.Client
 | 
			
		||||
func GetHTTPClient(proxy string) (*http.Client, error) {
 | 
			
		||||
	if proxy != "" {
 | 
			
		||||
		proxyURL, err := url.Parse(proxy)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return &http.Client{
 | 
			
		||||
			Transport: &http.Transport{
 | 
			
		||||
				Proxy: http.ProxyURL(proxyURL),
 | 
			
		||||
			},
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
	return &http.Client{}, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package wordpress
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
@@ -8,6 +9,7 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/errof"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
@@ -30,11 +32,10 @@ type WpCveInfos struct {
 | 
			
		||||
 | 
			
		||||
//WpCveInfo is for wpscan json
 | 
			
		||||
type WpCveInfo struct {
 | 
			
		||||
	ID        string `json:"id"`
 | 
			
		||||
	Title     string `json:"title"`
 | 
			
		||||
	CreatedAt string `json:"created_at"`
 | 
			
		||||
	UpdatedAt string `json:"updated_at"`
 | 
			
		||||
	// PublishedDate string     `json:"published_date"`
 | 
			
		||||
	ID         string     `json:"id"`
 | 
			
		||||
	Title      string     `json:"title"`
 | 
			
		||||
	CreatedAt  time.Time  `json:"created_at"`
 | 
			
		||||
	UpdatedAt  time.Time  `json:"updated_at"`
 | 
			
		||||
	VulnType   string     `json:"vuln_type"`
 | 
			
		||||
	References References `json:"references"`
 | 
			
		||||
	FixedIn    string     `json:"fixed_in"`
 | 
			
		||||
@@ -57,7 +58,8 @@ func DetectWordPressCves(r *models.ScanResult, cnf *c.WpScanConf) (int, error) {
 | 
			
		||||
	// Core
 | 
			
		||||
	ver := strings.Replace(r.WordPressPackages.CoreVersion(), ".", "", -1)
 | 
			
		||||
	if ver == "" {
 | 
			
		||||
		return 0, xerrors.New("Failed to get WordPress core version")
 | 
			
		||||
		return 0, errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to get WordPress core version."))
 | 
			
		||||
	}
 | 
			
		||||
	url := fmt.Sprintf("https://wpscan.com/api/v3/wordpresses/%s", ver)
 | 
			
		||||
	wpVinfos, err := wpscan(url, ver, cnf.Token)
 | 
			
		||||
@@ -112,8 +114,7 @@ func DetectWordPressCves(r *models.ScanResult, cnf *c.WpScanConf) (int, error) {
 | 
			
		||||
func wpscan(url, name, token string) (vinfos []models.VulnInfo, err error) {
 | 
			
		||||
	body, err := httpRequest(url, token)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to access to wpscan.comm. body: %s, err: %s", string(body), err))
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if body == "" {
 | 
			
		||||
		util.Log.Debugf("wpscan.com response body is empty. URL: %s", url)
 | 
			
		||||
@@ -196,10 +197,12 @@ func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnI
 | 
			
		||||
				CveID: cveID,
 | 
			
		||||
				CveContents: models.NewCveContents(
 | 
			
		||||
					models.CveContent{
 | 
			
		||||
						Type:       models.WpScan,
 | 
			
		||||
						CveID:      cveID,
 | 
			
		||||
						Title:      vulnerability.Title,
 | 
			
		||||
						References: refs,
 | 
			
		||||
						Type:         models.WpScan,
 | 
			
		||||
						CveID:        cveID,
 | 
			
		||||
						Title:        vulnerability.Title,
 | 
			
		||||
						References:   refs,
 | 
			
		||||
						Published:    vulnerability.CreatedAt,
 | 
			
		||||
						LastModified: vulnerability.UpdatedAt,
 | 
			
		||||
					},
 | 
			
		||||
				),
 | 
			
		||||
				VulnType: vulnerability.VulnType,
 | 
			
		||||
@@ -217,21 +220,27 @@ func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnI
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpRequest(url, token string) (string, error) {
 | 
			
		||||
	retry := 1
 | 
			
		||||
	util.Log.Debugf("%s", url)
 | 
			
		||||
	req, err := http.NewRequest("GET", url, nil)
 | 
			
		||||
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Set("Authorization", fmt.Sprintf("Token token=%s", token))
 | 
			
		||||
	client, err := util.GetHTTPClient(config.Conf.HTTPProxy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Set("Authorization", fmt.Sprintf("Token token=%s", token))
 | 
			
		||||
loop:
 | 
			
		||||
	resp, err := new(http.Client).Do(req)
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
		return "", errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
 | 
			
		||||
	}
 | 
			
		||||
	body, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
		return "", errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	if resp.StatusCode == 200 {
 | 
			
		||||
@@ -239,14 +248,13 @@ loop:
 | 
			
		||||
	} else if resp.StatusCode == 404 {
 | 
			
		||||
		// This package is not in wpscan
 | 
			
		||||
		return "", nil
 | 
			
		||||
	} else if resp.StatusCode == 429 && retry <= 3 {
 | 
			
		||||
		// 429 Too Many Requests
 | 
			
		||||
		util.Log.Debugf("sleep %d min(s): %s", retry, resp.Status)
 | 
			
		||||
		time.Sleep(time.Duration(retry) * time.Minute)
 | 
			
		||||
		retry++
 | 
			
		||||
		goto loop
 | 
			
		||||
	} else if resp.StatusCode == 429 {
 | 
			
		||||
		return "", errof.New(errof.ErrWpScanAPILimitExceeded,
 | 
			
		||||
			fmt.Sprintf("wpscan.com API limit exceeded: %+v", resp.Status))
 | 
			
		||||
	} else {
 | 
			
		||||
		util.Log.Warnf("wpscan.com unknown status code: %+v", resp.Status)
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func removeInactives(pkgs models.WordPressPackages) (removed models.WordPressPackages) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user