diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index ea5ea805e3..fa91a7178c 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -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 diff --git a/.goreleaser.yml b/.goreleaser.yml index e789f2f309..a4dd6bc271 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -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 @@ -77,6 +109,7 @@ builds: - CGO_ENABLED=0 goos: - linux + - windows goarch: - 386 - amd64 @@ -115,8 +148,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 diff --git a/GNUmakefile b/GNUmakefile index 1f96adecb9..fa6bfcbbdd 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -87,10 +87,10 @@ build-snmp2cpe: ./contrib/snmp2cpe/cmd/main.go # integration-test BASE_DIR := '${PWD}/integration/results' -# $(shell mkdir -p ${BASE_DIR}) -NOW=$(shell date --iso-8601=seconds) +CURRENT := `find ${BASE_DIR} -type d -exec basename {} \; | sort -nr | head -n 1` +NOW=$(shell date '+%Y-%m-%dT%H-%M-%S%z') NOW_JSON_DIR := '${BASE_DIR}/$(NOW)' -ONE_SEC_AFTER=$(shell date -d '+1 second' --iso-8601=seconds) +ONE_SEC_AFTER=$(shell date -d '+1 second' '+%Y-%m-%dT%H-%M-%S%z') ONE_SEC_AFTER_JSON_DIR := '${BASE_DIR}/$(ONE_SEC_AFTER)' LIBS := 'bundler' 'pip' 'pipenv' 'poetry' 'composer' 'npm' 'yarn' 'pnpm' 'cargo' 'gomod' 'gosum' 'gobinary' 'jar' 'pom' 'gradle' 'nuget-lock' 'nuget-config' 'dotnet-deps' 'conan' 'nvd_exact' 'nvd_rough' 'nvd_vendor_product' 'nvd_match_no_jvn' 'jvn_vendor_product' 'jvn_vendor_product_nover' @@ -110,14 +110,14 @@ endif mkdir -p ${NOW_JSON_DIR} sleep 1 ./vuls.old scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS} - cp ${BASE_DIR}/current/*.json ${NOW_JSON_DIR} + cp ${BASE_DIR}/$(CURRENT)/*.json ${NOW_JSON_DIR} - cp integration/data/results/*.json ${NOW_JSON_DIR} ./vuls.old report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${NOW} mkdir -p ${ONE_SEC_AFTER_JSON_DIR} sleep 1 ./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS} - cp ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR} + cp ${BASE_DIR}/$(CURRENT)/*.json ${ONE_SEC_AFTER_JSON_DIR} - cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR} ./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${ONE_SEC_AFTER} @@ -143,14 +143,14 @@ endif mkdir -p ${NOW_JSON_DIR} sleep 1 ./vuls.old scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS} - cp -f ${BASE_DIR}/current/*.json ${NOW_JSON_DIR} + cp -f ${BASE_DIR}/$(CURRENT)/*.json ${NOW_JSON_DIR} - cp integration/data/results/*.json ${NOW_JSON_DIR} ./vuls.old report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${NOW} mkdir -p ${ONE_SEC_AFTER_JSON_DIR} sleep 1 ./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS} - cp -f ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR} + cp -f ${BASE_DIR}/$(CURRENT)/*.json ${ONE_SEC_AFTER_JSON_DIR} - cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR} ./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${ONE_SEC_AFTER} @@ -167,14 +167,14 @@ endif sleep 1 # new vs new ./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS} - cp -f ${BASE_DIR}/current/*.json ${NOW_JSON_DIR} + cp -f ${BASE_DIR}/$(CURRENT)/*.json ${NOW_JSON_DIR} cp integration/data/results/*.json ${NOW_JSON_DIR} ./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${NOW} mkdir -p ${ONE_SEC_AFTER_JSON_DIR} sleep 1 ./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS} - cp -f ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR} + cp -f ${BASE_DIR}/$(CURRENT)/*.json ${ONE_SEC_AFTER_JSON_DIR} cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR} ./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${ONE_SEC_AFTER} diff --git a/README.md b/README.md index 37769d30ab..afca825692 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/config/config.go b/config/config.go index 13667f546c..5c18b0b046 100644 --- a/config/config.go +++ b/config/config.go @@ -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"` diff --git a/config/config_windows.go b/config/config_windows.go new file mode 100644 index 0000000000..adce777957 --- /dev/null +++ b/config/config_windows.go @@ -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 +} diff --git a/config/os.go b/config/os.go index 3f8a8cbe1f..67a1500af3 100644 --- a/config/os.go +++ b/config/os.go @@ -315,6 +315,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 } diff --git a/config/os_test.go b/config/os_test.go index d8747bf7b2..6052a9f658 100644 --- a/config/os_test.go +++ b/config/os_test.go @@ -623,6 +623,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) { diff --git a/config/syslogconf.go b/config/syslogconf.go index d68fe70f86..33cfdcbf68 100644 --- a/config/syslogconf.go +++ b/config/syslogconf.go @@ -1,3 +1,5 @@ +//go:build !windows + package config import ( diff --git a/config/tomlloader.go b/config/tomlloader.go index 63948363ed..f9f704a769 100644 --- a/config/tomlloader.go +++ b/config/tomlloader.go @@ -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 } diff --git a/config/windows.go b/config/windows.go new file mode 100644 index 0000000000..1121477b10 --- /dev/null +++ b/config/windows.go @@ -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 +} diff --git a/detector/detector.go b/detector/detector.go index 59437f874b..c8880eaa1d 100644 --- a/detector/detector.go +++ b/detector/detector.go @@ -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) diff --git a/detector/util.go b/detector/util.go index a89357435a..a119b1e550 100644 --- a/detector/util.go +++ b/detector/util.go @@ -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 { diff --git a/go.mod b/go.mod index a41d2a1e29..07fce3cf03 100644 --- a/go.mod +++ b/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 @@ -38,19 +38,21 @@ require ( github.com/parnurzeal/gorequest v0.2.16 github.com/pkg/errors v0.9.1 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 ) @@ -67,9 +69,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 @@ -143,7 +145,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 @@ -151,7 +153,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 @@ -163,7 +165,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 diff --git a/go.sum b/go.sum index 39fc911c86..444f105f19 100644 --- a/go.sum +++ b/go.sum @@ -233,8 +233,8 @@ github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2y github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 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= @@ -244,9 +244,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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= @@ -720,6 +719,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= @@ -741,8 +742,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= @@ -771,24 +772,25 @@ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8 github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 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.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= @@ -879,7 +881,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= @@ -916,6 +917,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +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-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1069,7 +1071,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= diff --git a/gost/microsoft.go b/gost/microsoft.go index 831040b8b8..ffd52838a4 100644 --- a/gost/microsoft.go +++ b/gost/microsoft.go @@ -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) + } + + 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 osName string - osName, ok := r.Optional["OSName"].(string) - if !ok { - logging.Log.Warnf("This Windows has wrong type option(OSName). UUID: %s", r.ServerUUID) + 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) + 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) + } + + 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) } - case nil: - logging.Log.Warnf("This Windows has no option(InstalledProducts). UUID: %s", r.ServerUUID) + 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 { + 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 } - 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) + 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 } - unapplied[strings.TrimPrefix(s, "KB")] = struct{}{} - } - case []string: - for _, kbID := range kbIDs { - unapplied[strings.TrimPrefix(kbID, "KB")] = struct{}{} + 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)) + } } - case nil: - logging.Log.Warnf("This Windows has no option(KBID). UUID: %s", r.ServerUUID) + default: } + } + filtered = unique(append(filtered, maps.Keys(m)...)) - for _, pkg := range r.Packages { - matches := kbIDPattern.FindAllStringSubmatch(pkg.Name, -1) - for _, match := range matches { - applied[match[1]] = struct{}{} + 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 { - 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) + cves, err = ms.driver.GetFilteredCvesMicrosoft(filtered, append(applied, unapplied...)) + if err != nil { + return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err) } + } - 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 + 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) + } } - unapplied[strings.TrimPrefix(s, "KB")] = struct{}{} } - case []string: - for _, kbID := range kbIDs { - unapplied[strings.TrimPrefix(kbID, "KB")] = struct{}{} + if len(kbs) > 0 { + p.KBs = kbs + ps = append(ps, p) } - case nil: - logging.Log.Warnf("This Windows has no option(UnappliedKBID). UUID: %s", r.ServerUUID) } - } - - 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) - } + cve.Products = ps + if len(cve.Products) == 0 { + continue + } + nCVEs++ - for cveID, cve := range cves { 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 diff --git a/gost/util.go b/gost/util.go index 243703a06b..2e60c1fb3f 100644 --- a/gost/util.go +++ b/gost/util.go @@ -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) +} diff --git a/integration b/integration index a36b4595ee..fbd9e815f6 160000 --- a/integration +++ b/integration @@ -1 +1 @@ -Subproject commit a36b4595ee6b728ec7ec505e860de14f5791e7a8 +Subproject commit fbd9e815f67a7fbbfc7033943958568a69841661 diff --git a/models/scanresults.go b/models/scanresults.go index 0d6b124e69..508b992577 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -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 { diff --git a/models/vulninfos.go b/models/vulninfos.go index b7087561ad..6d8cad4ff3 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -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" } diff --git a/models/vulninfos_test.go b/models/vulninfos_test.go index edca0dbda3..3830fc3ecd 100644 --- a/models/vulninfos_test.go +++ b/models/vulninfos_test.go @@ -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) + } + }) + } +} diff --git a/reporter/syslog.go b/reporter/syslog.go index 8b6e8f476f..03e9d2a5e3 100644 --- a/reporter/syslog.go +++ b/reporter/syslog.go @@ -1,3 +1,5 @@ +//go:build !windows + package reporter import ( diff --git a/reporter/util.go b/reporter/util.go index f08491c2ce..c14a7c246e 100644 --- a/reporter/util.go +++ b/reporter/util.go @@ -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()}) } diff --git a/scanner/base.go b/scanner/base.go index d2feaf25e6..b75b786600 100644 --- a/scanner/base.go +++ b/scanner/base.go @@ -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, diff --git a/scanner/debian.go b/scanner/debian.go index ef293b05ea..a4cbbc08c1 100644 --- a/scanner/debian.go +++ b/scanner/debian.go @@ -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 { diff --git a/scanner/executil.go b/scanner/executil.go index 228c636a6f..c732d29187 100644 --- a/scanner/executil.go +++ b/scanner/executil.go @@ -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) +} diff --git a/scanner/executil_test.go b/scanner/executil_test.go index 4ed33b166c..d7c70656dc 100644 --- a/scanner/executil_test.go +++ b/scanner/executil_test.go @@ -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'`, }, } diff --git a/scanner/scanner.go b/scanner/scanner.go index 97ab532713..40767a696d 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -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,122 @@ 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) { + serverName := header.Get("X-Vuls-Server-Name") + if toLocalFile && serverName == "" { + return models.ScanResult{}, errServerNameHeader + } + 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 - } + switch family { + case constant.Windows: + osInfo, hotfixs, err := parseSystemInfo(toUTF8(body)) + if err != nil { + return models.ScanResult{}, xerrors.Errorf("Failed to parse systeminfo.exe. err: %w", err) + } - 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") - } + 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 family == constant.Debian { + kernelVersion := header.Get("X-Vuls-Kernel-Version") 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 = "" - } + kernelVersion = formatKernelVersion(osInfo) } - } - serverName := header.Get("X-Vuls-Server-Name") - if toLocalFile && serverName == "" { - return models.ScanResult{}, errServerNameHeader - } + w := &windows{ + base: base{ + Distro: config.Distro{Family: family, Release: release}, + osPackages: osPackages{ + Kernel: models.Kernel{Version: kernelVersion}, + }, + log: logging.Log, + }, + } - distro := config.Distro{ - Family: family, - Release: release, - } + kbs, err := w.detectKBsFromKernelVersion() + if err != nil { + return models.ScanResult{}, xerrors.Errorf("Failed to detect KBs from kernel version. err: %w", err) + } - kernel := models.Kernel{ - Release: kernelRelease, - Version: kernelVersion, - } - installedPackages, srcPackages, err := ParseInstalledPkgs(distro, kernel, body) - if err != nil { - return models.ScanResult{}, 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: kernelVersion, + }, + 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") + } - return models.ScanResult{ - ServerName: serverName, - Family: family, - Release: release, - RunningKernel: models.Kernel{ + 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 +402,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 +448,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 +459,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 +495,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 +563,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 +593,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 +606,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 +752,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 +795,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 diff --git a/scanner/scanner_test.go b/scanner/scanner_test.go index f0bb9e5af0..332a61f219 100644 --- a/scanner/scanner_test.go +++ b/scanner/scanner_test.go @@ -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", + }, + WindowsKB: &models.WindowsKB{ + Applied: []string{"5012117", "4562830", "5003791", "5007401", "5012599", "5011651", "5005699"}, + Unapplied: []string{}, + }, + }, + }, } 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) + } } } diff --git a/scanner/utils.go b/scanner/utils.go index 05fea0723c..43328280ed 100644 --- a/scanner/utils.go +++ b/scanner/utils.go @@ -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 } diff --git a/scanner/windows.go b/scanner/windows.go new file mode 100644 index 0000000000..4bdbe5b17a --- /dev/null +++ b/scanner/windows.go @@ -0,0 +1,4445 @@ +package scanner + +import ( + "bufio" + "fmt" + "net" + "regexp" + "strconv" + "strings" + + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + + "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/constant" + "github.com/future-architect/vuls/logging" + "github.com/future-architect/vuls/models" +) + +// inherit OsTypeInterface +type windows struct { + base +} + +type osInfo struct { + productName string + version string + build string + revision string + edition string + servicePack string + arch string + installationType string +} + +func newWindows(c config.ServerInfo) *windows { + d := &windows{ + base: base{ + osPackages: osPackages{ + Packages: models.Packages{}, + VulnInfos: models.VulnInfos{}, + }, + }, + } + d.log = logging.NewNormalLogger() + d.setServerInfo(c) + return d +} + +func detectWindows(c config.ServerInfo) (bool, osTypeInterface) { + tmp := c + tmp.Distro.Family = constant.Windows + + if isLocalExec(c.Port, c.Host) { + if r, r2 := exec(tmp, `$CurrentVersion = (Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion"); Format-List -InputObject $CurrentVersion -Property ProductName, CurrentVersion, CurrentMajorVersionNumber, CurrentMinorVersionNumber, CurrentBuildNumber, UBR, CSDVersion, EditionID, InstallationType`, noSudo), exec(tmp, `(Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment").PROCESSOR_ARCHITECTURE`, noSudo); (r.isSuccess() && r.Stdout != "") && (r2.isSuccess() && r2.Stdout != "") { + w := newWindows(c) + osInfo, err := parseRegistry(r.Stdout, strings.TrimSpace(r2.Stdout)) + if err != nil { + w.setErrs([]error{xerrors.Errorf("Failed to parse Registry. err: %w", err)}) + return true, w + } + + release, err := detectOSName(osInfo) + if err != nil { + w.setErrs([]error{xerrors.Errorf("Failed to detect os name. err: %w", err)}) + return true, w + } + w.setDistro(constant.Windows, release) + w.Kernel = models.Kernel{Version: formatKernelVersion(osInfo)} + return true, w + } + } + + if r := exec(tmp, "Get-ComputerInfo -Property WindowsProductName, OsVersion, WindowsEditionId, OsCSDVersion, CsSystemType, WindowsInstallationType", noSudo); r.isSuccess() && r.Stdout != "" { + w := newWindows(c) + osInfo, err := parseGetComputerInfo(r.Stdout) + if err != nil { + w.setErrs([]error{xerrors.Errorf("Failed to parse Get-ComputerInfo. err: %w", err)}) + return true, w + } + + release, err := detectOSName(osInfo) + if err != nil { + w.setErrs([]error{xerrors.Errorf("Failed to detect os name. err: %w", err)}) + return true, w + } + w.setDistro(constant.Windows, release) + w.Kernel = models.Kernel{Version: formatKernelVersion(osInfo)} + return true, w + } + + if r := exec(tmp, "$WmiOS = (Get-WmiObject Win32_OperatingSystem); Format-List -InputObject $WmiOS -Property Caption, Version, OperatingSystemSKU, CSDVersion; $WmiCS = (Get-WmiObject Win32_ComputerSystem); Format-List -InputObject $WmiCS -Property SystemType, DomainRole", noSudo); r.isSuccess() && r.Stdout != "" { + w := newWindows(c) + osInfo, err := parseWmiObject(r.Stdout) + if err != nil { + w.setErrs([]error{xerrors.Errorf("Failed to parse Get-WmiObject. err: %w", err)}) + return true, w + } + + release, err := detectOSName(osInfo) + if err != nil { + w.setErrs([]error{xerrors.Errorf("Failed to detect os name. err: %w", err)}) + return true, w + } + w.setDistro(constant.Windows, release) + w.Kernel = models.Kernel{Version: formatKernelVersion(osInfo)} + return true, w + } + + if r := exec(tmp, "systeminfo.exe", noSudo); r.isSuccess() && r.Stdout != "" { + w := newWindows(c) + osInfo, _, err := parseSystemInfo(r.Stdout) + if err != nil { + w.setErrs([]error{xerrors.Errorf("Failed to parse systeminfo.exe. err: %w", err)}) + return true, w + } + + release, err := detectOSName(osInfo) + if err != nil { + w.setErrs([]error{xerrors.Errorf("Failed to detect os name. err: %w", err)}) + return true, w + } + w.setDistro(constant.Windows, release) + w.Kernel = models.Kernel{Version: formatKernelVersion(osInfo)} + return true, w + } + + return false, nil +} + +func parseSystemInfo(stdout string) (osInfo, []string, error) { + var ( + o osInfo + kbs []string + ) + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + + switch { + case strings.HasPrefix(line, "OS 名:"): + line = strings.NewReplacer("OS 名:", "OS Name:").Replace(line) + case strings.HasPrefix(line, "OS バージョン:"): + line = strings.NewReplacer("OS バージョン:", "OS Version:", "ビルド", "Build").Replace(line) + case strings.HasPrefix(line, "システムの種類:"): + line = strings.NewReplacer("システムの種類:", "System Type:").Replace(line) + case strings.HasPrefix(line, "OS 構成:"): + line = strings.NewReplacer("OS 構成:", "OS Configuration:", "サーバー", "Server", "ワークステーション", "Workstation").Replace(line) + case strings.HasPrefix(line, "ホットフィックス:"): + line = strings.NewReplacer("ホットフィックス:", "Hotfix(s):", "ホットフィックスがインストールされています。", "Hotfix(s) Installed.").Replace(line) + default: + } + + switch { + case strings.HasPrefix(line, "OS Name:"): + o.productName = strings.TrimSpace(strings.TrimPrefix(line, "OS Name:")) + case strings.HasPrefix(line, "OS Version:"): + s := strings.TrimSpace(strings.TrimPrefix(line, "OS Version:")) + lhs, build, _ := strings.Cut(s, " Build ") + vb, sp, _ := strings.Cut(lhs, " ") + o.version = strings.TrimSuffix(vb, fmt.Sprintf(".%s", build)) + o.build = build + if sp != "N/A" { + o.servicePack = sp + } + case strings.HasPrefix(line, "System Type:"): + o.arch = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "System Type:"), "PC")) + case strings.HasPrefix(line, "OS Configuration:"): + switch { + case strings.Contains(line, "Server"): + o.installationType = "Server" + case strings.Contains(line, "Workstation"): + o.installationType = "Client" + default: + return osInfo{}, nil, xerrors.Errorf("Failed to detect installation type. line: %s", line) + } + case strings.HasPrefix(line, "Hotfix(s):"): + nKB, err := strconv.Atoi(strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "Hotfix(s):"), "Hotfix(s) Installed."))) + if err != nil { + return osInfo{}, nil, xerrors.Errorf("Failed to detect number of installed hotfix from %s", line) + } + for i := 0; i < nKB; i++ { + scanner.Scan() + line := scanner.Text() + _, rhs, found := strings.Cut(line, ":") + if !found { + continue + } + s := strings.TrimSpace(rhs) + if strings.HasPrefix(s, "KB") { + kbs = append(kbs, strings.TrimPrefix(s, "KB")) + } + } + default: + } + } + return o, kbs, nil +} + +func parseGetComputerInfo(stdout string) (osInfo, error) { + var o osInfo + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + + switch { + case strings.HasPrefix(line, "WindowsProductName"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect ProductName. expected: "WindowsProductName : ", line: "%s"`, line) + } + o.productName = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "OsVersion"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect OsVersion. expected: "OsVersion : ", line: "%s"`, line) + } + ss := strings.Split(strings.TrimSpace(rhs), ".") + o.version = strings.Join(ss[0:len(ss)-1], ".") + o.build = ss[len(ss)-1] + case strings.HasPrefix(line, "WindowsEditionId"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect WindowsEditionId. expected: "WindowsEditionId : ", line: "%s"`, line) + } + o.edition = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "OsCSDVersion"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect OsCSDVersion. expected: "OsCSDVersion : ", line: "%s"`, line) + } + o.servicePack = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "CsSystemType"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect CsSystemType. expected: "CsSystemType : ", line: "%s"`, line) + } + o.arch = strings.TrimSpace(strings.TrimSuffix(rhs, "PC")) + case strings.HasPrefix(line, "WindowsInstallationType"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect WindowsInstallationType. expected: "WindowsInstallationType : ", line: "%s"`, line) + } + o.installationType = strings.TrimSpace(rhs) + default: + } + } + return o, nil +} + +func parseWmiObject(stdout string) (osInfo, error) { + var o osInfo + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + + switch { + case strings.HasPrefix(line, "Caption"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect Caption. expected: "Caption : ", line: "%s"`, line) + } + o.productName = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "Version"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect Version. expected: "Version : ", line: "%s"`, line) + } + ss := strings.Split(strings.TrimSpace(rhs), ".") + o.version = strings.Join(ss[0:len(ss)-1], ".") + o.build = ss[len(ss)-1] + case strings.HasPrefix(line, "OperatingSystemSKU"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect OperatingSystemSKU. expected: "OperatingSystemSKU : ", line: "%s"`, line) + } + switch n := strings.TrimSpace(rhs); n { + case "0": + o.edition = "Undefined" + case "1": + o.edition = "Ultimate" + o.installationType = "Client" + case "2": + o.edition = "Home Basic" + o.installationType = "Client" + case "3": + o.edition = "Home Premium" + o.installationType = "Client" + case "4": + o.edition = "Enterprise" + o.installationType = "Client" + case "6": + o.edition = "Business" + o.installationType = "Client" + case "7": + o.edition = "Windows Server Standard Edition (Desktop Experience installation)" + o.installationType = "Server" + case "8": + o.edition = "Windows Server Datacenter Edition (Desktop Experience installation)" + o.installationType = "Server" + case "9": + o.edition = "Small Business Server" + o.installationType = "Server" + case "10": + o.edition = "Enterprise Server" + o.installationType = "Server" + case "11": + o.edition = "Starter" + case "12": + o.edition = "Datacenter Server Core" + o.installationType = "Server Core" + case "13": + o.edition = "Standard Server Core" + o.installationType = "Server Core" + case "14": + o.edition = "Enterprise Server Core" + o.installationType = "Server Core" + case "17": + o.edition = "Web Server" + o.installationType = "Server" + case "19": + o.edition = "Home Server" + o.installationType = "Server" + case "20": + o.edition = "Storage Express Server" + o.installationType = "Server" + case "21": + o.edition = "Windows Storage Server Standard" + o.installationType = "Server" + case "22": + o.edition = "Windows Storage Server Workgroup" + o.installationType = "Server" + case "23": + o.edition = "Storage Enterprise Server" + o.installationType = "Server" + case "24": + o.edition = "Server For Small Business" + o.installationType = "Server" + case "25": + o.edition = "Small Business Server Premium" + o.installationType = "Server" + case "27": + o.edition = "Enterprise" + o.installationType = "Client" + case "28": + o.edition = "Ultimate" + o.installationType = "Client" + case "29": + o.edition = "Windows Server Web Server Edition (Server Core installation)" + o.installationType = "Server Core" + case "36": + o.edition = "Windows Server Standard Edition without Hyper-V" + o.installationType = "Server" + case "37": + o.edition = "Windows Server Datacenter Edition without Hyper-V (full installation)" + o.installationType = "Server" + case "38": + o.edition = "Windows Server Enterprise Edition without Hyper-V (full installation)" + o.installationType = "Server" + case "39": + o.edition = "Windows Server Datacenter Edition without Hyper-V (Server Core installation)" + o.installationType = "Server Core" + case "40": + o.edition = "Windows Server Standard Edition without Hyper-V (Server Core installation)" + o.installationType = "Server Core" + case "41": + o.edition = "Windows Server Enterprise Edition without Hyper-V (Server Core installation)" + o.installationType = "Server Core" + case "42": + o.edition = "Microsoft Hyper-V Server" + o.installationType = "Server" + case "43": + o.edition = "Storage Server Express Edition (Server Core installation)" + o.installationType = "Server Core" + case "44": + o.edition = "Storage Server Standard Edition (Server Core installation)" + o.installationType = "Server Core" + case "45": + o.edition = "Storage Server Workgroup Edition (Server Core installation)" + o.installationType = "Server Core" + case "46": + o.edition = "Storage Server Enterprise Edition (Server Core installation)" + o.installationType = "Server Core" + case "48": + o.edition = "Professional" + o.installationType = "Client" + case "50": + o.edition = "Windows Server Essentials (Desktop Experience installation)" + o.installationType = "Server" + case "63": + o.edition = "Small Business Server Premium (Server Core installation)" + o.installationType = "Server Core" + case "64": + o.edition = "Windows Compute Cluster Server without Hyper-V" + o.installationType = "Server" + case "97": + o.edition = "Windows RT" + o.installationType = "Client" + case "101": + o.edition = "Home" + o.installationType = "Client" + case "103": + o.edition = "Media Center" + o.installationType = "Client" + case "104": + o.edition = "Mobile" + o.installationType = "Client" + case "123": + o.edition = "Windows IoT (Internet of Things) Core" + o.installationType = "Client" + case "143": + o.edition = "Windows Server Datacenter Edition (Nano Server installation)" + o.installationType = "Server" + case "144": + o.edition = "Windows Server Standard Edition (Nano Server installation)" + o.installationType = "Server" + case "147": + o.edition = "Windows Server Datacenter Edition (Server Core installation)" + o.installationType = "Server Core" + case "148": + o.edition = "Windows Server Standard Edition (Server Core installation)" + o.installationType = "Server Core" + case "175": + o.edition = "Windows Enterprise for Virtual Desktops (Azure Virtual Desktop)" + o.installationType = "Client" + default: + } + + case strings.HasPrefix(line, "CSDVersion"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect CSDVersion. expected: "CSDVersion : ", line: "%s"`, line) + } + o.servicePack = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "SystemType"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect SystemType. expected: "SystemType : ", line: "%s"`, line) + } + o.arch = strings.TrimSpace(strings.TrimSuffix(rhs, "PC")) + case strings.HasPrefix(line, "DomainRole"): + if o.installationType != "" { + break + } + + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect DomainRole. expected: "DomainRole : ", line: "%s"`, line) + } + switch domainRole := strings.TrimSpace(rhs); domainRole { // https://learn.microsoft.com/en-us/windows/win32/api/dsrole/ne-dsrole-dsrole_machine_role + case "0", "1": + o.installationType = "Client" + case "2", "3": + o.installationType = "Server" + case "4", "5": + o.installationType = "Controller" + default: + return osInfo{}, xerrors.Errorf("Failed to detect Installation Type from DomainRole. err: %s is invalid DomainRole", domainRole) + } + default: + } + } + return o, nil +} + +func parseRegistry(stdout, arch string) (osInfo, error) { + var ( + o osInfo + major string + minor string + ) + + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + + switch { + case strings.HasPrefix(line, "ProductName"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect ProductName. expected: "ProductName : ", line: "%s"`, line) + } + o.productName = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "CurrentVersion"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect CurrentVersion. expected: "CurrentVersion : ", line: "%s"`, line) + } + o.version = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "CurrentMajorVersionNumber"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect CurrentMajorVersionNumber. expected: "CurrentMajorVersionNumber : ", line: "%s"`, line) + } + major = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "CurrentMinorVersionNumber"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect CurrentMinorVersionNumber. expected: "CurrentMinorVersionNumber : ", line: "%s"`, line) + } + minor = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "CurrentBuildNumber"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect CurrentBuildNumber. expected: "CurrentBuildNumber : ", line: "%s"`, line) + } + o.build = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "UBR"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect UBR. expected: "UBR : ", line: "%s"`, line) + } + o.revision = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "EditionID"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect EditionID. expected: "EditionID : ", line: "%s"`, line) + } + o.edition = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "CSDVersion"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect CSDVersion. expected: "CSDVersion : ", line: "%s"`, line) + } + o.servicePack = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "InstallationType"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return osInfo{}, xerrors.Errorf(`Failed to detect InstallationType. expected: "InstallationType : ", line: "%s"`, line) + } + o.installationType = strings.TrimSpace(rhs) + default: + } + } + if major != "" && minor != "" { + o.version = fmt.Sprintf("%s.%s", major, minor) + } + + formatted, err := formatArch(arch) + if err != nil { + return osInfo{}, xerrors.Errorf("Failed to format arch. arch: %s, err: %w", arch, err) + } + o.arch = formatted + + return o, nil +} + +func detectOSName(osInfo osInfo) (string, error) { + osName, err := detectOSNameFromOSInfo(osInfo) + if err != nil { + return "", xerrors.Errorf("Failed to detect OS Name from OSInfo: %+v, err: %w", osInfo, err) + } + return osName, nil +} + +func detectOSNameFromOSInfo(osInfo osInfo) (string, error) { + switch osInfo.version { + case "5.0": + switch osInfo.installationType { + case "Client": + if osInfo.servicePack != "" { + return fmt.Sprintf("Microsoft Windows 2000 %s", osInfo.servicePack), nil + } + return "Microsoft Windows 2000", nil + case "Server": + if osInfo.servicePack != "" { + return fmt.Sprintf("Microsoft Windows 2000 Server %s", osInfo.servicePack), nil + } + return "Microsoft Windows 2000 Server", nil + } + case "5.1": + switch osInfo.installationType { + case "Client": + var n string + switch osInfo.edition { + case "Professional": + n = "Microsoft Windows XP Professional" + case "Media Center": + n = "Microsoft Windows XP Media Center Edition 2005" + case "Tablet PC": + n = "Microsoft Windows XP Tablet PC Edition 2005" + default: + n = "Microsoft Windows XP" + } + switch osInfo.arch { + case "x64-based": + n = fmt.Sprintf("%s x64 Edition", n) + } + if osInfo.servicePack != "" { + return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil + } + return n, nil + } + case "5.2": + switch osInfo.installationType { + case "Client": + var n string + switch osInfo.edition { + case "Professional": + n = "Microsoft Windows XP Professional" + case "Media Center": + n = "Microsoft Windows XP Media Center Edition 2005" + case "Tablet PC": + n = "Microsoft Windows XP Tablet PC Edition 2005" + default: + n = "Microsoft Windows XP" + } + switch osInfo.arch { + case "x64-based": + n = fmt.Sprintf("%s x64 Edition", n) + } + if osInfo.servicePack != "" { + return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil + } + return n, nil + case "Server": + n := "Microsoft Windows Server 2003" + if strings.Contains(osInfo.productName, "R2") { + n = "Microsoft Windows Server 2003 R2" + } + switch osInfo.arch { + case "x64-based": + n = fmt.Sprintf("%s x64 Edition", n) + case "Itanium-based": + if osInfo.edition == "Enterprise" { + n = fmt.Sprintf("%s, Enterprise Edition for Itanium-based Systems", n) + } else { + n = fmt.Sprintf("%s for Itanium-based Systems", n) + } + } + if osInfo.servicePack != "" { + return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil + } + return n, nil + } + case "6.0": + switch osInfo.installationType { + case "Client": + var n string + switch osInfo.arch { + case "x64-based": + n = "Windows Vista x64 Editions" + default: + n = "Windows Vista" + } + if osInfo.servicePack != "" { + return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil + } + return n, nil + case "Server": + arch, err := formatArch(osInfo.arch) + if err != nil { + return "", err + } + if osInfo.servicePack != "" { + return fmt.Sprintf("Windows Server 2008 for %s Systems %s", arch, osInfo.servicePack), nil + } + return fmt.Sprintf("Windows Server 2008 for %s Systems", arch), nil + case "Server Core": + arch, err := formatArch(osInfo.arch) + if err != nil { + return "", err + } + if osInfo.servicePack != "" { + return fmt.Sprintf("Windows Server 2008 for %s Systems %s (Server Core installation)", arch, osInfo.servicePack), nil + } + return fmt.Sprintf("Windows Server 2008 for %s Systems (Server Core installation)", arch), nil + } + case "6.1": + switch osInfo.installationType { + case "Client": + arch, err := formatArch(osInfo.arch) + if err != nil { + return "", err + } + if osInfo.servicePack != "" { + return fmt.Sprintf("Windows 7 for %s Systems %s", arch, osInfo.servicePack), nil + } + return fmt.Sprintf("Windows 7 for %s Systems", arch), nil + case "Server": + arch, err := formatArch(osInfo.arch) + if err != nil { + return "", err + } + if osInfo.servicePack != "" { + return fmt.Sprintf("Windows Server 2008 R2 for %s Systems %s", arch, osInfo.servicePack), nil + } + return fmt.Sprintf("Windows Server 2008 R2 for %s Systems", arch), nil + case "Server Core": + arch, err := formatArch(osInfo.arch) + if err != nil { + return "", err + } + if osInfo.servicePack != "" { + return fmt.Sprintf("Windows Server 2008 R2 for %s Systems %s (Server Core installation)", arch, osInfo.servicePack), nil + } + return fmt.Sprintf("Windows Server 2008 R2 for %s Systems (Server Core installation)", arch), nil + } + case "6.2": + switch osInfo.installationType { + case "Client": + arch, err := formatArch(osInfo.arch) + if err != nil { + return "", err + } + return fmt.Sprintf("Windows 8 for %s Systems", arch), nil + case "Server": + return "Windows Server 2012", nil + case "Server Core": + return "Windows Server 2012 (Server Core installation)", nil + } + case "6.3": + switch osInfo.installationType { + case "Client": + arch, err := formatArch(osInfo.arch) + if err != nil { + return "", err + } + return fmt.Sprintf("Windows 8.1 for %s Systems", arch), nil + case "Server": + return "Windows Server 2012 R2", nil + case "Server Core": + return "Windows Server 2012 R2 (Server Core installation)", nil + } + case "10.0": + switch osInfo.installationType { + case "Client": + if strings.Contains(osInfo.productName, "Windows 11") { + arch, err := formatArch(osInfo.arch) + if err != nil { + return "", err + } + name, err := formatNamebyBuild("11", osInfo.build) + if err != nil { + return "", err + } + return fmt.Sprintf("%s for %s Systems", name, arch), nil + } + + arch, err := formatArch(osInfo.arch) + if err != nil { + return "", err + } + name, err := formatNamebyBuild("10", osInfo.build) + if err != nil { + return "", err + } + return fmt.Sprintf("%s for %s Systems", name, arch), nil + case "Server": + return formatNamebyBuild("Server", osInfo.build) + case "Server Core": + name, err := formatNamebyBuild("Server", osInfo.build) + if err != nil { + return "", err + } + return fmt.Sprintf("%s (Server Core installation)", name), nil + } + } + return "", xerrors.New("OS Name not found") +} + +func formatArch(arch string) (string, error) { + switch arch { + case "AMD64", "x64-based": + return "x64-based", nil + case "ARM64", "ARM64-based": + return "ARM64-based", nil + case "IA64", "Itanium-based": + return "Itanium-based", nil + case "x86", "X86-based": + return "32-bit", nil + default: + return "", xerrors.New("CPU Architecture not found") + } +} + +type buildNumber struct { + build string + name string +} + +var ( + winBuilds = map[string][]buildNumber{ + "10": { + { + build: "10240", + name: "Windows 10", // not "Windows 10 Version 1507" + }, + { + build: "10586", + name: "Windows 10 Version 1511", + }, + { + build: "14393", + name: "Windows 10 Version 1607", + }, + { + build: "15063", + name: "Windows 10 Version 1703", + }, + { + build: "16299", + name: "Windows 10 Version 1709", + }, + { + build: "17134", + name: "Windows 10 Version 1803", + }, + { + build: "17763", + name: "Windows 10 Version 1809", + }, + { + build: "18362", + name: "Windows 10 Version 1903", + }, + { + build: "18363", + name: "Windows 10 Version 1909", + }, + { + build: "19041", + name: "Windows 10 Version 2004", + }, + { + build: "19042", + name: "Windows 10 Version 20H2", + }, + { + build: "19043", + name: "Windows 10 Version 21H1", + }, + { + build: "19044", + name: "Windows 10 Version 21H2", + }, + { + build: "19045", + name: "Windows 10 Version 22H2", + }, + // It seems that there are cases where the Product Name is Windows 10 even though it is Windows 11 + // ref: https://docs.microsoft.com/en-us/answers/questions/586548/in-the-official-version-of-windows-11-why-the-key.html + { + build: "22000", + name: "Windows 11 Version 21H2", + }, + { + build: "22621", + name: "Windows 11 Version 22H2", + }, + }, + "11": { + { + build: "22000", + name: "Windows 11 Version 21H2", + }, + { + build: "22621", + name: "Windows 11 Version 22H2", + }, + }, + "Server": { + { + build: "14393", + name: "Windows Server 2016", + }, + { + build: "16299", + name: "Windows Server, Version 1709", + }, + { + build: "17134", + name: "Windows Server, Version 1803", + }, + { + build: "17763", + name: "Windows Server, Version 1809", + }, + { + build: "17763", + name: "Windows Server 2019", + }, + { + build: "18362", + name: "Windows Server, Version 1903", + }, + { + build: "18363", + name: "Windows Server, Version 1909", + }, + { + build: "19041", + name: "Windows Server, Version 2004", + }, + { + build: "19042", + name: "Windows Server, Version 20H2", + }, + { + build: "20348", + name: "Windows Server 2022", + }, + }, + } +) + +func formatNamebyBuild(osType string, mybuild string) (string, error) { + builds, ok := winBuilds[osType] + if !ok { + return "", xerrors.New("OS Type not found") + } + + nMybuild, err := strconv.Atoi(mybuild) + if err != nil { + return "", xerrors.Errorf("Failed to parse build number. err: %w", err) + } + + v := builds[0].name + for _, b := range builds { + nBuild, err := strconv.Atoi(b.build) + if err != nil { + return "", xerrors.Errorf("Failed to parse build number. err: %w", err) + } + if nMybuild < nBuild { + break + } + v = b.name + } + return v, nil +} + +func formatKernelVersion(osInfo osInfo) string { + v := fmt.Sprintf("%s.%s", osInfo.version, osInfo.build) + if osInfo.revision != "" { + v = fmt.Sprintf("%s.%s", v, osInfo.revision) + } + return v +} + +func (o *windows) checkScanMode() error { + return nil +} + +func (o *windows) checkIfSudoNoPasswd() error { + return nil +} + +func (o *windows) checkDeps() error { + return nil +} + +func (o *windows) preCure() error { + if err := o.detectIPAddr(); err != nil { + o.log.Warnf("Failed to detect IP addresses: %s", err) + o.warns = append(o.warns, err) + } + return nil +} + +func (o *windows) postScan() error { + return nil +} + +func (o *windows) detectIPAddr() error { + var err error + o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs, err = o.ip() + return err +} + +func (o *windows) ip() ([]string, []string, error) { + r := o.exec("ipconfig.exe", noSudo) + if !r.isSuccess() { + return nil, nil, xerrors.Errorf("Failed to detect IP address: %v", r) + } + ipv4Addrs, ipv6Addrs := o.parseIP(r.Stdout) + return ipv4Addrs, ipv6Addrs, nil +} + +func (o *windows) parseIP(stdout string) ([]string, []string) { + var ipv4Addrs, ipv6Addrs []string + + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + t := strings.TrimSpace(scanner.Text()) + lhs, rhs, ok := strings.Cut(t, ":") + if !ok { + continue + } + switch { + case strings.HasPrefix(lhs, "IPv4 Address"), strings.Contains(lhs, "Autoconfiguration IPv4 Address"), strings.HasPrefix(lhs, "IPv4 アドレス"), strings.HasPrefix(lhs, "自動構成 IPv4 アドレス"): + rhs = strings.NewReplacer("(Duplicate)", "", "(Preferred)", "", "(重複)", "", "(優先)", "").Replace(rhs) + if ip := net.ParseIP(strings.TrimSpace(rhs)); ip != nil { + ipv4Addrs = append(ipv4Addrs, ip.String()) + } + case strings.HasPrefix(lhs, "IPv6 Address"), strings.HasPrefix(lhs, "Temporary IPv6 Address"), strings.HasPrefix(lhs, "IPv6 アドレス"), strings.HasPrefix(lhs, "一時 IPv6 アドレス"): + if ip := net.ParseIP(strings.TrimSpace(rhs)); ip != nil { + ipv6Addrs = append(ipv6Addrs, ip.String()) + } + case strings.HasPrefix(lhs, "Link-local IPv6 Address"), strings.HasPrefix(lhs, "リンクローカル IPv6 アドレス"): + lhs, _, ok := strings.Cut(rhs, "%") + if !ok { + break + } + if ip := net.ParseIP(strings.TrimSpace(lhs)); ip != nil { + ipv6Addrs = append(ipv6Addrs, ip.String()) + } + default: + } + } + return ipv4Addrs, ipv6Addrs +} + +func (o *windows) scanPackages() error { + if r := o.exec("$Packages = (Get-Package); Format-List -InputObject $Packages -Property Name, Version, ProviderName", noSudo); r.isSuccess() { + installed, _, err := o.parseInstalledPackages(r.Stdout) + if err != nil { + return xerrors.Errorf("Failed to parse installed packages. err: %w", err) + } + o.Packages = installed + } + + kbs, err := o.scanKBs() + if err != nil { + return xerrors.Errorf("Failed to scan KB. err: %w", err) + } + o.windowsKB = kbs + + return nil +} + +func (o *windows) parseInstalledPackages(stdout string) (models.Packages, models.SrcPackages, error) { + installed := models.Packages{} + + var name, version string + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + switch { + case line == "": + name, version = "", "" + case strings.HasPrefix(line, "Name"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return nil, nil, xerrors.Errorf(`Failed to detect PackageName. expected: "Name : ", line: "%s"`, line) + } + name = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "Version"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return nil, nil, xerrors.Errorf(`Failed to detect Version. expected: "Version : ", line: "%s"`, line) + } + version = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "ProviderName"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return nil, nil, xerrors.Errorf(`Failed to detect ProviderName. expected: "ProviderName : ", line: "%s"`, line) + } + + switch strings.TrimSpace(rhs) { + case "msu": + default: + if name != "" { + installed[name] = models.Package{Name: name, Version: version} + } + } + default: + } + } + + return installed, nil, nil +} + +func (o *windows) scanKBs() (*models.WindowsKB, error) { + applied, unapplied := map[string]struct{}{}, map[string]struct{}{} + if r := o.exec("$Hotfix = (Get-Hotfix); Format-List -InputObject $Hotfix -Property HotFixID", noSudo); r.isSuccess() { + kbs, err := o.parseGetHotfix(r.Stdout) + if err != nil { + return nil, xerrors.Errorf("Failed to parse Get-Hotifx. err: %w", err) + } + for _, kb := range kbs { + applied[kb] = struct{}{} + } + } + + if r := o.exec("$Packages = (Get-Package -ProviderName msu); Format-List -InputObject $Packages -Property Name", noSudo); r.isSuccess() { + kbs, err := o.parseGetPackageMSU(r.Stdout) + if err != nil { + return nil, xerrors.Errorf("Failed to parse Get-Package. err: %w", err) + } + for _, kb := range kbs { + applied[kb] = struct{}{} + } + } + + if isLocalExec(o.getServerInfo().Port, o.getServerInfo().Host) { + var searcher string + switch c := o.getServerInfo().Windows; c.ServerSelection { + case 3: // https://learn.microsoft.com/en-us/windows/win32/wua_sdk/using-wua-to-scan-for-updates-offline + searcher = fmt.Sprintf("$UpdateSession = (New-Object -ComObject Microsoft.Update.Session); $UpdateServiceManager = (New-Object -ComObject Microsoft.Update.ServiceManager); $UpdateService = $UpdateServiceManager.AddScanPackageService(\"Offline Sync Service\", \"%s\", 1); $UpdateSearcher = $UpdateSession.CreateUpdateSearcher(); $UpdateSearcher.ServerSelection = %d; $UpdateSearcher.ServiceID = $UpdateService.ServiceID;", c.CabPath, c.ServerSelection) + default: + searcher = fmt.Sprintf("$UpdateSession = (New-Object -ComObject Microsoft.Update.Session); $UpdateSearcher = $UpdateSession.CreateUpdateSearcher(); $UpdateSearcher.ServerSelection = %d;", c.ServerSelection) + } + + if r := o.exec(fmt.Sprintf(`%s $UpdateSearcher.search("IsInstalled = 1 and RebootRequired = 0 and Type='Software'").Updates | ForEach-Object -MemberName KBArticleIDs`, searcher), noSudo); r.isSuccess() { + kbs, err := o.parseWindowsUpdaterSearch(r.Stdout) + if err != nil { + return nil, xerrors.Errorf("Failed to parse Windows Update Search. err: %w", err) + } + for _, kb := range kbs { + applied[kb] = struct{}{} + } + } + if r := o.exec(fmt.Sprintf(`%s $UpdateSearcher.search("IsInstalled = 0 and Type='Software'").Updates | ForEach-Object -MemberName KBArticleIDs`, searcher), noSudo); r.isSuccess() { + kbs, err := o.parseWindowsUpdaterSearch(r.Stdout) + if err != nil { + return nil, xerrors.Errorf("Failed to parse Windows Update Search. err: %w", err) + } + for _, kb := range kbs { + unapplied[kb] = struct{}{} + } + } + if r := o.exec(fmt.Sprintf(`%s $UpdateSearcher.search("IsInstalled = 1 and RebootRequired = 1 and Type='Software'").Updates | ForEach-Object -MemberName KBArticleIDs`, searcher), noSudo); r.isSuccess() { + kbs, err := o.parseWindowsUpdaterSearch(r.Stdout) + if err != nil { + return nil, xerrors.Errorf("Failed to parse Windows Update Search. err: %w", err) + } + for _, kb := range kbs { + unapplied[kb] = struct{}{} + } + } + } + + if r := o.exec("$UpdateSearcher = (New-Object -ComObject Microsoft.Update.Session).CreateUpdateSearcher(); $HistoryCount = $UpdateSearcher.GetTotalHistoryCount(); $UpdateSearcher.QueryHistory(0, $HistoryCount) | Sort-Object -Property Date | Format-List -Property Title, Operation, ResultCode", noSudo); r.isSuccess() { + kbs, err := o.parseWindowsUpdateHistory(r.Stdout) + if err != nil { + return nil, xerrors.Errorf("Failed to parse Windows Update History. err: %w", err) + } + for _, kb := range kbs { + applied[kb] = struct{}{} + } + } + + kbs, err := o.detectKBsFromKernelVersion() + if err != nil { + return nil, xerrors.Errorf("Failed to detect KBs from kernel version. err: %w", err) + } + for _, kb := range kbs.Applied { + applied[kb] = struct{}{} + } + for _, kb := range kbs.Unapplied { + unapplied[kb] = struct{}{} + } + + return &models.WindowsKB{Applied: maps.Keys(applied), Unapplied: maps.Keys(unapplied)}, nil +} + +func (o *windows) parseGetHotfix(stdout string) ([]string, error) { + var kbs []string + + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + switch { + case strings.HasPrefix(line, "HotFixID"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return nil, xerrors.Errorf(`Failed to detect HotFixID. expected: "HotFixID : ", line: "%s"`, line) + } + kbs = append(kbs, strings.TrimPrefix(strings.TrimSpace(rhs), "KB")) + default: + } + } + + return kbs, nil +} + +func (o *windows) parseGetPackageMSU(stdout string) ([]string, error) { + var kbs []string + + kbIDPattern := regexp.MustCompile(`KB(\d{6,7})`) + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + switch { + case strings.HasPrefix(line, "Name"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return nil, xerrors.Errorf(`Failed to detect PackageName. expected: "Name : ", line: "%s"`, line) + } + + for _, m := range kbIDPattern.FindAllStringSubmatch(strings.TrimSpace(rhs), -1) { + kbs = append(kbs, m[1]) + } + default: + } + } + + return kbs, nil +} + +func (o *windows) parseWindowsUpdaterSearch(stdout string) ([]string, error) { + var kbs []string + + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + if line := scanner.Text(); line != "" { + kbs = append(kbs, line) + } + } + + return kbs, nil +} + +func (o *windows) parseWindowsUpdateHistory(stdout string) ([]string, error) { + kbs := map[string]struct{}{} + + kbIDPattern := regexp.MustCompile(`KB(\d{6,7})`) + var title, operation string + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + switch { + case line == "": + title, operation = "", "" + case strings.HasPrefix(line, "Title"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return nil, xerrors.Errorf(`Failed to detect Title. expected: "Title : ", line: "%s"`, line) + } + title = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "Operation"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return nil, xerrors.Errorf(`Failed to detect Operation. expected: "Operation : <Operation>", line: "%s"`, line) + } + operation = strings.TrimSpace(rhs) + case strings.HasPrefix(line, "ResultCode"): + _, rhs, found := strings.Cut(line, ":") + if !found { + return nil, xerrors.Errorf(`Failed to detect ResultCode. expected: "ResultCode : <ResultCode>", line: "%s"`, line) + } + + // https://learn.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-operationresultcode + if strings.TrimSpace(rhs) == "2" { + for _, m := range kbIDPattern.FindAllStringSubmatch(title, -1) { + // https://learn.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-updateoperation + switch operation { + case "1": + kbs[m[1]] = struct{}{} + case "2": + delete(kbs, m[1]) + default: + } + } + } + default: + } + } + + return maps.Keys(kbs), nil +} + +type windowsRelease struct { + revision string + kb string +} + +type updateProgram struct { + rollup []windowsRelease + securityOnly []string +} + +var windowsReleases = map[string]map[string]map[string]updateProgram{ + "Client": { + "7": { + // https://support.microsoft.com/en-us/topic/windows-7-sp1-and-windows-server-2008-r2-sp1-update-history-720c2590-fd58-26ba-16cc-6d8f3b547599 + "SP1": { + rollup: []windowsRelease{ + {revision: "", kb: "3172605"}, + {revision: "", kb: "3179573"}, + {revision: "", kb: "3185278"}, + {revision: "", kb: "3185330"}, + {revision: "", kb: "3192403"}, + {revision: "", kb: "3197868"}, + {revision: "", kb: "3197869"}, + {revision: "", kb: "3207752"}, + {revision: "", kb: "3212646"}, + {revision: "", kb: "4012215"}, + {revision: "", kb: "4012218"}, + {revision: "", kb: "4015549"}, + {revision: "", kb: "4015552"}, + {revision: "", kb: "4019264"}, + {revision: "", kb: "4019265"}, + {revision: "", kb: "4022719"}, + {revision: "", kb: "4022168"}, + {revision: "", kb: "4025341"}, + {revision: "", kb: "4025340"}, + {revision: "", kb: "4034664"}, + {revision: "", kb: "4034670"}, + {revision: "", kb: "4038777"}, + {revision: "", kb: "4038803"}, + {revision: "", kb: "4041681"}, + {revision: "", kb: "4041686"}, + {revision: "", kb: "4048957"}, + {revision: "", kb: "4051034"}, + {revision: "", kb: "4054518"}, + {revision: "", kb: "4056894"}, + {revision: "", kb: "4057400"}, + {revision: "", kb: "4074598"}, + {revision: "", kb: "4075211"}, + {revision: "", kb: "4088875"}, + {revision: "", kb: "4088881"}, + {revision: "", kb: "4093118"}, + {revision: "", kb: "4093113"}, + {revision: "", kb: "4103718"}, + {revision: "", kb: "4103713"}, + {revision: "", kb: "4284826"}, + {revision: "", kb: "4284842"}, + {revision: "", kb: "4338818"}, + {revision: "", kb: "4338821"}, + {revision: "", kb: "4343900"}, + {revision: "", kb: "4343894"}, + {revision: "", kb: "4457144"}, + {revision: "", kb: "4457139"}, + {revision: "", kb: "4462923"}, + {revision: "", kb: "4462927"}, + {revision: "", kb: "4467107"}, + {revision: "", kb: "4467108"}, + {revision: "", kb: "4471318"}, + {revision: "", kb: "4480970"}, + {revision: "", kb: "4480955"}, + {revision: "", kb: "4486563"}, + {revision: "", kb: "4486565"}, + {revision: "", kb: "4489878"}, + {revision: "", kb: "4489892"}, + {revision: "", kb: "4493472"}, + {revision: "", kb: "4493453"}, + {revision: "", kb: "4499164"}, + {revision: "", kb: "4499178"}, + {revision: "", kb: "4503292"}, + {revision: "", kb: "4503277"}, + {revision: "", kb: "4507449"}, + {revision: "", kb: "4507437"}, + {revision: "", kb: "4512506"}, + {revision: "", kb: "4512514"}, + {revision: "", kb: "4516065"}, + {revision: "", kb: "4516048"}, + {revision: "", kb: "4524157"}, + {revision: "", kb: "4519976"}, + {revision: "", kb: "4519972"}, + {revision: "", kb: "4525235"}, + {revision: "", kb: "4525251"}, + {revision: "", kb: "4530734"}, + {revision: "", kb: "4534310"}, + {revision: "", kb: "4539601"}, + {revision: "", kb: "4537820"}, + {revision: "", kb: "4540688"}, + {revision: "", kb: "4550964"}, + {revision: "", kb: "4556836"}, + {revision: "", kb: "4561643"}, + {revision: "", kb: "4565524"}, + {revision: "", kb: "4571729"}, + {revision: "", kb: "4577051"}, + {revision: "", kb: "4580345"}, + {revision: "", kb: "4586827"}, + {revision: "", kb: "4592471"}, + {revision: "", kb: "4598279"}, + {revision: "", kb: "4601347"}, + {revision: "", kb: "5000841"}, + {revision: "", kb: "5001335"}, + {revision: "", kb: "5003233"}, + {revision: "", kb: "5003667"}, + {revision: "", kb: "5004953"}, + {revision: "", kb: "5004289"}, + {revision: "", kb: "5005088"}, + {revision: "", kb: "5005633"}, + {revision: "", kb: "5006743"}, + {revision: "", kb: "5007236"}, + {revision: "", kb: "5008244"}, + {revision: "", kb: "5009610"}, + {revision: "", kb: "5010404"}, + {revision: "", kb: "5011552"}, + {revision: "", kb: "5012626"}, + {revision: "", kb: "5014012"}, + {revision: "", kb: "5014748"}, + {revision: "", kb: "5015861"}, + {revision: "", kb: "5016676"}, + {revision: "", kb: "5017361"}, + {revision: "", kb: "5018454"}, + {revision: "", kb: "5020000"}, + {revision: "", kb: "5021291"}, + {revision: "", kb: "5022338"}, + {revision: "", kb: "5022872"}, + }, + securityOnly: []string{ + "3192391", + "3197867", + "3205394", + "3212642", + "4012212", + "4015546", + "4019263", + "4022722", + "4025337", + "4034679", + "4038779", + "4041678", + "4048960", + "4054521", + "4056897", + "4074587", + "4088878", + "4093108", + "4103712", + "4284867", + "4338823", + "4343899", + "4457145", + "4462915", + "4467106", + "4471328", + "4480960", + "4486564", + "4489885", + "4493448", + "4499175", + "4503269", + "4507456", + "4512486", + "4516033", + "4520003", + "4525233", + "4530692", + "4534314", + "4537813", + "4541500", + "4550965", + "4556843", + "4561669", + "4565539", + "4571719", + "4577053", + "4580387", + "4586805", + "4592503", + "4598289", + "4601363", + "5000851", + "5001392", + "5003228", + "5003694", + "5004951", + "5004307", + "5005089", + "5005615", + "5006728", + "5007233", + "5008282", + "5009621", + "5010422", + "5011529", + "5012649", + "5013999", + "5014742", + "5015862", + "5016679", + "5017373", + "5018479", + "5020013", + "5021288", + "5022339", + "5022874", + }, + }, + }, + "8.1": { + // https://support.microsoft.com/en-us/topic/windows-8-1-and-windows-server-2012-r2-update-history-47d81dd2-6804-b6ae-4112-20089467c7a6 + "": { + rollup: []windowsRelease{ + {revision: "", kb: "3172614"}, + {revision: "", kb: "3179574"}, + {revision: "", kb: "3185279"}, + {revision: "", kb: "3185331"}, + {revision: "", kb: "3192404"}, + {revision: "", kb: "3197874"}, + {revision: "", kb: "3197875"}, + {revision: "", kb: "3205401"}, + {revision: "", kb: "4012216"}, + {revision: "", kb: "4012219"}, + {revision: "", kb: "4015550"}, + {revision: "", kb: "4015553"}, + {revision: "", kb: "4019215"}, + {revision: "", kb: "4019217"}, + {revision: "", kb: "4022726"}, + {revision: "", kb: "4022720"}, + {revision: "", kb: "4025336"}, + {revision: "", kb: "4025335"}, + {revision: "", kb: "4034681"}, + {revision: "", kb: "4034663"}, + {revision: "", kb: "4038792"}, + {revision: "", kb: "4038774"}, + {revision: "", kb: "4041693"}, + {revision: "", kb: "4041685"}, + {revision: "", kb: "4048958"}, + {revision: "", kb: "4050946"}, + {revision: "", kb: "4054519"}, + {revision: "", kb: "4056895"}, + {revision: "", kb: "4057401"}, + {revision: "", kb: "4074594"}, + {revision: "", kb: "4075212"}, + {revision: "", kb: "4088876"}, + {revision: "", kb: "4088882"}, + {revision: "", kb: "4093114"}, + {revision: "", kb: "4093121"}, + {revision: "", kb: "4103725"}, + {revision: "", kb: "4103724"}, + {revision: "", kb: "4284815"}, + {revision: "", kb: "4284863"}, + {revision: "", kb: "4338815"}, + {revision: "", kb: "4338831"}, + {revision: "", kb: "4343898"}, + {revision: "", kb: "4343891"}, + {revision: "", kb: "4457129"}, + {revision: "", kb: "4457133"}, + {revision: "", kb: "4462926"}, + {revision: "", kb: "4462921"}, + {revision: "", kb: "4467697"}, + {revision: "", kb: "4467695"}, + {revision: "", kb: "4471320"}, + {revision: "", kb: "4480963"}, + {revision: "", kb: "4480969"}, + {revision: "", kb: "4487000"}, + {revision: "", kb: "4487016"}, + {revision: "", kb: "4489881"}, + {revision: "", kb: "4489893"}, + {revision: "", kb: "4493446"}, + {revision: "", kb: "4493443"}, + {revision: "", kb: "4499151"}, + {revision: "", kb: "4499182"}, + {revision: "", kb: "4503276"}, + {revision: "", kb: "4503283"}, + {revision: "", kb: "4507448"}, + {revision: "", kb: "4507463"}, + {revision: "", kb: "4512488"}, + {revision: "", kb: "4512478"}, + {revision: "", kb: "4516067"}, + {revision: "", kb: "4516041"}, + {revision: "", kb: "4524156"}, + {revision: "", kb: "4520005"}, + {revision: "", kb: "4520012"}, + {revision: "", kb: "4525243"}, + {revision: "", kb: "4525252"}, + {revision: "", kb: "4530702"}, + {revision: "", kb: "4534297"}, + {revision: "", kb: "4534324"}, + {revision: "", kb: "4537821"}, + {revision: "", kb: "4537819"}, + {revision: "", kb: "4541509"}, + {revision: "", kb: "4541334"}, + {revision: "", kb: "4550961"}, + {revision: "", kb: "4550958"}, + {revision: "", kb: "4556846"}, + {revision: "", kb: "4561666"}, + {revision: "", kb: "4565541"}, + {revision: "", kb: "4571703"}, + {revision: "", kb: "4577066"}, + {revision: "", kb: "4580347"}, + {revision: "", kb: "4586845"}, + {revision: "", kb: "4592484"}, + {revision: "", kb: "4598285"}, + {revision: "", kb: "4601384"}, + {revision: "", kb: "5000848"}, + {revision: "", kb: "5001382"}, + {revision: "", kb: "5003209"}, + {revision: "", kb: "5003671"}, + {revision: "", kb: "5004954"}, + {revision: "", kb: "5004298"}, + {revision: "", kb: "5005076"}, + {revision: "", kb: "5005613"}, + {revision: "", kb: "5006714"}, + {revision: "", kb: "5007247"}, + {revision: "", kb: "5008263"}, + {revision: "", kb: "5009624"}, + {revision: "", kb: "5010419"}, + {revision: "", kb: "5011564"}, + {revision: "", kb: "5012670"}, + {revision: "", kb: "5014011"}, + {revision: "", kb: "5014738"}, + {revision: "", kb: "5015874"}, + {revision: "", kb: "5016681"}, + {revision: "", kb: "5017367"}, + {revision: "", kb: "5018474"}, + {revision: "", kb: "5020023"}, + {revision: "", kb: "5021294"}, + {revision: "", kb: "5022352"}, + {revision: "", kb: "5022899"}, + }, + securityOnly: []string{ + "3192392", + "3197873", + "3205400", + "4012213", + "4015547", + "4019213", + "4022717", + "4025333", + "4034672", + "4038793", + "4041687", + "4048961", + "4054522", + "4056898", + "4074597", + "4088879", + "4093115", + "4103715", + "4284878", + "4338824", + "4343888", + "4457143", + "4462941", + "4467703", + "4471322", + "4480964", + "4487028", + "4489883", + "4493467", + "4499165", + "4503290", + "4507457", + "4512489", + "4516064", + "4519990", + "4525250", + "4530730", + "4534309", + "4537803", + "4541505", + "4550970", + "4556853", + "4561673", + "4565540", + "4571723", + "4577071", + "4580358", + "4586823", + "4592495", + "4598275", + "4601349", + "5000853", + "5001393", + "5003220", + "5003681", + "5004958", + "5004285", + "5005106", + "5005627", + "5006729", + "5007255", + "5008285", + "5009595", + "5010395", + "5011560", + "5012639", + "5014001", + "5014746", + "5015877", + "5016683", + "5017365", + "5018476", + "5020010", + "5021296", + "5022346", + "5022894", + }, + }, + }, + "10": { + // https://learn.microsoft.com/en-us/windows/release-health/release-information + // https://support.microsoft.com/en-us/topic/windows-10-update-history-93345c32-4ae1-6d1c-f885-6c0b718adf3b + "10240": { + rollup: []windowsRelease{ + {revision: "16405", kb: "3074683"}, + {revision: "16413", kb: "3081424"}, + {revision: "16430", kb: "3081436"}, + {revision: "16433", kb: "3081438"}, + {revision: "16445", kb: "3081444"}, + {revision: "16463", kb: "3081448"}, + {revision: "16487", kb: "3081455"}, + {revision: "16520", kb: "3093266"}, + {revision: "16549", kb: "3097617"}, + {revision: "16566", kb: "3105210"}, + {revision: "16590", kb: "3105213"}, + {revision: "16601", kb: "3116869"}, + {revision: "16644", kb: "3124266"}, + {revision: "16683", kb: "3135174"}, + {revision: "16725", kb: "3140745"}, + {revision: "16769", kb: "3147461"}, + {revision: "16771", kb: "3147461"}, + {revision: "16854", kb: "3156387"}, + {revision: "16942", kb: "3163017"}, + {revision: "17024", kb: "3163912"}, + {revision: "17071", kb: "3176492"}, + {revision: "17113", kb: "3185611"}, + {revision: "17113", kb: "3193821"}, + {revision: "17146", kb: "3192440"}, + {revision: "17190", kb: "3198585"}, + {revision: "17202", kb: "3205383"}, + {revision: "17236", kb: "3210720"}, + {revision: "17319", kb: "4012606"}, + {revision: "17320", kb: "4016637"}, + {revision: "17354", kb: "4015221"}, + {revision: "17394", kb: "4019474"}, + {revision: "17443", kb: "4022727"}, + {revision: "17446", kb: "4032695"}, + {revision: "17488", kb: "4025338"}, + {revision: "17533", kb: "4034668"}, + {revision: "17609", kb: "4038781"}, + {revision: "17643", kb: "4042895"}, + {revision: "17673", kb: "4048956"}, + {revision: "17709", kb: "4053581"}, + {revision: "17738", kb: "4056893"}, + {revision: "17741", kb: "4075199"}, + {revision: "17741", kb: "4077735"}, + {revision: "17770", kb: "4074596"}, + {revision: "17797", kb: "4088786"}, + {revision: "17831", kb: "4093111"}, + {revision: "17861", kb: "4103716"}, + {revision: "17889", kb: "4284860"}, + {revision: "17914", kb: "4338829"}, + {revision: "17918", kb: "4345455"}, + {revision: "17946", kb: "4343892"}, + {revision: "17976", kb: "4457132"}, + {revision: "18005", kb: "4462922"}, + {revision: "18036", kb: "4467680"}, + {revision: "18063", kb: "4471323"}, + {revision: "18064", kb: "4483228"}, + {revision: "18094", kb: "4480962"}, + {revision: "18132", kb: "4487018"}, + {revision: "18135", kb: "4491101"}, + {revision: "18158", kb: "4489872"}, + {revision: "18186", kb: "4493475"}, + {revision: "18187", kb: "4498375"}, + {revision: "18215", kb: "4499154"}, + {revision: "18218", kb: "4505051"}, + {revision: "18244", kb: "4503291"}, + {revision: "18275", kb: "4507458"}, + {revision: "18305", kb: "4512497"}, + {revision: "18308", kb: "4517276"}, + {revision: "18333", kb: "4516070"}, + {revision: "18334", kb: "4522009"}, + {revision: "18335", kb: "4524153"}, + {revision: "18368", kb: "4520011"}, + {revision: "18395", kb: "4525232"}, + {revision: "18427", kb: "4530681"}, + {revision: "18453", kb: "4534306"}, + {revision: "18486", kb: "4537776"}, + {revision: "18519", kb: "4540693"}, + {revision: "18545", kb: "4550930"}, + {revision: "18575", kb: "4556826"}, + {revision: "18608", kb: "4561649"}, + {revision: "18609", kb: "4567518"}, + {revision: "18638", kb: "4565513"}, + {revision: "18666", kb: "4571692"}, + {revision: "18696", kb: "4577049"}, + {revision: "18725", kb: "4580327"}, + {revision: "18756", kb: "4586787"}, + {revision: "18782", kb: "4592464"}, + {revision: "18818", kb: "4598231"}, + {revision: "18841", kb: "4601331"}, + {revision: "18842", kb: "4601331"}, + {revision: "18874", kb: "5000807"}, + {revision: "18875", kb: "5001631"}, + {revision: "18906", kb: "5001340"}, + {revision: "18932", kb: "5003172"}, + {revision: "18967", kb: "5003687"}, + {revision: "18969", kb: "5004950"}, + {revision: "19003", kb: "5004249"}, + {revision: "19022", kb: "5005040"}, + {revision: "19060", kb: "5005569"}, + {revision: "19086", kb: "5006675"}, + {revision: "19119", kb: "5007207"}, + {revision: "19145", kb: "5008230"}, + {revision: "19177", kb: "5009585"}, + {revision: "19179", kb: "5010789"}, + {revision: "19204", kb: "5010358"}, + {revision: "19235", kb: "5011491"}, + {revision: "19265", kb: "5012653"}, + {revision: "19297", kb: "5013963"}, + {revision: "19325", kb: "5014710"}, + {revision: "19360", kb: "5015832"}, + {revision: "19387", kb: "5016639"}, + {revision: "19444", kb: "5017327"}, + {revision: "19507", kb: "5018425"}, + {revision: "19509", kb: "5020440"}, + {revision: "19567", kb: "5019970"}, + {revision: "19624", kb: "5021243"}, + {revision: "19685", kb: "5022297"}, + {revision: "19747", kb: "5022858"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-update-history-2ad7900f-882c-1dfc-f9d7-82b7ca162010 + "10586": { + rollup: []windowsRelease{ + {revision: "3", kb: "3105211"}, + {revision: "11", kb: "3118754"}, + {revision: "14", kb: "3120677"}, + {revision: "17", kb: "3116908"}, + {revision: "29", kb: "3116900"}, + {revision: "36", kb: "3124200"}, + {revision: "63", kb: "3124263"}, + {revision: "71", kb: "3124262"}, + {revision: "104", kb: "3135173"}, + {revision: "122", kb: "3140743"}, + {revision: "164", kb: "3140768"}, + {revision: "218", kb: "3147458"}, + {revision: "318", kb: "3156421"}, + {revision: "420", kb: "3163018"}, + {revision: "494", kb: "3172985"}, + {revision: "545", kb: "3176493"}, + {revision: "589", kb: "3185614"}, + {revision: "633", kb: "3192441"}, + {revision: "679", kb: "3198586"}, + {revision: "682", kb: "3198586"}, + {revision: "713", kb: "3205386"}, + {revision: "753", kb: "3210721"}, + {revision: "839", kb: "4013198"}, + {revision: "842", kb: "4016636"}, + {revision: "873", kb: "4015219"}, + {revision: "916", kb: "4019473"}, + {revision: "962", kb: "4022714"}, + {revision: "965", kb: "4032693"}, + {revision: "1007", kb: "4025344"}, + {revision: "1045", kb: "4034660"}, + {revision: "1106", kb: "4038783"}, + {revision: "1176", kb: "4041689"}, + {revision: "1177", kb: "4052232"}, + {revision: "1232", kb: "4048952"}, + {revision: "1295", kb: "4053578"}, + {revision: "1356", kb: "4056888"}, + {revision: "1358", kb: "4075200"}, + {revision: "1417", kb: "4074591"}, + {revision: "1478", kb: "4088779"}, + {revision: "1540", kb: "4093109"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-and-windows-server-2016-update-history-4acfbc84-a290-1b54-536a-1c0430e9f3fd + "14393": { + rollup: []windowsRelease{ + {revision: "10", kb: "3176929"}, + {revision: "51", kb: "3176495"}, + {revision: "82", kb: "3176934"}, + {revision: "105", kb: "3176938"}, + {revision: "187", kb: "3189866"}, + {revision: "187", kb: "3193494"}, + {revision: "189", kb: "3193494"}, + {revision: "222", kb: "3194496"}, + {revision: "321", kb: "3194798"}, + {revision: "351", kb: "3197954"}, + {revision: "447", kb: "3200970"}, + {revision: "448", kb: "3200970"}, + {revision: "479", kb: "3201845"}, + {revision: "571", kb: "3206632"}, + {revision: "576", kb: "3206632"}, + {revision: "693", kb: "3213986"}, + {revision: "729", kb: "4010672"}, + {revision: "953", kb: "4013429"}, + {revision: "969", kb: "4015438"}, + {revision: "970", kb: "4016635"}, + {revision: "1066", kb: "4015217"}, + {revision: "1083", kb: "4015217"}, + {revision: "1198", kb: "4019472"}, + {revision: "1230", kb: "4023680"}, + {revision: "1358", kb: "4022715"}, + {revision: "1378", kb: "4022723"}, + {revision: "1480", kb: "4025339"}, + {revision: "1532", kb: "4025334"}, + {revision: "1537", kb: "4038220"}, + {revision: "1593", kb: "4034658"}, + {revision: "1613", kb: "4034661"}, + {revision: "1670", kb: "4039396"}, + {revision: "1715", kb: "4038782"}, + {revision: "1737", kb: "4038801"}, + {revision: "1770", kb: "4041691"}, + {revision: "1794", kb: "4041688"}, + {revision: "1797", kb: "4052231"}, + {revision: "1884", kb: "4048953"}, + {revision: "1914", kb: "4051033"}, + {revision: "1944", kb: "4053579"}, + {revision: "2007", kb: "4056890"}, + {revision: "2034", kb: "4057142"}, + {revision: "2035", kb: "4057142"}, + {revision: "2068", kb: "4074590"}, + {revision: "2097", kb: "4077525"}, + {revision: "2125", kb: "4088787"}, + {revision: "2126", kb: "4088787"}, + {revision: "2155", kb: "4088889"}, + {revision: "2156", kb: "4096309"}, + {revision: "2189", kb: "4093119"}, + {revision: "2214", kb: "4093120"}, + {revision: "2248", kb: "4103723"}, + {revision: "2273", kb: "4103720"}, + {revision: "2312", kb: "4284880"}, + {revision: "2339", kb: "4284833"}, + {revision: "2363", kb: "4338814"}, + {revision: "2368", kb: "4345418"}, + {revision: "2395", kb: "4338822"}, + {revision: "2396", kb: "4346877"}, + {revision: "2430", kb: "4343887"}, + {revision: "2457", kb: "4343884"}, + {revision: "2485", kb: "4457131"}, + {revision: "2515", kb: "4457127"}, + {revision: "2551", kb: "4462917"}, + {revision: "2580", kb: "4462928"}, + {revision: "2608", kb: "4467691"}, + {revision: "2639", kb: "4467684"}, + {revision: "2641", kb: "4478877"}, + {revision: "2665", kb: "4471321"}, + {revision: "2670", kb: "4483229"}, + {revision: "2724", kb: "4480961"}, + {revision: "2759", kb: "4480977"}, + {revision: "2791", kb: "4487026"}, + {revision: "2828", kb: "4487006"}, + {revision: "2848", kb: "4489882"}, + {revision: "2879", kb: "4489889"}, + {revision: "2906", kb: "4493470"}, + {revision: "2908", kb: "4499418"}, + {revision: "2941", kb: "4493473"}, + {revision: "2969", kb: "4494440"}, + {revision: "2972", kb: "4505052"}, + {revision: "2999", kb: "4499177"}, + {revision: "3025", kb: "4503267"}, + {revision: "3053", kb: "4503294"}, + {revision: "3056", kb: "4509475"}, + {revision: "3085", kb: "4507460"}, + {revision: "3115", kb: "4507459"}, + {revision: "3144", kb: "4512517"}, + {revision: "3181", kb: "4512495"}, + {revision: "3204", kb: "4516044"}, + {revision: "3206", kb: "4522010"}, + {revision: "3242", kb: "4516061"}, + {revision: "3243", kb: "4524152"}, + {revision: "3274", kb: "4519998"}, + {revision: "3300", kb: "4519979"}, + {revision: "3326", kb: "4525236"}, + {revision: "3384", kb: "4530689"}, + {revision: "3443", kb: "4534271"}, + {revision: "3474", kb: "4534307"}, + {revision: "3504", kb: "4537764"}, + {revision: "3542", kb: "4537806"}, + {revision: "3564", kb: "4540670"}, + {revision: "3595", kb: "4541329"}, + {revision: "3630", kb: "4550929"}, + {revision: "3659", kb: "4550947"}, + {revision: "3686", kb: "4556813"}, + {revision: "3750", kb: "4561616"}, + {revision: "3755", kb: "4567517"}, + {revision: "3808", kb: "4565511"}, + {revision: "3866", kb: "4571694"}, + {revision: "3930", kb: "4577015"}, + {revision: "3986", kb: "4580346"}, + {revision: "4046", kb: "4586830"}, + {revision: "4048", kb: "4594441"}, + {revision: "4104", kb: "4593226"}, + {revision: "4169", kb: "4598243"}, + {revision: "4225", kb: "4601318"}, + {revision: "4283", kb: "5000803"}, + {revision: "4288", kb: "5001633"}, + {revision: "4350", kb: "5001347"}, + {revision: "4402", kb: "5003197"}, + {revision: "4467", kb: "5003638"}, + {revision: "4470", kb: "5004948"}, + {revision: "4530", kb: "5004238"}, + {revision: "4532", kb: "5005393"}, + {revision: "4583", kb: "5005043"}, + {revision: "4651", kb: "5005573"}, + {revision: "4704", kb: "5006669"}, + {revision: "4770", kb: "5007192"}, + {revision: "4771", kb: "5008601"}, + {revision: "4825", kb: "5008207"}, + {revision: "4827", kb: "5010195"}, + {revision: "4886", kb: "5009546"}, + {revision: "4889", kb: "5010790"}, + {revision: "4946", kb: "5010359"}, + {revision: "5006", kb: "5011495"}, + {revision: "5066", kb: "5012596"}, + {revision: "5125", kb: "5013952"}, + {revision: "5127", kb: "5015019"}, + {revision: "5192", kb: "5014702"}, + {revision: "5246", kb: "5015808"}, + {revision: "5291", kb: "5016622"}, + {revision: "5356", kb: "5017305"}, + {revision: "5427", kb: "5018411"}, + {revision: "5429", kb: "5020439"}, + {revision: "5501", kb: "5019964"}, + {revision: "5502", kb: "5021654"}, + {revision: "5582", kb: "5021235"}, + {revision: "5648", kb: "5022289"}, + {revision: "5717", kb: "5022838"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-update-history-83aa43c0-82e0-92d8-1580-10642c9ed612 + "15063": { + rollup: []windowsRelease{ + {revision: "13", kb: "4016251"}, + {revision: "138", kb: "4015583"}, + {revision: "250", kb: "4016240"}, + {revision: "296", kb: "4016871"}, + {revision: "297", kb: "4016871"}, + {revision: "332", kb: "4020102"}, + {revision: "413", kb: "4022725"}, + {revision: "414", kb: "4022725"}, + {revision: "447", kb: "4022716"}, + {revision: "483", kb: "4025342"}, + {revision: "502", kb: "4032188"}, + {revision: "540", kb: "4034674"}, + {revision: "608", kb: "4038788"}, + {revision: "632", kb: "4040724"}, + {revision: "674", kb: "4041676"}, + {revision: "675", kb: "4049370"}, + {revision: "726", kb: "4048954"}, + {revision: "728", kb: "4048954"}, + {revision: "729", kb: "4055254"}, + {revision: "786", kb: "4053580"}, + {revision: "850", kb: "4056891"}, + {revision: "877", kb: "4057144"}, + {revision: "909", kb: "4074592"}, + {revision: "936", kb: "4077528"}, + {revision: "936", kb: "4092077"}, + {revision: "966", kb: "4088782"}, + {revision: "968", kb: "4088782"}, + {revision: "994", kb: "4088891"}, + {revision: "1029", kb: "4093107"}, + {revision: "1058", kb: "4093117"}, + {revision: "1088", kb: "4103731"}, + {revision: "1112", kb: "4103722"}, + {revision: "1155", kb: "4284874"}, + {revision: "1182", kb: "4284830"}, + {revision: "1206", kb: "4338826"}, + {revision: "1209", kb: "4345419"}, + {revision: "1235", kb: "4338827"}, + {revision: "1266", kb: "4343885"}, + {revision: "1292", kb: "4343889"}, + {revision: "1324", kb: "4457138"}, + {revision: "1356", kb: "4457141"}, + {revision: "1358", kb: "4457141"}, + {revision: "1387", kb: "4462937"}, + {revision: "1418", kb: "4462939"}, + {revision: "1446", kb: "4467696"}, + {revision: "1478", kb: "4467699"}, + {revision: "1506", kb: "4471327"}, + {revision: "1508", kb: "4483230"}, + {revision: "1563", kb: "4480973"}, + {revision: "1596", kb: "4480959"}, + {revision: "1631", kb: "4487020"}, + {revision: "1659", kb: "4487011"}, + {revision: "1689", kb: "4489871"}, + {revision: "1716", kb: "4489888"}, + {revision: "1746", kb: "4493474"}, + {revision: "1784", kb: "4493436"}, + {revision: "1785", kb: "4502112"}, + {revision: "1805", kb: "4499181"}, + {revision: "1808", kb: "4505055"}, + {revision: "1839", kb: "4499162"}, + {revision: "1868", kb: "4503279"}, + {revision: "1897", kb: "4503289"}, + {revision: "1898", kb: "4509476"}, + {revision: "1928", kb: "4507450"}, + {revision: "1955", kb: "4507467"}, + {revision: "1988", kb: "4512507"}, + {revision: "2021", kb: "4512474"}, + {revision: "2045", kb: "4516068"}, + {revision: "2046", kb: "4522011"}, + {revision: "2078", kb: "4516059"}, + {revision: "2079", kb: "4524151"}, + {revision: "2108", kb: "4520010"}, + {revision: "2172", kb: "4525245"}, + {revision: "2224", kb: "4530711"}, + {revision: "2254", kb: "4534296"}, + {revision: "2284", kb: "4537765"}, + {revision: "2313", kb: "4540705"}, + {revision: "2346", kb: "4550939"}, + {revision: "2375", kb: "4556804"}, + {revision: "2409", kb: "4561605"}, + {revision: "2411", kb: "4567516"}, + {revision: "2439", kb: "4565499"}, + {revision: "2467", kb: "4571689"}, + {revision: "2500", kb: "4577021"}, + {revision: "2525", kb: "4580370"}, + {revision: "2554", kb: "4586782"}, + {revision: "2584", kb: "4592473"}, + {revision: "2614", kb: "4599208"}, + {revision: "2642", kb: "4601330"}, + {revision: "2679", kb: "5000812"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-and-windows-server-update-history-8e779ac1-e840-d3b8-524e-91037bf7645a + "16299": { + rollup: []windowsRelease{ + {revision: "19", kb: "4043961"}, + {revision: "64", kb: "4048955"}, + {revision: "98", kb: "4051963"}, + {revision: "125", kb: "4054517"}, + {revision: "192", kb: "4056892"}, + {revision: "194", kb: "4073290"}, + {revision: "201", kb: "4073291"}, + {revision: "214", kb: "4058258"}, + {revision: "248", kb: "4074588"}, + {revision: "251", kb: "4090913"}, + {revision: "309", kb: "4088776"}, + {revision: "334", kb: "4089848"}, + {revision: "371", kb: "4093112"}, + {revision: "402", kb: "4093105"}, + {revision: "431", kb: "4103727"}, + {revision: "461", kb: "4103714"}, + {revision: "492", kb: "4284819"}, + {revision: "522", kb: "4284822"}, + {revision: "547", kb: "4338825"}, + {revision: "551", kb: "4345420"}, + {revision: "579", kb: "4338817"}, + {revision: "611", kb: "4343897"}, + {revision: "637", kb: "4343893"}, + {revision: "665", kb: "4457142"}, + {revision: "666", kb: "4464217"}, + {revision: "699", kb: "4457136"}, + {revision: "726", kb: "4462918"}, + {revision: "755", kb: "4462932"}, + {revision: "785", kb: "4467686"}, + {revision: "820", kb: "4467681"}, + {revision: "846", kb: "4471329"}, + {revision: "847", kb: "4483232"}, + {revision: "904", kb: "4480978"}, + {revision: "936", kb: "4480967"}, + {revision: "967", kb: "4486996"}, + {revision: "1004", kb: "4487021"}, + {revision: "1029", kb: "4489886"}, + {revision: "1059", kb: "4489890"}, + {revision: "1087", kb: "4493441"}, + {revision: "1127", kb: "4493440"}, + {revision: "1146", kb: "4499179"}, + {revision: "1150", kb: "4505062"}, + {revision: "1182", kb: "4499147"}, + {revision: "1217", kb: "4503284"}, + {revision: "1237", kb: "4503281"}, + {revision: "1239", kb: "4509477"}, + {revision: "1268", kb: "4507455"}, + {revision: "1296", kb: "4507465"}, + {revision: "1331", kb: "4512516"}, + {revision: "1365", kb: "4512494"}, + {revision: "1387", kb: "4516066"}, + {revision: "1392", kb: "4522012"}, + {revision: "1420", kb: "4516071"}, + {revision: "1421", kb: "4524150"}, + {revision: "1451", kb: "4520004"}, + {revision: "1481", kb: "4520006"}, + {revision: "1508", kb: "4525241"}, + {revision: "1565", kb: "4530714"}, + {revision: "1625", kb: "4534276"}, + {revision: "1654", kb: "4534318"}, + {revision: "1686", kb: "4537789"}, + {revision: "1717", kb: "4537816"}, + {revision: "1747", kb: "4540681"}, + {revision: "1775", kb: "4541330"}, + {revision: "1776", kb: "4554342"}, + {revision: "1806", kb: "4550927"}, + {revision: "1868", kb: "4556812"}, + {revision: "1932", kb: "4561602"}, + {revision: "1937", kb: "4567515"}, + {revision: "1992", kb: "4565508"}, + {revision: "2045", kb: "4571741"}, + {revision: "2107", kb: "4577041"}, + {revision: "2166", kb: "4580328"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-update-history-0d8c2da6-3dba-66e4-2ef2-059192bf7869 + "17134": { + rollup: []windowsRelease{ + {revision: "48", kb: "4103721"}, + {revision: "81", kb: "4100403"}, + {revision: "83", kb: "4338548"}, + {revision: "112", kb: "4284835"}, + {revision: "137", kb: "4284848"}, + {revision: "165", kb: "4338819"}, + {revision: "167", kb: "4345421"}, + {revision: "191", kb: "4340917"}, + {revision: "228", kb: "4343909"}, + {revision: "254", kb: "4346783"}, + {revision: "285", kb: "4457128"}, + {revision: "286", kb: "4464218"}, + {revision: "320", kb: "4458469"}, + {revision: "345", kb: "4462919"}, + {revision: "376", kb: "4462933"}, + {revision: "407", kb: "4467702"}, + {revision: "441", kb: "4467682"}, + {revision: "471", kb: "4471324"}, + {revision: "472", kb: "4483234"}, + {revision: "523", kb: "4480966"}, + {revision: "556", kb: "4480976"}, + {revision: "590", kb: "4487017"}, + {revision: "619", kb: "4487029"}, + {revision: "648", kb: "4489868"}, + {revision: "677", kb: "4489894"}, + {revision: "706", kb: "4493464"}, + {revision: "753", kb: "4493437"}, + {revision: "765", kb: "4499167"}, + {revision: "766", kb: "4505064"}, + {revision: "799", kb: "4499183"}, + {revision: "829", kb: "4503286"}, + {revision: "858", kb: "4503288"}, + {revision: "860", kb: "4509478"}, + {revision: "885", kb: "4507435"}, + {revision: "915", kb: "4507466"}, + {revision: "950", kb: "4512501"}, + {revision: "984", kb: "4512509"}, + {revision: "1006", kb: "4516058"}, + {revision: "1009", kb: "4522014"}, + {revision: "1039", kb: "4516045"}, + {revision: "1040", kb: "4524149"}, + {revision: "1069", kb: "4520008"}, + {revision: "1099", kb: "4519978"}, + {revision: "1130", kb: "4525237"}, + {revision: "1184", kb: "4530717"}, + {revision: "1246", kb: "4534293"}, + {revision: "1276", kb: "4534308"}, + {revision: "1304", kb: "4537762"}, + {revision: "1345", kb: "4537795"}, + {revision: "1365", kb: "4540689"}, + {revision: "1399", kb: "4541333"}, + {revision: "1401", kb: "4554349"}, + {revision: "1425", kb: "4550922"}, + {revision: "1456", kb: "4550944"}, + {revision: "1488", kb: "4556807"}, + {revision: "1550", kb: "4561621"}, + {revision: "1553", kb: "4567514"}, + {revision: "1610", kb: "4565489"}, + {revision: "1667", kb: "4571709"}, + {revision: "1726", kb: "4577032"}, + {revision: "1792", kb: "4580330"}, + {revision: "1845", kb: "4586785"}, + {revision: "1902", kb: "4592446"}, + {revision: "1967", kb: "4598245"}, + {revision: "2026", kb: "4601354"}, + {revision: "2087", kb: "5000809"}, + {revision: "2088", kb: "5001565"}, + {revision: "2090", kb: "5001634"}, + {revision: "2145", kb: "5001339"}, + {revision: "2208", kb: "5003174"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-and-windows-server-2019-update-history-725fc2e1-4443-6831-a5ca-51ff5cbcb059 + "17763": { + rollup: []windowsRelease{ + {revision: "1", kb: ""}, + {revision: "55", kb: "4464330"}, + {revision: "107", kb: "4464455"}, + {revision: "134", kb: "4467708"}, + {revision: "168", kb: "4469342"}, + {revision: "194", kb: "4471332"}, + {revision: "195", kb: "4483235"}, + {revision: "253", kb: "4480116"}, + {revision: "292", kb: "4476976"}, + {revision: "316", kb: "4487044"}, + {revision: "348", kb: "4482887"}, + {revision: "379", kb: "4489899"}, + {revision: "402", kb: "4490481"}, + {revision: "404", kb: "4490481"}, + {revision: "437", kb: "4493509"}, + {revision: "439", kb: "4501835"}, + {revision: "475", kb: "4495667"}, + {revision: "503", kb: "4494441"}, + {revision: "504", kb: "4505056"}, + {revision: "529", kb: "4497934"}, + {revision: "557", kb: "4503327"}, + {revision: "592", kb: "4501371"}, + {revision: "593", kb: "4509479"}, + {revision: "615", kb: "4507469"}, + {revision: "652", kb: "4505658"}, + {revision: "678", kb: "4511553"}, + {revision: "720", kb: "4512534"}, + {revision: "737", kb: "4512578"}, + {revision: "740", kb: "4522015"}, + {revision: "774", kb: "4516077"}, + {revision: "775", kb: "4524148"}, + {revision: "805", kb: "4519338"}, + {revision: "832", kb: "4520062"}, + {revision: "864", kb: "4523205"}, + {revision: "914", kb: "4530715"}, + {revision: "973", kb: "4534273"}, + {revision: "1012", kb: "4534321"}, + {revision: "1039", kb: "4532691"}, + {revision: "1075", kb: "4537818"}, + {revision: "1098", kb: "4538461"}, + {revision: "1131", kb: "4541331"}, + {revision: "1132", kb: "4554354"}, + {revision: "1158", kb: "4549949"}, + {revision: "1192", kb: "4550969"}, + {revision: "1217", kb: "4551853"}, + {revision: "1282", kb: "4561608"}, + {revision: "1294", kb: "4567513"}, + {revision: "1339", kb: "4558998"}, + {revision: "1369", kb: "4559003"}, + {revision: "1397", kb: "4565349"}, + {revision: "1432", kb: "4571748"}, + {revision: "1457", kb: "4570333"}, + {revision: "1490", kb: "4577069"}, + {revision: "1518", kb: "4577668"}, + {revision: "1554", kb: "4580390"}, + {revision: "1577", kb: "4586793"}, + {revision: "1579", kb: "4594442"}, + {revision: "1613", kb: "4586839"}, + {revision: "1637", kb: "4592440"}, + {revision: "1697", kb: "4598230"}, + {revision: "1728", kb: "4598296"}, + {revision: "1757", kb: "4601345"}, + {revision: "1790", kb: "4601383"}, + {revision: "1817", kb: "5000822"}, + {revision: "1821", kb: "5001568"}, + {revision: "1823", kb: "5001638"}, + {revision: "1852", kb: "5000854"}, + {revision: "1879", kb: "5001342"}, + {revision: "1911", kb: "5001384"}, + {revision: "1935", kb: "5003171"}, + {revision: "1971", kb: "5003217"}, + {revision: "1999", kb: "5003646"}, + {revision: "2028", kb: "5003703"}, + {revision: "2029", kb: "5004947"}, + {revision: "2061", kb: "5004244"}, + {revision: "2090", kb: "5004308"}, + {revision: "2091", kb: "5005394"}, + {revision: "2114", kb: "5005030"}, + {revision: "2145", kb: "5005102"}, + {revision: "2183", kb: "5005568"}, + {revision: "2210", kb: "5005625"}, + {revision: "2213", kb: "5005625"}, + {revision: "2237", kb: "5006672"}, + {revision: "2268", kb: "5006744"}, + {revision: "2300", kb: "5007206"}, + {revision: "2305", kb: "5008602"}, + {revision: "2330", kb: "5007266"}, + {revision: "2366", kb: "5008218"}, + {revision: "2369", kb: "5010196"}, + {revision: "2452", kb: "5009557"}, + {revision: "2458", kb: "5010791"}, + {revision: "2510", kb: "5009616"}, + {revision: "2565", kb: "5010351"}, + {revision: "2628", kb: "5010427"}, + {revision: "2686", kb: "5011503"}, + {revision: "2746", kb: "5011551"}, + {revision: "2803", kb: "5012647"}, + {revision: "2867", kb: "5012636"}, + {revision: "2928", kb: "5013941"}, + {revision: "2931", kb: "5015018"}, + {revision: "2989", kb: "5014022"}, + {revision: "3046", kb: "5014692"}, + {revision: "3113", kb: "5014669"}, + {revision: "3165", kb: "5015811"}, + {revision: "3232", kb: "5015880"}, + {revision: "3287", kb: "5016623"}, + {revision: "3346", kb: "5016690"}, + {revision: "3406", kb: "5017315"}, + {revision: "3469", kb: "5017379"}, + {revision: "3532", kb: "5018419"}, + {revision: "3534", kb: "5020438"}, + {revision: "3650", kb: "5019966"}, + {revision: "3653", kb: "5021655"}, + {revision: "3770", kb: "5021237"}, + {revision: "3772", kb: "5022554"}, + {revision: "3887", kb: "5022286"}, + {revision: "4010", kb: "5022840"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-update-history-e6058e7c-4116-38f1-b984-4fcacfba5e5d + "18362": { + rollup: []windowsRelease{ + {revision: "116", kb: "4505057"}, + {revision: "145", kb: "4497935"}, + {revision: "175", kb: "4503293"}, + {revision: "207", kb: "4501375"}, + {revision: "239", kb: "4507453"}, + {revision: "267", kb: "4505903"}, + {revision: "295", kb: "4512508"}, + {revision: "329", kb: "4512941"}, + {revision: "356", kb: "4515384"}, + {revision: "357", kb: "4522016"}, + {revision: "387", kb: "4517211"}, + {revision: "388", kb: "4524147"}, + {revision: "418", kb: "4517389"}, + {revision: "449", kb: "4522355"}, + {revision: "476", kb: "4524570"}, + {revision: "535", kb: "4530684"}, + {revision: "592", kb: "4528760"}, + {revision: "628", kb: "4532695"}, + {revision: "657", kb: "4532693"}, + {revision: "693", kb: "4535996"}, + {revision: "719", kb: "4540673"}, + {revision: "720", kb: "4551762"}, + {revision: "752", kb: "4541335"}, + {revision: "753", kb: "4554364"}, + {revision: "778", kb: "4549951"}, + {revision: "815", kb: "4550945"}, + {revision: "836", kb: "4556799"}, + {revision: "900", kb: "4560960"}, + {revision: "904", kb: "4567512"}, + {revision: "959", kb: "4565483"}, + {revision: "997", kb: "4559004"}, + {revision: "1016", kb: "4565351"}, + {revision: "1049", kb: "4566116"}, + {revision: "1082", kb: "4574727"}, + {revision: "1110", kb: "4577062"}, + {revision: "1139", kb: "4577671"}, + {revision: "1171", kb: "4580386"}, + {revision: "1198", kb: "4586786"}, + {revision: "1199", kb: "4594443"}, + {revision: "1237", kb: "4586819"}, + {revision: "1256", kb: "4592449"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-update-history-53c270dc-954f-41f7-7ced-488578904dfe + "18363": { + rollup: []windowsRelease{ + {revision: "476", kb: "4524570"}, + {revision: "535", kb: "4530684"}, + {revision: "592", kb: "4528760"}, + {revision: "628", kb: "4532695"}, + {revision: "657", kb: "4532693"}, + {revision: "693", kb: "4535996"}, + {revision: "719", kb: "4540673"}, + {revision: "720", kb: "4551762"}, + {revision: "752", kb: "4541335"}, + {revision: "753", kb: "4554364"}, + {revision: "778", kb: "4549951"}, + {revision: "815", kb: "4550945"}, + {revision: "836", kb: "4556799"}, + {revision: "900", kb: "4560960"}, + {revision: "904", kb: "4567512"}, + {revision: "959", kb: "4565483"}, + {revision: "997", kb: "4559004"}, + {revision: "1016", kb: "4565351"}, + {revision: "1049", kb: "4566116"}, + {revision: "1082", kb: "4574727"}, + {revision: "1110", kb: "4577062"}, + {revision: "1139", kb: "4577671"}, + {revision: "1171", kb: "4580386"}, + {revision: "1198", kb: "4586786"}, + {revision: "1199", kb: "4594443"}, + {revision: "1237", kb: "4586819"}, + {revision: "1256", kb: "4592449"}, + {revision: "1316", kb: "4598229"}, + {revision: "1350", kb: "4598298"}, + {revision: "1377", kb: "4601315"}, + {revision: "1379", kb: "5001028"}, + {revision: "1411", kb: "4601380"}, + {revision: "1440", kb: "5000808"}, + {revision: "1441", kb: "5001566"}, + {revision: "1443", kb: "5001648"}, + {revision: "1474", kb: "5000850"}, + {revision: "1500", kb: "5001337"}, + {revision: "1533", kb: "5001396"}, + {revision: "1556", kb: "5003169"}, + {revision: "1593", kb: "5003212"}, + {revision: "1621", kb: "5003635"}, + {revision: "1645", kb: "5003698"}, + {revision: "1646", kb: "5004946"}, + {revision: "1679", kb: "5004245"}, + {revision: "1714", kb: "5004293"}, + {revision: "1734", kb: "5005031"}, + {revision: "1766", kb: "5005103"}, + {revision: "1801", kb: "5005566"}, + {revision: "1830", kb: "5005624"}, + {revision: "1832", kb: "5005624"}, + {revision: "1854", kb: "5006667"}, + {revision: "1916", kb: "5007189"}, + {revision: "1977", kb: "5008206"}, + {revision: "2037", kb: "5009545"}, + {revision: "2039", kb: "5010792"}, + {revision: "2094", kb: "5010345"}, + {revision: "2158", kb: "5011485"}, + {revision: "2212", kb: "5012591"}, + {revision: "2274", kb: "5013945"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-update-history-24ea91f4-36e7-d8fd-0ddb-d79d9d0cdbda + "19041": { + rollup: []windowsRelease{ + {revision: "264", kb: ""}, + {revision: "329", kb: "4557957"}, + {revision: "331", kb: "4567523"}, + {revision: "388", kb: "4565503"}, + {revision: "423", kb: "4568831"}, + {revision: "450", kb: "4566782"}, + {revision: "488", kb: "4571744"}, + {revision: "508", kb: "4571756"}, + {revision: "546", kb: "4577063"}, + {revision: "572", kb: "4579311"}, + {revision: "610", kb: "4580364"}, + {revision: "630", kb: "4586781"}, + {revision: "631", kb: "4594440"}, + {revision: "662", kb: "4586853"}, + {revision: "685", kb: "4592438"}, + {revision: "746", kb: "4598242"}, + {revision: "789", kb: "4598291"}, + {revision: "804", kb: "4601319"}, + {revision: "844", kb: "4601382"}, + {revision: "867", kb: "5000802"}, + {revision: "868", kb: "5001567"}, + {revision: "870", kb: "5001649"}, + {revision: "906", kb: "5000842"}, + {revision: "928", kb: "5001330"}, + {revision: "964", kb: "5001391"}, + {revision: "985", kb: "5003173"}, + {revision: "1023", kb: "5003214"}, + {revision: "1052", kb: "5003637"}, + {revision: "1055", kb: "5004476"}, + {revision: "1081", kb: "5003690"}, + {revision: "1082", kb: "5004760"}, + {revision: "1083", kb: "5004945"}, + {revision: "1110", kb: "5004237"}, + {revision: "1151", kb: "5004296"}, + {revision: "1165", kb: "5005033"}, + {revision: "1202", kb: "5005101"}, + {revision: "1237", kb: "5005565"}, + {revision: "1266", kb: "5005611"}, + {revision: "1288", kb: "5006670"}, + {revision: "1320", kb: "5006738"}, + {revision: "1348", kb: "5007186"}, + {revision: "1387", kb: "5007253"}, + {revision: "1415", kb: "5008212"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-update-history-7dd3071a-3906-fa2c-c342-f7f86728a6e3 + "19042": { + rollup: []windowsRelease{ + {revision: "572", kb: ""}, + {revision: "610", kb: "4580364"}, + {revision: "630", kb: "4586781"}, + {revision: "631", kb: "4594440"}, + {revision: "662", kb: "4586853"}, + {revision: "685", kb: "4592438"}, + {revision: "746", kb: "4598242"}, + {revision: "789", kb: "4598291"}, + {revision: "804", kb: "4601319"}, + {revision: "844", kb: "4601382"}, + {revision: "867", kb: "5000802"}, + {revision: "868", kb: "5001567"}, + {revision: "870", kb: "5001649"}, + {revision: "906", kb: "5000842"}, + {revision: "928", kb: "5001330"}, + {revision: "964", kb: "5001391"}, + {revision: "985", kb: "5003173"}, + {revision: "1023", kb: "5003214"}, + {revision: "1052", kb: "5003637"}, + {revision: "1055", kb: "5004476"}, + {revision: "1081", kb: "5003690"}, + {revision: "1082", kb: "5004760"}, + {revision: "1083", kb: "5004945"}, + {revision: "1110", kb: "5004237"}, + {revision: "1151", kb: "5004296"}, + {revision: "1165", kb: "5005033"}, + {revision: "1202", kb: "5005101"}, + {revision: "1237", kb: "5005565"}, + {revision: "1266", kb: "5005611"}, + {revision: "1288", kb: "5006670"}, + {revision: "1320", kb: "5006738"}, + {revision: "1348", kb: "5007186"}, + {revision: "1387", kb: "5007253"}, + {revision: "1415", kb: "5008212"}, + {revision: "1466", kb: "5009543"}, + {revision: "1469", kb: "5010793"}, + {revision: "1503", kb: "5009596"}, + {revision: "1526", kb: "5010342"}, + {revision: "1566", kb: "5010415"}, + {revision: "1586", kb: "5011487"}, + {revision: "1620", kb: "5011543"}, + {revision: "1645", kb: "5012599"}, + {revision: "1682", kb: "5011831"}, + {revision: "1706", kb: "5013942"}, + {revision: "1708", kb: "5015020"}, + {revision: "1741", kb: "5014023"}, + {revision: "1766", kb: "5014699"}, + {revision: "1767", kb: "5016139"}, + {revision: "1806", kb: "5014666"}, + {revision: "1826", kb: "5015807"}, + {revision: "1865", kb: "5015878"}, + {revision: "1889", kb: "5016616"}, + {revision: "1949", kb: "5016688"}, + {revision: "2006", kb: "5017308"}, + {revision: "2075", kb: "5017380"}, + {revision: "2130", kb: "5018410"}, + {revision: "2132", kb: "5020435"}, + {revision: "2193", kb: "5018482"}, + {revision: "2194", kb: "5020953"}, + {revision: "2251", kb: "5019959"}, + {revision: "2311", kb: "5020030"}, + {revision: "2364", kb: "5021233"}, + {revision: "2486", kb: "5022282"}, + {revision: "2546", kb: "5019275"}, + {revision: "2604", kb: "5022834"}, + {revision: "2673", kb: "5022906"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-update-history-1b6aac92-bf01-42b5-b158-f80c6d93eb11 + "19043": { + rollup: []windowsRelease{ + {revision: "985", kb: "5003173"}, + {revision: "1023", kb: "5003214"}, + {revision: "1052", kb: "5003637"}, + {revision: "1055", kb: "5004476"}, + {revision: "1081", kb: "5003690"}, + {revision: "1082", kb: "5004760"}, + {revision: "1083", kb: "5004945"}, + {revision: "1110", kb: "5004237"}, + {revision: "1151", kb: "5004296"}, + {revision: "1165", kb: "5005033"}, + {revision: "1202", kb: "5005101"}, + {revision: "1237", kb: "5005565"}, + {revision: "1266", kb: "5005611"}, + {revision: "1288", kb: "5006670"}, + {revision: "1320", kb: "5006738"}, + {revision: "1348", kb: "5007186"}, + {revision: "1387", kb: "5007253"}, + {revision: "1415", kb: "5008212"}, + {revision: "1466", kb: "5009543"}, + {revision: "1469", kb: "5010793"}, + {revision: "1503", kb: "5009596"}, + {revision: "1526", kb: "5010342"}, + {revision: "1566", kb: "5010415"}, + {revision: "1586", kb: "5011487"}, + {revision: "1620", kb: "5011543"}, + {revision: "1645", kb: "5012599"}, + {revision: "1682", kb: "5011831"}, + {revision: "1706", kb: "5013942"}, + {revision: "1708", kb: "5015020"}, + {revision: "1741", kb: "5014023"}, + {revision: "1766", kb: "5014699"}, + {revision: "1767", kb: "5016139"}, + {revision: "1806", kb: "5014666"}, + {revision: "1826", kb: "5015807"}, + {revision: "1865", kb: "5015878"}, + {revision: "1889", kb: "5016616"}, + {revision: "1949", kb: "5016688"}, + {revision: "2006", kb: "5017308"}, + {revision: "2075", kb: "5017380"}, + {revision: "2130", kb: "5018410"}, + {revision: "2132", kb: "5020435"}, + {revision: "2193", kb: "5018482"}, + {revision: "2194", kb: "5020953"}, + {revision: "2251", kb: "5019959"}, + {revision: "2311", kb: "5020030"}, + {revision: "2364", kb: "5021233"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-update-history-857b8ccb-71e4-49e5-b3f6-7073197d98fb + "19044": { + rollup: []windowsRelease{ + {revision: "1288", kb: ""}, + {revision: "1387", kb: "5007253"}, + {revision: "1415", kb: "5008212"}, + {revision: "1466", kb: "5009543"}, + {revision: "1469", kb: "5010793"}, + {revision: "1503", kb: "5009596"}, + {revision: "1526", kb: "5010342"}, + {revision: "1566", kb: "5010415"}, + {revision: "1586", kb: "5011487"}, + {revision: "1620", kb: "5011543"}, + {revision: "1645", kb: "5012599"}, + {revision: "1682", kb: "5011831"}, + {revision: "1706", kb: "5013942"}, + {revision: "1708", kb: "5015020"}, + {revision: "1741", kb: "5014023"}, + {revision: "1766", kb: "5014699"}, + {revision: "1767", kb: "5016139"}, + {revision: "1806", kb: "5014666"}, + {revision: "1826", kb: "5015807"}, + {revision: "1865", kb: "5015878"}, + {revision: "1889", kb: "5016616"}, + {revision: "1949", kb: "5016688"}, + {revision: "2006", kb: "5017308"}, + {revision: "2075", kb: "5017380"}, + {revision: "2130", kb: "5018410"}, + {revision: "2132", kb: "5020435"}, + {revision: "2193", kb: "5018482"}, + {revision: "2194", kb: "5020953"}, + {revision: "2251", kb: "5019959"}, + {revision: "2311", kb: "5020030"}, + {revision: "2364", kb: "5021233"}, + {revision: "2486", kb: "5022282"}, + {revision: "2546", kb: "5019275"}, + {revision: "2604", kb: "5022834"}, + {revision: "2673", kb: "5022906"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-10-update-history-8127c2c6-6edf-4fdf-8b9f-0f7be1ef3562 + "19045": { + rollup: []windowsRelease{ + {revision: "2130", kb: ""}, + {revision: "2194", kb: "5020953"}, + {revision: "2251", kb: "5019959"}, + {revision: "2311", kb: "5020030"}, + {revision: "2364", kb: "5021233"}, + {revision: "2486", kb: "5022282"}, + {revision: "2546", kb: "5019275"}, + {revision: "2604", kb: "5022834"}, + {revision: "2673", kb: "5022906"}, + }, + }, + }, + "11": { + // https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information + // https://support.microsoft.com/en-us/topic/windows-11-version-21h2-update-history-a19cd327-b57f-44b9-84e0-26ced7109ba9/ + "22000": { + rollup: []windowsRelease{ + {revision: "194", kb: ""}, + {revision: "258", kb: "5006674"}, + {revision: "282", kb: "5006746"}, + {revision: "318", kb: "5007215"}, + {revision: "348", kb: "5007262"}, + {revision: "376", kb: "5008215"}, + {revision: "434", kb: "5009566"}, + {revision: "438", kb: "5010795"}, + {revision: "469", kb: "5008353"}, + {revision: "493", kb: "5010386"}, + {revision: "527", kb: "5010414"}, + {revision: "556", kb: "5011493"}, + {revision: "593", kb: "5011563"}, + {revision: "613", kb: "5012592"}, + {revision: "652", kb: "5012643"}, + {revision: "675", kb: "5013943"}, + {revision: "708", kb: "5014019"}, + {revision: "739", kb: "5014697"}, + {revision: "740", kb: "5016138"}, + {revision: "778", kb: "5014668"}, + {revision: "795", kb: "5015814"}, + {revision: "832", kb: "5015882"}, + {revision: "856", kb: "5016629"}, + {revision: "918", kb: "5016691"}, + {revision: "978", kb: "5017328"}, + {revision: "1042", kb: "5017383"}, + {revision: "1098", kb: "5018418"}, + {revision: "1100", kb: "5020387"}, + {revision: "1165", kb: "5018483"}, + {revision: "1219", kb: "5019961"}, + {revision: "1281", kb: "5019157"}, + {revision: "1335", kb: "5021234"}, + {revision: "1455", kb: "5022287"}, + {revision: "1516", kb: "5019274"}, + {revision: "1574", kb: "5022836"}, + {revision: "1641", kb: "5022905"}, + }, + }, + // https://support.microsoft.com/en-us/topic/windows-11-version-22h2-update-history-ec4229c3-9c5f-4e75-9d6d-9025ab70fcce + "22621": { + rollup: []windowsRelease{ + {revision: "521", kb: ""}, + {revision: "525", kb: "5019311"}, + {revision: "608", kb: "5017389"}, + {revision: "674", kb: "5018427"}, + {revision: "675", kb: "5019509"}, + {revision: "755", kb: "5018496"}, + {revision: "819", kb: "5019980"}, + {revision: "900", kb: "5020044"}, + {revision: "963", kb: "5021255"}, + {revision: "1105", kb: "5022303"}, + {revision: "1194", kb: "5022360"}, + {revision: "1265", kb: "5022845"}, + }, + }, + }, + }, + "Server": { + "2008": { + // https://support.microsoft.com/en-us/topic/windows-server-2008-sp2-update-history-9197740a-7430-f69f-19ff-4998a4e8b25b + "SP2": { + rollup: []windowsRelease{ + {revision: "", kb: "4458010"}, + {revision: "", kb: "4458315"}, + {revision: "", kb: "4463097"}, + {revision: "", kb: "4463105"}, + {revision: "", kb: "4467706"}, + {revision: "", kb: "4467687"}, + {revision: "", kb: "4471325"}, + {revision: "", kb: "4480968"}, + {revision: "", kb: "4480974"}, + {revision: "", kb: "4487023"}, + {revision: "", kb: "4487022"}, + {revision: "", kb: "4489880"}, + {revision: "", kb: "4489887"}, + {revision: "", kb: "4493471"}, + {revision: "", kb: "4493460"}, + {revision: "", kb: "4499149"}, + {revision: "", kb: "4499184"}, + {revision: "", kb: "4503273"}, + {revision: "", kb: "4503271"}, + {revision: "", kb: "4507452"}, + {revision: "", kb: "4507451"}, + {revision: "", kb: "4512476"}, + {revision: "", kb: "4512499"}, + {revision: "", kb: "4516026"}, + {revision: "", kb: "4516030"}, + {revision: "", kb: "4520002"}, + {revision: "", kb: "4520015"}, + {revision: "", kb: "4525234"}, + {revision: "", kb: "4525244"}, + {revision: "", kb: "4530695"}, + {revision: "", kb: "4534303"}, + {revision: "", kb: "4537810"}, + {revision: "", kb: "4541506"}, + {revision: "", kb: "4550951"}, + {revision: "", kb: "4556860"}, + {revision: "", kb: "4561670"}, + {revision: "", kb: "4565536"}, + {revision: "", kb: "4571730"}, + {revision: "", kb: "4577064"}, + {revision: "", kb: "4580378"}, + {revision: "", kb: "4586807"}, + {revision: "", kb: "4592498"}, + {revision: "", kb: "4598288"}, + {revision: "", kb: "4601360"}, + {revision: "", kb: "5000844"}, + {revision: "", kb: "5001389"}, + {revision: "", kb: "5003210"}, + {revision: "", kb: "5003661"}, + {revision: "", kb: "5004955"}, + {revision: "", kb: "5004305"}, + {revision: "", kb: "5005090"}, + {revision: "", kb: "5005606"}, + {revision: "", kb: "5006736"}, + {revision: "", kb: "5007263"}, + {revision: "", kb: "5008274"}, + {revision: "", kb: "5009627"}, + {revision: "", kb: "5010384"}, + {revision: "", kb: "5011534"}, + {revision: "", kb: "5012658"}, + {revision: "", kb: "5014010"}, + {revision: "", kb: "5014752"}, + {revision: "", kb: "5015866"}, + {revision: "", kb: "5016669"}, + {revision: "", kb: "5017358"}, + {revision: "", kb: "5018450"}, + {revision: "", kb: "5020019"}, + {revision: "", kb: "5021289"}, + {revision: "", kb: "5022340"}, + {revision: "", kb: "5022890"}, + }, + securityOnly: []string{ + "4457984", + "4463104", + "4467700", + "4471319", + "4480957", + "4487019", + "4489876", + "4493458", + "4499180", + "4503287", + "4507461", + "4512491", + "4516051", + "4520009", + "4525239", + "4530719", + "4534312", + "4537822", + "4541504", + "4550957", + "4556854", + "4561645", + "4565529", + "4571746", + "4577070", + "4580385", + "4586817", + "4592504", + "4598287", + "4601366", + "5000856", + "5001332", + "5003225", + "5003695", + "5004959", + "5004299", + "5005095", + "5005618", + "5006715", + "5007246", + "5008271", + "5009601", + "5010403", + "5011525", + "5012632", + "5014006", + "5014743", + "5015870", + "5016686", + "5017371", + "5018446", + "5020005", + "5021293", + "5022353", + "5022893", + }, + }, + }, + "2008 R2": { + // https://support.microsoft.com/en-us/topic/windows-7-sp1-and-windows-server-2008-r2-sp1-update-history-720c2590-fd58-26ba-16cc-6d8f3b547599 + "SP1": { + rollup: []windowsRelease{ + {revision: "", kb: "3172605"}, + {revision: "", kb: "3179573"}, + {revision: "", kb: "3185278"}, + {revision: "", kb: "3185330"}, + {revision: "", kb: "3192403"}, + {revision: "", kb: "3197868"}, + {revision: "", kb: "3197869"}, + {revision: "", kb: "3207752"}, + {revision: "", kb: "3212646"}, + {revision: "", kb: "4012215"}, + {revision: "", kb: "4012218"}, + {revision: "", kb: "4015549"}, + {revision: "", kb: "4015552"}, + {revision: "", kb: "4019264"}, + {revision: "", kb: "4019265"}, + {revision: "", kb: "4022719"}, + {revision: "", kb: "4022168"}, + {revision: "", kb: "4025341"}, + {revision: "", kb: "4025340"}, + {revision: "", kb: "4034664"}, + {revision: "", kb: "4034670"}, + {revision: "", kb: "4038777"}, + {revision: "", kb: "4038803"}, + {revision: "", kb: "4041681"}, + {revision: "", kb: "4041686"}, + {revision: "", kb: "4048957"}, + {revision: "", kb: "4051034"}, + {revision: "", kb: "4054518"}, + {revision: "", kb: "4056894"}, + {revision: "", kb: "4057400"}, + {revision: "", kb: "4074598"}, + {revision: "", kb: "4075211"}, + {revision: "", kb: "4088875"}, + {revision: "", kb: "4088881"}, + {revision: "", kb: "4093118"}, + {revision: "", kb: "4093113"}, + {revision: "", kb: "4103718"}, + {revision: "", kb: "4103713"}, + {revision: "", kb: "4284826"}, + {revision: "", kb: "4284842"}, + {revision: "", kb: "4338818"}, + {revision: "", kb: "4338821"}, + {revision: "", kb: "4343900"}, + {revision: "", kb: "4343894"}, + {revision: "", kb: "4457144"}, + {revision: "", kb: "4457139"}, + {revision: "", kb: "4462923"}, + {revision: "", kb: "4462927"}, + {revision: "", kb: "4467107"}, + {revision: "", kb: "4467108"}, + {revision: "", kb: "4471318"}, + {revision: "", kb: "4480970"}, + {revision: "", kb: "4480955"}, + {revision: "", kb: "4486563"}, + {revision: "", kb: "4486565"}, + {revision: "", kb: "4489878"}, + {revision: "", kb: "4489892"}, + {revision: "", kb: "4493472"}, + {revision: "", kb: "4493453"}, + {revision: "", kb: "4499164"}, + {revision: "", kb: "4499178"}, + {revision: "", kb: "4503292"}, + {revision: "", kb: "4503277"}, + {revision: "", kb: "4507449"}, + {revision: "", kb: "4507437"}, + {revision: "", kb: "4512506"}, + {revision: "", kb: "4512514"}, + {revision: "", kb: "4516065"}, + {revision: "", kb: "4516048"}, + {revision: "", kb: "4524157"}, + {revision: "", kb: "4519976"}, + {revision: "", kb: "4519972"}, + {revision: "", kb: "4525235"}, + {revision: "", kb: "4525251"}, + {revision: "", kb: "4530734"}, + {revision: "", kb: "4534310"}, + {revision: "", kb: "4539601"}, + {revision: "", kb: "4537820"}, + {revision: "", kb: "4540688"}, + {revision: "", kb: "4550964"}, + {revision: "", kb: "4556836"}, + {revision: "", kb: "4561643"}, + {revision: "", kb: "4565524"}, + {revision: "", kb: "4571729"}, + {revision: "", kb: "4577051"}, + {revision: "", kb: "4580345"}, + {revision: "", kb: "4586827"}, + {revision: "", kb: "4592471"}, + {revision: "", kb: "4598279"}, + {revision: "", kb: "4601347"}, + {revision: "", kb: "5000841"}, + {revision: "", kb: "5001335"}, + {revision: "", kb: "5003233"}, + {revision: "", kb: "5003667"}, + {revision: "", kb: "5004953"}, + {revision: "", kb: "5004289"}, + {revision: "", kb: "5005088"}, + {revision: "", kb: "5005633"}, + {revision: "", kb: "5006743"}, + {revision: "", kb: "5007236"}, + {revision: "", kb: "5008244"}, + {revision: "", kb: "5009610"}, + {revision: "", kb: "5010404"}, + {revision: "", kb: "5011552"}, + {revision: "", kb: "5012626"}, + {revision: "", kb: "5014012"}, + {revision: "", kb: "5014748"}, + {revision: "", kb: "5015861"}, + {revision: "", kb: "5016676"}, + {revision: "", kb: "5017361"}, + {revision: "", kb: "5018454"}, + {revision: "", kb: "5020000"}, + {revision: "", kb: "5021291"}, + {revision: "", kb: "5022338"}, + {revision: "", kb: "5022872"}, + }, + securityOnly: []string{ + "3192391", + "3197867", + "3205394", + "3212642", + "4012212", + "4015546", + "4019263", + "4022722", + "4025337", + "4034679", + "4038779", + "4041678", + "4048960", + "4054521", + "4056897", + "4074587", + "4088878", + "4093108", + "4103712", + "4284867", + "4338823", + "4343899", + "4457145", + "4462915", + "4467106", + "4471328", + "4480960", + "4486564", + "4489885", + "4493448", + "4499175", + "4503269", + "4507456", + "4512486", + "4516033", + "4520003", + "4525233", + "4530692", + "4534314", + "4537813", + "4541500", + "4550965", + "4556843", + "4561669", + "4565539", + "4571719", + "4577053", + "4580387", + "4586805", + "4592503", + "4598289", + "4601363", + "5000851", + "5001392", + "5003228", + "5003694", + "5004951", + "5004307", + "5005089", + "5005615", + "5006728", + "5007233", + "5008282", + "5009621", + "5010422", + "5011529", + "5012649", + "5013999", + "5014742", + "5015862", + "5016679", + "5017373", + "5018479", + "5020013", + "5021288", + "5022339", + "5022874", + }, + }, + }, + "2012": { + // https://support.microsoft.com/en-us/topic/windows-server-2012-update-history-abfb9afd-2ebf-1c19-4224-ad86f8741edd + "": { + rollup: []windowsRelease{ + {revision: "", kb: "3172615"}, + {revision: "", kb: "3179575"}, + {revision: "", kb: "3185280"}, + {revision: "", kb: "3185332"}, + {revision: "", kb: "3192406"}, + {revision: "", kb: "3197877"}, + {revision: "", kb: "3197878"}, + {revision: "", kb: "3205409"}, + {revision: "", kb: "4012217"}, + {revision: "", kb: "4012220"}, + {revision: "", kb: "4015551"}, + {revision: "", kb: "4015554"}, + {revision: "", kb: "4019216"}, + {revision: "", kb: "4019218"}, + {revision: "", kb: "4022724"}, + {revision: "", kb: "4022721"}, + {revision: "", kb: "4025331"}, + {revision: "", kb: "4025332"}, + {revision: "", kb: "4034665"}, + {revision: "", kb: "4034659"}, + {revision: "", kb: "4038799"}, + {revision: "", kb: "4038797"}, + {revision: "", kb: "4041690"}, + {revision: "", kb: "4041692"}, + {revision: "", kb: "4048959"}, + {revision: "", kb: "4050945"}, + {revision: "", kb: "4054520"}, + {revision: "", kb: "4056896"}, + {revision: "", kb: "4057402"}, + {revision: "", kb: "4074593"}, + {revision: "", kb: "4075213"}, + {revision: "", kb: "4088877"}, + {revision: "", kb: "4088883"}, + {revision: "", kb: "4093123"}, + {revision: "", kb: "4093116"}, + {revision: "", kb: "4103730"}, + {revision: "", kb: "4103719"}, + {revision: "", kb: "4284855"}, + {revision: "", kb: "4284852"}, + {revision: "", kb: "4338830"}, + {revision: "", kb: "4338816"}, + {revision: "", kb: "4343901"}, + {revision: "", kb: "4343895"}, + {revision: "", kb: "4457135"}, + {revision: "", kb: "4457134"}, + {revision: "", kb: "4462929"}, + {revision: "", kb: "4462925"}, + {revision: "", kb: "4467701"}, + {revision: "", kb: "4467683"}, + {revision: "", kb: "4471330"}, + {revision: "", kb: "4480975"}, + {revision: "", kb: "4480971"}, + {revision: "", kb: "4487025"}, + {revision: "", kb: "4487024"}, + {revision: "", kb: "4489891"}, + {revision: "", kb: "4489920"}, + {revision: "", kb: "4493451"}, + {revision: "", kb: "4493462"}, + {revision: "", kb: "4499171"}, + {revision: "", kb: "4499145"}, + {revision: "", kb: "4503285"}, + {revision: "", kb: "4503295"}, + {revision: "", kb: "4507462"}, + {revision: "", kb: "4507447"}, + {revision: "", kb: "4512518"}, + {revision: "", kb: "4512512"}, + {revision: "", kb: "4516055"}, + {revision: "", kb: "4516069"}, + {revision: "", kb: "4524154"}, + {revision: "", kb: "4520007"}, + {revision: "", kb: "4520013"}, + {revision: "", kb: "4525246"}, + {revision: "", kb: "4525242"}, + {revision: "", kb: "4530691"}, + {revision: "", kb: "4534283"}, + {revision: "", kb: "4534320"}, + {revision: "", kb: "4537814"}, + {revision: "", kb: "4537807"}, + {revision: "", kb: "4541510"}, + {revision: "", kb: "4541332"}, + {revision: "", kb: "4550917"}, + {revision: "", kb: "4550960"}, + {revision: "", kb: "4556840"}, + {revision: "", kb: "4561612"}, + {revision: "", kb: "4565537"}, + {revision: "", kb: "4571736"}, + {revision: "", kb: "4577038"}, + {revision: "", kb: "4580382"}, + {revision: "", kb: "4586834"}, + {revision: "", kb: "4592468"}, + {revision: "", kb: "4598278"}, + {revision: "", kb: "4601348"}, + {revision: "", kb: "5000847"}, + {revision: "", kb: "5001387"}, + {revision: "", kb: "5003208"}, + {revision: "", kb: "5003697"}, + {revision: "", kb: "5004956"}, + {revision: "", kb: "5004294"}, + {revision: "", kb: "5005099"}, + {revision: "", kb: "5005623"}, + {revision: "", kb: "5006739"}, + {revision: "", kb: "5007260"}, + {revision: "", kb: "5008277"}, + {revision: "", kb: "5009586"}, + {revision: "", kb: "5010392"}, + {revision: "", kb: "5011535"}, + {revision: "", kb: "5012650"}, + {revision: "", kb: "5014017"}, + {revision: "", kb: "5014747"}, + {revision: "", kb: "5015863"}, + {revision: "", kb: "5016672"}, + {revision: "", kb: "5017370"}, + {revision: "", kb: "5018457"}, + {revision: "", kb: "5020009"}, + {revision: "", kb: "5021285"}, + {revision: "", kb: "5022348"}, + {revision: "", kb: "5022903"}, + }, + securityOnly: []string{ + "3192393", + "3197876", + "3205408", + "4012214", + "4015548", + "4019214", + "4022718", + "4025343", + "4034666", + "4038786", + "4041679", + "4048962", + "4054523", + "4056899", + "4074589", + "4088880", + "4093122", + "4103726", + "4284846", + "4338820", + "4343896", + "4457140", + "4462931", + "4467678", + "4471326", + "4480972", + "4486993", + "4489884", + "4493450", + "4499158", + "4503263", + "4507464", + "4512482", + "4516062", + "4519985", + "4525253", + "4530698", + "4534288", + "4537794", + "4540694", + "4550971", + "4556852", + "4561674", + "4565535", + "4571702", + "4577048", + "4580353", + "4586808", + "4592497", + "4598297", + "4601357", + "5000840", + "5001383", + "5003203", + "5003696", + "5004960", + "5004302", + "5005094", + "5005607", + "5006732", + "5007245", + "5008255", + "5009619", + "5010412", + "5011527", + "5012666", + "5014018", + "5014741", + "5015875", + "5016684", + "5017377", + "5018478", + "5020003", + "5021303", + "5022343", + "5022895", + }, + }, + }, + "2012 R2": { + // https://support.microsoft.com/en-us/topic/windows-8-1-and-windows-server-2012-r2-update-history-47d81dd2-6804-b6ae-4112-20089467c7a6 + "": { + rollup: []windowsRelease{ + {revision: "", kb: "3172614"}, + {revision: "", kb: "3179574"}, + {revision: "", kb: "3185279"}, + {revision: "", kb: "3185331"}, + {revision: "", kb: "3192404"}, + {revision: "", kb: "3197874"}, + {revision: "", kb: "3197875"}, + {revision: "", kb: "3205401"}, + {revision: "", kb: "4012216"}, + {revision: "", kb: "4012219"}, + {revision: "", kb: "4015550"}, + {revision: "", kb: "4015553"}, + {revision: "", kb: "4019215"}, + {revision: "", kb: "4019217"}, + {revision: "", kb: "4022726"}, + {revision: "", kb: "4022720"}, + {revision: "", kb: "4025336"}, + {revision: "", kb: "4025335"}, + {revision: "", kb: "4034681"}, + {revision: "", kb: "4034663"}, + {revision: "", kb: "4038792"}, + {revision: "", kb: "4038774"}, + {revision: "", kb: "4041693"}, + {revision: "", kb: "4041685"}, + {revision: "", kb: "4048958"}, + {revision: "", kb: "4050946"}, + {revision: "", kb: "4054519"}, + {revision: "", kb: "4056895"}, + {revision: "", kb: "4057401"}, + {revision: "", kb: "4074594"}, + {revision: "", kb: "4075212"}, + {revision: "", kb: "4088876"}, + {revision: "", kb: "4088882"}, + {revision: "", kb: "4093114"}, + {revision: "", kb: "4093121"}, + {revision: "", kb: "4103725"}, + {revision: "", kb: "4103724"}, + {revision: "", kb: "4284815"}, + {revision: "", kb: "4284863"}, + {revision: "", kb: "4338815"}, + {revision: "", kb: "4338831"}, + {revision: "", kb: "4343898"}, + {revision: "", kb: "4343891"}, + {revision: "", kb: "4457129"}, + {revision: "", kb: "4457133"}, + {revision: "", kb: "4462926"}, + {revision: "", kb: "4462921"}, + {revision: "", kb: "4467697"}, + {revision: "", kb: "4467695"}, + {revision: "", kb: "4471320"}, + {revision: "", kb: "4480963"}, + {revision: "", kb: "4480969"}, + {revision: "", kb: "4487000"}, + {revision: "", kb: "4487016"}, + {revision: "", kb: "4489881"}, + {revision: "", kb: "4489893"}, + {revision: "", kb: "4493446"}, + {revision: "", kb: "4493443"}, + {revision: "", kb: "4499151"}, + {revision: "", kb: "4499182"}, + {revision: "", kb: "4503276"}, + {revision: "", kb: "4503283"}, + {revision: "", kb: "4507448"}, + {revision: "", kb: "4507463"}, + {revision: "", kb: "4512488"}, + {revision: "", kb: "4512478"}, + {revision: "", kb: "4516067"}, + {revision: "", kb: "4516041"}, + {revision: "", kb: "4524156"}, + {revision: "", kb: "4520005"}, + {revision: "", kb: "4520012"}, + {revision: "", kb: "4525243"}, + {revision: "", kb: "4525252"}, + {revision: "", kb: "4530702"}, + {revision: "", kb: "4534297"}, + {revision: "", kb: "4534324"}, + {revision: "", kb: "4537821"}, + {revision: "", kb: "4537819"}, + {revision: "", kb: "4541509"}, + {revision: "", kb: "4541334"}, + {revision: "", kb: "4550961"}, + {revision: "", kb: "4550958"}, + {revision: "", kb: "4556846"}, + {revision: "", kb: "4561666"}, + {revision: "", kb: "4565541"}, + {revision: "", kb: "4571703"}, + {revision: "", kb: "4577066"}, + {revision: "", kb: "4580347"}, + {revision: "", kb: "4586845"}, + {revision: "", kb: "4592484"}, + {revision: "", kb: "4598285"}, + {revision: "", kb: "4601384"}, + {revision: "", kb: "5000848"}, + {revision: "", kb: "5001382"}, + {revision: "", kb: "5003209"}, + {revision: "", kb: "5003671"}, + {revision: "", kb: "5004954"}, + {revision: "", kb: "5004298"}, + {revision: "", kb: "5005076"}, + {revision: "", kb: "5005613"}, + {revision: "", kb: "5006714"}, + {revision: "", kb: "5007247"}, + {revision: "", kb: "5008263"}, + {revision: "", kb: "5009624"}, + {revision: "", kb: "5010419"}, + {revision: "", kb: "5011564"}, + {revision: "", kb: "5012670"}, + {revision: "", kb: "5014011"}, + {revision: "", kb: "5014738"}, + {revision: "", kb: "5015874"}, + {revision: "", kb: "5016681"}, + {revision: "", kb: "5017367"}, + {revision: "", kb: "5018474"}, + {revision: "", kb: "5020023"}, + {revision: "", kb: "5021294"}, + {revision: "", kb: "5022352"}, + {revision: "", kb: "5022899"}, + }, + securityOnly: []string{ + "3192392", + "3197873", + "3205400", + "4012213", + "4015547", + "4019213", + "4022717", + "4025333", + "4034672", + "4038793", + "4041687", + "4048961", + "4054522", + "4056898", + "4074597", + "4088879", + "4093115", + "4103715", + "4284878", + "4338824", + "4343888", + "4457143", + "4462941", + "4467703", + "4471322", + "4480964", + "4487028", + "4489883", + "4493467", + "4499165", + "4503290", + "4507457", + "4512489", + "4516064", + "4519990", + "4525250", + "4530730", + "4534309", + "4537803", + "4541505", + "4550970", + "4556853", + "4561673", + "4565540", + "4571723", + "4577071", + "4580358", + "4586823", + "4592495", + "4598275", + "4601349", + "5000853", + "5001393", + "5003220", + "5003681", + "5004958", + "5004285", + "5005106", + "5005627", + "5006729", + "5007255", + "5008285", + "5009595", + "5010395", + "5011560", + "5012639", + "5014001", + "5014746", + "5015877", + "5016683", + "5017365", + "5018476", + "5020010", + "5021296", + "5022346", + "5022894", + }, + }, + }, + "2016": { + // https://support.microsoft.com/en-us/topic/windows-10-and-windows-server-2016-update-history-4acfbc84-a290-1b54-536a-1c0430e9f3fd + "14393": { + rollup: []windowsRelease{ + {revision: "10", kb: "3176929"}, + {revision: "51", kb: "3176495"}, + {revision: "82", kb: "3176934"}, + {revision: "105", kb: "3176938"}, + {revision: "187", kb: "3189866"}, + {revision: "187", kb: "3193494"}, + {revision: "189", kb: "3193494"}, + {revision: "222", kb: "3194496"}, + {revision: "321", kb: "3194798"}, + {revision: "351", kb: "3197954"}, + {revision: "447", kb: "3200970"}, + {revision: "448", kb: "3200970"}, + {revision: "479", kb: "3201845"}, + {revision: "571", kb: "3206632"}, + {revision: "576", kb: "3206632"}, + {revision: "693", kb: "3213986"}, + {revision: "729", kb: "4010672"}, + {revision: "953", kb: "4013429"}, + {revision: "969", kb: "4015438"}, + {revision: "970", kb: "4016635"}, + {revision: "1066", kb: "4015217"}, + {revision: "1083", kb: "4015217"}, + {revision: "1198", kb: "4019472"}, + {revision: "1230", kb: "4023680"}, + {revision: "1358", kb: "4022715"}, + {revision: "1378", kb: "4022723"}, + {revision: "1480", kb: "4025339"}, + {revision: "1532", kb: "4025334"}, + {revision: "1537", kb: "4038220"}, + {revision: "1593", kb: "4034658"}, + {revision: "1613", kb: "4034661"}, + {revision: "1670", kb: "4039396"}, + {revision: "1715", kb: "4038782"}, + {revision: "1737", kb: "4038801"}, + {revision: "1770", kb: "4041691"}, + {revision: "1794", kb: "4041688"}, + {revision: "1797", kb: "4052231"}, + {revision: "1884", kb: "4048953"}, + {revision: "1914", kb: "4051033"}, + {revision: "1944", kb: "4053579"}, + {revision: "2007", kb: "4056890"}, + {revision: "2034", kb: "4057142"}, + {revision: "2035", kb: "4057142"}, + {revision: "2068", kb: "4074590"}, + {revision: "2097", kb: "4077525"}, + {revision: "2125", kb: "4088787"}, + {revision: "2126", kb: "4088787"}, + {revision: "2155", kb: "4088889"}, + {revision: "2156", kb: "4096309"}, + {revision: "2189", kb: "4093119"}, + {revision: "2214", kb: "4093120"}, + {revision: "2248", kb: "4103723"}, + {revision: "2273", kb: "4103720"}, + {revision: "2312", kb: "4284880"}, + {revision: "2339", kb: "4284833"}, + {revision: "2363", kb: "4338814"}, + {revision: "2368", kb: "4345418"}, + {revision: "2395", kb: "4338822"}, + {revision: "2396", kb: "4346877"}, + {revision: "2430", kb: "4343887"}, + {revision: "2457", kb: "4343884"}, + {revision: "2485", kb: "4457131"}, + {revision: "2515", kb: "4457127"}, + {revision: "2551", kb: "4462917"}, + {revision: "2580", kb: "4462928"}, + {revision: "2608", kb: "4467691"}, + {revision: "2639", kb: "4467684"}, + {revision: "2641", kb: "4478877"}, + {revision: "2665", kb: "4471321"}, + {revision: "2670", kb: "4483229"}, + {revision: "2724", kb: "4480961"}, + {revision: "2759", kb: "4480977"}, + {revision: "2791", kb: "4487026"}, + {revision: "2828", kb: "4487006"}, + {revision: "2848", kb: "4489882"}, + {revision: "2879", kb: "4489889"}, + {revision: "2906", kb: "4493470"}, + {revision: "2908", kb: "4499418"}, + {revision: "2941", kb: "4493473"}, + {revision: "2969", kb: "4494440"}, + {revision: "2972", kb: "4505052"}, + {revision: "2999", kb: "4499177"}, + {revision: "3025", kb: "4503267"}, + {revision: "3053", kb: "4503294"}, + {revision: "3056", kb: "4509475"}, + {revision: "3085", kb: "4507460"}, + {revision: "3115", kb: "4507459"}, + {revision: "3144", kb: "4512517"}, + {revision: "3181", kb: "4512495"}, + {revision: "3204", kb: "4516044"}, + {revision: "3206", kb: "4522010"}, + {revision: "3242", kb: "4516061"}, + {revision: "3243", kb: "4524152"}, + {revision: "3274", kb: "4519998"}, + {revision: "3300", kb: "4519979"}, + {revision: "3326", kb: "4525236"}, + {revision: "3384", kb: "4530689"}, + {revision: "3443", kb: "4534271"}, + {revision: "3474", kb: "4534307"}, + {revision: "3504", kb: "4537764"}, + {revision: "3542", kb: "4537806"}, + {revision: "3564", kb: "4540670"}, + {revision: "3595", kb: "4541329"}, + {revision: "3630", kb: "4550929"}, + {revision: "3659", kb: "4550947"}, + {revision: "3686", kb: "4556813"}, + {revision: "3750", kb: "4561616"}, + {revision: "3755", kb: "4567517"}, + {revision: "3808", kb: "4565511"}, + {revision: "3866", kb: "4571694"}, + {revision: "3930", kb: "4577015"}, + {revision: "3986", kb: "4580346"}, + {revision: "4046", kb: "4586830"}, + {revision: "4048", kb: "4594441"}, + {revision: "4104", kb: "4593226"}, + {revision: "4169", kb: "4598243"}, + {revision: "4225", kb: "4601318"}, + {revision: "4283", kb: "5000803"}, + {revision: "4288", kb: "5001633"}, + {revision: "4350", kb: "5001347"}, + {revision: "4402", kb: "5003197"}, + {revision: "4467", kb: "5003638"}, + {revision: "4470", kb: "5004948"}, + {revision: "4530", kb: "5004238"}, + {revision: "4532", kb: "5005393"}, + {revision: "4583", kb: "5005043"}, + {revision: "4651", kb: "5005573"}, + {revision: "4704", kb: "5006669"}, + {revision: "4770", kb: "5007192"}, + {revision: "4771", kb: "5008601"}, + {revision: "4825", kb: "5008207"}, + {revision: "4827", kb: "5010195"}, + {revision: "4886", kb: "5009546"}, + {revision: "4889", kb: "5010790"}, + {revision: "4946", kb: "5010359"}, + {revision: "5006", kb: "5011495"}, + {revision: "5066", kb: "5012596"}, + {revision: "5125", kb: "5013952"}, + {revision: "5127", kb: "5015019"}, + {revision: "5192", kb: "5014702"}, + {revision: "5246", kb: "5015808"}, + {revision: "5291", kb: "5016622"}, + {revision: "5356", kb: "5017305"}, + {revision: "5427", kb: "5018411"}, + {revision: "5429", kb: "5020439"}, + {revision: "5501", kb: "5019964"}, + {revision: "5502", kb: "5021654"}, + {revision: "5582", kb: "5021235"}, + {revision: "5648", kb: "5022289"}, + {revision: "5717", kb: "5022838"}, + }, + }, + }, + "Version 1709": { + // https://support.microsoft.com/en-us/topic/windows-10-update-history-8127c2c6-6edf-4fdf-8b9f-0f7be1ef3562 + "16299": { + rollup: []windowsRelease{ + {revision: "19", kb: "4043961"}, + {revision: "64", kb: "4048955"}, + {revision: "98", kb: "4051963"}, + {revision: "125", kb: "4054517"}, + {revision: "192", kb: "4056892"}, + {revision: "194", kb: "4073290"}, + {revision: "201", kb: "4073291"}, + {revision: "214", kb: "4058258"}, + {revision: "248", kb: "4074588"}, + {revision: "251", kb: "4090913"}, + {revision: "309", kb: "4088776"}, + {revision: "334", kb: "4089848"}, + {revision: "371", kb: "4093112"}, + {revision: "402", kb: "4093105"}, + {revision: "431", kb: "4103727"}, + {revision: "461", kb: "4103714"}, + {revision: "492", kb: "4284819"}, + {revision: "522", kb: "4284822"}, + {revision: "547", kb: "4338825"}, + {revision: "551", kb: "4345420"}, + {revision: "579", kb: "4338817"}, + {revision: "611", kb: "4343897"}, + {revision: "637", kb: "4343893"}, + {revision: "665", kb: "4457142"}, + {revision: "666", kb: "4464217"}, + {revision: "699", kb: "4457136"}, + {revision: "726", kb: "4462918"}, + {revision: "755", kb: "4462932"}, + {revision: "785", kb: "4467686"}, + {revision: "820", kb: "4467681"}, + {revision: "846", kb: "4471329"}, + {revision: "847", kb: "4483232"}, + {revision: "904", kb: "4480978"}, + {revision: "936", kb: "4480967"}, + {revision: "967", kb: "4486996"}, + {revision: "1004", kb: "4487021"}, + {revision: "1029", kb: "4489886"}, + {revision: "1059", kb: "4489890"}, + {revision: "1087", kb: "4493441"}, + {revision: "1127", kb: "4493440"}, + {revision: "1146", kb: "4499179"}, + {revision: "1150", kb: "4505062"}, + {revision: "1182", kb: "4499147"}, + {revision: "1217", kb: "4503284"}, + {revision: "1237", kb: "4503281"}, + {revision: "1239", kb: "4509477"}, + {revision: "1268", kb: "4507455"}, + {revision: "1296", kb: "4507465"}, + {revision: "1331", kb: "4512516"}, + {revision: "1365", kb: "4512494"}, + {revision: "1387", kb: "4516066"}, + {revision: "1392", kb: "4522012"}, + {revision: "1420", kb: "4516071"}, + {revision: "1421", kb: "4524150"}, + {revision: "1451", kb: "4520004"}, + {revision: "1481", kb: "4520006"}, + {revision: "1508", kb: "4525241"}, + {revision: "1565", kb: "4530714"}, + {revision: "1625", kb: "4534276"}, + {revision: "1654", kb: "4534318"}, + {revision: "1686", kb: "4537789"}, + {revision: "1717", kb: "4537816"}, + {revision: "1747", kb: "4540681"}, + {revision: "1775", kb: "4541330"}, + {revision: "1776", kb: "4554342"}, + {revision: "1806", kb: "4550927"}, + {revision: "1868", kb: "4556812"}, + {revision: "1932", kb: "4561602"}, + {revision: "1937", kb: "4567515"}, + {revision: "1992", kb: "4565508"}, + {revision: "2045", kb: "4571741"}, + {revision: "2107", kb: "4577041"}, + {revision: "2166", kb: "4580328"}, + }, + }, + }, + "Version 1803": { + "17134": { + rollup: []windowsRelease{}, + }, + }, + "Version 1809": { + // https://support.microsoft.com/en-us/topic/windows-10-and-windows-server-2019-update-history-725fc2e1-4443-6831-a5ca-51ff5cbcb059 + "17763": { + rollup: []windowsRelease{ + {revision: "1", kb: ""}, + {revision: "55", kb: "4464330"}, + {revision: "107", kb: "4464455"}, + {revision: "134", kb: "4467708"}, + {revision: "168", kb: "4469342"}, + {revision: "194", kb: "4471332"}, + {revision: "195", kb: "4483235"}, + {revision: "253", kb: "4480116"}, + {revision: "292", kb: "4476976"}, + {revision: "316", kb: "4487044"}, + {revision: "348", kb: "4482887"}, + {revision: "379", kb: "4489899"}, + {revision: "402", kb: "4490481"}, + {revision: "404", kb: "4490481"}, + {revision: "437", kb: "4493509"}, + {revision: "439", kb: "4501835"}, + {revision: "475", kb: "4495667"}, + {revision: "503", kb: "4494441"}, + {revision: "504", kb: "4505056"}, + {revision: "529", kb: "4497934"}, + {revision: "557", kb: "4503327"}, + {revision: "592", kb: "4501371"}, + {revision: "593", kb: "4509479"}, + {revision: "615", kb: "4507469"}, + {revision: "652", kb: "4505658"}, + {revision: "678", kb: "4511553"}, + {revision: "720", kb: "4512534"}, + {revision: "737", kb: "4512578"}, + {revision: "740", kb: "4522015"}, + {revision: "774", kb: "4516077"}, + {revision: "775", kb: "4524148"}, + {revision: "805", kb: "4519338"}, + {revision: "832", kb: "4520062"}, + {revision: "864", kb: "4523205"}, + {revision: "914", kb: "4530715"}, + {revision: "973", kb: "4534273"}, + {revision: "1012", kb: "4534321"}, + {revision: "1039", kb: "4532691"}, + {revision: "1075", kb: "4537818"}, + {revision: "1098", kb: "4538461"}, + {revision: "1131", kb: "4541331"}, + {revision: "1132", kb: "4554354"}, + {revision: "1158", kb: "4549949"}, + {revision: "1192", kb: "4550969"}, + {revision: "1217", kb: "4551853"}, + {revision: "1282", kb: "4561608"}, + {revision: "1294", kb: "4567513"}, + {revision: "1339", kb: "4558998"}, + {revision: "1369", kb: "4559003"}, + {revision: "1397", kb: "4565349"}, + {revision: "1432", kb: "4571748"}, + {revision: "1457", kb: "4570333"}, + {revision: "1490", kb: "4577069"}, + {revision: "1518", kb: "4577668"}, + {revision: "1554", kb: "4580390"}, + {revision: "1577", kb: "4586793"}, + {revision: "1579", kb: "4594442"}, + {revision: "1613", kb: "4586839"}, + {revision: "1637", kb: "4592440"}, + {revision: "1697", kb: "4598230"}, + {revision: "1728", kb: "4598296"}, + {revision: "1757", kb: "4601345"}, + {revision: "1790", kb: "4601383"}, + {revision: "1817", kb: "5000822"}, + {revision: "1821", kb: "5001568"}, + {revision: "1823", kb: "5001638"}, + {revision: "1852", kb: "5000854"}, + {revision: "1879", kb: "5001342"}, + {revision: "1911", kb: "5001384"}, + {revision: "1935", kb: "5003171"}, + {revision: "1971", kb: "5003217"}, + {revision: "1999", kb: "5003646"}, + {revision: "2028", kb: "5003703"}, + {revision: "2029", kb: "5004947"}, + {revision: "2061", kb: "5004244"}, + {revision: "2090", kb: "5004308"}, + {revision: "2091", kb: "5005394"}, + {revision: "2114", kb: "5005030"}, + {revision: "2145", kb: "5005102"}, + {revision: "2183", kb: "5005568"}, + {revision: "2210", kb: "5005625"}, + {revision: "2213", kb: "5005625"}, + {revision: "2237", kb: "5006672"}, + {revision: "2268", kb: "5006744"}, + {revision: "2300", kb: "5007206"}, + {revision: "2305", kb: "5008602"}, + {revision: "2330", kb: "5007266"}, + {revision: "2366", kb: "5008218"}, + {revision: "2369", kb: "5010196"}, + {revision: "2452", kb: "5009557"}, + {revision: "2458", kb: "5010791"}, + {revision: "2510", kb: "5009616"}, + {revision: "2565", kb: "5010351"}, + {revision: "2628", kb: "5010427"}, + {revision: "2686", kb: "5011503"}, + {revision: "2746", kb: "5011551"}, + {revision: "2803", kb: "5012647"}, + {revision: "2867", kb: "5012636"}, + {revision: "2928", kb: "5013941"}, + {revision: "2931", kb: "5015018"}, + {revision: "2989", kb: "5014022"}, + {revision: "3046", kb: "5014692"}, + {revision: "3113", kb: "5014669"}, + {revision: "3165", kb: "5015811"}, + {revision: "3232", kb: "5015880"}, + {revision: "3287", kb: "5016623"}, + {revision: "3346", kb: "5016690"}, + {revision: "3406", kb: "5017315"}, + {revision: "3469", kb: "5017379"}, + {revision: "3532", kb: "5018419"}, + {revision: "3534", kb: "5020438"}, + {revision: "3650", kb: "5019966"}, + {revision: "3653", kb: "5021655"}, + {revision: "3770", kb: "5021237"}, + {revision: "3772", kb: "5022554"}, + {revision: "3887", kb: "5022286"}, + {revision: "4010", kb: "5022840"}, + }, + }, + }, + "2019": { + // https://support.microsoft.com/en-us/topic/windows-10-and-windows-server-2019-update-history-725fc2e1-4443-6831-a5ca-51ff5cbcb059 + "17763": { + rollup: []windowsRelease{ + {revision: "1", kb: ""}, + {revision: "55", kb: "4464330"}, + {revision: "107", kb: "4464455"}, + {revision: "134", kb: "4467708"}, + {revision: "168", kb: "4469342"}, + {revision: "194", kb: "4471332"}, + {revision: "195", kb: "4483235"}, + {revision: "253", kb: "4480116"}, + {revision: "292", kb: "4476976"}, + {revision: "316", kb: "4487044"}, + {revision: "348", kb: "4482887"}, + {revision: "379", kb: "4489899"}, + {revision: "402", kb: "4490481"}, + {revision: "404", kb: "4490481"}, + {revision: "437", kb: "4493509"}, + {revision: "439", kb: "4501835"}, + {revision: "475", kb: "4495667"}, + {revision: "503", kb: "4494441"}, + {revision: "504", kb: "4505056"}, + {revision: "529", kb: "4497934"}, + {revision: "557", kb: "4503327"}, + {revision: "592", kb: "4501371"}, + {revision: "593", kb: "4509479"}, + {revision: "615", kb: "4507469"}, + {revision: "652", kb: "4505658"}, + {revision: "678", kb: "4511553"}, + {revision: "720", kb: "4512534"}, + {revision: "737", kb: "4512578"}, + {revision: "740", kb: "4522015"}, + {revision: "774", kb: "4516077"}, + {revision: "775", kb: "4524148"}, + {revision: "805", kb: "4519338"}, + {revision: "832", kb: "4520062"}, + {revision: "864", kb: "4523205"}, + {revision: "914", kb: "4530715"}, + {revision: "973", kb: "4534273"}, + {revision: "1012", kb: "4534321"}, + {revision: "1039", kb: "4532691"}, + {revision: "1075", kb: "4537818"}, + {revision: "1098", kb: "4538461"}, + {revision: "1131", kb: "4541331"}, + {revision: "1132", kb: "4554354"}, + {revision: "1158", kb: "4549949"}, + {revision: "1192", kb: "4550969"}, + {revision: "1217", kb: "4551853"}, + {revision: "1282", kb: "4561608"}, + {revision: "1294", kb: "4567513"}, + {revision: "1339", kb: "4558998"}, + {revision: "1369", kb: "4559003"}, + {revision: "1397", kb: "4565349"}, + {revision: "1432", kb: "4571748"}, + {revision: "1457", kb: "4570333"}, + {revision: "1490", kb: "4577069"}, + {revision: "1518", kb: "4577668"}, + {revision: "1554", kb: "4580390"}, + {revision: "1577", kb: "4586793"}, + {revision: "1579", kb: "4594442"}, + {revision: "1613", kb: "4586839"}, + {revision: "1637", kb: "4592440"}, + {revision: "1697", kb: "4598230"}, + {revision: "1728", kb: "4598296"}, + {revision: "1757", kb: "4601345"}, + {revision: "1790", kb: "4601383"}, + {revision: "1817", kb: "5000822"}, + {revision: "1821", kb: "5001568"}, + {revision: "1823", kb: "5001638"}, + {revision: "1852", kb: "5000854"}, + {revision: "1879", kb: "5001342"}, + {revision: "1911", kb: "5001384"}, + {revision: "1935", kb: "5003171"}, + {revision: "1971", kb: "5003217"}, + {revision: "1999", kb: "5003646"}, + {revision: "2028", kb: "5003703"}, + {revision: "2029", kb: "5004947"}, + {revision: "2061", kb: "5004244"}, + {revision: "2090", kb: "5004308"}, + {revision: "2091", kb: "5005394"}, + {revision: "2114", kb: "5005030"}, + {revision: "2145", kb: "5005102"}, + {revision: "2183", kb: "5005568"}, + {revision: "2210", kb: "5005625"}, + {revision: "2213", kb: "5005625"}, + {revision: "2237", kb: "5006672"}, + {revision: "2268", kb: "5006744"}, + {revision: "2300", kb: "5007206"}, + {revision: "2305", kb: "5008602"}, + {revision: "2330", kb: "5007266"}, + {revision: "2366", kb: "5008218"}, + {revision: "2369", kb: "5010196"}, + {revision: "2452", kb: "5009557"}, + {revision: "2458", kb: "5010791"}, + {revision: "2510", kb: "5009616"}, + {revision: "2565", kb: "5010351"}, + {revision: "2628", kb: "5010427"}, + {revision: "2686", kb: "5011503"}, + {revision: "2746", kb: "5011551"}, + {revision: "2803", kb: "5012647"}, + {revision: "2867", kb: "5012636"}, + {revision: "2928", kb: "5013941"}, + {revision: "2931", kb: "5015018"}, + {revision: "2989", kb: "5014022"}, + {revision: "3046", kb: "5014692"}, + {revision: "3113", kb: "5014669"}, + {revision: "3165", kb: "5015811"}, + {revision: "3232", kb: "5015880"}, + {revision: "3287", kb: "5016623"}, + {revision: "3346", kb: "5016690"}, + {revision: "3406", kb: "5017315"}, + {revision: "3469", kb: "5017379"}, + {revision: "3532", kb: "5018419"}, + {revision: "3534", kb: "5020438"}, + {revision: "3650", kb: "5019966"}, + {revision: "3653", kb: "5021655"}, + {revision: "3770", kb: "5021237"}, + {revision: "3772", kb: "5022554"}, + {revision: "3887", kb: "5022286"}, + {revision: "4010", kb: "5022840"}, + }, + }, + }, + "Version 1903": { + // https://support.microsoft.com/en-us/topic/windows-10-update-history-e6058e7c-4116-38f1-b984-4fcacfba5e5d + "18362": { + rollup: []windowsRelease{ + {revision: "116", kb: "4505057"}, + {revision: "145", kb: "4497935"}, + {revision: "175", kb: "4503293"}, + {revision: "207", kb: "4501375"}, + {revision: "239", kb: "4507453"}, + {revision: "267", kb: "4505903"}, + {revision: "295", kb: "4512508"}, + {revision: "329", kb: "4512941"}, + {revision: "356", kb: "4515384"}, + {revision: "357", kb: "4522016"}, + {revision: "387", kb: "4517211"}, + {revision: "388", kb: "4524147"}, + {revision: "418", kb: "4517389"}, + {revision: "449", kb: "4522355"}, + {revision: "476", kb: "4524570"}, + {revision: "535", kb: "4530684"}, + {revision: "592", kb: "4528760"}, + {revision: "628", kb: "4532695"}, + {revision: "657", kb: "4532693"}, + {revision: "693", kb: "4535996"}, + {revision: "719", kb: "4540673"}, + {revision: "720", kb: "4551762"}, + {revision: "752", kb: "4541335"}, + {revision: "753", kb: "4554364"}, + {revision: "778", kb: "4549951"}, + {revision: "815", kb: "4550945"}, + {revision: "836", kb: "4556799"}, + {revision: "900", kb: "4560960"}, + {revision: "904", kb: "4567512"}, + {revision: "959", kb: "4565483"}, + {revision: "997", kb: "4559004"}, + {revision: "1016", kb: "4565351"}, + {revision: "1049", kb: "4566116"}, + {revision: "1082", kb: "4574727"}, + {revision: "1110", kb: "4577062"}, + {revision: "1139", kb: "4577671"}, + {revision: "1171", kb: "4580386"}, + {revision: "1198", kb: "4586786"}, + {revision: "1199", kb: "4594443"}, + {revision: "1237", kb: "4586819"}, + {revision: "1256", kb: "4592449"}, + }, + }, + }, + "Version 1909": { + // https://support.microsoft.com/en-us/topic/windows-10-update-history-53c270dc-954f-41f7-7ced-488578904dfe + "18363": { + rollup: []windowsRelease{ + {revision: "476", kb: "4524570"}, + {revision: "535", kb: "4530684"}, + {revision: "592", kb: "4528760"}, + {revision: "628", kb: "4532695"}, + {revision: "657", kb: "4532693"}, + {revision: "693", kb: "4535996"}, + {revision: "719", kb: "4540673"}, + {revision: "720", kb: "4551762"}, + {revision: "752", kb: "4541335"}, + {revision: "753", kb: "4554364"}, + {revision: "778", kb: "4549951"}, + {revision: "815", kb: "4550945"}, + {revision: "836", kb: "4556799"}, + {revision: "900", kb: "4560960"}, + {revision: "904", kb: "4567512"}, + {revision: "959", kb: "4565483"}, + {revision: "997", kb: "4559004"}, + {revision: "1016", kb: "4565351"}, + {revision: "1049", kb: "4566116"}, + {revision: "1082", kb: "4574727"}, + {revision: "1110", kb: "4577062"}, + {revision: "1139", kb: "4577671"}, + {revision: "1171", kb: "4580386"}, + {revision: "1198", kb: "4586786"}, + {revision: "1199", kb: "4594443"}, + {revision: "1237", kb: "4586819"}, + {revision: "1256", kb: "4592449"}, + {revision: "1316", kb: "4598229"}, + {revision: "1350", kb: "4598298"}, + {revision: "1377", kb: "4601315"}, + {revision: "1379", kb: "5001028"}, + {revision: "1411", kb: "4601380"}, + {revision: "1440", kb: "5000808"}, + {revision: "1441", kb: "5001566"}, + {revision: "1443", kb: "5001648"}, + {revision: "1474", kb: "5000850"}, + {revision: "1500", kb: "5001337"}, + {revision: "1533", kb: "5001396"}, + {revision: "1556", kb: "5003169"}, + {revision: "1593", kb: "5003212"}, + {revision: "1621", kb: "5003635"}, + {revision: "1645", kb: "5003698"}, + {revision: "1646", kb: "5004946"}, + {revision: "1679", kb: "5004245"}, + {revision: "1714", kb: "5004293"}, + {revision: "1734", kb: "5005031"}, + {revision: "1766", kb: "5005103"}, + {revision: "1801", kb: "5005566"}, + {revision: "1830", kb: "5005624"}, + {revision: "1832", kb: "5005624"}, + {revision: "1854", kb: "5006667"}, + {revision: "1916", kb: "5007189"}, + {revision: "1977", kb: "5008206"}, + {revision: "2037", kb: "5009545"}, + {revision: "2039", kb: "5010792"}, + {revision: "2094", kb: "5010345"}, + {revision: "2158", kb: "5011485"}, + {revision: "2212", kb: "5012591"}, + {revision: "2274", kb: "5013945"}, + }, + }, + }, + "Version 2004": { + // https://support.microsoft.com/en-us/topic/windows-10-update-history-24ea91f4-36e7-d8fd-0ddb-d79d9d0cdbda + "19041": { + rollup: []windowsRelease{ + {revision: "264", kb: ""}, + {revision: "329", kb: "4557957"}, + {revision: "331", kb: "4567523"}, + {revision: "388", kb: "4565503"}, + {revision: "423", kb: "4568831"}, + {revision: "450", kb: "4566782"}, + {revision: "488", kb: "4571744"}, + {revision: "508", kb: "4571756"}, + {revision: "546", kb: "4577063"}, + {revision: "572", kb: "4579311"}, + {revision: "610", kb: "4580364"}, + {revision: "630", kb: "4586781"}, + {revision: "631", kb: "4594440"}, + {revision: "662", kb: "4586853"}, + {revision: "685", kb: "4592438"}, + {revision: "746", kb: "4598242"}, + {revision: "789", kb: "4598291"}, + {revision: "804", kb: "4601319"}, + {revision: "844", kb: "4601382"}, + {revision: "867", kb: "5000802"}, + {revision: "868", kb: "5001567"}, + {revision: "870", kb: "5001649"}, + {revision: "906", kb: "5000842"}, + {revision: "928", kb: "5001330"}, + {revision: "964", kb: "5001391"}, + {revision: "985", kb: "5003173"}, + {revision: "1023", kb: "5003214"}, + {revision: "1052", kb: "5003637"}, + {revision: "1055", kb: "5004476"}, + {revision: "1081", kb: "5003690"}, + {revision: "1082", kb: "5004760"}, + {revision: "1083", kb: "5004945"}, + {revision: "1110", kb: "5004237"}, + {revision: "1151", kb: "5004296"}, + {revision: "1165", kb: "5005033"}, + {revision: "1202", kb: "5005101"}, + {revision: "1237", kb: "5005565"}, + {revision: "1266", kb: "5005611"}, + {revision: "1288", kb: "5006670"}, + {revision: "1320", kb: "5006738"}, + {revision: "1348", kb: "5007186"}, + {revision: "1387", kb: "5007253"}, + {revision: "1415", kb: "5008212"}, + }, + }, + }, + "Version 20H2": { + // https://support.microsoft.com/en-us/topic/windows-10-update-history-7dd3071a-3906-fa2c-c342-f7f86728a6e3 + "19042": { + rollup: []windowsRelease{ + {revision: "572", kb: ""}, + {revision: "610", kb: "4580364"}, + {revision: "630", kb: "4586781"}, + {revision: "631", kb: "4594440"}, + {revision: "662", kb: "4586853"}, + {revision: "685", kb: "4592438"}, + {revision: "746", kb: "4598242"}, + {revision: "789", kb: "4598291"}, + {revision: "804", kb: "4601319"}, + {revision: "844", kb: "4601382"}, + {revision: "867", kb: "5000802"}, + {revision: "868", kb: "5001567"}, + {revision: "870", kb: "5001649"}, + {revision: "906", kb: "5000842"}, + {revision: "928", kb: "5001330"}, + {revision: "964", kb: "5001391"}, + {revision: "985", kb: "5003173"}, + {revision: "1023", kb: "5003214"}, + {revision: "1052", kb: "5003637"}, + {revision: "1055", kb: "5004476"}, + {revision: "1081", kb: "5003690"}, + {revision: "1082", kb: "5004760"}, + {revision: "1083", kb: "5004945"}, + {revision: "1110", kb: "5004237"}, + {revision: "1151", kb: "5004296"}, + {revision: "1165", kb: "5005033"}, + {revision: "1202", kb: "5005101"}, + {revision: "1237", kb: "5005565"}, + {revision: "1266", kb: "5005611"}, + {revision: "1288", kb: "5006670"}, + {revision: "1320", kb: "5006738"}, + {revision: "1348", kb: "5007186"}, + {revision: "1387", kb: "5007253"}, + {revision: "1415", kb: "5008212"}, + {revision: "1466", kb: "5009543"}, + {revision: "1469", kb: "5010793"}, + {revision: "1503", kb: "5009596"}, + {revision: "1526", kb: "5010342"}, + {revision: "1566", kb: "5010415"}, + {revision: "1586", kb: "5011487"}, + {revision: "1620", kb: "5011543"}, + {revision: "1645", kb: "5012599"}, + {revision: "1682", kb: "5011831"}, + {revision: "1706", kb: "5013942"}, + {revision: "1708", kb: "5015020"}, + {revision: "1741", kb: "5014023"}, + {revision: "1766", kb: "5014699"}, + {revision: "1767", kb: "5016139"}, + {revision: "1806", kb: "5014666"}, + {revision: "1826", kb: "5015807"}, + {revision: "1865", kb: "5015878"}, + {revision: "1889", kb: "5016616"}, + {revision: "1949", kb: "5016688"}, + {revision: "2006", kb: "5017308"}, + {revision: "2075", kb: "5017380"}, + {revision: "2130", kb: "5018410"}, + {revision: "2132", kb: "5020435"}, + {revision: "2193", kb: "5018482"}, + {revision: "2194", kb: "5020953"}, + {revision: "2251", kb: "5019959"}, + {revision: "2311", kb: "5020030"}, + {revision: "2364", kb: "5021233"}, + {revision: "2486", kb: "5022282"}, + {revision: "2546", kb: "5019275"}, + {revision: "2604", kb: "5022834"}, + {revision: "2673", kb: "5022906"}, + }, + }, + }, + "2022": { + // https://support.microsoft.com/en-us/topic/windows-server-2022-update-history-e1caa597-00c5-4ab9-9f3e-8212fe80b2ee + "20348": { + rollup: []windowsRelease{ + {revision: "230", kb: "5005575"}, + {revision: "261", kb: "5005619"}, + {revision: "288", kb: "5006699"}, + {revision: "320", kb: "5006745"}, + {revision: "350", kb: "5007205"}, + {revision: "380", kb: "5007254"}, + {revision: "405", kb: "5008223"}, + {revision: "407", kb: "5010197"}, + {revision: "469", kb: "5009555"}, + {revision: "473", kb: "5010796"}, + {revision: "502", kb: "5009608"}, + {revision: "524", kb: "5010354"}, + {revision: "558", kb: "5010421"}, + {revision: "587", kb: "5011497"}, + {revision: "617", kb: "5011558"}, + {revision: "643", kb: "5012604"}, + {revision: "681", kb: "5012637"}, + {revision: "707", kb: "5013944"}, + {revision: "709", kb: "5015013"}, + {revision: "740", kb: "5014021"}, + {revision: "768", kb: "5014678"}, + {revision: "803", kb: "5014665"}, + {revision: "825", kb: "5015827"}, + {revision: "859", kb: "5015879"}, + {revision: "887", kb: "5016627"}, + {revision: "946", kb: "5016693"}, + {revision: "1006", kb: "5017316"}, + {revision: "1070", kb: "5017381"}, + {revision: "1129", kb: "5018421"}, + {revision: "1131", kb: "5020436"}, + {revision: "1194", kb: "5018485"}, + {revision: "1249", kb: "5019081"}, + {revision: "1251", kb: "5021656"}, + {revision: "1311", kb: "5020032"}, + {revision: "1366", kb: "5021249"}, + {revision: "1368", kb: "5022553"}, + {revision: "1487", kb: "5022291"}, + {revision: "1547", kb: "5022842"}, + }, + }, + }, + }, +} + +func (o *windows) detectKBsFromKernelVersion() (models.WindowsKB, error) { + switch ss := strings.Split(o.Kernel.Version, "."); len(ss) { + case 3: + return models.WindowsKB{}, nil + case 4: + switch { + case strings.HasPrefix(o.getDistro().Release, "Windows 10 "), strings.HasPrefix(o.getDistro().Release, "Windows 11 "): + osver := strings.Split(o.getDistro().Release, " ")[1] + + verReleases, ok := windowsReleases["Client"][osver] + if !ok { + return models.WindowsKB{}, nil + } + + rels, ok := verReleases[ss[2]] + if !ok { + return models.WindowsKB{}, nil + } + + nMyRevision, err := strconv.Atoi(ss[3]) + if err != nil { + return models.WindowsKB{}, xerrors.Errorf("Failed to parse revision number. err: %w", err) + } + + var index int + for i, r := range rels.rollup { + nRevision, err := strconv.Atoi(r.revision) + if err != nil { + return models.WindowsKB{}, xerrors.Errorf("Failed to parse revision number. err: %w", err) + } + if nMyRevision < nRevision { + break + } + index = i + } + + var kbs models.WindowsKB + for _, r := range rels.rollup[:index+1] { + if r.kb != "" { + kbs.Applied = append(kbs.Applied, r.kb) + } + } + for _, r := range rels.rollup[index+1:] { + if r.kb != "" { + kbs.Unapplied = append(kbs.Unapplied, r.kb) + } + } + + return kbs, nil + case strings.HasPrefix(o.getDistro().Release, "Windows Server 2016"), strings.HasPrefix(o.getDistro().Release, "Windows Server, Version 1709"), strings.HasPrefix(o.getDistro().Release, "Windows Server, Version 1809"), strings.HasPrefix(o.getDistro().Release, "Windows Server 2019"), strings.HasPrefix(o.getDistro().Release, "Windows Server, Version 1903"), strings.HasPrefix(o.getDistro().Release, "Windows Server, Version 1909"), strings.HasPrefix(o.getDistro().Release, "Windows Server, Version 2004"), strings.HasPrefix(o.getDistro().Release, "Windows Server, Version 20H2"), strings.HasPrefix(o.getDistro().Release, "Windows Server 2022"): + osver := strings.TrimSpace(strings.NewReplacer("Windows Server", "", ",", "", "(Server Core installation)", "").Replace(o.getDistro().Release)) + + verReleases, ok := windowsReleases["Server"][osver] + if !ok { + return models.WindowsKB{}, nil + } + + rels, ok := verReleases[ss[2]] + if !ok { + return models.WindowsKB{}, nil + } + + nMyRevision, err := strconv.Atoi(ss[3]) + if err != nil { + return models.WindowsKB{}, xerrors.Errorf("Failed to parse revision number. err: %w", err) + } + + var index int + for i, r := range rels.rollup { + nRevision, err := strconv.Atoi(r.revision) + if err != nil { + return models.WindowsKB{}, xerrors.Errorf("Failed to parse revision number. err: %w", err) + } + if nMyRevision < nRevision { + break + } + index = i + } + + var kbs models.WindowsKB + for _, r := range rels.rollup[:index+1] { + if r.kb != "" { + kbs.Applied = append(kbs.Applied, r.kb) + } + } + for _, r := range rels.rollup[index+1:] { + if r.kb != "" { + kbs.Unapplied = append(kbs.Unapplied, r.kb) + } + } + + return kbs, nil + default: + return models.WindowsKB{}, nil + } + default: + return models.WindowsKB{}, xerrors.Errorf("unexpected kernel version. expected: <major version>.<minor version>.<build>(.<revision>), actual: %s", o.Kernel.Version) + } +} + +func (o *windows) detectPlatform() { + if o.getServerInfo().Mode.IsOffline() { + o.setPlatform(models.Platform{Name: "unknown"}) + return + } + + ok, instanceID, err := o.detectRunningOnAws() + if err != nil { + o.setPlatform(models.Platform{Name: "other"}) + return + } + if ok { + o.setPlatform(models.Platform{ + Name: "aws", + InstanceID: instanceID, + }) + return + } + + //TODO Azure, GCP... + o.setPlatform(models.Platform{Name: "other"}) +} + +func (o *windows) detectRunningOnAws() (bool, string, error) { + if r := o.exec("Invoke-WebRequest -MaximumRetryCount 3 -TimeoutSec 1 -NoProxy http://169.254.169.254/latest/meta-data/instance-id", noSudo); r.isSuccess() { + id := strings.TrimSpace(r.Stdout) + if o.isAwsInstanceID(id) { + return true, id, nil + } + } + + if r := o.exec("Invoke-WebRequest -Method Put -MaximumRetryCount 3 -TimeoutSec 1 -NoProxy -Headers @{\"X-aws-ec2-metadata-token-ttl-seconds\"=\"300\"} http://169.254.169.254/latest/api/token", noSudo); r.isSuccess() { + r := o.exec(fmt.Sprintf("Invoke-WebRequest -MaximumRetryCount 3 -TimeoutSec 1 -NoProxy -Headers @{\"X-aws-ec2-metadata-token\"=\"%s\"} http://169.254.169.254/latest/meta-data/instance-id", strings.TrimSpace(r.Stdout)), noSudo) + if r.isSuccess() { + id := strings.TrimSpace(r.Stdout) + if !o.isAwsInstanceID(id) { + return false, "", nil + } + return true, id, nil + } + } + + if r := o.exec("where.exe curl.exe", noSudo); r.isSuccess() { + if r := o.exec("curl.exe --max-time 1 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id", noSudo); r.isSuccess() { + id := strings.TrimSpace(r.Stdout) + if o.isAwsInstanceID(id) { + return true, id, nil + } + } + + if r := o.exec("curl.exe -X PUT --max-time 1 --noproxy 169.254.169.254 -H \"X-aws-ec2-metadata-token-ttl-seconds: 300\" http://169.254.169.254/latest/api/token", noSudo); r.isSuccess() { + if r := o.exec(fmt.Sprintf("curl.exe -H \"X-aws-ec2-metadata-token: %s\" --max-time 1 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id", strings.TrimSpace(r.Stdout)), noSudo); r.isSuccess() { + id := strings.TrimSpace(r.Stdout) + if !o.isAwsInstanceID(id) { + return false, "", nil + } + return true, id, nil + } + } + } + + return false, "", xerrors.Errorf("Failed to Invoke-WebRequest or curl.exe to AWS instance metadata on %s. container: %s", o.ServerInfo.ServerName, o.ServerInfo.Container.Name) +} diff --git a/scanner/windows_test.go b/scanner/windows_test.go new file mode 100644 index 0000000000..85822a0158 --- /dev/null +++ b/scanner/windows_test.go @@ -0,0 +1,777 @@ +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_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) + } + }) + } +} + +func Test_windows_parseIP(t *testing.T) { + tests := []struct { + name string + args string + ipv4Addrs []string + ipv6Addrs []string + }{ + { + name: "en", + args: ` + +Windows IP Configuration + + +Ethernet adapter イーサネット 4: + + Connection-specific DNS Suffix . : vuls.local + Link-local IPv6 Address . . . . . : fe80::19b6:ae27:d1fe:2041%33 + Link-local IPv6 Address . . . . . : fe80::7080:8828:5cc8:c0ba%33 + IPv4 Address. . . . . . . . . . . : 10.145.8.50 + Subnet Mask . . . . . . . . . . . : 255.255.0.0 + Default Gateway . . . . . . . . . : :: + +Ethernet adapter イーサネット 2: + + Connection-specific DNS Suffix . : + Link-local IPv6 Address . . . . . : fe80::f49d:2c16:4270:759d%9 + IPv4 Address. . . . . . . . . . . : 192.168.56.1 + Subnet Mask . . . . . . . . . . . : 255.255.255.0 + Default Gateway . . . . . . . . . : + +Wireless LAN adapter ローカル エリア接続* 1: + + Media State . . . . . . . . . . . : Media disconnected + Connection-specific DNS Suffix . : + +Wireless LAN adapter ローカル エリア接続* 2: + + Media State . . . . . . . . . . . : Media disconnected + Connection-specific DNS Suffix . : + +Wireless LAN adapter Wi-Fi: + + Connection-specific DNS Suffix . : + IPv4 Address. . . . . . . . . . . : 192.168.0.205 + Subnet Mask . . . . . . . . . . . : 255.255.255.0 + Default Gateway . . . . . . . . . : 192.168.0.1 + +Ethernet adapter Bluetooth ネットワーク接続: + + Media State . . . . . . . . . . . : Media disconnected + Connection-specific DNS Suffix . : +`, + ipv4Addrs: []string{"10.145.8.50", "192.168.56.1", "192.168.0.205"}, + ipv6Addrs: []string{"fe80::19b6:ae27:d1fe:2041", "fe80::7080:8828:5cc8:c0ba", "fe80::f49d:2c16:4270:759d"}, + }, + { + name: "ja", + args: ` + +Windows IP 構成 + + +イーサネット アダプター イーサネット 4: + + 接続固有の DNS サフィックス . . . . .: future.co.jp + リンクローカル IPv6 アドレス. . . . .: fe80::19b6:ae27:d1fe:2041%33 + リンクローカル IPv6 アドレス. . . . .: fe80::7080:8828:5cc8:c0ba%33 + IPv4 アドレス . . . . . . . . . . . .: 10.145.8.50 + サブネット マスク . . . . . . . . . .: 255.255.0.0 + デフォルト ゲートウェイ . . . . . . .: :: + +イーサネット アダプター イーサネット 2: + + 接続固有の DNS サフィックス . . . . .: + リンクローカル IPv6 アドレス. . . . .: fe80::f49d:2c16:4270:759d%9 + IPv4 アドレス . . . . . . . . . . . .: 192.168.56.1 + サブネット マスク . . . . . . . . . .: 255.255.255.0 + デフォルト ゲートウェイ . . . . . . .: + +Wireless LAN adapter ローカル エリア接続* 1: + + メディアの状態. . . . . . . . . . . .: メディアは接続されていません + 接続固有の DNS サフィックス . . . . .: + +Wireless LAN adapter ローカル エリア接続* 2: + + メディアの状態. . . . . . . . . . . .: メディアは接続されていません + 接続固有の DNS サフィックス . . . . .: + +Wireless LAN adapter Wi-Fi: + + 接続固有の DNS サフィックス . . . . .: + IPv4 アドレス . . . . . . . . . . . .: 192.168.0.205 + サブネット マスク . . . . . . . . . .: 255.255.255.0 + デフォルト ゲートウェイ . . . . . . .: 192.168.0.1 + +イーサネット アダプター Bluetooth ネットワーク接続: + + メディアの状態. . . . . . . . . . . .: メディアは接続されていません + 接続固有の DNS サフィックス . . . . .: +`, + ipv4Addrs: []string{"10.145.8.50", "192.168.56.1", "192.168.0.205"}, + ipv6Addrs: []string{"fe80::19b6:ae27:d1fe:2041", "fe80::7080:8828:5cc8:c0ba", "fe80::f49d:2c16:4270:759d"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotIPv4s, gotIPv6s := (&windows{}).parseIP(tt.args) + if !reflect.DeepEqual(gotIPv4s, tt.ipv4Addrs) { + t.Errorf("windows.parseIP() got = %v, want %v", gotIPv4s, tt.ipv4Addrs) + } + if !reflect.DeepEqual(gotIPv6s, tt.ipv6Addrs) { + t.Errorf("windows.parseIP() got = %v, want %v", gotIPv6s, tt.ipv6Addrs) + } + }) + } +} diff --git a/server/server.go b/server/server.go index 7078d32acb..87545b681a 100644 --- a/server/server.go +++ b/server/server.go @@ -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 diff --git a/subcmds/discover.go b/subcmds/discover.go index 06cc6d4c37..63da18f50f 100644 --- a/subcmds/discover.go +++ b/subcmds/discover.go @@ -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" diff --git a/subcmds/report.go b/subcmds/report.go index cc6d7976dd..bdf3c01a54 100644 --- a/subcmds/report.go +++ b/subcmds/report.go @@ -1,5 +1,4 @@ -//go:build !scanner -// +build !scanner +//go:build !scanner && !windows package subcmds diff --git a/subcmds/report_windows.go b/subcmds/report_windows.go new file mode 100644 index 0000000000..2addfdfad5 --- /dev/null +++ b/subcmds/report_windows.go @@ -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 +} diff --git a/tui/tui.go b/tui/tui.go index 7376075450..b98e231754 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -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() {