feat(windows): support Windows (#1581)

* chore(deps): mod update

* fix(scanner): do not attach tty because there is no need to enter ssh password

* feat(windows): support Windows
This commit is contained in:
MaineK00n
2023-03-28 19:00:33 +09:00
committed by GitHub
parent db21149f00
commit 947d668452
37 changed files with 6938 additions and 341 deletions

View File

@@ -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) {
family := header.Get("X-Vuls-OS-Family")
if family == "" {
return models.ScanResult{}, errOSFamilyHeader
}
release := header.Get("X-Vuls-OS-Release")
if release == "" {
return models.ScanResult{}, errOSReleaseHeader
}
kernelRelease := header.Get("X-Vuls-Kernel-Release")
if kernelRelease == "" {
logging.Log.Warn("If X-Vuls-Kernel-Release is not specified, there is a possibility of false detection")
}
kernelVersion := header.Get("X-Vuls-Kernel-Version")
if family == constant.Debian {
if kernelVersion == "" {
logging.Log.Warn("X-Vuls-Kernel-Version is empty. skip kernel vulnerability detection.")
} else {
if _, err := debver.NewVersion(kernelVersion); err != nil {
logging.Log.Warnf("X-Vuls-Kernel-Version is invalid. skip kernel vulnerability detection. actual kernelVersion: %s, err: %s", kernelVersion, err)
kernelVersion = ""
}
}
}
serverName := header.Get("X-Vuls-Server-Name")
if toLocalFile && serverName == "" {
return models.ScanResult{}, errServerNameHeader
}
distro := config.Distro{
Family: family,
Release: release,
family := header.Get("X-Vuls-OS-Family")
if family == "" {
return models.ScanResult{}, errOSFamilyHeader
}
kernel := models.Kernel{
Release: kernelRelease,
Version: kernelVersion,
}
installedPackages, srcPackages, err := ParseInstalledPkgs(distro, kernel, body)
if err != nil {
return models.ScanResult{}, err
}
switch family {
case constant.Windows:
osInfo, hotfixs, err := parseSystemInfo(toUTF8(body))
if err != nil {
return models.ScanResult{}, xerrors.Errorf("Failed to parse systeminfo.exe. err: %w", err)
}
return models.ScanResult{
ServerName: serverName,
Family: family,
Release: release,
RunningKernel: models.Kernel{
release := header.Get("X-Vuls-OS-Release")
if release == "" {
release, err = detectOSName(osInfo)
if err != nil {
return models.ScanResult{}, xerrors.Errorf("Failed to detect os name. err: %w", err)
}
}
kernelVersion := header.Get("X-Vuls-Kernel-Version")
if kernelVersion == "" {
kernelVersion = formatKernelVersion(osInfo)
}
w := &windows{
base: base{
Distro: config.Distro{Family: family, Release: release},
osPackages: osPackages{
Kernel: models.Kernel{Version: kernelVersion},
},
log: logging.Log,
},
}
kbs, err := w.detectKBsFromKernelVersion()
if err != nil {
return models.ScanResult{}, xerrors.Errorf("Failed to detect KBs from kernel version. err: %w", err)
}
applied, unapplied := map[string]struct{}{}, map[string]struct{}{}
for _, kb := range hotfixs {
applied[kb] = struct{}{}
}
for _, kb := range kbs.Applied {
applied[kb] = struct{}{}
}
for _, kb := range kbs.Unapplied {
unapplied[kb] = struct{}{}
}
return models.ScanResult{
ServerName: serverName,
Family: family,
Release: release,
RunningKernel: models.Kernel{
Version: 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")
}
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