feat: init nightly vuls for blackhat
This commit is contained in:
44
pkg/scan/cpe/cpe.go
Normal file
44
pkg/scan/cpe/cpe.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/knqyf263/go-cpe/naming"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
scanTypes "github.com/future-architect/vuls/pkg/scan/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "cpe analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
|
||||
ah.Host.Packages.CPE = map[string]types.CPE{}
|
||||
for _, c := range ah.Host.Config.Scan.CPE {
|
||||
if _, err := naming.UnbindFS(c.CPE); err != nil {
|
||||
return errors.Wrapf(err, "unbind %s", c.CPE)
|
||||
}
|
||||
key := c.CPE
|
||||
|
||||
if c.RunningOn != "" {
|
||||
if _, err := naming.UnbindFS(c.RunningOn); err != nil {
|
||||
return errors.Wrapf(err, "unbind %s", c.RunningOn)
|
||||
}
|
||||
key = fmt.Sprintf("%s_on_%s", c.CPE, c.RunningOn)
|
||||
}
|
||||
|
||||
ah.Host.Packages.CPE[key] = types.CPE{
|
||||
CPE: c.CPE,
|
||||
RunningOn: c.RunningOn,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
93
pkg/scan/os/os.go
Normal file
93
pkg/scan/os/os.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package os
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/scan/ospkg/apk"
|
||||
"github.com/future-architect/vuls/pkg/scan/ospkg/dpkg"
|
||||
"github.com/future-architect/vuls/pkg/scan/ospkg/rpm"
|
||||
"github.com/future-architect/vuls/pkg/scan/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "os analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *types.AnalyzerHost) error {
|
||||
status, stdout, stderr, err := ah.Host.Exec(ctx, "cat /etc/os-release", false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `exec "cat /etc/os-release"`)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
ah.Host.Family, ah.Host.Release, err = ParseOSRelease(stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse /etc/os-release")
|
||||
}
|
||||
|
||||
switch ah.Host.Family {
|
||||
case "debian", "ubuntu":
|
||||
ah.Analyzers = append(ah.Analyzers, dpkg.Analyzer{})
|
||||
case "redhat", "centos", "alma", "rocky", "fedora", "opensuse", "opensuse.tumbleweed", "opensuse.leap", "suse.linux.enterprise.server", "suse.linux.enterprise.desktop":
|
||||
ah.Analyzers = append(ah.Analyzers, rpm.Analyzer{})
|
||||
case "alpine":
|
||||
ah.Analyzers = append(ah.Analyzers, apk.Analyzer{})
|
||||
case "":
|
||||
return errors.New("family is unknown")
|
||||
default:
|
||||
return errors.New("not supported OS")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseOSRelease(stdout string) (string, string, error) {
|
||||
var family, versionID string
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
ss := strings.SplitN(line, "=", 2)
|
||||
if len(ss) != 2 {
|
||||
continue
|
||||
}
|
||||
key, value := strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1])
|
||||
|
||||
switch key {
|
||||
case "ID":
|
||||
switch id := strings.Trim(value, `"'`); id {
|
||||
case "almalinux":
|
||||
family = "alma"
|
||||
case "opensuse-leap", "opensuse-tumbleweed":
|
||||
family = strings.ReplaceAll(id, "-", ".")
|
||||
case "sles":
|
||||
family = "suse.linux.enterprise.server"
|
||||
case "sled":
|
||||
family = "suse.linux.enterprise.desktop"
|
||||
default:
|
||||
family = strings.ToLower(id)
|
||||
}
|
||||
case "VERSION_ID":
|
||||
versionID = strings.Trim(value, `"'`)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if family == "" {
|
||||
return "", "", errors.New("family is unknown")
|
||||
}
|
||||
return family, versionID, nil
|
||||
}
|
||||
71
pkg/scan/ospkg/apk/apk.go
Normal file
71
pkg/scan/ospkg/apk/apk.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package apk
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
scanTypes "github.com/future-architect/vuls/pkg/scan/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "apk analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
|
||||
status, stdout, stderr, err := ah.Host.Exec(ctx, "apk info -v", false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `exec "apk info -v"`)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse installed package")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
|
||||
pkgs := map[string]types.Package{}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
name, version, err := parseApkInfo(scanner.Text())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse apk info line")
|
||||
}
|
||||
if name == "" || version == "" {
|
||||
continue
|
||||
}
|
||||
pkgs[name] = types.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func parseApkInfo(line string) (string, string, error) {
|
||||
ss := strings.Split(line, "-")
|
||||
if len(ss) < 3 {
|
||||
if strings.Contains(ss[0], "WARNING") {
|
||||
return "", "", nil
|
||||
}
|
||||
return "", "", errors.Errorf(`unexpected package line format. accepts: "<package name>-<version>-<release>", received: "%s"`, line)
|
||||
}
|
||||
return strings.Join(ss[:len(ss)-2], "-"), strings.Join(ss[len(ss)-2:], "-"), nil
|
||||
}
|
||||
95
pkg/scan/ospkg/dpkg/dpkg.go
Normal file
95
pkg/scan/ospkg/dpkg/dpkg.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package dpkg
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
scanTypes "github.com/future-architect/vuls/pkg/scan/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "dpkg analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
|
||||
status, stdout, stderr, err := ah.Host.Exec(ctx, `dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Architecture},\${source:Package},\${source:Version}\n"`, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `exec "dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Architecture},\${source:Package},\${source:Version}\n"`)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse installed package")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
|
||||
pkgs := map[string]types.Package{}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
|
||||
name, status, version, arch, srcName, srcVersion, err := parseDPKGQueryLine(trimmed)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse dpkq query line")
|
||||
}
|
||||
|
||||
packageStatus := status[1]
|
||||
// Package status:
|
||||
// n = Not-installed
|
||||
// c = Config-files
|
||||
// H = Half-installed
|
||||
// U = Unpacked
|
||||
// F = Half-configured
|
||||
// W = Triggers-awaiting
|
||||
// t = Triggers-pending
|
||||
// i = Installed
|
||||
if packageStatus != 'i' {
|
||||
continue
|
||||
}
|
||||
pkgs[name] = types.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Arch: arch,
|
||||
SrcName: srcName,
|
||||
SrcVersion: srcVersion,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func parseDPKGQueryLine(line string) (string, string, string, string, string, string, error) {
|
||||
ss := strings.Split(line, ",")
|
||||
if len(ss) == 6 {
|
||||
// remove :amd64, i386...
|
||||
name, _, _ := strings.Cut(ss[0], ":")
|
||||
status := strings.TrimSpace(ss[1])
|
||||
if len(status) < 2 {
|
||||
return "", "", "", "", "", "", errors.Errorf(`unexpected db:Status-Abbrev format. accepts: "ii", received: "%s"`, status)
|
||||
}
|
||||
version := ss[2]
|
||||
arch := ss[3]
|
||||
srcName, _, _ := strings.Cut(ss[4], " ")
|
||||
srcVersion := ss[5]
|
||||
return name, status, version, arch, srcName, srcVersion, nil
|
||||
}
|
||||
return "", "", "", "", "", "", errors.Errorf(`unexpected package line format. accepts: "<bin name>,<status>,<bin version>,<arch>,<src name>,<src version>", received: "%s"`, line)
|
||||
}
|
||||
114
pkg/scan/ospkg/rpm/rpm.go
Normal file
114
pkg/scan/ospkg/rpm/rpm.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
scanTypes "github.com/future-architect/vuls/pkg/scan/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "rpm analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
|
||||
status, stdout, stderr, err := ah.Host.Exec(ctx, `rpm --version`, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `exec "rpm --version"`)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
cmd := `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR}\n"`
|
||||
rpmver, err := version.NewVersion(strings.TrimPrefix(strings.TrimSpace(stdout), "RPM version "))
|
||||
rpmModukaritylabel, err := version.NewVersion("4.15.0")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse rpm version for modularitylabel")
|
||||
}
|
||||
rpmEpochNum, err := version.NewVersion("4.8.0")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse rpm version for epochnum")
|
||||
}
|
||||
if rpmver.GreaterThanOrEqual(rpmModukaritylabel) {
|
||||
cmd = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR} %{MODULARITYLABEL}\n"`
|
||||
} else if rpmver.GreaterThanOrEqual(rpmEpochNum) {
|
||||
cmd = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR}\n"`
|
||||
}
|
||||
|
||||
status, stdout, stderr, err = ah.Host.Exec(ctx, cmd, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, `exec "%s"`, cmd)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse installed package")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
|
||||
pkgs := map[string]types.Package{}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
|
||||
name, version, release, arch, vendor, modularitylabel, err := parseRpmQaLine(trimmed)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse rpm -qa line")
|
||||
}
|
||||
|
||||
pkgs[name] = types.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Release: release,
|
||||
Arch: arch,
|
||||
Vendor: vendor,
|
||||
ModularityLabel: modularitylabel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func parseRpmQaLine(line string) (string, string, string, string, string, string, error) {
|
||||
ss := strings.Fields(line)
|
||||
if len(ss) < 6 {
|
||||
return "", "", "", "", "", "", errors.Errorf(`unexpected rpm -qa line format. accepts: "<name> <epoch> <version> <release> <arch> <vendor>( <modularitylabel>)", received: "%s"`, line)
|
||||
}
|
||||
|
||||
ver := ss[2]
|
||||
epoch := ss[1]
|
||||
if epoch != "0" && epoch != "(none)" {
|
||||
ver = fmt.Sprintf("%s:%s", epoch, ss[2])
|
||||
}
|
||||
|
||||
var modularitylabel string
|
||||
if len(ss) == 7 {
|
||||
modularitylabel = ss[5]
|
||||
}
|
||||
|
||||
return ss[0], ver, ss[3], ss[4], ss[5], modularitylabel, nil
|
||||
}
|
||||
54
pkg/scan/scan.go
Normal file
54
pkg/scan/scan.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/cmd/version"
|
||||
"github.com/future-architect/vuls/pkg/scan/cpe"
|
||||
"github.com/future-architect/vuls/pkg/scan/os"
|
||||
"github.com/future-architect/vuls/pkg/scan/systeminfo"
|
||||
scanTypes "github.com/future-architect/vuls/pkg/scan/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
func Scan(ctx context.Context, host *types.Host) error {
|
||||
ah := scanTypes.AnalyzerHost{Host: host}
|
||||
if ah.Host.Config.Scan.OSPkg != nil {
|
||||
if runtime.GOOS == "windows" {
|
||||
ah.Analyzers = append(ah.Analyzers, systeminfo.Analyzer{})
|
||||
} else {
|
||||
ah.Analyzers = append(ah.Analyzers, os.Analyzer{})
|
||||
}
|
||||
}
|
||||
if len(ah.Host.Config.Scan.CPE) > 0 {
|
||||
ah.Analyzers = append(ah.Analyzers, cpe.Analyzer{})
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
)
|
||||
for {
|
||||
if len(ah.Analyzers) == 0 {
|
||||
break
|
||||
}
|
||||
a := ah.Analyzers[0]
|
||||
if err = a.Analyze(ctx, &ah); err != nil {
|
||||
break
|
||||
}
|
||||
ah.Analyzers = ah.Analyzers[1:]
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
ah.Host.ScannedAt = &t
|
||||
ah.Host.ScannedVersion = version.Version
|
||||
ah.Host.ScannedRevision = version.Revision
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "analyze %s", ah.Host.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
480
pkg/scan/systeminfo/systeminfo.go
Normal file
480
pkg/scan/systeminfo/systeminfo.go
Normal file
@@ -0,0 +1,480 @@
|
||||
package systeminfo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/scan/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "systeminfo analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *types.AnalyzerHost) error {
|
||||
status, stdout, stderr, err := ah.Host.Exec(ctx, "systeminfo", false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `exec "systeminfo"`)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
ah.Host.Family, ah.Host.Release, ah.Host.Packages.KB, err = ParseSysteminfo(stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse systeminfo")
|
||||
}
|
||||
|
||||
if ah.Host.Family == "" {
|
||||
return errors.New("family is unknown")
|
||||
}
|
||||
if ah.Host.Release == "" {
|
||||
return errors.New("release is unknown")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseSysteminfo(stdout string) (string, string, []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 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 "", "", nil, errors.Errorf(`installation type not found from "%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 "", "", nil, errors.Errorf(`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:
|
||||
}
|
||||
}
|
||||
release, err := detectOSName(o)
|
||||
if err != nil {
|
||||
return "", "", nil, errors.Wrap(err, "detect os name")
|
||||
}
|
||||
|
||||
return "windows", release, kbs, nil
|
||||
}
|
||||
|
||||
type osInfo struct {
|
||||
productName string
|
||||
version string
|
||||
build string
|
||||
edition string
|
||||
servicePack string
|
||||
arch string
|
||||
installationType string
|
||||
}
|
||||
|
||||
func detectOSName(osInfo osInfo) (string, error) {
|
||||
osName, err := detectOSNameFromOSInfo(osInfo)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "detect OS Name from OSInfo: %#v", osInfo)
|
||||
}
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
n = fmt.Sprintf("%s x64 Edition", n)
|
||||
case "IA64":
|
||||
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":
|
||||
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 10") {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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 "", errors.New("OS Name not found")
|
||||
}
|
||||
|
||||
func formatArch(arch string) (string, error) {
|
||||
switch arch {
|
||||
case "x64-based":
|
||||
return "x64-based", nil
|
||||
case "ARM64-based":
|
||||
return "ARM64-based", nil
|
||||
case "Itanium-based":
|
||||
return "Itanium-based", nil
|
||||
case "X86-based":
|
||||
return "32-bit", nil
|
||||
default:
|
||||
return "", errors.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",
|
||||
},
|
||||
// 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",
|
||||
},
|
||||
},
|
||||
"11": {
|
||||
{
|
||||
build: "22000",
|
||||
name: "Windows 11", // not "Windows 11 Version 21H2"
|
||||
},
|
||||
},
|
||||
"Server": {
|
||||
{
|
||||
build: "14393",
|
||||
name: "Windows Server 2016",
|
||||
},
|
||||
{
|
||||
build: "16299",
|
||||
name: "Windows Server, Version 1709",
|
||||
},
|
||||
{
|
||||
build: "17134",
|
||||
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 "", errors.New("OS Type not found")
|
||||
}
|
||||
|
||||
v := builds[0].name
|
||||
for _, b := range builds {
|
||||
if mybuild == b.build {
|
||||
return b.name, nil
|
||||
}
|
||||
if mybuild < b.build {
|
||||
break
|
||||
}
|
||||
v = b.name
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
17
pkg/scan/types/types.go
Normal file
17
pkg/scan/types/types.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Analyzer interface {
|
||||
Name() string
|
||||
Analyze(context.Context, *AnalyzerHost) error
|
||||
}
|
||||
|
||||
type AnalyzerHost struct {
|
||||
Host *types.Host
|
||||
Analyzers []Analyzer
|
||||
}
|
||||
Reference in New Issue
Block a user