Compare commits
	
		
			3 Commits
		
	
	
		
			v0.24.0
			...
			v0.23.0-be
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d8495c0da6 | ||
| 
						 | 
					a6fba3ed55 | ||
| 
						 | 
					a9c82b43e5 | 
							
								
								
									
										20
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
								
							@@ -12,9 +12,6 @@ jobs:
 | 
			
		||||
      -
 | 
			
		||||
        name: Checkout
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
      - 
 | 
			
		||||
        name: install package for cross compile
 | 
			
		||||
        run: sudo apt update && sudo apt install -y gcc-aarch64-linux-gnu
 | 
			
		||||
      -
 | 
			
		||||
        name: Unshallow
 | 
			
		||||
        run: git fetch --prune --unshallow
 | 
			
		||||
@@ -22,13 +19,16 @@ jobs:
 | 
			
		||||
        name: Set up Go
 | 
			
		||||
        uses: actions/setup-go@v3
 | 
			
		||||
        with:
 | 
			
		||||
          go-version: 1.18
 | 
			
		||||
          go-version-file: go.mod
 | 
			
		||||
      -
 | 
			
		||||
        name: Run GoReleaser
 | 
			
		||||
        uses: goreleaser/goreleaser-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          version: latest
 | 
			
		||||
          args: release --rm-dist
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
        run: |
 | 
			
		||||
          docker run --rm \
 | 
			
		||||
            -e CGO_ENABLED=1 \
 | 
			
		||||
            -e GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
 | 
			
		||||
            -v /var/run/docker.sock:/var/run/docker.sock \
 | 
			
		||||
            -v `pwd`:/go/src/github.com/future-architect/vuls \
 | 
			
		||||
            -w /go/src/github.com/future-architect/vuls \
 | 
			
		||||
            ghcr.io/goreleaser/goreleaser-cross:v1.20 \
 | 
			
		||||
            release --clean
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ release:
 | 
			
		||||
    owner: future-architect
 | 
			
		||||
    name: vuls
 | 
			
		||||
builds:
 | 
			
		||||
- id: vuls-amd64
 | 
			
		||||
- id: vuls-linux-amd64
 | 
			
		||||
  goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  goarch:
 | 
			
		||||
@@ -21,7 +21,7 @@ builds:
 | 
			
		||||
  - -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
 | 
			
		||||
  binary: vuls
 | 
			
		||||
 | 
			
		||||
- id: vuls-arm64
 | 
			
		||||
- id: vuls-linux-arm64
 | 
			
		||||
  goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  goarch:
 | 
			
		||||
@@ -36,11 +36,42 @@ builds:
 | 
			
		||||
  - -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
 | 
			
		||||
  binary: vuls
 | 
			
		||||
 | 
			
		||||
- id: vuls-windows-amd64
 | 
			
		||||
  goos:
 | 
			
		||||
  - windows
 | 
			
		||||
  goarch:
 | 
			
		||||
  - amd64
 | 
			
		||||
  env:
 | 
			
		||||
  - CGO_ENABLED=1
 | 
			
		||||
  - CC=x86_64-w64-mingw32-gcc
 | 
			
		||||
  main: ./cmd/vuls/main.go
 | 
			
		||||
  flags:
 | 
			
		||||
  - -a
 | 
			
		||||
  ldflags: 
 | 
			
		||||
  - -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
 | 
			
		||||
  binary: vuls
 | 
			
		||||
 | 
			
		||||
- id: vuls-windows-arm64
 | 
			
		||||
  goos:
 | 
			
		||||
  - windows
 | 
			
		||||
  goarch:
 | 
			
		||||
  - arm64
 | 
			
		||||
  env:
 | 
			
		||||
  - CGO_ENABLED=1
 | 
			
		||||
  - CC=/llvm-mingw/bin/aarch64-w64-mingw32-gcc
 | 
			
		||||
  main: ./cmd/vuls/main.go
 | 
			
		||||
  flags:
 | 
			
		||||
  - -a
 | 
			
		||||
  ldflags: 
 | 
			
		||||
  - -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
 | 
			
		||||
  binary: vuls
 | 
			
		||||
 | 
			
		||||
- id: vuls-scanner
 | 
			
		||||
  env:
 | 
			
		||||
  - CGO_ENABLED=0
 | 
			
		||||
  goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  - windows
 | 
			
		||||
  goarch:
 | 
			
		||||
  - 386
 | 
			
		||||
  - amd64
 | 
			
		||||
@@ -60,6 +91,7 @@ builds:
 | 
			
		||||
  - CGO_ENABLED=0
 | 
			
		||||
  goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  - windows
 | 
			
		||||
  goarch:
 | 
			
		||||
  - 386
 | 
			
		||||
  - amd64
 | 
			
		||||
@@ -75,6 +107,7 @@ builds:
 | 
			
		||||
  - CGO_ENABLED=0
 | 
			
		||||
  goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  - windows
 | 
			
		||||
  goarch:
 | 
			
		||||
  - 386
 | 
			
		||||
  - amd64
 | 
			
		||||
@@ -92,8 +125,10 @@ archives:
 | 
			
		||||
- id: vuls
 | 
			
		||||
  name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
 | 
			
		||||
  builds:
 | 
			
		||||
  - vuls-amd64
 | 
			
		||||
  - vuls-arm64
 | 
			
		||||
  - vuls-linux-amd64
 | 
			
		||||
  - vuls-linux-arm64
 | 
			
		||||
  - vuls-windows-amd64
 | 
			
		||||
  - vuls-windows-arm64
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
 
 | 
			
		||||
@@ -48,10 +48,11 @@ Vuls is a tool created to solve the problems listed above. It has the following
 | 
			
		||||
 | 
			
		||||
### Scan for any vulnerabilities in Linux/FreeBSD Server
 | 
			
		||||
 | 
			
		||||
[Supports major Linux/FreeBSD](https://vuls.io/docs/en/supported-os.html)
 | 
			
		||||
[Supports major Linux/FreeBSD/Windows](https://vuls.io/docs/en/supported-os.html)
 | 
			
		||||
 | 
			
		||||
- Alpine, Amazon Linux, CentOS, AlmaLinux, Rocky Linux, Debian, Oracle Linux, Raspbian, RHEL, openSUSE, openSUSE Leap, SUSE Enterprise Linux, Fedora, and Ubuntu
 | 
			
		||||
- FreeBSD
 | 
			
		||||
- Windows
 | 
			
		||||
- Cloud, on-premise, Running Docker Container
 | 
			
		||||
 | 
			
		||||
### High-quality scan
 | 
			
		||||
@@ -72,6 +73,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
 | 
			
		||||
  - [Red Hat Security Advisories](https://access.redhat.com/security/security-updates/)
 | 
			
		||||
  - [Debian Security Bug Tracker](https://security-tracker.debian.org/tracker/)
 | 
			
		||||
  - [Ubuntu CVE Tracker](https://people.canonical.com/~ubuntu-security/cve/)
 | 
			
		||||
  - [Microsoft CVRF](https://api.msrc.microsoft.com/cvrf/v2.0/swagger/index)
 | 
			
		||||
 | 
			
		||||
- Commands(yum, zypper, pkg-audit)
 | 
			
		||||
  - RHSA / ALAS / ELSA / FreeBSD-SA
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@@ -7,9 +9,10 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Version of Vuls
 | 
			
		||||
@@ -117,6 +120,9 @@ func (c Config) ValidateOnScan() bool {
 | 
			
		||||
		if es := server.PortScan.Validate(); 0 < len(es) {
 | 
			
		||||
			errs = append(errs, es...)
 | 
			
		||||
		}
 | 
			
		||||
		if es := server.Windows.Validate(); 0 < len(es) {
 | 
			
		||||
			errs = append(errs, es...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
@@ -245,6 +251,7 @@ type ServerInfo struct {
 | 
			
		||||
	IgnoredJSONKeys    []string                    `toml:"ignoredJSONKeys,omitempty" json:"ignoredJSONKeys,omitempty"`
 | 
			
		||||
	WordPress          *WordPressConf              `toml:"wordpress,omitempty" json:"wordpress,omitempty"`
 | 
			
		||||
	PortScan           *PortScanConf               `toml:"portscan,omitempty" json:"portscan,omitempty"`
 | 
			
		||||
	Windows            *WindowsConf                `toml:"windows,omitempty" json:"windows,omitempty"`
 | 
			
		||||
 | 
			
		||||
	IPv4Addrs      []string          `toml:"-" json:"ipv4Addrs,omitempty"`
 | 
			
		||||
	IPv6Addrs      []string          `toml:"-" json:"ipv6Addrs,omitempty"`
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										350
									
								
								config/config_windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								config/config_windows.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,350 @@
 | 
			
		||||
//go:build windows
 | 
			
		||||
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Version of Vuls
 | 
			
		||||
var Version = "`make build` or `make install` will show the version"
 | 
			
		||||
 | 
			
		||||
// Revision of Git
 | 
			
		||||
var Revision string
 | 
			
		||||
 | 
			
		||||
// Conf has Configuration
 | 
			
		||||
var Conf Config
 | 
			
		||||
 | 
			
		||||
// Config is struct of Configuration
 | 
			
		||||
type Config struct {
 | 
			
		||||
	logging.LogOpts
 | 
			
		||||
 | 
			
		||||
	// scan, report
 | 
			
		||||
	HTTPProxy  string `valid:"url" json:"httpProxy,omitempty"`
 | 
			
		||||
	ResultsDir string `json:"resultsDir,omitempty"`
 | 
			
		||||
	Pipe       bool   `json:"pipe,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Default ServerInfo            `json:"default,omitempty"`
 | 
			
		||||
	Servers map[string]ServerInfo `json:"servers,omitempty"`
 | 
			
		||||
 | 
			
		||||
	ScanOpts
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	CveDict    GoCveDictConf  `json:"cveDict,omitempty"`
 | 
			
		||||
	OvalDict   GovalDictConf  `json:"ovalDict,omitempty"`
 | 
			
		||||
	Gost       GostConf       `json:"gost,omitempty"`
 | 
			
		||||
	Exploit    ExploitConf    `json:"exploit,omitempty"`
 | 
			
		||||
	Metasploit MetasploitConf `json:"metasploit,omitempty"`
 | 
			
		||||
	KEVuln     KEVulnConf     `json:"kevuln,omitempty"`
 | 
			
		||||
	Cti        CtiConf        `json:"cti,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Slack      SlackConf      `json:"-"`
 | 
			
		||||
	EMail      SMTPConf       `json:"-"`
 | 
			
		||||
	HTTP       HTTPConf       `json:"-"`
 | 
			
		||||
	AWS        AWSConf        `json:"-"`
 | 
			
		||||
	Azure      AzureConf      `json:"-"`
 | 
			
		||||
	ChatWork   ChatWorkConf   `json:"-"`
 | 
			
		||||
	GoogleChat GoogleChatConf `json:"-"`
 | 
			
		||||
	Telegram   TelegramConf   `json:"-"`
 | 
			
		||||
	WpScan     WpScanConf     `json:"-"`
 | 
			
		||||
	Saas       SaasConf       `json:"-"`
 | 
			
		||||
 | 
			
		||||
	ReportOpts
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportConf is an interface to Validate Report Config
 | 
			
		||||
type ReportConf interface {
 | 
			
		||||
	Validate() []error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanOpts is options for scan
 | 
			
		||||
type ScanOpts struct {
 | 
			
		||||
	Vvv bool `json:"vvv,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportOpts is options for report
 | 
			
		||||
type ReportOpts struct {
 | 
			
		||||
	CvssScoreOver       float64 `json:"cvssScoreOver,omitempty"`
 | 
			
		||||
	ConfidenceScoreOver int     `json:"confidenceScoreOver,omitempty"`
 | 
			
		||||
	TrivyCacheDBDir     string  `json:"trivyCacheDBDir,omitempty"`
 | 
			
		||||
	NoProgress          bool    `json:"noProgress,omitempty"`
 | 
			
		||||
	RefreshCve          bool    `json:"refreshCve,omitempty"`
 | 
			
		||||
	IgnoreUnfixed       bool    `json:"ignoreUnfixed,omitempty"`
 | 
			
		||||
	IgnoreUnscoredCves  bool    `json:"ignoreUnscoredCves,omitempty"`
 | 
			
		||||
	DiffPlus            bool    `json:"diffPlus,omitempty"`
 | 
			
		||||
	DiffMinus           bool    `json:"diffMinus,omitempty"`
 | 
			
		||||
	Diff                bool    `json:"diff,omitempty"`
 | 
			
		||||
	Lang                string  `json:"lang,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnConfigtest validates
 | 
			
		||||
func (c Config) ValidateOnConfigtest() bool {
 | 
			
		||||
	errs := c.checkSSHKeyExist()
 | 
			
		||||
	if _, err := govalidator.ValidateStruct(c); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		logging.Log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnScan validates configuration
 | 
			
		||||
func (c Config) ValidateOnScan() bool {
 | 
			
		||||
	errs := c.checkSSHKeyExist()
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := govalidator.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf(
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := govalidator.ValidateStruct(c); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, server := range c.Servers {
 | 
			
		||||
		if !server.Module.IsScanPort() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if es := server.PortScan.Validate(); 0 < len(es) {
 | 
			
		||||
			errs = append(errs, es...)
 | 
			
		||||
		}
 | 
			
		||||
		if es := server.Windows.Validate(); 0 < len(es) {
 | 
			
		||||
			errs = append(errs, es...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		logging.Log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Config) checkSSHKeyExist() (errs []error) {
 | 
			
		||||
	for serverName, v := range c.Servers {
 | 
			
		||||
		if v.Type == constant.ServerTypePseudo {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if v.KeyPath != "" {
 | 
			
		||||
			if _, err := os.Stat(v.KeyPath); err != nil {
 | 
			
		||||
				errs = append(errs, xerrors.Errorf(
 | 
			
		||||
					"%s is invalid. keypath: %s not exists", serverName, v.KeyPath))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnReport validates configuration
 | 
			
		||||
func (c *Config) ValidateOnReport() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := govalidator.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf(
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := govalidator.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, rc := range []ReportConf{
 | 
			
		||||
		&c.EMail,
 | 
			
		||||
		&c.Slack,
 | 
			
		||||
		&c.ChatWork,
 | 
			
		||||
		&c.GoogleChat,
 | 
			
		||||
		&c.Telegram,
 | 
			
		||||
		&c.HTTP,
 | 
			
		||||
		&c.AWS,
 | 
			
		||||
		&c.Azure,
 | 
			
		||||
	} {
 | 
			
		||||
		if es := rc.Validate(); 0 < len(es) {
 | 
			
		||||
			errs = append(errs, es...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, cnf := range []VulnDictInterface{
 | 
			
		||||
		&Conf.CveDict,
 | 
			
		||||
		&Conf.OvalDict,
 | 
			
		||||
		&Conf.Gost,
 | 
			
		||||
		&Conf.Exploit,
 | 
			
		||||
		&Conf.Metasploit,
 | 
			
		||||
		&Conf.KEVuln,
 | 
			
		||||
		&Conf.Cti,
 | 
			
		||||
	} {
 | 
			
		||||
		if err := cnf.Validate(); err != nil {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf("Failed to validate %s: %+v", cnf.GetName(), err))
 | 
			
		||||
		}
 | 
			
		||||
		if err := cnf.CheckHTTPHealth(); err != nil {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf("Run %s as server mode before reporting: %+v", cnf.GetName(), err))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		logging.Log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnSaaS validates configuration
 | 
			
		||||
func (c Config) ValidateOnSaaS() bool {
 | 
			
		||||
	saaserrs := c.Saas.Validate()
 | 
			
		||||
	for _, err := range saaserrs {
 | 
			
		||||
		logging.Log.Error("Failed to validate SaaS conf: %+w", err)
 | 
			
		||||
	}
 | 
			
		||||
	return len(saaserrs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WpScanConf is wpscan.com config
 | 
			
		||||
type WpScanConf struct {
 | 
			
		||||
	Token          string `toml:"token,omitempty" json:"-"`
 | 
			
		||||
	DetectInactive bool   `toml:"detectInactive,omitempty" json:"detectInactive,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfo has SSH Info, additional CPE packages to scan.
 | 
			
		||||
type ServerInfo struct {
 | 
			
		||||
	BaseName           string                      `toml:"-" json:"-"`
 | 
			
		||||
	ServerName         string                      `toml:"-" json:"serverName,omitempty"`
 | 
			
		||||
	User               string                      `toml:"user,omitempty" json:"user,omitempty"`
 | 
			
		||||
	Host               string                      `toml:"host,omitempty" json:"host,omitempty"`
 | 
			
		||||
	IgnoreIPAddresses  []string                    `toml:"ignoreIPAddresses,omitempty" json:"ignoreIPAddresses,omitempty"`
 | 
			
		||||
	JumpServer         []string                    `toml:"jumpServer,omitempty" json:"jumpServer,omitempty"`
 | 
			
		||||
	Port               string                      `toml:"port,omitempty" json:"port,omitempty"`
 | 
			
		||||
	SSHConfigPath      string                      `toml:"sshConfigPath,omitempty" json:"sshConfigPath,omitempty"`
 | 
			
		||||
	KeyPath            string                      `toml:"keyPath,omitempty" json:"keyPath,omitempty"`
 | 
			
		||||
	CpeNames           []string                    `toml:"cpeNames,omitempty" json:"cpeNames,omitempty"`
 | 
			
		||||
	ScanMode           []string                    `toml:"scanMode,omitempty" json:"scanMode,omitempty"`
 | 
			
		||||
	ScanModules        []string                    `toml:"scanModules,omitempty" json:"scanModules,omitempty"`
 | 
			
		||||
	OwaspDCXMLPath     string                      `toml:"owaspDCXMLPath,omitempty" json:"owaspDCXMLPath,omitempty"`
 | 
			
		||||
	ContainersOnly     bool                        `toml:"containersOnly,omitempty" json:"containersOnly,omitempty"`
 | 
			
		||||
	ContainersIncluded []string                    `toml:"containersIncluded,omitempty" json:"containersIncluded,omitempty"`
 | 
			
		||||
	ContainersExcluded []string                    `toml:"containersExcluded,omitempty" json:"containersExcluded,omitempty"`
 | 
			
		||||
	ContainerType      string                      `toml:"containerType,omitempty" json:"containerType,omitempty"`
 | 
			
		||||
	Containers         map[string]ContainerSetting `toml:"containers,omitempty" json:"containers,omitempty"`
 | 
			
		||||
	IgnoreCves         []string                    `toml:"ignoreCves,omitempty" json:"ignoreCves,omitempty"`
 | 
			
		||||
	IgnorePkgsRegexp   []string                    `toml:"ignorePkgsRegexp,omitempty" json:"ignorePkgsRegexp,omitempty"`
 | 
			
		||||
	GitHubRepos        map[string]GitHubConf       `toml:"githubs" json:"githubs,omitempty"` // key: owner/repo
 | 
			
		||||
	UUIDs              map[string]string           `toml:"uuids,omitempty" json:"uuids,omitempty"`
 | 
			
		||||
	Memo               string                      `toml:"memo,omitempty" json:"memo,omitempty"`
 | 
			
		||||
	Enablerepo         []string                    `toml:"enablerepo,omitempty" json:"enablerepo,omitempty"` // For CentOS, Alma, Rocky, RHEL, Amazon
 | 
			
		||||
	Optional           map[string]interface{}      `toml:"optional,omitempty" json:"optional,omitempty"`     // Optional key-value set that will be outputted to JSON
 | 
			
		||||
	Lockfiles          []string                    `toml:"lockfiles,omitempty" json:"lockfiles,omitempty"`   // ie) path/to/package-lock.json
 | 
			
		||||
	FindLock           bool                        `toml:"findLock,omitempty" json:"findLock,omitempty"`
 | 
			
		||||
	FindLockDirs       []string                    `toml:"findLockDirs,omitempty" json:"findLockDirs,omitempty"`
 | 
			
		||||
	Type               string                      `toml:"type,omitempty" json:"type,omitempty"` // "pseudo" or ""
 | 
			
		||||
	IgnoredJSONKeys    []string                    `toml:"ignoredJSONKeys,omitempty" json:"ignoredJSONKeys,omitempty"`
 | 
			
		||||
	WordPress          *WordPressConf              `toml:"wordpress,omitempty" json:"wordpress,omitempty"`
 | 
			
		||||
	PortScan           *PortScanConf               `toml:"portscan,omitempty" json:"portscan,omitempty"`
 | 
			
		||||
	Windows            *WindowsConf                `toml:"windows,omitempty" json:"windows,omitempty"`
 | 
			
		||||
 | 
			
		||||
	IPv4Addrs      []string          `toml:"-" json:"ipv4Addrs,omitempty"`
 | 
			
		||||
	IPv6Addrs      []string          `toml:"-" json:"ipv6Addrs,omitempty"`
 | 
			
		||||
	IPSIdentifiers map[string]string `toml:"-" json:"ipsIdentifiers,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// internal use
 | 
			
		||||
	LogMsgAnsiColor string     `toml:"-" json:"-"` // DebugLog Color
 | 
			
		||||
	Container       Container  `toml:"-" json:"-"`
 | 
			
		||||
	Distro          Distro     `toml:"-" json:"-"`
 | 
			
		||||
	Mode            ScanMode   `toml:"-" json:"-"`
 | 
			
		||||
	Module          ScanModule `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ContainerSetting is used for loading container setting in config.toml
 | 
			
		||||
type ContainerSetting struct {
 | 
			
		||||
	Cpes             []string `json:"cpes,omitempty"`
 | 
			
		||||
	OwaspDCXMLPath   string   `json:"owaspDCXMLPath,omitempty"`
 | 
			
		||||
	IgnorePkgsRegexp []string `json:"ignorePkgsRegexp,omitempty"`
 | 
			
		||||
	IgnoreCves       []string `json:"ignoreCves,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WordPressConf used for WordPress Scanning
 | 
			
		||||
type WordPressConf struct {
 | 
			
		||||
	OSUser  string `toml:"osUser,omitempty" json:"osUser,omitempty"`
 | 
			
		||||
	DocRoot string `toml:"docRoot,omitempty" json:"docRoot,omitempty"`
 | 
			
		||||
	CmdPath string `toml:"cmdPath,omitempty" json:"cmdPath,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsZero return  whether this struct is not specified in config.toml
 | 
			
		||||
func (cnf WordPressConf) IsZero() bool {
 | 
			
		||||
	return cnf.OSUser == "" && cnf.DocRoot == "" && cnf.CmdPath == ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GitHubConf is used for GitHub Security Alerts
 | 
			
		||||
type GitHubConf struct {
 | 
			
		||||
	Token                 string `json:"-"`
 | 
			
		||||
	IgnoreGitHubDismissed bool   `json:"ignoreGitHubDismissed,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetServerName returns ServerName if this serverInfo is about host.
 | 
			
		||||
// If this serverInfo is about a container, returns containerID@ServerName
 | 
			
		||||
func (s ServerInfo) GetServerName() string {
 | 
			
		||||
	if len(s.Container.ContainerID) == 0 {
 | 
			
		||||
		return s.ServerName
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s@%s", s.Container.Name, s.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Distro has distribution info
 | 
			
		||||
type Distro struct {
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l Distro) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s %s", l.Family, l.Release)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MajorVersion returns Major version
 | 
			
		||||
func (l Distro) MajorVersion() (int, error) {
 | 
			
		||||
	switch l.Family {
 | 
			
		||||
	case constant.Amazon:
 | 
			
		||||
		return strconv.Atoi(getAmazonLinuxVersion(l.Release))
 | 
			
		||||
	case constant.CentOS:
 | 
			
		||||
		if 0 < len(l.Release) {
 | 
			
		||||
			return strconv.Atoi(strings.Split(strings.TrimPrefix(l.Release, "stream"), ".")[0])
 | 
			
		||||
		}
 | 
			
		||||
	case constant.OpenSUSE:
 | 
			
		||||
		if l.Release != "" {
 | 
			
		||||
			if l.Release == "tumbleweed" {
 | 
			
		||||
				return 0, nil
 | 
			
		||||
			}
 | 
			
		||||
			return strconv.Atoi(strings.Split(l.Release, ".")[0])
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		if 0 < len(l.Release) {
 | 
			
		||||
			return strconv.Atoi(strings.Split(l.Release, ".")[0])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 0, xerrors.New("Release is empty")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsContainer returns whether this ServerInfo is about container
 | 
			
		||||
func (s ServerInfo) IsContainer() bool {
 | 
			
		||||
	return 0 < len(s.Container.ContainerID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetContainer set container
 | 
			
		||||
func (s *ServerInfo) SetContainer(d Container) {
 | 
			
		||||
	s.Container = d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information.
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Image       string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								config/os.go
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								config/os.go
									
									
									
									
									
								
							@@ -311,6 +311,88 @@ func GetEOL(family, release string) (eol EOL, found bool) {
 | 
			
		||||
			"36": {StandardSupportUntil: time.Date(2023, 5, 16, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
			"37": {StandardSupportUntil: time.Date(2023, 12, 15, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
		}[major(release)]
 | 
			
		||||
	case constant.Windows:
 | 
			
		||||
		// https://learn.microsoft.com/ja-jp/lifecycle/products/?products=windows
 | 
			
		||||
 | 
			
		||||
		lhs, rhs, _ := strings.Cut(strings.TrimSuffix(release, "(Server Core installation)"), "for")
 | 
			
		||||
		switch strings.TrimSpace(lhs) {
 | 
			
		||||
		case "Windows 7":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2013, 4, 9, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
			if strings.Contains(rhs, "Service Pack 1") {
 | 
			
		||||
				eol, found = EOL{StandardSupportUntil: time.Date(2020, 1, 14, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
			}
 | 
			
		||||
		case "Windows 8":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2016, 1, 12, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 8.1":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2023, 1, 10, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2017, 5, 9, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 1511":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2017, 10, 10, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 1607":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2018, 4, 10, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 1703":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2018, 10, 9, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 1709":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2019, 4, 9, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 1803":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2019, 11, 12, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 1809":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2020, 11, 10, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 1903":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2020, 12, 8, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 1909":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2021, 5, 11, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 2004":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2021, 12, 14, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 20H2":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2022, 5, 10, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 21H1":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2022, 12, 13, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 21H2":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2023, 6, 13, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 10 Version 22H2":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2024, 5, 14, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 11 Version 21H2":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2024, 10, 8, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows 11 Version 22H2":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2025, 10, 14, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server 2008":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2011, 7, 12, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
			if strings.Contains(rhs, "Service Pack 2") {
 | 
			
		||||
				eol, found = EOL{StandardSupportUntil: time.Date(2020, 1, 14, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
			}
 | 
			
		||||
		case "Windows Server 2008 R2":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2013, 4, 9, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
			if strings.Contains(rhs, "Service Pack 1") {
 | 
			
		||||
				eol, found = EOL{StandardSupportUntil: time.Date(2020, 1, 14, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
			}
 | 
			
		||||
		case "Windows Server 2012":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2023, 10, 10, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server 2012 R2":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2023, 10, 10, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server 2016":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2027, 1, 12, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server, Version 1709":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2019, 4, 9, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server, Version 1803":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2019, 11, 12, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server, Version 1809":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2020, 11, 10, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server 2019":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2029, 1, 9, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server, Version 1903":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2020, 12, 8, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server, Version 1909":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2021, 5, 11, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server, Version 2004":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2021, 12, 14, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server, Version 20H2":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2022, 8, 9, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		case "Windows Server 2022":
 | 
			
		||||
			eol, found = EOL{StandardSupportUntil: time.Date(2031, 10, 14, 23, 59, 59, 0, time.UTC)}, true
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -607,6 +607,22 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Windows 10 EOL",
 | 
			
		||||
			fields:   fields{family: Windows, release: "Windows 10 for x64-based Systems"},
 | 
			
		||||
			now:      time.Date(2022, 12, 8, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: true,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Windows 10 Version 22H2 supported",
 | 
			
		||||
			fields:   fields{family: Windows, release: "Windows 10 Version 22H2 for x64-based Systems"},
 | 
			
		||||
			now:      time.Date(2022, 12, 8, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
 
 | 
			
		||||
@@ -294,6 +294,13 @@ func setDefaultIfEmpty(server *ServerInfo) error {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if server.Windows == nil {
 | 
			
		||||
		server.Windows = Conf.Default.Windows
 | 
			
		||||
		if server.Windows == nil {
 | 
			
		||||
			server.Windows = &WindowsConf{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(server.IgnoredJSONKeys) == 0 {
 | 
			
		||||
		server.IgnoredJSONKeys = Conf.Default.IgnoredJSONKeys
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								config/windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								config/windows.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// WindowsConf used for Windows Update Setting
 | 
			
		||||
type WindowsConf struct {
 | 
			
		||||
	ServerSelection int    `toml:"serverSelection,omitempty" json:"serverSelection,omitempty"`
 | 
			
		||||
	CabPath         string `toml:"cabPath,omitempty" json:"cabPath,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *WindowsConf) Validate() []error {
 | 
			
		||||
	switch c.ServerSelection {
 | 
			
		||||
	case 0, 1, 2:
 | 
			
		||||
	case 3:
 | 
			
		||||
		if _, err := os.Stat(c.CabPath); err != nil {
 | 
			
		||||
			return []error{xerrors.Errorf("%s does not exist. err: %w", c.CabPath, err)}
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return []error{xerrors.Errorf("ServerSelection: %d does not support . Reference: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-uamg/07e2bfa4-6795-4189-b007-cc50b476181a", c.ServerSelection)}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -473,7 +473,7 @@ func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult, logOpts l
 | 
			
		||||
	nCVEs, err := client.DetectCVEs(r, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		switch r.Family {
 | 
			
		||||
		case constant.Debian, constant.Ubuntu:
 | 
			
		||||
		case constant.Debian, constant.Ubuntu, constant.Windows:
 | 
			
		||||
			return xerrors.Errorf("Failed to detect CVEs with gost: %w", err)
 | 
			
		||||
		default:
 | 
			
		||||
			return xerrors.Errorf("Failed to detect unfixed CVEs with gost: %w", err)
 | 
			
		||||
@@ -481,7 +481,7 @@ func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult, logOpts l
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch r.Family {
 | 
			
		||||
	case constant.Debian, constant.Ubuntu:
 | 
			
		||||
	case constant.Debian, constant.Ubuntu, constant.Windows:
 | 
			
		||||
		logging.Log.Infof("%s: %d CVEs are detected with gost", r.FormatServerName(), nCVEs)
 | 
			
		||||
	default:
 | 
			
		||||
		logging.Log.Infof("%s: %d unfixed CVEs are detected with gost", r.FormatServerName(), nCVEs)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,9 @@ package detector
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -221,25 +219,23 @@ func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// jsonDirPattern is file name pattern of JSON directory
 | 
			
		||||
// 2016-11-16T10:43:28+09:00
 | 
			
		||||
// 2016-11-16T10:43:28Z
 | 
			
		||||
var jsonDirPattern = regexp.MustCompile(
 | 
			
		||||
	`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
 | 
			
		||||
 | 
			
		||||
// ListValidJSONDirs returns valid json directory as array
 | 
			
		||||
// Returned array is sorted so that recent directories are at the head
 | 
			
		||||
func ListValidJSONDirs(resultsDir string) (dirs []string, err error) {
 | 
			
		||||
	var dirInfo []fs.DirEntry
 | 
			
		||||
	if dirInfo, err = os.ReadDir(resultsDir); err != nil {
 | 
			
		||||
		err = xerrors.Errorf("Failed to read %s: %w",
 | 
			
		||||
			config.Conf.ResultsDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	dirInfo, err := os.ReadDir(resultsDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to read %s: %w", config.Conf.ResultsDir, err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range dirInfo {
 | 
			
		||||
		if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
 | 
			
		||||
			jsonDir := filepath.Join(resultsDir, d.Name())
 | 
			
		||||
			dirs = append(dirs, jsonDir)
 | 
			
		||||
		if !d.IsDir() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, layout := range []string{"2006-01-02T15:04:05Z", "2006-01-02T15:04:05-07:00", "2006-01-02T15-04-05-0700"} {
 | 
			
		||||
			if _, err := time.Parse(layout, d.Name()); err == nil {
 | 
			
		||||
				dirs = append(dirs, filepath.Join(resultsDir, d.Name()))
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(dirs, func(i, j int) bool {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								go.mod
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
module github.com/future-architect/vuls
 | 
			
		||||
 | 
			
		||||
go 1.18
 | 
			
		||||
go 1.20
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Azure/azure-sdk-for-go v66.0.0+incompatible
 | 
			
		||||
@@ -36,19 +36,21 @@ require (
 | 
			
		||||
	github.com/package-url/packageurl-go v0.1.1-0.20220203205134-d70459300c8a
 | 
			
		||||
	github.com/parnurzeal/gorequest v0.2.16
 | 
			
		||||
	github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
 | 
			
		||||
	github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
 | 
			
		||||
	github.com/sirupsen/logrus v1.9.0
 | 
			
		||||
	github.com/spf13/cobra v1.6.1
 | 
			
		||||
	github.com/vulsio/go-cti v0.0.2-0.20220613013115-8c7e57a6aa86
 | 
			
		||||
	github.com/vulsio/go-cve-dictionary v0.8.2
 | 
			
		||||
	github.com/vulsio/go-cti v0.0.2
 | 
			
		||||
	github.com/vulsio/go-cve-dictionary v0.8.3
 | 
			
		||||
	github.com/vulsio/go-exploitdb v0.4.4
 | 
			
		||||
	github.com/vulsio/go-kev v0.1.1-0.20220118062020-5f69b364106f
 | 
			
		||||
	github.com/vulsio/go-msfdb v0.2.1-0.20211028071756-4a9759bd9f14
 | 
			
		||||
	github.com/vulsio/gost v0.4.2-0.20230203045609-dcfab39a9ff4
 | 
			
		||||
	github.com/vulsio/goval-dictionary v0.8.0
 | 
			
		||||
	github.com/vulsio/go-kev v0.1.1
 | 
			
		||||
	github.com/vulsio/go-msfdb v0.2.1
 | 
			
		||||
	github.com/vulsio/gost v0.4.2
 | 
			
		||||
	github.com/vulsio/goval-dictionary v0.8.2
 | 
			
		||||
	go.etcd.io/bbolt v1.3.6
 | 
			
		||||
	golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
 | 
			
		||||
	golang.org/x/oauth2 v0.1.0
 | 
			
		||||
	golang.org/x/sync v0.1.0
 | 
			
		||||
	golang.org/x/text v0.7.0
 | 
			
		||||
	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -65,9 +67,9 @@ require (
 | 
			
		||||
	github.com/Azure/go-autorest/autorest/to v0.3.0 // indirect
 | 
			
		||||
	github.com/Azure/go-autorest/logger v0.2.1 // indirect
 | 
			
		||||
	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
 | 
			
		||||
	github.com/PuerkitoBio/goquery v1.6.1 // indirect
 | 
			
		||||
	github.com/PuerkitoBio/goquery v1.8.1 // indirect
 | 
			
		||||
	github.com/VividCortex/ewma v1.2.0 // indirect
 | 
			
		||||
	github.com/andybalholm/cascadia v1.2.0 // indirect
 | 
			
		||||
	github.com/andybalholm/cascadia v1.3.1 // indirect
 | 
			
		||||
	github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce // indirect
 | 
			
		||||
	github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 // indirect
 | 
			
		||||
	github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect
 | 
			
		||||
@@ -142,7 +144,7 @@ require (
 | 
			
		||||
	github.com/sergi/go-diff v1.3.1 // indirect
 | 
			
		||||
	github.com/smartystreets/assertions v1.13.0 // indirect
 | 
			
		||||
	github.com/spdx/tools-golang v0.3.0 // indirect
 | 
			
		||||
	github.com/spf13/afero v1.9.3 // indirect
 | 
			
		||||
	github.com/spf13/afero v1.9.4 // indirect
 | 
			
		||||
	github.com/spf13/cast v1.5.0 // indirect
 | 
			
		||||
	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 | 
			
		||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
			
		||||
@@ -150,7 +152,7 @@ require (
 | 
			
		||||
	github.com/stretchr/objx v0.5.0 // indirect
 | 
			
		||||
	github.com/stretchr/testify v1.8.1 // indirect
 | 
			
		||||
	github.com/subosito/gotenv v1.4.2 // indirect
 | 
			
		||||
	github.com/ulikunitz/xz v0.5.10 // indirect
 | 
			
		||||
	github.com/ulikunitz/xz v0.5.11 // indirect
 | 
			
		||||
	github.com/xanzy/ssh-agent v0.3.3 // indirect
 | 
			
		||||
	go.opencensus.io v0.24.0 // indirect
 | 
			
		||||
	go.uber.org/atomic v1.10.0 // indirect
 | 
			
		||||
@@ -162,7 +164,7 @@ require (
 | 
			
		||||
	golang.org/x/net v0.7.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.5.0 // indirect
 | 
			
		||||
	golang.org/x/term v0.5.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.7.0 // indirect
 | 
			
		||||
	golang.org/x/time v0.3.0 // indirect
 | 
			
		||||
	golang.org/x/tools v0.6.0 // indirect
 | 
			
		||||
	google.golang.org/api v0.107.0 // indirect
 | 
			
		||||
	google.golang.org/appengine v1.6.7 // indirect
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								go.sum
									
									
									
									
									
								
							@@ -91,8 +91,8 @@ github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v
 | 
			
		||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
 | 
			
		||||
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
 | 
			
		||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
 | 
			
		||||
github.com/Ullaakut/nmap/v2 v2.1.2-0.20210406060955-59a52fe80a4f h1:U5oMIt9/cuLbHnVgNddFoJ6ebcMx52Unq2+/Wglo1XU=
 | 
			
		||||
github.com/Ullaakut/nmap/v2 v2.1.2-0.20210406060955-59a52fe80a4f/go.mod h1:bWPItdcCK9CkZcAaC7yS9N+t2zijtIjAWBcQtOzV9nM=
 | 
			
		||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
 | 
			
		||||
@@ -102,9 +102,8 @@ github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk
 | 
			
		||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
 | 
			
		||||
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
 | 
			
		||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
 | 
			
		||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 | 
			
		||||
github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
 | 
			
		||||
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
 | 
			
		||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
 | 
			
		||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
 | 
			
		||||
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
 | 
			
		||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
 | 
			
		||||
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30xLN2sUZcMXl50hg+PJCIDdJgIvIbVcKqLJ/ZrtM=
 | 
			
		||||
@@ -540,6 +539,8 @@ github.com/rubenv/sql-migrate v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36t
 | 
			
		||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
			
		||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
 | 
			
		||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
 | 
			
		||||
github.com/samber/lo v1.33.0 h1:2aKucr+rQV6gHpY3bpeZu69uYoQOzVhGT3J22Op6Cjk=
 | 
			
		||||
github.com/samber/lo v1.33.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
 | 
			
		||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
 | 
			
		||||
@@ -560,8 +561,8 @@ github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsS
 | 
			
		||||
github.com/spdx/tools-golang v0.3.0 h1:rtm+DHk3aAt74Fh0Wgucb4pCxjXV8SqHCPEb2iBd30k=
 | 
			
		||||
github.com/spdx/tools-golang v0.3.0/go.mod h1:RO4Y3IFROJnz+43JKm1YOrbtgQNljW4gAPpA/sY2eqo=
 | 
			
		||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
 | 
			
		||||
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
 | 
			
		||||
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
 | 
			
		||||
github.com/spf13/afero v1.9.4 h1:Sd43wM1IWz/s1aVXdOBkjJvuP8UdyqioeE4AmM0QsBs=
 | 
			
		||||
github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
 | 
			
		||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
 | 
			
		||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
 | 
			
		||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
 | 
			
		||||
@@ -591,24 +592,24 @@ github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNG
 | 
			
		||||
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
 | 
			
		||||
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
 | 
			
		||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 | 
			
		||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
 | 
			
		||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 | 
			
		||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
 | 
			
		||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 | 
			
		||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
 | 
			
		||||
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
 | 
			
		||||
github.com/vulsio/go-cti v0.0.2-0.20220613013115-8c7e57a6aa86 h1:/Xie1YmCGo+SMpOP5xhZ7bzRBTvTu6zGZlCv1cahE8E=
 | 
			
		||||
github.com/vulsio/go-cti v0.0.2-0.20220613013115-8c7e57a6aa86/go.mod h1:EBt6G1VZylPciq3CHKmBIth6nDbcPOU59lqOily2aZM=
 | 
			
		||||
github.com/vulsio/go-cve-dictionary v0.8.2 h1:abj5449xjuHVRPIeNS41TE+MDMZmw+nbjxm3QZbL4Ks=
 | 
			
		||||
github.com/vulsio/go-cve-dictionary v0.8.2/go.mod h1:GOeHvUi9MaPJgNgnYXry73lnypShLett5yfpw00IJrg=
 | 
			
		||||
github.com/vulsio/go-cti v0.0.2 h1:EL11fvKgeQxuwlLDrN5szafH364B2VWGuRSoATT/KaU=
 | 
			
		||||
github.com/vulsio/go-cti v0.0.2/go.mod h1:oICScdF/y+skYH6yORuwSaSYCuIYy30SZRiK+kUUm8k=
 | 
			
		||||
github.com/vulsio/go-cve-dictionary v0.8.3 h1:76meG1GJrXqUdI0HeliUBsdGuMm55XNEPnkPDdQdqyE=
 | 
			
		||||
github.com/vulsio/go-cve-dictionary v0.8.3/go.mod h1:aqf+5NVAvmW8iLJImsrWYb7nHetX1dqP0O/8FYfrI4I=
 | 
			
		||||
github.com/vulsio/go-exploitdb v0.4.4 h1:h5y6xI4wrpzwo6kmLKU7eb/GryP2kcqgjo8C+VvAFXE=
 | 
			
		||||
github.com/vulsio/go-exploitdb v0.4.4/go.mod h1:nUQwEq6AEp62jeHV1Bf2wq080/7qxu+wguDW/lAnLIo=
 | 
			
		||||
github.com/vulsio/go-kev v0.1.1-0.20220118062020-5f69b364106f h1:s28XqL35U+N2xkl6bLXPH68IqzmliuqeF37x5pzNLuc=
 | 
			
		||||
github.com/vulsio/go-kev v0.1.1-0.20220118062020-5f69b364106f/go.mod h1:NrXTTkGG83ZYl7ypHHLqqzx6HvVkWH37qCizU5UoCS8=
 | 
			
		||||
github.com/vulsio/go-msfdb v0.2.1-0.20211028071756-4a9759bd9f14 h1:2uYZw2gQ0kymwerTS1FXZbNgptnlye+SB7o3QlLDIBo=
 | 
			
		||||
github.com/vulsio/go-msfdb v0.2.1-0.20211028071756-4a9759bd9f14/go.mod h1:NGdcwWxCK/ES8vZ/crzREqI69S5gH1MivCpSp1pa2Rc=
 | 
			
		||||
github.com/vulsio/gost v0.4.2-0.20230203045609-dcfab39a9ff4 h1:aitlGPmn5WPb9aR6MFsikt+/EaxJtMNttaeayXsDxs0=
 | 
			
		||||
github.com/vulsio/gost v0.4.2-0.20230203045609-dcfab39a9ff4/go.mod h1:6xRvzXkpm8nJ/jMmL/TJZvabfVZyy2aB1nr4wtmJ1KI=
 | 
			
		||||
github.com/vulsio/goval-dictionary v0.8.0 h1:hwxIwSEo7C3yPGOcrzr5jyKhBnxEidtUVNPIlbrBg+8=
 | 
			
		||||
github.com/vulsio/goval-dictionary v0.8.0/go.mod h1:6gfsQfQN0jkO3ZNJlHP5r+2iyx375CBiMBdCcL8MmwM=
 | 
			
		||||
github.com/vulsio/go-kev v0.1.1 h1:Xi0FjUj2czQpnurfbXxSrJFbaePolbTrM+gfYxsvj2o=
 | 
			
		||||
github.com/vulsio/go-kev v0.1.1/go.mod h1:3CiN3/Ojlodj9ACt2SAhAk5L36m27czTKDfSEf8U8Qg=
 | 
			
		||||
github.com/vulsio/go-msfdb v0.2.1 h1:s3Czz+WdgtaXjHRy+1fUzSdEjZGXie354IvT+9syAY0=
 | 
			
		||||
github.com/vulsio/go-msfdb v0.2.1/go.mod h1:8A7AyeSqZtFxfd5bljiB1/z2hvkFPe3/jpRtV/mqGbo=
 | 
			
		||||
github.com/vulsio/gost v0.4.2 h1:WtjSeTkvvmJdhn6Dv2Ew934MC4dGmojjC6cu7Q9sHhA=
 | 
			
		||||
github.com/vulsio/gost v0.4.2/go.mod h1:PxCHzwylur7/EiP7Jo6UPRYkipi76EhA015FOTjKol0=
 | 
			
		||||
github.com/vulsio/goval-dictionary v0.8.2 h1:6aI10z/RFZjADzP4fvf7I1zGqbY3EfAsF0I1VOh/ep0=
 | 
			
		||||
github.com/vulsio/goval-dictionary v0.8.2/go.mod h1:yRO+Xuce12lSQiV6gdMb86uc8V5Vncgzc6U84WvB/5k=
 | 
			
		||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
 | 
			
		||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 | 
			
		||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
 | 
			
		||||
@@ -696,7 +697,6 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
			
		||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
 | 
			
		||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 | 
			
		||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
@@ -730,6 +730,7 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
 | 
			
		||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 | 
			
		||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
			
		||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 | 
			
		||||
@@ -832,7 +833,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
 | 
			
		||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
 | 
			
		||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 | 
			
		||||
 
 | 
			
		||||
@@ -4,17 +4,23 @@
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/hashicorp/go-version"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
	"golang.org/x/exp/slices"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	gostmodels "github.com/vulsio/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -23,123 +29,256 @@ type Microsoft struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var kbIDPattern = regexp.MustCompile(`KB(\d{6,7})`)
 | 
			
		||||
 | 
			
		||||
// DetectCVEs fills cve information that has in Gost
 | 
			
		||||
func (ms Microsoft) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
 | 
			
		||||
	if ms.driver == nil {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	var applied, unapplied []string
 | 
			
		||||
	if r.WindowsKB != nil {
 | 
			
		||||
		applied = r.WindowsKB.Applied
 | 
			
		||||
		unapplied = r.WindowsKB.Unapplied
 | 
			
		||||
	}
 | 
			
		||||
	if ms.driver == nil {
 | 
			
		||||
		u, err := util.URLPathJoin(ms.baseURL, "microsoft", "kbs")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	var osName string
 | 
			
		||||
	osName, ok := r.Optional["OSName"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		logging.Log.Warnf("This Windows has wrong type option(OSName). UUID: %s", r.ServerUUID)
 | 
			
		||||
		content := map[string]interface{}{"applied": applied, "unapplied": unapplied}
 | 
			
		||||
		var body []byte
 | 
			
		||||
		var errs []error
 | 
			
		||||
		var resp *http.Response
 | 
			
		||||
		f := func() error {
 | 
			
		||||
			resp, body, errs = gorequest.New().Timeout(10 * time.Second).Post(u).SendStruct(content).Type("json").EndBytes()
 | 
			
		||||
			if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
				return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", u, resp, errs)
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		notify := func(err error, t time.Duration) {
 | 
			
		||||
			logging.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %+v", t, err)
 | 
			
		||||
		}
 | 
			
		||||
		if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("HTTP Error: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var r struct {
 | 
			
		||||
			Applied   []string `json:"applied"`
 | 
			
		||||
			Unapplied []string `json:"unapplied"`
 | 
			
		||||
		}
 | 
			
		||||
		if err := json.Unmarshal(body, &r); err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
 | 
			
		||||
		}
 | 
			
		||||
		applied = r.Applied
 | 
			
		||||
		unapplied = r.Unapplied
 | 
			
		||||
	} else {
 | 
			
		||||
		applied, unapplied, err = ms.driver.GetExpandKB(applied, unapplied)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var products []string
 | 
			
		||||
	if _, ok := r.Optional["InstalledProducts"]; ok {
 | 
			
		||||
		switch ps := r.Optional["InstalledProducts"].(type) {
 | 
			
		||||
		case []interface{}:
 | 
			
		||||
			for _, p := range ps {
 | 
			
		||||
				pname, ok := p.(string)
 | 
			
		||||
				if !ok {
 | 
			
		||||
					logging.Log.Warnf("skip products: %v", p)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				products = append(products, pname)
 | 
			
		||||
			}
 | 
			
		||||
		case []string:
 | 
			
		||||
			for _, p := range ps {
 | 
			
		||||
				products = append(products, p)
 | 
			
		||||
			}
 | 
			
		||||
		case nil:
 | 
			
		||||
			logging.Log.Warnf("This Windows has no option(InstalledProducts). UUID: %s", r.ServerUUID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	applied, unapplied := map[string]struct{}{}, map[string]struct{}{}
 | 
			
		||||
	if _, ok := r.Optional["KBID"]; ok {
 | 
			
		||||
		switch kbIDs := r.Optional["KBID"].(type) {
 | 
			
		||||
		case []interface{}:
 | 
			
		||||
			for _, kbID := range kbIDs {
 | 
			
		||||
				s, ok := kbID.(string)
 | 
			
		||||
				if !ok {
 | 
			
		||||
					logging.Log.Warnf("skip KBID: %v", kbID)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				unapplied[strings.TrimPrefix(s, "KB")] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		case []string:
 | 
			
		||||
			for _, kbID := range kbIDs {
 | 
			
		||||
				unapplied[strings.TrimPrefix(kbID, "KB")] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		case nil:
 | 
			
		||||
			logging.Log.Warnf("This Windows has no option(KBID). UUID: %s", r.ServerUUID)
 | 
			
		||||
	if ms.driver == nil {
 | 
			
		||||
		u, err := util.URLPathJoin(ms.baseURL, "microsoft", "products")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, pkg := range r.Packages {
 | 
			
		||||
			matches := kbIDPattern.FindAllStringSubmatch(pkg.Name, -1)
 | 
			
		||||
			for _, match := range matches {
 | 
			
		||||
				applied[match[1]] = struct{}{}
 | 
			
		||||
		content := map[string]interface{}{"release": r.Release, "kbs": append(applied, unapplied...)}
 | 
			
		||||
		var body []byte
 | 
			
		||||
		var errs []error
 | 
			
		||||
		var resp *http.Response
 | 
			
		||||
		f := func() error {
 | 
			
		||||
			resp, body, errs = gorequest.New().Timeout(10 * time.Second).Post(u).SendStruct(content).Type("json").EndBytes()
 | 
			
		||||
			if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
				return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", u, resp, errs)
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		notify := func(err error, t time.Duration) {
 | 
			
		||||
			logging.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %+v", t, err)
 | 
			
		||||
		}
 | 
			
		||||
		if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("HTTP Error: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := json.Unmarshal(body, &products); err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		switch kbIDs := r.Optional["AppliedKBID"].(type) {
 | 
			
		||||
		case []interface{}:
 | 
			
		||||
			for _, kbID := range kbIDs {
 | 
			
		||||
				s, ok := kbID.(string)
 | 
			
		||||
				if !ok {
 | 
			
		||||
					logging.Log.Warnf("skip KBID: %v", kbID)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				applied[strings.TrimPrefix(s, "KB")] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		case []string:
 | 
			
		||||
			for _, kbID := range kbIDs {
 | 
			
		||||
				applied[strings.TrimPrefix(kbID, "KB")] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		case nil:
 | 
			
		||||
			logging.Log.Warnf("This Windows has no option(AppliedKBID). UUID: %s", r.ServerUUID)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch kbIDs := r.Optional["UnappliedKBID"].(type) {
 | 
			
		||||
		case []interface{}:
 | 
			
		||||
			for _, kbID := range kbIDs {
 | 
			
		||||
				s, ok := kbID.(string)
 | 
			
		||||
				if !ok {
 | 
			
		||||
					logging.Log.Warnf("skip KBID: %v", kbID)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				unapplied[strings.TrimPrefix(s, "KB")] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		case []string:
 | 
			
		||||
			for _, kbID := range kbIDs {
 | 
			
		||||
				unapplied[strings.TrimPrefix(kbID, "KB")] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		case nil:
 | 
			
		||||
			logging.Log.Warnf("This Windows has no option(UnappliedKBID). UUID: %s", r.ServerUUID)
 | 
			
		||||
		ps, err := ms.driver.GetRelatedProducts(r.Release, append(applied, unapplied...))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		products = ps
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logging.Log.Debugf(`GetCvesByMicrosoftKBID query body {"osName": %s, "installedProducts": %q, "applied": %q, "unapplied: %q"}`, osName, products, maps.Keys(applied), maps.Keys(unapplied))
 | 
			
		||||
	cves, err := ms.driver.GetCvesByMicrosoftKBID(osName, products, maps.Keys(applied), maps.Keys(unapplied))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err)
 | 
			
		||||
	m := map[string]struct{}{}
 | 
			
		||||
	for _, p := range products {
 | 
			
		||||
		m[p] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	for _, n := range []string{"Microsoft Edge (Chromium-based)", fmt.Sprintf("Microsoft Edge on %s", r.Release), fmt.Sprintf("Microsoft Edge (Chromium-based) in IE Mode on %s", r.Release), fmt.Sprintf("Microsoft Edge (EdgeHTML-based) on %s", r.Release)} {
 | 
			
		||||
		delete(m, n)
 | 
			
		||||
	}
 | 
			
		||||
	filtered := []string{r.Release}
 | 
			
		||||
	for _, p := range r.Packages {
 | 
			
		||||
		switch p.Name {
 | 
			
		||||
		case "Microsoft Edge":
 | 
			
		||||
			if ss := strings.Split(p.Version, "."); len(ss) > 0 {
 | 
			
		||||
				v, err := strconv.ParseInt(ss[0], 10, 8)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				if v > 44 {
 | 
			
		||||
					filtered = append(filtered, "Microsoft Edge (Chromium-based)", fmt.Sprintf("Microsoft Edge on %s", r.Release), fmt.Sprintf("Microsoft Edge (Chromium-based) in IE Mode on %s", r.Release))
 | 
			
		||||
				} else {
 | 
			
		||||
					filtered = append(filtered, fmt.Sprintf("Microsoft Edge on %s", r.Release), fmt.Sprintf("Microsoft Edge (EdgeHTML-based) on %s", r.Release))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	filtered = unique(append(filtered, maps.Keys(m)...))
 | 
			
		||||
 | 
			
		||||
	var cves map[string]gostmodels.MicrosoftCVE
 | 
			
		||||
	if ms.driver == nil {
 | 
			
		||||
		u, err := util.URLPathJoin(ms.baseURL, "microsoft", "filtered-cves")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		content := map[string]interface{}{"products": filtered, "kbs": append(applied, unapplied...)}
 | 
			
		||||
		var body []byte
 | 
			
		||||
		var errs []error
 | 
			
		||||
		var resp *http.Response
 | 
			
		||||
		f := func() error {
 | 
			
		||||
			resp, body, errs = gorequest.New().Timeout(10 * time.Second).Post(u).SendStruct(content).Type("json").EndBytes()
 | 
			
		||||
			if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
				return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", u, resp, errs)
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		notify := func(err error, t time.Duration) {
 | 
			
		||||
			logging.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %+v", t, err)
 | 
			
		||||
		}
 | 
			
		||||
		if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("HTTP Error: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := json.Unmarshal(body, &cves); err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		cves, err = ms.driver.GetFilteredCvesMicrosoft(filtered, append(applied, unapplied...))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for cveID, cve := range cves {
 | 
			
		||||
		var ps []gostmodels.MicrosoftProduct
 | 
			
		||||
		for _, p := range cve.Products {
 | 
			
		||||
			if len(p.KBs) == 0 {
 | 
			
		||||
				ps = append(ps, p)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var kbs []gostmodels.MicrosoftKB
 | 
			
		||||
			for _, kb := range p.KBs {
 | 
			
		||||
				if _, err := strconv.Atoi(kb.Article); err != nil {
 | 
			
		||||
					switch {
 | 
			
		||||
					case strings.HasPrefix(p.Name, "Microsoft Edge"):
 | 
			
		||||
						p, ok := r.Packages["Microsoft Edge"]
 | 
			
		||||
						if !ok {
 | 
			
		||||
							break
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						if kb.FixedBuild == "" {
 | 
			
		||||
							kbs = append(kbs, kb)
 | 
			
		||||
							break
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						vera, err := version.NewVersion(p.Version)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							kbs = append(kbs, kb)
 | 
			
		||||
							break
 | 
			
		||||
						}
 | 
			
		||||
						verb, err := version.NewVersion(kb.FixedBuild)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							kbs = append(kbs, kb)
 | 
			
		||||
							break
 | 
			
		||||
						}
 | 
			
		||||
						if vera.LessThan(verb) {
 | 
			
		||||
							kbs = append(kbs, kb)
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					if slices.Contains(applied, kb.Article) {
 | 
			
		||||
						kbs = []gostmodels.MicrosoftKB{}
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					if slices.Contains(unapplied, kb.Article) {
 | 
			
		||||
						kbs = append(kbs, kb)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if len(kbs) > 0 {
 | 
			
		||||
				p.KBs = kbs
 | 
			
		||||
				ps = append(ps, p)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		cve.Products = ps
 | 
			
		||||
		if len(cve.Products) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		nCVEs++
 | 
			
		||||
 | 
			
		||||
		cveCont, mitigations := ms.ConvertToModel(&cve)
 | 
			
		||||
		uniqKB := map[string]struct{}{}
 | 
			
		||||
		var stats models.PackageFixStatuses
 | 
			
		||||
		for _, p := range cve.Products {
 | 
			
		||||
			for _, kb := range p.KBs {
 | 
			
		||||
				if _, err := strconv.Atoi(kb.Article); err == nil {
 | 
			
		||||
					uniqKB[fmt.Sprintf("KB%s", kb.Article)] = struct{}{}
 | 
			
		||||
				if _, err := strconv.Atoi(kb.Article); err != nil {
 | 
			
		||||
					switch {
 | 
			
		||||
					case strings.HasPrefix(p.Name, "Microsoft Edge"):
 | 
			
		||||
						s := models.PackageFixStatus{
 | 
			
		||||
							Name:     "Microsoft Edge",
 | 
			
		||||
							FixState: "fixed",
 | 
			
		||||
							FixedIn:  kb.FixedBuild,
 | 
			
		||||
						}
 | 
			
		||||
						if kb.FixedBuild == "" {
 | 
			
		||||
							s.FixState = "unknown"
 | 
			
		||||
						}
 | 
			
		||||
						stats = append(stats, s)
 | 
			
		||||
					default:
 | 
			
		||||
						stats = append(stats, models.PackageFixStatus{
 | 
			
		||||
							Name:     p.Name,
 | 
			
		||||
							FixState: "unknown",
 | 
			
		||||
							FixedIn:  kb.FixedBuild,
 | 
			
		||||
						})
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					uniqKB[kb.Article] = struct{}{}
 | 
			
		||||
					uniqKB[fmt.Sprintf("KB%s", kb.Article)] = struct{}{}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if len(uniqKB) == 0 && len(stats) == 0 {
 | 
			
		||||
			for _, p := range cve.Products {
 | 
			
		||||
				switch {
 | 
			
		||||
				case strings.HasPrefix(p.Name, "Microsoft Edge"):
 | 
			
		||||
					stats = append(stats, models.PackageFixStatus{
 | 
			
		||||
						Name:     "Microsoft Edge",
 | 
			
		||||
						FixState: "unknown",
 | 
			
		||||
					})
 | 
			
		||||
				default:
 | 
			
		||||
					stats = append(stats, models.PackageFixStatus{
 | 
			
		||||
						Name:     p.Name,
 | 
			
		||||
						FixState: "unknown",
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		advisories := []models.DistroAdvisory{}
 | 
			
		||||
		for kb := range uniqKB {
 | 
			
		||||
			advisories = append(advisories, models.DistroAdvisory{
 | 
			
		||||
@@ -149,14 +288,16 @@ func (ms Microsoft) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r.ScannedCves[cveID] = models.VulnInfo{
 | 
			
		||||
			CveID:            cveID,
 | 
			
		||||
			Confidences:      models.Confidences{models.WindowsUpdateSearch},
 | 
			
		||||
			DistroAdvisories: advisories,
 | 
			
		||||
			CveContents:      models.NewCveContents(*cveCont),
 | 
			
		||||
			Mitigations:      mitigations,
 | 
			
		||||
			CveID:             cveID,
 | 
			
		||||
			Confidences:       models.Confidences{models.WindowsUpdateSearch},
 | 
			
		||||
			DistroAdvisories:  advisories,
 | 
			
		||||
			CveContents:       models.NewCveContents(*cveCont),
 | 
			
		||||
			Mitigations:       mitigations,
 | 
			
		||||
			AffectedPackages:  stats,
 | 
			
		||||
			WindowsKBFixedIns: maps.Keys(uniqKB),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return len(cves), nil
 | 
			
		||||
	return nCVEs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModel converts gost model to vuls model
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
@@ -189,3 +190,11 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
 | 
			
		||||
func major(osVer string) (majorVersion string) {
 | 
			
		||||
	return strings.Split(osVer, ".")[0]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func unique[T comparable](s []T) []T {
 | 
			
		||||
	m := map[T]struct{}{}
 | 
			
		||||
	for _, v := range s {
 | 
			
		||||
		m[v] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	return maps.Keys(m)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ type ScanResult struct {
 | 
			
		||||
	WordPressPackages WordPressPackages        `json:",omitempty"`
 | 
			
		||||
	GitHubManifests   DependencyGraphManifests `json:"gitHubManifests,omitempty"`
 | 
			
		||||
	LibraryScanners   LibraryScanners          `json:"libraries,omitempty"`
 | 
			
		||||
	WindowsKB         *WindowsKB               `json:"windowsKB,omitempty"`
 | 
			
		||||
	CweDict           CweDict                  `json:"cweDict,omitempty"`
 | 
			
		||||
	Optional          map[string]interface{}   `json:",omitempty"`
 | 
			
		||||
	Config            struct {
 | 
			
		||||
@@ -83,6 +84,12 @@ type Kernel struct {
 | 
			
		||||
	RebootRequired bool   `json:"rebootRequired"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WindowsKB has applied and unapplied KBs
 | 
			
		||||
type WindowsKB struct {
 | 
			
		||||
	Applied   []string `json:"applied,omitempty"`
 | 
			
		||||
	Unapplied []string `json:"unapplied,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterInactiveWordPressLibs is filter function.
 | 
			
		||||
func (r *ScanResult) FilterInactiveWordPressLibs(detectInactive bool) {
 | 
			
		||||
	if detectInactive {
 | 
			
		||||
 
 | 
			
		||||
@@ -267,6 +267,7 @@ type VulnInfo struct {
 | 
			
		||||
	GitHubSecurityAlerts GitHubSecurityAlerts `json:"gitHubSecurityAlerts,omitempty"`
 | 
			
		||||
	WpPackageFixStats    WpPackageFixStats    `json:"wpPackageFixStats,omitempty"`
 | 
			
		||||
	LibraryFixedIns      LibraryFixedIns      `json:"libraryFixedIns,omitempty"`
 | 
			
		||||
	WindowsKBFixedIns    []string             `json:"windowsKBFixedIns,omitempty"`
 | 
			
		||||
	VulnType             string               `json:"vulnType,omitempty"`
 | 
			
		||||
	DiffStatus           DiffStatus           `json:"diffStatus,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
@@ -531,7 +532,7 @@ func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) {
 | 
			
		||||
 | 
			
		||||
// Cvss3Scores returns CVSS V3 Score
 | 
			
		||||
func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
 | 
			
		||||
	order := []CveContentType{RedHatAPI, RedHat, SUSE, Nvd, Jvn}
 | 
			
		||||
	order := []CveContentType{RedHatAPI, RedHat, SUSE, Microsoft, Nvd, Jvn}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if conts, found := v.CveContents[ctype]; found {
 | 
			
		||||
			for _, cont := range conts {
 | 
			
		||||
@@ -661,6 +662,7 @@ func (v VulnInfo) PatchStatus(packs Packages) string {
 | 
			
		||||
	if len(v.CpeURIs) != 0 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, p := range v.AffectedPackages {
 | 
			
		||||
		if p.NotFixedYet {
 | 
			
		||||
			return "unfixed"
 | 
			
		||||
@@ -680,6 +682,13 @@ func (v VulnInfo) PatchStatus(packs Packages) string {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, c := range v.Confidences {
 | 
			
		||||
		if c == WindowsUpdateSearch && len(v.WindowsKBFixedIns) == 0 {
 | 
			
		||||
			return "unfixed"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "fixed"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1717,3 +1717,103 @@ func TestVulnInfos_FilterByConfidenceOver(t *testing.T) {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVulnInfo_PatchStatus(t *testing.T) {
 | 
			
		||||
	type fields struct {
 | 
			
		||||
		Confidences       Confidences
 | 
			
		||||
		AffectedPackages  PackageFixStatuses
 | 
			
		||||
		CpeURIs           []string
 | 
			
		||||
		WindowsKBFixedIns []string
 | 
			
		||||
	}
 | 
			
		||||
	type args struct {
 | 
			
		||||
		packs Packages
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name   string
 | 
			
		||||
		fields fields
 | 
			
		||||
		args   args
 | 
			
		||||
		want   string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "cpe",
 | 
			
		||||
			fields: fields{
 | 
			
		||||
				CpeURIs: []string{"cpe:/a:microsoft:internet_explorer:10"},
 | 
			
		||||
			},
 | 
			
		||||
			want: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "package unfixed",
 | 
			
		||||
			fields: fields{
 | 
			
		||||
				AffectedPackages: PackageFixStatuses{
 | 
			
		||||
					{
 | 
			
		||||
						Name:        "bash",
 | 
			
		||||
						NotFixedYet: true,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: "unfixed",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "package unknown",
 | 
			
		||||
			fields: fields{
 | 
			
		||||
				AffectedPackages: PackageFixStatuses{
 | 
			
		||||
					{
 | 
			
		||||
						Name: "bash",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			args: args{
 | 
			
		||||
				packs: Packages{"bash": {
 | 
			
		||||
					Name: "bash",
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			want: "unknown",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "package fixed",
 | 
			
		||||
			fields: fields{
 | 
			
		||||
				AffectedPackages: PackageFixStatuses{
 | 
			
		||||
					{
 | 
			
		||||
						Name: "bash",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			args: args{
 | 
			
		||||
				packs: Packages{"bash": {
 | 
			
		||||
					Name:       "bash",
 | 
			
		||||
					Version:    "4.3-9.1",
 | 
			
		||||
					NewVersion: "5.0-4",
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			want: "fixed",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "windows unfixed",
 | 
			
		||||
			fields: fields{
 | 
			
		||||
				Confidences: Confidences{WindowsUpdateSearch},
 | 
			
		||||
			},
 | 
			
		||||
			want: "unfixed",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "windows fixed",
 | 
			
		||||
			fields: fields{
 | 
			
		||||
				Confidences:       Confidences{WindowsUpdateSearch},
 | 
			
		||||
				WindowsKBFixedIns: []string{"000000"},
 | 
			
		||||
			},
 | 
			
		||||
			want: "fixed",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			v := VulnInfo{
 | 
			
		||||
				Confidences:       tt.fields.Confidences,
 | 
			
		||||
				AffectedPackages:  tt.fields.AffectedPackages,
 | 
			
		||||
				CpeURIs:           tt.fields.CpeURIs,
 | 
			
		||||
				WindowsKBFixedIns: tt.fields.WindowsKBFixedIns,
 | 
			
		||||
			}
 | 
			
		||||
			if got := v.PatchStatus(tt.args.packs); got != tt.want {
 | 
			
		||||
				t.Errorf("VulnInfo.PatchStatus() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package reporter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -81,24 +80,23 @@ func loadOneServerScanResult(jsonFile string) (*models.ScanResult, error) {
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// jsonDirPattern is file name pattern of JSON directory
 | 
			
		||||
// 2016-11-16T10:43:28+09:00
 | 
			
		||||
// 2016-11-16T10:43:28Z
 | 
			
		||||
var jsonDirPattern = regexp.MustCompile(
 | 
			
		||||
	`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
 | 
			
		||||
 | 
			
		||||
// ListValidJSONDirs returns valid json directory as array
 | 
			
		||||
// Returned array is sorted so that recent directories are at the head
 | 
			
		||||
func ListValidJSONDirs(resultsDir string) (dirs []string, err error) {
 | 
			
		||||
	var dirInfo []fs.DirEntry
 | 
			
		||||
	if dirInfo, err = os.ReadDir(resultsDir); err != nil {
 | 
			
		||||
		err = xerrors.Errorf("Failed to read %s: %w", resultsDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	dirInfo, err := os.ReadDir(resultsDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to read %s: %w", resultsDir, err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range dirInfo {
 | 
			
		||||
		if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
 | 
			
		||||
			jsonDir := filepath.Join(resultsDir, d.Name())
 | 
			
		||||
			dirs = append(dirs, jsonDir)
 | 
			
		||||
		if !d.IsDir() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, layout := range []string{"2006-01-02T15:04:05Z", "2006-01-02T15:04:05-07:00", "2006-01-02T15-04-05-0700"} {
 | 
			
		||||
			if _, err := time.Parse(layout, d.Name()); err == nil {
 | 
			
		||||
				dirs = append(dirs, filepath.Join(resultsDir, d.Name()))
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(dirs, func(i, j int) bool {
 | 
			
		||||
@@ -258,9 +256,13 @@ No CVE-IDs are found in updatable packages.
 | 
			
		||||
		// v2max := vinfo.MaxCvss2Score().Value.Score
 | 
			
		||||
		// v3max := vinfo.MaxCvss3Score().Value.Score
 | 
			
		||||
 | 
			
		||||
		packnames := strings.Join(vinfo.AffectedPackages.Names(), ", ")
 | 
			
		||||
		// packname := vinfo.AffectedPackages.FormatTuiSummary()
 | 
			
		||||
		// packname += strings.Join(vinfo.CpeURIs, ", ")
 | 
			
		||||
		pkgNames := vinfo.AffectedPackages.Names()
 | 
			
		||||
		pkgNames = append(pkgNames, vinfo.CpeURIs...)
 | 
			
		||||
		pkgNames = append(pkgNames, vinfo.GitHubSecurityAlerts.Names()...)
 | 
			
		||||
		pkgNames = append(pkgNames, vinfo.WpPackageFixStats.Names()...)
 | 
			
		||||
		pkgNames = append(pkgNames, vinfo.LibraryFixedIns.Names()...)
 | 
			
		||||
		pkgNames = append(pkgNames, vinfo.WindowsKBFixedIns...)
 | 
			
		||||
		packnames := strings.Join(pkgNames, ", ")
 | 
			
		||||
 | 
			
		||||
		exploits := ""
 | 
			
		||||
		if 0 < len(vinfo.Exploits) || 0 < len(vinfo.Metasploits) {
 | 
			
		||||
@@ -431,6 +433,10 @@ No CVE-IDs are found in updatable packages.
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(vuln.WindowsKBFixedIns) > 0 {
 | 
			
		||||
			data = append(data, []string{"Windows KB", fmt.Sprintf("FixedIn: %s", strings.Join(vuln.WindowsKBFixedIns, ", "))})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, confidence := range vuln.Confidences {
 | 
			
		||||
			data = append(data, []string{"Confidence", confidence.String()})
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,7 @@ type base struct {
 | 
			
		||||
	osPackages
 | 
			
		||||
	LibraryScanners []models.LibraryScanner
 | 
			
		||||
	WordPress       models.WordPressPackages
 | 
			
		||||
	windowsKB       *models.WindowsKB
 | 
			
		||||
 | 
			
		||||
	log   logging.Logger
 | 
			
		||||
	errs  []error
 | 
			
		||||
@@ -506,6 +507,7 @@ func (l *base) convertToModel() models.ScanResult {
 | 
			
		||||
		EnabledDnfModules: l.EnabledDnfModules,
 | 
			
		||||
		WordPressPackages: l.WordPress,
 | 
			
		||||
		LibraryScanners:   l.LibraryScanners,
 | 
			
		||||
		WindowsKB:         l.windowsKB,
 | 
			
		||||
		Optional:          l.ServerInfo.Optional,
 | 
			
		||||
		Errors:            errs,
 | 
			
		||||
		Warnings:          warns,
 | 
			
		||||
 
 | 
			
		||||
@@ -42,16 +42,10 @@ 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) (bool, osTypeInterface, error) {
 | 
			
		||||
func detectDebian(c config.ServerInfo) (bool, osTypeInterface) {
 | 
			
		||||
	if r := exec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
 | 
			
		||||
		if r.Error != nil {
 | 
			
		||||
			return false, nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		if r.ExitStatus == 255 {
 | 
			
		||||
			return false, &unknown{base{ServerInfo: c}}, xerrors.Errorf("Unable to connect via SSH. Scan with -vvv option to print SSH debugging messages and check SSH settings.\n%s", r)
 | 
			
		||||
		}
 | 
			
		||||
		logging.Log.Debugf("Not Debian like Linux. %s", r)
 | 
			
		||||
		return false, nil, nil
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Raspbian
 | 
			
		||||
@@ -64,7 +58,7 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
 | 
			
		||||
		if len(result) > 2 && result[0] == constant.Raspbian {
 | 
			
		||||
			deb := newDebian(c)
 | 
			
		||||
			deb.setDistro(strings.ToLower(trim(result[0])), trim(result[2]))
 | 
			
		||||
			return true, deb, nil
 | 
			
		||||
			return true, deb
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -84,7 +78,7 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
		return true, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
 | 
			
		||||
@@ -104,7 +98,7 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
		return true, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Debian
 | 
			
		||||
@@ -112,11 +106,11 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
 | 
			
		||||
	if r := exec(c, cmd, noSudo); r.isSuccess() {
 | 
			
		||||
		deb := newDebian(c)
 | 
			
		||||
		deb.setDistro(constant.Debian, trim(r.Stdout))
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
		return true, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logging.Log.Debugf("Not Debian like Linux: %s", c.ServerName)
 | 
			
		||||
	return false, nil, nil
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func trim(str string) string {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,17 +3,24 @@ package scanner
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	ex "os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	homedir "github.com/mitchellh/go-homedir"
 | 
			
		||||
	"github.com/saintfish/chardet"
 | 
			
		||||
	"golang.org/x/text/encoding/japanese"
 | 
			
		||||
	"golang.org/x/text/encoding/unicode"
 | 
			
		||||
	"golang.org/x/text/transform"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	homedir "github.com/mitchellh/go-homedir"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type execResult struct {
 | 
			
		||||
@@ -152,15 +159,14 @@ func localExec(c config.ServerInfo, cmdstr string, sudo bool) (result execResult
 | 
			
		||||
	cmdstr = decorateCmd(c, cmdstr, sudo)
 | 
			
		||||
	var cmd *ex.Cmd
 | 
			
		||||
	switch c.Distro.Family {
 | 
			
		||||
	// case conf.FreeBSD, conf.Alpine, conf.Debian:
 | 
			
		||||
	// cmd = ex.Command("/bin/sh", "-c", cmdstr)
 | 
			
		||||
	case constant.Windows:
 | 
			
		||||
		cmd = ex.Command("powershell.exe", "-NoProfile", "-NonInteractive", cmdstr)
 | 
			
		||||
	default:
 | 
			
		||||
		cmd = ex.Command("/bin/sh", "-c", cmdstr)
 | 
			
		||||
	}
 | 
			
		||||
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
	cmd.Stdout = &stdoutBuf
 | 
			
		||||
	cmd.Stderr = &stderrBuf
 | 
			
		||||
 | 
			
		||||
	if err := cmd.Run(); err != nil {
 | 
			
		||||
		result.Error = err
 | 
			
		||||
		if exitError, ok := err.(*ex.ExitError); ok {
 | 
			
		||||
@@ -172,42 +178,47 @@ func localExec(c config.ServerInfo, cmdstr string, sudo bool) (result execResult
 | 
			
		||||
	} else {
 | 
			
		||||
		result.ExitStatus = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result.Stdout = stdoutBuf.String()
 | 
			
		||||
	result.Stderr = stderrBuf.String()
 | 
			
		||||
	result.Stdout = toUTF8(stdoutBuf.String())
 | 
			
		||||
	result.Stderr = toUTF8(stderrBuf.String())
 | 
			
		||||
	result.Cmd = strings.Replace(cmdstr, "\n", "", -1)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execResult) {
 | 
			
		||||
func sshExecExternal(c config.ServerInfo, cmdstr string, sudo bool) (result execResult) {
 | 
			
		||||
	sshBinaryPath, err := ex.LookPath("ssh")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return execResult{Error: err}
 | 
			
		||||
	}
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		sshBinaryPath = "ssh.exe"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	args := []string{"-tt"}
 | 
			
		||||
	var args []string
 | 
			
		||||
 | 
			
		||||
	if c.SSHConfigPath != "" {
 | 
			
		||||
		args = append(args, "-F", c.SSHConfigPath)
 | 
			
		||||
	} else {
 | 
			
		||||
		home, err := homedir.Dir()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			msg := fmt.Sprintf("Failed to get HOME directory: %s", err)
 | 
			
		||||
			result.Stderr = msg
 | 
			
		||||
			result.ExitStatus = 997
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		controlPath := filepath.Join(home, ".vuls", `controlmaster-%r-`+c.ServerName+`.%p`)
 | 
			
		||||
 | 
			
		||||
		args = append(args,
 | 
			
		||||
			"-o", "StrictHostKeyChecking=yes",
 | 
			
		||||
			"-o", "LogLevel=quiet",
 | 
			
		||||
			"-o", "ConnectionAttempts=3",
 | 
			
		||||
			"-o", "ConnectTimeout=10",
 | 
			
		||||
			"-o", "ControlMaster=auto",
 | 
			
		||||
			"-o", fmt.Sprintf("ControlPath=%s", controlPath),
 | 
			
		||||
			"-o", "Controlpersist=10m",
 | 
			
		||||
		)
 | 
			
		||||
		if runtime.GOOS != "windows" {
 | 
			
		||||
			home, err := homedir.Dir()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				msg := fmt.Sprintf("Failed to get HOME directory: %s", err)
 | 
			
		||||
				result.Stderr = msg
 | 
			
		||||
				result.ExitStatus = 997
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			controlPath := filepath.Join(home, ".vuls", `controlmaster-%r-`+c.ServerName+`.%p`)
 | 
			
		||||
			args = append(args,
 | 
			
		||||
				"-o", "ControlMaster=auto",
 | 
			
		||||
				"-o", fmt.Sprintf("ControlPath=%s", controlPath),
 | 
			
		||||
				"-o", "Controlpersist=10m")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Conf.Vvv {
 | 
			
		||||
@@ -228,16 +239,18 @@ func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execRes
 | 
			
		||||
	}
 | 
			
		||||
	args = append(args, c.Host)
 | 
			
		||||
 | 
			
		||||
	cmd = decorateCmd(c, cmd, sudo)
 | 
			
		||||
	cmd = fmt.Sprintf("stty cols 1000; %s", cmd)
 | 
			
		||||
 | 
			
		||||
	args = append(args, cmd)
 | 
			
		||||
	execCmd := ex.Command(sshBinaryPath, args...)
 | 
			
		||||
 | 
			
		||||
	cmdstr = decorateCmd(c, cmdstr, sudo)
 | 
			
		||||
	var cmd *ex.Cmd
 | 
			
		||||
	switch c.Distro.Family {
 | 
			
		||||
	case constant.Windows:
 | 
			
		||||
		cmd = ex.Command(sshBinaryPath, append(args, "powershell.exe", "-NoProfile", "-NonInteractive", fmt.Sprintf(`"%s`, cmdstr))...)
 | 
			
		||||
	default:
 | 
			
		||||
		cmd = ex.Command(sshBinaryPath, append(args, fmt.Sprintf("stty cols 1000; %s", cmdstr))...)
 | 
			
		||||
	}
 | 
			
		||||
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
	execCmd.Stdout = &stdoutBuf
 | 
			
		||||
	execCmd.Stderr = &stderrBuf
 | 
			
		||||
	if err := execCmd.Run(); err != nil {
 | 
			
		||||
	cmd.Stdout = &stdoutBuf
 | 
			
		||||
	cmd.Stderr = &stderrBuf
 | 
			
		||||
	if err := cmd.Run(); err != nil {
 | 
			
		||||
		if e, ok := err.(*ex.ExitError); ok {
 | 
			
		||||
			if s, ok := e.Sys().(syscall.WaitStatus); ok {
 | 
			
		||||
				result.ExitStatus = s.ExitStatus()
 | 
			
		||||
@@ -250,9 +263,8 @@ func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execRes
 | 
			
		||||
	} else {
 | 
			
		||||
		result.ExitStatus = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result.Stdout = stdoutBuf.String()
 | 
			
		||||
	result.Stderr = stderrBuf.String()
 | 
			
		||||
	result.Stdout = toUTF8(stdoutBuf.String())
 | 
			
		||||
	result.Stderr = toUTF8(stderrBuf.String())
 | 
			
		||||
	result.Servername = c.ServerName
 | 
			
		||||
	result.Container = c.Container
 | 
			
		||||
	result.Host = c.Host
 | 
			
		||||
@@ -280,7 +292,7 @@ func dockerShell(family string) string {
 | 
			
		||||
 | 
			
		||||
func decorateCmd(c config.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
	if sudo && c.User != "root" && !c.IsContainer() {
 | 
			
		||||
		cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
			
		||||
		cmd = fmt.Sprintf("sudo %s", cmd)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If you are using pipe and you want to detect preprocessing errors, remove comment out
 | 
			
		||||
@@ -306,10 +318,40 @@ func decorateCmd(c config.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
				c.Container.Name, dockerShell(c.Distro.Family), cmd)
 | 
			
		||||
			// LXC required root privilege
 | 
			
		||||
			if c.User != "root" {
 | 
			
		||||
				cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
			
		||||
				cmd = fmt.Sprintf("sudo %s", cmd)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	//  cmd = fmt.Sprintf("set -x; %s", cmd)
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toUTF8(s string) string {
 | 
			
		||||
	d := chardet.NewTextDetector()
 | 
			
		||||
	res, err := d.DetectBest([]byte(s))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return s
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var bs []byte
 | 
			
		||||
	switch res.Charset {
 | 
			
		||||
	case "UTF-8":
 | 
			
		||||
		bs, err = []byte(s), nil
 | 
			
		||||
	case "UTF-16LE":
 | 
			
		||||
		bs, err = io.ReadAll(transform.NewReader(strings.NewReader(s), unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
 | 
			
		||||
	case "UTF-16BE":
 | 
			
		||||
		bs, err = io.ReadAll(transform.NewReader(strings.NewReader(s), unicode.UTF16(unicode.BigEndian, unicode.UseBOM).NewDecoder()))
 | 
			
		||||
	case "Shift_JIS":
 | 
			
		||||
		bs, err = io.ReadAll(transform.NewReader(strings.NewReader(s), japanese.ShiftJIS.NewDecoder()))
 | 
			
		||||
	case "EUC-JP":
 | 
			
		||||
		bs, err = io.ReadAll(transform.NewReader(strings.NewReader(s), japanese.EUCJP.NewDecoder()))
 | 
			
		||||
	case "ISO-2022-JP":
 | 
			
		||||
		bs, err = io.ReadAll(transform.NewReader(strings.NewReader(s), japanese.ISO2022JP.NewDecoder()))
 | 
			
		||||
	default:
 | 
			
		||||
		bs, err = []byte(s), nil
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return s
 | 
			
		||||
	}
 | 
			
		||||
	return string(bs)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,14 +39,14 @@ func TestDecorateCmd(t *testing.T) {
 | 
			
		||||
			conf:     config.ServerInfo{User: "non-root"},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: "sudo -S ls",
 | 
			
		||||
			expected: "sudo ls",
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "non-root"},
 | 
			
		||||
			cmd:      "ls | grep hoge",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: "sudo -S ls | grep hoge",
 | 
			
		||||
			expected: "sudo ls | grep hoge",
 | 
			
		||||
		},
 | 
			
		||||
		// -------------docker-------------
 | 
			
		||||
		// root sudo false docker
 | 
			
		||||
@@ -192,7 +192,7 @@ func TestDecorateCmd(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `sudo -S lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
 | 
			
		||||
			expected: `sudo lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true, lxc
 | 
			
		||||
		{
 | 
			
		||||
@@ -203,7 +203,7 @@ func TestDecorateCmd(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `sudo -S lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
 | 
			
		||||
			expected: `sudo lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true lxc
 | 
			
		||||
		{
 | 
			
		||||
@@ -214,7 +214,7 @@ func TestDecorateCmd(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls | grep hoge",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `sudo -S lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls | grep hoge'`,
 | 
			
		||||
			expected: `sudo lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls | grep hoge'`,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,12 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	ex "os/exec"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	debver "github.com/knqyf263/go-deb-version"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
@@ -149,64 +151,127 @@ func (s Scanner) Configtest() error {
 | 
			
		||||
 | 
			
		||||
// ViaHTTP scans servers by HTTP header and body
 | 
			
		||||
func ViaHTTP(header http.Header, body string, toLocalFile bool) (models.ScanResult, error) {
 | 
			
		||||
	family := header.Get("X-Vuls-OS-Family")
 | 
			
		||||
	if family == "" {
 | 
			
		||||
		return models.ScanResult{}, errOSFamilyHeader
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	release := header.Get("X-Vuls-OS-Release")
 | 
			
		||||
	if release == "" {
 | 
			
		||||
		return models.ScanResult{}, errOSReleaseHeader
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kernelRelease := header.Get("X-Vuls-Kernel-Release")
 | 
			
		||||
	if kernelRelease == "" {
 | 
			
		||||
		logging.Log.Warn("If X-Vuls-Kernel-Release is not specified, there is a possibility of false detection")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kernelVersion := header.Get("X-Vuls-Kernel-Version")
 | 
			
		||||
	if family == constant.Debian {
 | 
			
		||||
		if kernelVersion == "" {
 | 
			
		||||
			logging.Log.Warn("X-Vuls-Kernel-Version is empty. skip kernel vulnerability detection.")
 | 
			
		||||
		} else {
 | 
			
		||||
			if _, err := debver.NewVersion(kernelVersion); err != nil {
 | 
			
		||||
				logging.Log.Warnf("X-Vuls-Kernel-Version is invalid. skip kernel vulnerability detection. actual kernelVersion: %s, err: %s", kernelVersion, err)
 | 
			
		||||
				kernelVersion = ""
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serverName := header.Get("X-Vuls-Server-Name")
 | 
			
		||||
	if toLocalFile && serverName == "" {
 | 
			
		||||
		return models.ScanResult{}, errServerNameHeader
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	distro := config.Distro{
 | 
			
		||||
		Family:  family,
 | 
			
		||||
		Release: release,
 | 
			
		||||
	family := header.Get("X-Vuls-OS-Family")
 | 
			
		||||
	if family == "" {
 | 
			
		||||
		return models.ScanResult{}, errOSFamilyHeader
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kernel := models.Kernel{
 | 
			
		||||
		Release: kernelRelease,
 | 
			
		||||
		Version: kernelVersion,
 | 
			
		||||
	}
 | 
			
		||||
	installedPackages, srcPackages, err := ParseInstalledPkgs(distro, kernel, body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return models.ScanResult{}, err
 | 
			
		||||
	}
 | 
			
		||||
	switch family {
 | 
			
		||||
	case constant.Windows:
 | 
			
		||||
		osInfo, hotfixs, err := parseSystemInfo(body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return models.ScanResult{}, xerrors.Errorf("Failed to parse systeminfo.exe. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	return models.ScanResult{
 | 
			
		||||
		ServerName: serverName,
 | 
			
		||||
		Family:     family,
 | 
			
		||||
		Release:    release,
 | 
			
		||||
		RunningKernel: models.Kernel{
 | 
			
		||||
		release := header.Get("X-Vuls-OS-Release")
 | 
			
		||||
		if release == "" {
 | 
			
		||||
			release, err = detectOSName(osInfo)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return models.ScanResult{}, xerrors.Errorf("Failed to detect os name. err: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		kernelVersion := header.Get("X-Vuls-Kernel-Version")
 | 
			
		||||
		if kernelVersion == "" {
 | 
			
		||||
			kernelVersion = formatKernelVersion(osInfo)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		w := &windows{
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro: config.Distro{Family: family, Release: release},
 | 
			
		||||
				osPackages: osPackages{
 | 
			
		||||
					Kernel: models.Kernel{Version: kernelVersion},
 | 
			
		||||
				},
 | 
			
		||||
				log: logging.Log,
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		v, err := w.detectKernelVersion(hotfixs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return models.ScanResult{}, xerrors.Errorf("Failed to detect kernel version. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		w.Kernel = models.Kernel{Version: v}
 | 
			
		||||
 | 
			
		||||
		kbs, err := w.detectKBsFromKernelVersion()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return models.ScanResult{}, xerrors.Errorf("Failed to detect KBs from kernel version. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		applied, unapplied := map[string]struct{}{}, map[string]struct{}{}
 | 
			
		||||
		for _, kb := range hotfixs {
 | 
			
		||||
			applied[kb] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
		for _, kb := range kbs.Applied {
 | 
			
		||||
			applied[kb] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
		for _, kb := range kbs.Unapplied {
 | 
			
		||||
			unapplied[kb] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return models.ScanResult{
 | 
			
		||||
			ServerName: serverName,
 | 
			
		||||
			Family:     family,
 | 
			
		||||
			Release:    release,
 | 
			
		||||
			RunningKernel: models.Kernel{
 | 
			
		||||
				Version: v,
 | 
			
		||||
			},
 | 
			
		||||
			WindowsKB:   &models.WindowsKB{Applied: maps.Keys(applied), Unapplied: maps.Keys(unapplied)},
 | 
			
		||||
			ScannedCves: models.VulnInfos{},
 | 
			
		||||
		}, nil
 | 
			
		||||
	default:
 | 
			
		||||
		release := header.Get("X-Vuls-OS-Release")
 | 
			
		||||
		if release == "" {
 | 
			
		||||
			return models.ScanResult{}, errOSReleaseHeader
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		kernelRelease := header.Get("X-Vuls-Kernel-Release")
 | 
			
		||||
		if kernelRelease == "" {
 | 
			
		||||
			logging.Log.Warn("If X-Vuls-Kernel-Release is not specified, there is a possibility of false detection")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		kernelVersion := header.Get("X-Vuls-Kernel-Version")
 | 
			
		||||
		if family == constant.Debian {
 | 
			
		||||
			if kernelVersion == "" {
 | 
			
		||||
				logging.Log.Warn("X-Vuls-Kernel-Version is empty. skip kernel vulnerability detection.")
 | 
			
		||||
			} else {
 | 
			
		||||
				if _, err := debver.NewVersion(kernelVersion); err != nil {
 | 
			
		||||
					logging.Log.Warnf("X-Vuls-Kernel-Version is invalid. skip kernel vulnerability detection. actual kernelVersion: %s, err: %s", kernelVersion, err)
 | 
			
		||||
					kernelVersion = ""
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		distro := config.Distro{
 | 
			
		||||
			Family:  family,
 | 
			
		||||
			Release: release,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		kernel := models.Kernel{
 | 
			
		||||
			Release: kernelRelease,
 | 
			
		||||
			Version: kernelVersion,
 | 
			
		||||
		},
 | 
			
		||||
		Packages:    installedPackages,
 | 
			
		||||
		SrcPackages: srcPackages,
 | 
			
		||||
		ScannedCves: models.VulnInfos{},
 | 
			
		||||
	}, nil
 | 
			
		||||
		}
 | 
			
		||||
		installedPackages, srcPackages, err := ParseInstalledPkgs(distro, kernel, body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return models.ScanResult{}, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return models.ScanResult{
 | 
			
		||||
			ServerName: serverName,
 | 
			
		||||
			Family:     family,
 | 
			
		||||
			Release:    release,
 | 
			
		||||
			RunningKernel: models.Kernel{
 | 
			
		||||
				Release: kernelRelease,
 | 
			
		||||
				Version: kernelVersion,
 | 
			
		||||
			},
 | 
			
		||||
			Packages:    installedPackages,
 | 
			
		||||
			SrcPackages: srcPackages,
 | 
			
		||||
			ScannedCves: models.VulnInfos{},
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseInstalledPkgs parses installed pkgs line
 | 
			
		||||
@@ -342,7 +407,14 @@ func validateSSHConfig(c *config.ServerInfo) error {
 | 
			
		||||
 | 
			
		||||
	logging.Log.Debugf("Validating SSH Settings for Server:%s ...", c.GetServerName())
 | 
			
		||||
 | 
			
		||||
	sshBinaryPath, err := ex.LookPath("ssh")
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		c.Distro.Family = constant.Windows
 | 
			
		||||
	}
 | 
			
		||||
	defer func(c *config.ServerInfo) {
 | 
			
		||||
		c.Distro.Family = ""
 | 
			
		||||
	}(c)
 | 
			
		||||
 | 
			
		||||
	sshBinaryPath, err := lookpath(c.Distro.Family, "ssh")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to lookup ssh binary path. err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -381,7 +453,7 @@ func validateSSHConfig(c *config.ServerInfo) error {
 | 
			
		||||
		return xerrors.New("Failed to find any known_hosts to use. Please check the UserKnownHostsFile and GlobalKnownHostsFile settings for SSH")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sshKeyscanBinaryPath, err := ex.LookPath("ssh-keyscan")
 | 
			
		||||
	sshKeyscanBinaryPath, err := lookpath(c.Distro.Family, "ssh-keyscan")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to lookup ssh-keyscan binary path. err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -392,7 +464,7 @@ func validateSSHConfig(c *config.ServerInfo) error {
 | 
			
		||||
	}
 | 
			
		||||
	serverKeys := parseSSHScan(r.Stdout)
 | 
			
		||||
 | 
			
		||||
	sshKeygenBinaryPath, err := ex.LookPath("ssh-keygen")
 | 
			
		||||
	sshKeygenBinaryPath, err := lookpath(c.Distro.Family, "ssh-keygen")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to lookup ssh-keygen binary path. err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -428,6 +500,19 @@ func validateSSHConfig(c *config.ServerInfo) error {
 | 
			
		||||
		buildSSHKeyScanCmd(sshKeyscanBinaryPath, c.Port, knownHostsPaths[0], sshConfig))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func lookpath(family, file string) (string, error) {
 | 
			
		||||
	switch family {
 | 
			
		||||
	case constant.Windows:
 | 
			
		||||
		return fmt.Sprintf("%s.exe", strings.TrimPrefix(file, ".exe")), nil
 | 
			
		||||
	default:
 | 
			
		||||
		p, err := ex.LookPath(file)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		return p, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func buildSSHBaseCmd(sshBinaryPath string, c *config.ServerInfo, options []string) []string {
 | 
			
		||||
	cmd := []string{sshBinaryPath}
 | 
			
		||||
	if len(options) > 0 {
 | 
			
		||||
@@ -483,6 +568,7 @@ type sshConfiguration struct {
 | 
			
		||||
func parseSSHConfiguration(stdout string) sshConfiguration {
 | 
			
		||||
	sshConfig := sshConfiguration{}
 | 
			
		||||
	for _, line := range strings.Split(stdout, "\n") {
 | 
			
		||||
		line = strings.TrimSuffix(line, "\r")
 | 
			
		||||
		switch {
 | 
			
		||||
		case strings.HasPrefix(line, "user "):
 | 
			
		||||
			sshConfig.user = strings.TrimPrefix(line, "user ")
 | 
			
		||||
@@ -512,6 +598,7 @@ func parseSSHConfiguration(stdout string) sshConfiguration {
 | 
			
		||||
func parseSSHScan(stdout string) map[string]string {
 | 
			
		||||
	keys := map[string]string{}
 | 
			
		||||
	for _, line := range strings.Split(stdout, "\n") {
 | 
			
		||||
		line = strings.TrimSuffix(line, "\r")
 | 
			
		||||
		if line == "" || strings.HasPrefix(line, "# ") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
@@ -524,6 +611,7 @@ func parseSSHScan(stdout string) map[string]string {
 | 
			
		||||
 | 
			
		||||
func parseSSHKeygen(stdout string) (string, string, error) {
 | 
			
		||||
	for _, line := range strings.Split(stdout, "\n") {
 | 
			
		||||
		line = strings.TrimSuffix(line, "\r")
 | 
			
		||||
		if line == "" || strings.HasPrefix(line, "# ") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
@@ -669,10 +757,20 @@ func (s Scanner) detectOS(c config.ServerInfo) osTypeInterface {
 | 
			
		||||
		return osType
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType, fatalErr := s.detectDebianWithRetry(c); fatalErr != nil {
 | 
			
		||||
		osType.setErrs([]error{xerrors.Errorf("Failed to detect OS: %w", fatalErr)})
 | 
			
		||||
	if !isLocalExec(c.Port, c.Host) {
 | 
			
		||||
		if err := testFirstSSHConnection(c); err != nil {
 | 
			
		||||
			osType := &unknown{base{ServerInfo: c}}
 | 
			
		||||
			osType.setErrs([]error{xerrors.Errorf("Failed to test first SSH Connection. err: %w", err)})
 | 
			
		||||
			return osType
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType := detectWindows(c); itsMe {
 | 
			
		||||
		logging.Log.Debugf("Windows. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return osType
 | 
			
		||||
	} else if itsMe {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType := detectDebian(c); itsMe {
 | 
			
		||||
		logging.Log.Debugf("Debian based Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return osType
 | 
			
		||||
	}
 | 
			
		||||
@@ -702,28 +800,23 @@ func (s Scanner) detectOS(c config.ServerInfo) osTypeInterface {
 | 
			
		||||
	return osType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Retry as it may stall on the first SSH connection
 | 
			
		||||
// https://github.com/future-architect/vuls/pull/753
 | 
			
		||||
func (s Scanner) detectDebianWithRetry(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err error) {
 | 
			
		||||
	type Response struct {
 | 
			
		||||
		itsMe bool
 | 
			
		||||
		deb   osTypeInterface
 | 
			
		||||
		err   error
 | 
			
		||||
	}
 | 
			
		||||
	resChan := make(chan Response, 1)
 | 
			
		||||
	go func(c config.ServerInfo) {
 | 
			
		||||
		itsMe, osType, fatalErr := detectDebian(c)
 | 
			
		||||
		resChan <- Response{itsMe, osType, fatalErr}
 | 
			
		||||
	}(c)
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(time.Duration(3) * time.Second)
 | 
			
		||||
	select {
 | 
			
		||||
	case res := <-resChan:
 | 
			
		||||
		return res.itsMe, res.deb, res.err
 | 
			
		||||
	case <-timeout:
 | 
			
		||||
		time.Sleep(100 * time.Millisecond)
 | 
			
		||||
		return detectDebian(c)
 | 
			
		||||
func testFirstSSHConnection(c config.ServerInfo) error {
 | 
			
		||||
	for i := 3; i > 0; i-- {
 | 
			
		||||
		rChan := make(chan execResult, 1)
 | 
			
		||||
		go func() {
 | 
			
		||||
			rChan <- exec(c, "exit", noSudo)
 | 
			
		||||
		}()
 | 
			
		||||
		select {
 | 
			
		||||
		case r := <-rChan:
 | 
			
		||||
			if r.ExitStatus == 255 {
 | 
			
		||||
				return xerrors.Errorf("Unable to connect via SSH. Scan with -vvv option to print SSH debugging messages and check SSH settings.\n%s", r)
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		case <-time.After(time.Duration(3) * time.Second):
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	logging.Log.Warnf("First SSH Connection to Host: %s:%s timeout", c.Host, c.Port)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkScanModes checks scan mode
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@ import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/exp/slices"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
@@ -104,6 +106,74 @@ func TestViaHTTP(t *testing.T) {
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			header: map[string]string{
 | 
			
		||||
				"X-Vuls-OS-Family": "windows",
 | 
			
		||||
			},
 | 
			
		||||
			body: `
 | 
			
		||||
Host Name:                 DESKTOP
 | 
			
		||||
OS Name:                   Microsoft Windows 10 Pro
 | 
			
		||||
OS Version:                10.0.19044 N/A Build 19044
 | 
			
		||||
OS Manufacturer:           Microsoft Corporation
 | 
			
		||||
OS Configuration:          Member Workstation
 | 
			
		||||
OS Build Type:             Multiprocessor Free
 | 
			
		||||
Registered Owner:          Windows User
 | 
			
		||||
Registered Organization:
 | 
			
		||||
Product ID:                00000-00000-00000-AA000
 | 
			
		||||
Original Install Date:     2022/04/13, 12:25:41
 | 
			
		||||
System Boot Time:          2022/06/06, 16:43:45
 | 
			
		||||
System Manufacturer:       HP
 | 
			
		||||
System Model:              HP EliteBook 830 G7 Notebook PC
 | 
			
		||||
System Type:               x64-based PC
 | 
			
		||||
Processor(s):              1 Processor(s) Installed.
 | 
			
		||||
						   [01]: Intel64 Family 6 Model 142 Stepping 12 GenuineIntel ~1803 Mhz
 | 
			
		||||
BIOS Version:              HP S70 Ver. 01.05.00, 2021/04/26
 | 
			
		||||
Windows Directory:         C:\WINDOWS
 | 
			
		||||
System Directory:          C:\WINDOWS\system32
 | 
			
		||||
Boot Device:               \Device\HarddiskVolume2
 | 
			
		||||
System Locale:             en-us;English (United States)
 | 
			
		||||
Input Locale:              en-us;English (United States)
 | 
			
		||||
Time Zone:                 (UTC-08:00) Pacific Time (US & Canada)
 | 
			
		||||
Total Physical Memory:     15,709 MB
 | 
			
		||||
Available Physical Memory: 12,347 MB
 | 
			
		||||
Virtual Memory: Max Size:  18,141 MB
 | 
			
		||||
Virtual Memory: Available: 14,375 MB
 | 
			
		||||
Virtual Memory: In Use:    3,766 MB
 | 
			
		||||
Page File Location(s):     C:\pagefile.sys
 | 
			
		||||
Domain:                    WORKGROUP
 | 
			
		||||
Logon Server:              \\DESKTOP
 | 
			
		||||
Hotfix(s):                 7 Hotfix(s) Installed.
 | 
			
		||||
						   [01]: KB5012117
 | 
			
		||||
						   [02]: KB4562830
 | 
			
		||||
						   [03]: KB5003791
 | 
			
		||||
						   [04]: KB5007401
 | 
			
		||||
						   [05]: KB5012599
 | 
			
		||||
						   [06]: KB5011651
 | 
			
		||||
						   [07]: KB5005699
 | 
			
		||||
Network Card(s):           1 NIC(s) Installed.
 | 
			
		||||
						   [01]: Intel(R) Wi-Fi 6 AX201 160MHz
 | 
			
		||||
								 Connection Name: Wi-Fi
 | 
			
		||||
								 DHCP Enabled:    Yes
 | 
			
		||||
								 DHCP Server:     192.168.0.1
 | 
			
		||||
								 IP address(es)
 | 
			
		||||
								 [01]: 192.168.0.205
 | 
			
		||||
Hyper-V Requirements:      VM Monitor Mode Extensions: Yes
 | 
			
		||||
						   Virtualization Enabled In Firmware: Yes
 | 
			
		||||
						   Second Level Address Translation: Yes
 | 
			
		||||
						   Data Execution Prevention Available: Yes
 | 
			
		||||
`,
 | 
			
		||||
			expectedResult: models.ScanResult{
 | 
			
		||||
				Family:  "windows",
 | 
			
		||||
				Release: "Windows 10 Version 21H2 for x64-based Systems",
 | 
			
		||||
				RunningKernel: models.Kernel{
 | 
			
		||||
					Version: "10.0.19044.1645",
 | 
			
		||||
				},
 | 
			
		||||
				WindowsKB: &models.WindowsKB{
 | 
			
		||||
					Applied:   []string{"5009543", "5011487", "5007401", "5011651", "5008212", "5012117", "4562830", "5005699", "5011543", "5012599", "5007253", "5010793", "5010415", "5003791", "5009596", "5010342"},
 | 
			
		||||
					Unapplied: []string{"5021233", "5019275", "5015020", "5014023", "5014666", "5017380", "5020435", "5020030", "5011831", "5014699", "5017308", "5018482", "5022834", "5016139", "5016688", "5018410", "5022282", "5013942", "5015807", "5015878", "5016616", "5020953", "5019959", "5022906"},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
@@ -144,6 +214,18 @@ func TestViaHTTP(t *testing.T) {
 | 
			
		||||
				t.Errorf("release: expected %s, actual %s", expectedPack.Release, pack.Release)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if tt.expectedResult.WindowsKB != nil {
 | 
			
		||||
			slices.Sort(tt.expectedResult.WindowsKB.Applied)
 | 
			
		||||
			slices.Sort(tt.expectedResult.WindowsKB.Unapplied)
 | 
			
		||||
		}
 | 
			
		||||
		if result.WindowsKB != nil {
 | 
			
		||||
			slices.Sort(result.WindowsKB.Applied)
 | 
			
		||||
			slices.Sort(result.WindowsKB.Unapplied)
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(tt.expectedResult.WindowsKB, result.WindowsKB) {
 | 
			
		||||
			t.Errorf("windows KB: expected %s, actual %s", tt.expectedResult.WindowsKB, result.WindowsKB)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (
 | 
			
		||||
 | 
			
		||||
// EnsureResultDir ensures the directory for scan results
 | 
			
		||||
func EnsureResultDir(resultsDir string, scannedAt time.Time) (currentDir string, err error) {
 | 
			
		||||
	jsonDirName := scannedAt.Format(time.RFC3339)
 | 
			
		||||
	jsonDirName := scannedAt.Format("2006-01-02T15-04-05-0700")
 | 
			
		||||
	if resultsDir == "" {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		resultsDir = filepath.Join(wd, "results")
 | 
			
		||||
@@ -51,19 +51,6 @@ func EnsureResultDir(resultsDir string, scannedAt time.Time) (currentDir string,
 | 
			
		||||
	if err := os.MkdirAll(jsonDir, 0700); err != nil {
 | 
			
		||||
		return "", xerrors.Errorf("Failed to create dir: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	symlinkPath := filepath.Join(resultsDir, "current")
 | 
			
		||||
	if _, err := os.Lstat(symlinkPath); err == nil {
 | 
			
		||||
		if err := os.Remove(symlinkPath); err != nil {
 | 
			
		||||
			return "", xerrors.Errorf(
 | 
			
		||||
				"Failed to remove symlink. path: %s, err: %w", symlinkPath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := os.Symlink(jsonDir, symlinkPath); err != nil {
 | 
			
		||||
		return "", xerrors.Errorf(
 | 
			
		||||
			"Failed to create symlink: path: %s, err: %w", symlinkPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return jsonDir, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4408
									
								
								scanner/windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4408
									
								
								scanner/windows.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										736
									
								
								scanner/windows_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										736
									
								
								scanner/windows_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,736 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"golang.org/x/exp/slices"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Test_parseSystemInfo(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    string
 | 
			
		||||
		osInfo  osInfo
 | 
			
		||||
		kbs     []string
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "happy",
 | 
			
		||||
			args: `
 | 
			
		||||
Host Name:                 DESKTOP
 | 
			
		||||
OS Name:                   Microsoft Windows 10 Pro
 | 
			
		||||
OS Version:                10.0.19044 N/A Build 19044
 | 
			
		||||
OS Manufacturer:           Microsoft Corporation
 | 
			
		||||
OS Configuration:          Member Workstation
 | 
			
		||||
OS Build Type:             Multiprocessor Free
 | 
			
		||||
Registered Owner:          Windows User
 | 
			
		||||
Registered Organization:
 | 
			
		||||
Product ID:                00000-00000-00000-AA000
 | 
			
		||||
Original Install Date:     2022/04/13, 12:25:41
 | 
			
		||||
System Boot Time:          2022/06/06, 16:43:45
 | 
			
		||||
System Manufacturer:       HP
 | 
			
		||||
System Model:              HP EliteBook 830 G7 Notebook PC
 | 
			
		||||
System Type:               x64-based PC
 | 
			
		||||
Processor(s):              1 Processor(s) Installed.
 | 
			
		||||
						   [01]: Intel64 Family 6 Model 142 Stepping 12 GenuineIntel ~1803 Mhz
 | 
			
		||||
BIOS Version:              HP S70 Ver. 01.05.00, 2021/04/26
 | 
			
		||||
Windows Directory:         C:\WINDOWS
 | 
			
		||||
System Directory:          C:\WINDOWS\system32
 | 
			
		||||
Boot Device:               \Device\HarddiskVolume2
 | 
			
		||||
System Locale:             en-us;English (United States)
 | 
			
		||||
Input Locale:              en-us;English (United States)
 | 
			
		||||
Time Zone:                 (UTC-08:00) Pacific Time (US & Canada)
 | 
			
		||||
Total Physical Memory:     15,709 MB
 | 
			
		||||
Available Physical Memory: 12,347 MB
 | 
			
		||||
Virtual Memory: Max Size:  18,141 MB
 | 
			
		||||
Virtual Memory: Available: 14,375 MB
 | 
			
		||||
Virtual Memory: In Use:    3,766 MB
 | 
			
		||||
Page File Location(s):     C:\pagefile.sys
 | 
			
		||||
Domain:                    WORKGROUP
 | 
			
		||||
Logon Server:              \\DESKTOP
 | 
			
		||||
Hotfix(s):                 7 Hotfix(s) Installed.
 | 
			
		||||
						   [01]: KB5012117
 | 
			
		||||
						   [02]: KB4562830
 | 
			
		||||
						   [03]: KB5003791
 | 
			
		||||
						   [04]: KB5007401
 | 
			
		||||
						   [05]: KB5012599
 | 
			
		||||
						   [06]: KB5011651
 | 
			
		||||
						   [07]: KB5005699
 | 
			
		||||
Network Card(s):           1 NIC(s) Installed.
 | 
			
		||||
						   [01]: Intel(R) Wi-Fi 6 AX201 160MHz
 | 
			
		||||
								 Connection Name: Wi-Fi
 | 
			
		||||
								 DHCP Enabled:    Yes
 | 
			
		||||
								 DHCP Server:     192.168.0.1
 | 
			
		||||
								 IP address(es)
 | 
			
		||||
								 [01]: 192.168.0.205
 | 
			
		||||
Hyper-V Requirements:      VM Monitor Mode Extensions: Yes
 | 
			
		||||
						   Virtualization Enabled In Firmware: Yes
 | 
			
		||||
						   Second Level Address Translation: Yes
 | 
			
		||||
						   Data Execution Prevention Available: Yes
 | 
			
		||||
`,
 | 
			
		||||
			osInfo: osInfo{
 | 
			
		||||
				productName:      "Microsoft Windows 10 Pro",
 | 
			
		||||
				version:          "10.0",
 | 
			
		||||
				build:            "19044",
 | 
			
		||||
				revision:         "",
 | 
			
		||||
				edition:          "",
 | 
			
		||||
				servicePack:      "",
 | 
			
		||||
				arch:             "x64-based",
 | 
			
		||||
				installationType: "Client",
 | 
			
		||||
			},
 | 
			
		||||
			kbs: []string{"5012117", "4562830", "5003791", "5007401", "5012599", "5011651", "5005699"},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			osInfo, kbs, err := parseSystemInfo(tt.args)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("parseSystemInfo() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo != tt.osInfo {
 | 
			
		||||
				t.Errorf("parseSystemInfo() got = %v, want %v", osInfo, tt.osInfo)
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(kbs, tt.kbs) {
 | 
			
		||||
				t.Errorf("parseSystemInfo() got = %v, want %v", kbs, tt.kbs)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_parseGetComputerInfo(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    string
 | 
			
		||||
		want    osInfo
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "happy",
 | 
			
		||||
			args: `
 | 
			
		||||
WindowsProductName         : Windows 10 Pro
 | 
			
		||||
OsVersion                  : 10.0.19044
 | 
			
		||||
WindowsEditionId           : Professional
 | 
			
		||||
OsCSDVersion               :
 | 
			
		||||
CsSystemType               : x64-based PC
 | 
			
		||||
WindowsInstallationType    : Client
 | 
			
		||||
`,
 | 
			
		||||
			want: osInfo{
 | 
			
		||||
				productName:      "Windows 10 Pro",
 | 
			
		||||
				version:          "10.0",
 | 
			
		||||
				build:            "19044",
 | 
			
		||||
				revision:         "",
 | 
			
		||||
				edition:          "Professional",
 | 
			
		||||
				servicePack:      "",
 | 
			
		||||
				arch:             "x64-based",
 | 
			
		||||
				installationType: "Client",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			got, err := parseGetComputerInfo(tt.args)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("parseGetComputerInfo() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if got != tt.want {
 | 
			
		||||
				t.Errorf("parseGetComputerInfo() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_parseWmiObject(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    string
 | 
			
		||||
		want    osInfo
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "happy",
 | 
			
		||||
			args: `
 | 
			
		||||
Caption            : Microsoft Windows 10 Pro
 | 
			
		||||
Version            : 10.0.19044
 | 
			
		||||
OperatingSystemSKU : 48
 | 
			
		||||
CSDVersion         :
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DomainRole : 1
 | 
			
		||||
SystemType : x64-based PC`,
 | 
			
		||||
			want: osInfo{
 | 
			
		||||
				productName:      "Microsoft Windows 10 Pro",
 | 
			
		||||
				version:          "10.0",
 | 
			
		||||
				build:            "19044",
 | 
			
		||||
				revision:         "",
 | 
			
		||||
				edition:          "Professional",
 | 
			
		||||
				servicePack:      "",
 | 
			
		||||
				arch:             "x64-based",
 | 
			
		||||
				installationType: "Client",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			got, err := parseWmiObject(tt.args)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("parseWmiObject() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if got != tt.want {
 | 
			
		||||
				t.Errorf("parseWmiObject() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_parseRegistry(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
		arch   string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    args
 | 
			
		||||
		want    osInfo
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "happy",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: `
 | 
			
		||||
ProductName               : Windows 10 Pro
 | 
			
		||||
CurrentVersion            : 6.3
 | 
			
		||||
CurrentMajorVersionNumber : 10
 | 
			
		||||
CurrentMinorVersionNumber : 0
 | 
			
		||||
CurrentBuildNumber        : 19044
 | 
			
		||||
UBR                       : 2364
 | 
			
		||||
EditionID                 : Professional
 | 
			
		||||
InstallationType          : Client`,
 | 
			
		||||
				arch: "AMD64",
 | 
			
		||||
			},
 | 
			
		||||
			want: osInfo{
 | 
			
		||||
				productName:      "Windows 10 Pro",
 | 
			
		||||
				version:          "10.0",
 | 
			
		||||
				build:            "19044",
 | 
			
		||||
				revision:         "2364",
 | 
			
		||||
				edition:          "Professional",
 | 
			
		||||
				servicePack:      "",
 | 
			
		||||
				arch:             "x64-based",
 | 
			
		||||
				installationType: "Client",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			got, err := parseRegistry(tt.args.stdout, tt.args.arch)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("parseRegistry() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("parseRegistry() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_detectOSName(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    osInfo
 | 
			
		||||
		want    string
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "Windows 10 for x64-based Systems",
 | 
			
		||||
			args: osInfo{
 | 
			
		||||
				productName:      "Microsoft Windows 10 Pro",
 | 
			
		||||
				version:          "10.0",
 | 
			
		||||
				build:            "10585",
 | 
			
		||||
				revision:         "",
 | 
			
		||||
				edition:          "Professional",
 | 
			
		||||
				servicePack:      "",
 | 
			
		||||
				arch:             "x64-based",
 | 
			
		||||
				installationType: "Client",
 | 
			
		||||
			},
 | 
			
		||||
			want: "Windows 10 for x64-based Systems",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Windows 10 Version 21H2 for x64-based Systems",
 | 
			
		||||
			args: osInfo{
 | 
			
		||||
				productName:      "Microsoft Windows 10 Pro",
 | 
			
		||||
				version:          "10.0",
 | 
			
		||||
				build:            "19044",
 | 
			
		||||
				revision:         "",
 | 
			
		||||
				edition:          "Professional",
 | 
			
		||||
				servicePack:      "",
 | 
			
		||||
				arch:             "x64-based",
 | 
			
		||||
				installationType: "Client",
 | 
			
		||||
			},
 | 
			
		||||
			want: "Windows 10 Version 21H2 for x64-based Systems",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Windows Server 2022",
 | 
			
		||||
			args: osInfo{
 | 
			
		||||
				productName:      "Windows Server",
 | 
			
		||||
				version:          "10.0",
 | 
			
		||||
				build:            "30000",
 | 
			
		||||
				revision:         "",
 | 
			
		||||
				edition:          "",
 | 
			
		||||
				servicePack:      "",
 | 
			
		||||
				arch:             "x64-based",
 | 
			
		||||
				installationType: "Server",
 | 
			
		||||
			},
 | 
			
		||||
			want: "Windows Server 2022",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "err",
 | 
			
		||||
			args: osInfo{
 | 
			
		||||
				productName:      "Microsoft Windows 10 Pro",
 | 
			
		||||
				version:          "10.0",
 | 
			
		||||
				build:            "build",
 | 
			
		||||
				revision:         "",
 | 
			
		||||
				edition:          "Professional",
 | 
			
		||||
				servicePack:      "",
 | 
			
		||||
				arch:             "x64-based",
 | 
			
		||||
				installationType: "Client",
 | 
			
		||||
			},
 | 
			
		||||
			wantErr: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			got, err := detectOSName(tt.args)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("detectOSName() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if got != tt.want {
 | 
			
		||||
				t.Errorf("detectOSName() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_formatKernelVersion(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args osInfo
 | 
			
		||||
		want string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "major.minor.build.revision",
 | 
			
		||||
			args: osInfo{
 | 
			
		||||
				version:  "10.0",
 | 
			
		||||
				build:    "19045",
 | 
			
		||||
				revision: "2130",
 | 
			
		||||
			},
 | 
			
		||||
			want: "10.0.19045.2130",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "major.minor.build",
 | 
			
		||||
			args: osInfo{
 | 
			
		||||
				version: "10.0",
 | 
			
		||||
				build:   "19045",
 | 
			
		||||
			},
 | 
			
		||||
			want: "10.0.19045",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := formatKernelVersion(tt.args); got != tt.want {
 | 
			
		||||
				t.Errorf("formatKernelVersion() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_parseInstalledPackages(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    args
 | 
			
		||||
		want    models.Packages
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "happy",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: `
 | 
			
		||||
Name         : Git
 | 
			
		||||
Version      : 2.35.1.2
 | 
			
		||||
ProviderName : Programs
 | 
			
		||||
 | 
			
		||||
Name         : Oracle Database 11g Express Edition
 | 
			
		||||
Version      : 11.2.0
 | 
			
		||||
ProviderName : msi
 | 
			
		||||
 | 
			
		||||
Name         : 2022-12 x64 ベース システム用 Windows 10 Version 21H2 の累積更新プログラム (KB5021233)
 | 
			
		||||
Version      :
 | 
			
		||||
ProviderName : msu
 | 
			
		||||
`,
 | 
			
		||||
			},
 | 
			
		||||
			want: models.Packages{
 | 
			
		||||
				"Git": {
 | 
			
		||||
					Name:    "Git",
 | 
			
		||||
					Version: "2.35.1.2",
 | 
			
		||||
				},
 | 
			
		||||
				"Oracle Database 11g Express Edition": {
 | 
			
		||||
					Name:    "Oracle Database 11g Express Edition",
 | 
			
		||||
					Version: "11.2.0",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &windows{}
 | 
			
		||||
			got, _, err := o.parseInstalledPackages(tt.args.stdout)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("windows.parseInstalledPackages() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("windows.parseInstalledPackages() got = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_parseGetHotfix(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    args
 | 
			
		||||
		want    []string
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "happy",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: `
 | 
			
		||||
HotFixID : KB5020872
 | 
			
		||||
 | 
			
		||||
HotFixID : KB4562830
 | 
			
		||||
`,
 | 
			
		||||
			},
 | 
			
		||||
			want:    []string{"5020872", "4562830"},
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &windows{}
 | 
			
		||||
			got, err := o.parseGetHotfix(tt.args.stdout)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("windows.parseGetHotfix() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("windows.parseGetHotfix() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_parseGetPackageMSU(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    args
 | 
			
		||||
		want    []string
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "happy",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: `
 | 
			
		||||
Name         : Git
 | 
			
		||||
Version      : 2.35.1.2
 | 
			
		||||
ProviderName : Programs
 | 
			
		||||
 | 
			
		||||
Name         : Oracle Database 11g Express Edition
 | 
			
		||||
Version      : 11.2.0
 | 
			
		||||
ProviderName : msi
 | 
			
		||||
 | 
			
		||||
Name         : 2022-12 x64 ベース システム用 Windows 10 Version 21H2 の累積更新プログラム (KB5021233)
 | 
			
		||||
Version      :
 | 
			
		||||
ProviderName : msu
 | 
			
		||||
`,
 | 
			
		||||
			},
 | 
			
		||||
			want:    []string{"5021233"},
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &windows{}
 | 
			
		||||
			got, err := o.parseGetPackageMSU(tt.args.stdout)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("windows.parseGetPackageMSU() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("windows.parseGetPackageMSU() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_parseWindowsUpdaterSearch(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    args
 | 
			
		||||
		want    []string
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "happy",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: `5012170
 | 
			
		||||
5021233
 | 
			
		||||
5021088
 | 
			
		||||
`,
 | 
			
		||||
			},
 | 
			
		||||
			want:    []string{"5012170", "5021233", "5021088"},
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &windows{}
 | 
			
		||||
			got, err := o.parseWindowsUpdaterSearch(tt.args.stdout)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("windows.parseWindowsUpdaterSearch() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("windows.parseWindowsUpdaterSearch() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_parseWindowsUpdateHistory(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    args
 | 
			
		||||
		want    []string
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "happy",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: `
 | 
			
		||||
Title      : 2022-10 x64 ベース システム用 Windows 10 Version 21H2 の累積更新プログラム (KB5020435)
 | 
			
		||||
Operation  : 1
 | 
			
		||||
ResultCode : 2
 | 
			
		||||
 | 
			
		||||
Title      : 2022-10 x64 ベース システム用 Windows 10 Version 21H2 の累積更新プログラム (KB5020435)
 | 
			
		||||
Operation  : 2
 | 
			
		||||
ResultCode : 2
 | 
			
		||||
 | 
			
		||||
Title      : 2022-12 x64 (KB5021088) 向け Windows 10 Version 21H2 用 .NET Framework 3.5、4.8 および 4.8.1 の累積的な更新プログラム
 | 
			
		||||
Operation  : 1
 | 
			
		||||
ResultCode : 2
 | 
			
		||||
 | 
			
		||||
Title      : 2022-12 x64 ベース システム用 Windows 10 Version 21H2 の累積更新プログラム (KB5021233)
 | 
			
		||||
Operation  : 1
 | 
			
		||||
ResultCode : 2
 | 
			
		||||
`,
 | 
			
		||||
			},
 | 
			
		||||
			want:    []string{"5021088", "5021233"},
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &windows{}
 | 
			
		||||
			got, err := o.parseWindowsUpdateHistory(tt.args.stdout)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("windows.parseWindowsUpdateHistory() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			slices.Sort(got)
 | 
			
		||||
			if !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("windows.parseWindowsUpdateHistory() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_windows_detectKernelVersion(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		base    base
 | 
			
		||||
		args    []string
 | 
			
		||||
		want    string
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "major.minor.build, applied on 10",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.19045"}},
 | 
			
		||||
			},
 | 
			
		||||
			args: []string{"5020030", "5019275"},
 | 
			
		||||
			want: "10.0.19045.2546",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "major.minor.build, zero applied on 10",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.19045"}},
 | 
			
		||||
			},
 | 
			
		||||
			args: []string{},
 | 
			
		||||
			want: "10.0.19045",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "major.minor.build.revision",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.19045.2130"}},
 | 
			
		||||
			},
 | 
			
		||||
			want: "10.0.19045.2130",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "major.minor.build, applied on 11",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows 11 Version 22H2 for x64-based Systems"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.22621"}},
 | 
			
		||||
			},
 | 
			
		||||
			args: []string{"5017389", "5022303"},
 | 
			
		||||
			want: "10.0.22621.1105",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "major.minor.build, applied on server 2022",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows Server 2022"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.20348"}},
 | 
			
		||||
			},
 | 
			
		||||
			args: []string{"5022842"},
 | 
			
		||||
			want: "10.0.20348.1547",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "major.minor",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0"}},
 | 
			
		||||
			},
 | 
			
		||||
			wantErr: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &windows{
 | 
			
		||||
				base: tt.base,
 | 
			
		||||
			}
 | 
			
		||||
			got, err := o.detectKernelVersion(tt.args)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("windows.detectKernelVersion() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if got != tt.want {
 | 
			
		||||
				t.Errorf("windows.detectKernelVersion() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_windows_detectKBsFromKernelVersion(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		base    base
 | 
			
		||||
		want    models.WindowsKB
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "10.0.19045.2129",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.19045.2129"}},
 | 
			
		||||
			},
 | 
			
		||||
			want: models.WindowsKB{
 | 
			
		||||
				Applied:   nil,
 | 
			
		||||
				Unapplied: []string{"5020953", "5019959", "5020030", "5021233", "5022282", "5019275", "5022834", "5022906"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "10.0.19045.2130",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.19045.2130"}},
 | 
			
		||||
			},
 | 
			
		||||
			want: models.WindowsKB{
 | 
			
		||||
				Applied:   nil,
 | 
			
		||||
				Unapplied: []string{"5020953", "5019959", "5020030", "5021233", "5022282", "5019275", "5022834", "5022906"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "10.0.22621.1105",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows 11 Version 22H2 for x64-based Systems"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.22621.1105"}},
 | 
			
		||||
			},
 | 
			
		||||
			want: models.WindowsKB{
 | 
			
		||||
				Applied:   []string{"5019311", "5017389", "5018427", "5019509", "5018496", "5019980", "5020044", "5021255", "5022303"},
 | 
			
		||||
				Unapplied: []string{"5022360", "5022845"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "10.0.20348.1547",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows Server 2022"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.20348.1547"}},
 | 
			
		||||
			},
 | 
			
		||||
			want: models.WindowsKB{
 | 
			
		||||
				Applied:   []string{"5005575", "5005619", "5006699", "5006745", "5007205", "5007254", "5008223", "5010197", "5009555", "5010796", "5009608", "5010354", "5010421", "5011497", "5011558", "5012604", "5012637", "5013944", "5015013", "5014021", "5014678", "5014665", "5015827", "5015879", "5016627", "5016693", "5017316", "5017381", "5018421", "5020436", "5018485", "5019081", "5021656", "5020032", "5021249", "5022553", "5022291", "5022842"},
 | 
			
		||||
				Unapplied: nil,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "err",
 | 
			
		||||
			base: base{
 | 
			
		||||
				Distro:     config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
 | 
			
		||||
				osPackages: osPackages{Kernel: models.Kernel{Version: "10.0"}},
 | 
			
		||||
			},
 | 
			
		||||
			wantErr: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &windows{
 | 
			
		||||
				base: tt.base,
 | 
			
		||||
			}
 | 
			
		||||
			got, err := o.detectKBsFromKernelVersion()
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("windows.detectKBsFromKernelVersion() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("windows.detectKBsFromKernelVersion() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -39,13 +39,14 @@ func (h VulsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mediatype == "application/json" {
 | 
			
		||||
	switch mediatype {
 | 
			
		||||
	case "application/json":
 | 
			
		||||
		if err = json.NewDecoder(req.Body).Decode(&r); err != nil {
 | 
			
		||||
			logging.Log.Error(err)
 | 
			
		||||
			http.Error(w, "Invalid JSON", http.StatusBadRequest)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	} else if mediatype == "text/plain" {
 | 
			
		||||
	case "text/plain":
 | 
			
		||||
		buf := new(bytes.Buffer)
 | 
			
		||||
		if _, err := io.Copy(buf, req.Body); err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusBadRequest)
 | 
			
		||||
@@ -56,7 +57,7 @@ func (h VulsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusBadRequest)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
	default:
 | 
			
		||||
		logging.Log.Error(mediatype)
 | 
			
		||||
		http.Error(w, fmt.Sprintf("Invalid Content-Type: %s", contentType), http.StatusUnsupportedMediaType)
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -247,6 +247,10 @@ host                = "{{$ip}}"
 | 
			
		||||
#scanTechniques = ["sS"]
 | 
			
		||||
#sourcePort = "65535"
 | 
			
		||||
 | 
			
		||||
#[servers.{{index $names $i}}.windows]
 | 
			
		||||
#serverSelection = 3
 | 
			
		||||
#cabPath = "/path/to/wsusscn2.cab"
 | 
			
		||||
 | 
			
		||||
#[servers.{{index $names $i}}.optional]
 | 
			
		||||
#key = "value1"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
//go:build !scanner
 | 
			
		||||
// +build !scanner
 | 
			
		||||
//go:build !scanner && !windows
 | 
			
		||||
 | 
			
		||||
package subcmds
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										372
									
								
								subcmds/report_windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								subcmds/report_windows.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,372 @@
 | 
			
		||||
//go:build !scanner && windows
 | 
			
		||||
 | 
			
		||||
package subcmds
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/utils"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/detector"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/reporter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ReportCmd is subcommand for reporting
 | 
			
		||||
type ReportCmd struct {
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	formatJSON          bool
 | 
			
		||||
	formatOneEMail      bool
 | 
			
		||||
	formatCsv           bool
 | 
			
		||||
	formatFullText      bool
 | 
			
		||||
	formatOneLineText   bool
 | 
			
		||||
	formatList          bool
 | 
			
		||||
	formatCycloneDXJSON bool
 | 
			
		||||
	formatCycloneDXXML  bool
 | 
			
		||||
	gzip                bool
 | 
			
		||||
 | 
			
		||||
	toSlack      bool
 | 
			
		||||
	toChatWork   bool
 | 
			
		||||
	toGoogleChat bool
 | 
			
		||||
	toTelegram   bool
 | 
			
		||||
	toEmail      bool
 | 
			
		||||
	toLocalFile  bool
 | 
			
		||||
	toS3         bool
 | 
			
		||||
	toAzureBlob  bool
 | 
			
		||||
	toHTTP       bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ReportCmd) Name() string { return "report" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ReportCmd) Synopsis() string { return "Reporting" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ReportCmd) Usage() string {
 | 
			
		||||
	return `report:
 | 
			
		||||
	report
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-to-file]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-confidence-over=80]
 | 
			
		||||
		[-diff]
 | 
			
		||||
		[-diff-minus]
 | 
			
		||||
		[-diff-plus]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-to-email]
 | 
			
		||||
		[-to-http]
 | 
			
		||||
		[-to-slack]
 | 
			
		||||
		[-to-chatwork]
 | 
			
		||||
		[-to-googlechat]
 | 
			
		||||
		[-to-telegram]
 | 
			
		||||
		[-to-localfile]
 | 
			
		||||
		[-to-s3]
 | 
			
		||||
		[-to-azure-blob]
 | 
			
		||||
		[-format-json]
 | 
			
		||||
		[-format-one-email]
 | 
			
		||||
		[-format-one-line-text]
 | 
			
		||||
		[-format-list]
 | 
			
		||||
		[-format-full-text]
 | 
			
		||||
		[-format-csv]
 | 
			
		||||
		[-format-cyclonedx-json]
 | 
			
		||||
		[-format-cyclonedx-xml]
 | 
			
		||||
		[-gzip]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-quiet]
 | 
			
		||||
		[-no-progress]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
		[-http="http://vuls-report-server"]
 | 
			
		||||
		[-trivy-cachedb-dir=/path/to/dir]
 | 
			
		||||
 | 
			
		||||
		[RFC3339 datetime format under results dir]
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&config.Conf.Lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&config.Conf.Debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&config.Conf.DebugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
	f.BoolVar(&config.Conf.Quiet, "quiet", false, "Quiet mode. No output on stdout")
 | 
			
		||||
	f.BoolVar(&config.Conf.NoProgress, "no-progress", false, "Suppress progress bar")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&config.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := logging.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&config.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
	f.BoolVar(&config.Conf.LogToFile, "log-to-file", false, "Output log to file")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&config.Conf.RefreshCve, "refresh-cve", false,
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(&config.Conf.CvssScoreOver, "cvss-over", 0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.IntVar(&config.Conf.ConfidenceScoreOver, "confidence-over", 80,
 | 
			
		||||
		"-confidence-over=40 means reporting Confidence Score 40 and over (default: 80)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&config.Conf.DiffMinus, "diff-minus", false,
 | 
			
		||||
		"Minus Difference between previous result and current result")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&config.Conf.DiffPlus, "diff-plus", false,
 | 
			
		||||
		"Plus Difference between previous result and current result")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&config.Conf.Diff, "diff", false,
 | 
			
		||||
		"Plus & Minus Difference between previous result and current result")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&config.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&config.Conf.IgnoreUnfixed, "ignore-unfixed", false,
 | 
			
		||||
		"Don't report the unfixed CVEs")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&config.Conf.HTTPProxy, "http-proxy", "",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatJSON, "format-json", false, "JSON format")
 | 
			
		||||
	f.BoolVar(&p.formatCsv, "format-csv", false, "CSV format")
 | 
			
		||||
	f.BoolVar(&p.formatOneEMail, "format-one-email", false,
 | 
			
		||||
		"Send all the host report via only one EMail (Specify with -to-email)")
 | 
			
		||||
	f.BoolVar(&p.formatOneLineText, "format-one-line-text", false,
 | 
			
		||||
		"One line summary in plain text")
 | 
			
		||||
	f.BoolVar(&p.formatList, "format-list", false, "Display as list format")
 | 
			
		||||
	f.BoolVar(&p.formatFullText, "format-full-text", false,
 | 
			
		||||
		"Detail report in plain text")
 | 
			
		||||
	f.BoolVar(&p.formatCycloneDXJSON, "format-cyclonedx-json", false, "CycloneDX JSON format")
 | 
			
		||||
	f.BoolVar(&p.formatCycloneDXXML, "format-cyclonedx-xml", false, "CycloneDX XML format")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
 | 
			
		||||
	f.BoolVar(&p.toChatWork, "to-chatwork", false, "Send report via chatwork")
 | 
			
		||||
	f.BoolVar(&p.toGoogleChat, "to-googlechat", false, "Send report via Google Chat")
 | 
			
		||||
	f.BoolVar(&p.toTelegram, "to-telegram", false, "Send report via Telegram")
 | 
			
		||||
	f.BoolVar(&p.toEmail, "to-email", false, "Send report via Email")
 | 
			
		||||
	f.BoolVar(&p.toLocalFile, "to-localfile", false, "Write report to localfile")
 | 
			
		||||
	f.BoolVar(&p.toS3, "to-s3", false, "Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/txt)")
 | 
			
		||||
	f.BoolVar(&p.toHTTP, "to-http", false, "Send report via HTTP POST")
 | 
			
		||||
	f.BoolVar(&p.toAzureBlob, "to-azure-blob", false,
 | 
			
		||||
		"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/txt)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.gzip, "gzip", false, "gzip compression")
 | 
			
		||||
	f.BoolVar(&config.Conf.Pipe, "pipe", false, "Use args passed via PIPE")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&config.Conf.TrivyCacheDBDir, "trivy-cachedb-dir",
 | 
			
		||||
		utils.DefaultCacheDir(), "/path/to/dir")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	logging.Log = logging.NewCustomLogger(config.Conf.Debug, config.Conf.Quiet, config.Conf.LogToFile, config.Conf.LogDir, "", "")
 | 
			
		||||
	logging.Log.Infof("vuls-%s-%s", config.Version, config.Revision)
 | 
			
		||||
 | 
			
		||||
	if p.configPath == "" {
 | 
			
		||||
		for _, cnf := range []config.VulnDictInterface{
 | 
			
		||||
			&config.Conf.CveDict,
 | 
			
		||||
			&config.Conf.OvalDict,
 | 
			
		||||
			&config.Conf.Gost,
 | 
			
		||||
			&config.Conf.Exploit,
 | 
			
		||||
			&config.Conf.Metasploit,
 | 
			
		||||
			&config.Conf.KEVuln,
 | 
			
		||||
		} {
 | 
			
		||||
			cnf.Init()
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if err := config.Load(p.configPath); err != nil {
 | 
			
		||||
			logging.Log.Errorf("Error loading %s. err: %+v", p.configPath, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	config.Conf.Slack.Enabled = p.toSlack
 | 
			
		||||
	config.Conf.ChatWork.Enabled = p.toChatWork
 | 
			
		||||
	config.Conf.GoogleChat.Enabled = p.toGoogleChat
 | 
			
		||||
	config.Conf.Telegram.Enabled = p.toTelegram
 | 
			
		||||
	config.Conf.EMail.Enabled = p.toEmail
 | 
			
		||||
	config.Conf.AWS.Enabled = p.toS3
 | 
			
		||||
	config.Conf.Azure.Enabled = p.toAzureBlob
 | 
			
		||||
	config.Conf.HTTP.Enabled = p.toHTTP
 | 
			
		||||
 | 
			
		||||
	if config.Conf.Diff {
 | 
			
		||||
		config.Conf.DiffPlus, config.Conf.DiffMinus = true, true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var dir string
 | 
			
		||||
	var err error
 | 
			
		||||
	if config.Conf.DiffPlus || config.Conf.DiffMinus {
 | 
			
		||||
		dir, err = reporter.JSONDir(config.Conf.ResultsDir, []string{})
 | 
			
		||||
	} else {
 | 
			
		||||
		dir, err = reporter.JSONDir(config.Conf.ResultsDir, f.Args())
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logging.Log.Errorf("Failed to read from JSON: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logging.Log.Info("Validating config...")
 | 
			
		||||
	if !config.Conf.ValidateOnReport() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !(p.formatJSON || p.formatOneLineText ||
 | 
			
		||||
		p.formatList || p.formatFullText || p.formatCsv ||
 | 
			
		||||
		p.formatCycloneDXJSON || p.formatCycloneDXXML) {
 | 
			
		||||
		p.formatList = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var loaded models.ScanResults
 | 
			
		||||
	if loaded, err = reporter.LoadScanResults(dir); err != nil {
 | 
			
		||||
		logging.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	logging.Log.Infof("Loaded: %s", dir)
 | 
			
		||||
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	hasError := false
 | 
			
		||||
	for _, r := range loaded {
 | 
			
		||||
		if len(r.Errors) == 0 {
 | 
			
		||||
			res = append(res, r)
 | 
			
		||||
		} else {
 | 
			
		||||
			logging.Log.Errorf("Ignored since errors occurred during scanning: %s, err: %v",
 | 
			
		||||
				r.ServerName, r.Errors)
 | 
			
		||||
			hasError = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(res) == 0 {
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range res {
 | 
			
		||||
		logging.Log.Debugf("%s: %s",
 | 
			
		||||
			r.ServerInfo(), pp.Sprintf("%s", config.Conf.Servers[r.ServerName]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if res, err = detector.Detect(res, dir); err != nil {
 | 
			
		||||
		logging.Log.Errorf("%+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []reporter.ResultWriter{
 | 
			
		||||
		reporter.StdoutWriter{
 | 
			
		||||
			FormatFullText:    p.formatFullText,
 | 
			
		||||
			FormatOneLineText: p.formatOneLineText,
 | 
			
		||||
			FormatList:        p.formatList,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toSlack {
 | 
			
		||||
		reports = append(reports, reporter.SlackWriter{
 | 
			
		||||
			FormatOneLineText: p.formatOneLineText,
 | 
			
		||||
			Cnf:               config.Conf.Slack,
 | 
			
		||||
			Proxy:             config.Conf.HTTPProxy,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toChatWork {
 | 
			
		||||
		reports = append(reports, reporter.ChatWorkWriter{Cnf: config.Conf.ChatWork, Proxy: config.Conf.HTTPProxy})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toGoogleChat {
 | 
			
		||||
		reports = append(reports, reporter.GoogleChatWriter{Cnf: config.Conf.GoogleChat, Proxy: config.Conf.HTTPProxy})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toTelegram {
 | 
			
		||||
		reports = append(reports, reporter.TelegramWriter{Cnf: config.Conf.Telegram})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toEmail {
 | 
			
		||||
		reports = append(reports, reporter.EMailWriter{
 | 
			
		||||
			FormatOneEMail:    p.formatOneEMail,
 | 
			
		||||
			FormatOneLineText: p.formatOneLineText,
 | 
			
		||||
			FormatList:        p.formatList,
 | 
			
		||||
			Cnf:               config.Conf.EMail,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toHTTP {
 | 
			
		||||
		reports = append(reports, reporter.HTTPRequestWriter{URL: config.Conf.HTTP.URL})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toLocalFile {
 | 
			
		||||
		reports = append(reports, reporter.LocalFileWriter{
 | 
			
		||||
			CurrentDir:          dir,
 | 
			
		||||
			DiffPlus:            config.Conf.DiffPlus,
 | 
			
		||||
			DiffMinus:           config.Conf.DiffMinus,
 | 
			
		||||
			FormatJSON:          p.formatJSON,
 | 
			
		||||
			FormatCsv:           p.formatCsv,
 | 
			
		||||
			FormatFullText:      p.formatFullText,
 | 
			
		||||
			FormatOneLineText:   p.formatOneLineText,
 | 
			
		||||
			FormatList:          p.formatList,
 | 
			
		||||
			FormatCycloneDXJSON: p.formatCycloneDXJSON,
 | 
			
		||||
			FormatCycloneDXXML:  p.formatCycloneDXXML,
 | 
			
		||||
			Gzip:                p.gzip,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toS3 {
 | 
			
		||||
		w := reporter.S3Writer{
 | 
			
		||||
			FormatJSON:        p.formatJSON,
 | 
			
		||||
			FormatFullText:    p.formatFullText,
 | 
			
		||||
			FormatOneLineText: p.formatOneLineText,
 | 
			
		||||
			FormatList:        p.formatList,
 | 
			
		||||
			Gzip:              p.gzip,
 | 
			
		||||
			AWSConf:           config.Conf.AWS,
 | 
			
		||||
		}
 | 
			
		||||
		if err := w.Validate(); err != nil {
 | 
			
		||||
			logging.Log.Errorf("Check if there is a bucket beforehand: %s, err: %+v", config.Conf.AWS.S3Bucket, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, w)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toAzureBlob {
 | 
			
		||||
		w := reporter.AzureBlobWriter{
 | 
			
		||||
			FormatJSON:        p.formatJSON,
 | 
			
		||||
			FormatFullText:    p.formatFullText,
 | 
			
		||||
			FormatOneLineText: p.formatOneLineText,
 | 
			
		||||
			FormatList:        p.formatList,
 | 
			
		||||
			Gzip:              p.gzip,
 | 
			
		||||
			AzureConf:         config.Conf.Azure,
 | 
			
		||||
		}
 | 
			
		||||
		if err := w.Validate(); err != nil {
 | 
			
		||||
			logging.Log.Errorf("Check if there is a container beforehand: %s, err: %+v", config.Conf.Azure.ContainerName, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, w)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(res...); err != nil {
 | 
			
		||||
			logging.Log.Errorf("Failed to report. err: %+v", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if hasError {
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
@@ -614,6 +614,7 @@ func summaryLines(r models.ScanResult) string {
 | 
			
		||||
		pkgNames = append(pkgNames, vinfo.GitHubSecurityAlerts.Names()...)
 | 
			
		||||
		pkgNames = append(pkgNames, vinfo.WpPackageFixStats.Names()...)
 | 
			
		||||
		pkgNames = append(pkgNames, vinfo.LibraryFixedIns.Names()...)
 | 
			
		||||
		pkgNames = append(pkgNames, vinfo.WindowsKBFixedIns...)
 | 
			
		||||
 | 
			
		||||
		av := vinfo.AttackVector()
 | 
			
		||||
		for _, pname := range vinfo.AffectedPackages.Names() {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user