feat(scan): support external port scanner(nmap) in host machine (#1207)
* feat(scan): load portscan settings from config.toml * feat(scan): support external port scanner:nmap * style: rename variable * feat(scan): logging apply options * feat(scan): remove spoof ip address option * feat(scan): more validate port scan config * style: change comment * fix: parse port number as uint16 * feat(discover): add portscan section * feat(discover): change default scanTechniques * feat(docker): add nmap and version update * feat(scan): nmap module upgrade * fix: wrap err using %w * feat(scan): print cmd using external port scanner * feat(scan): more details external port scan command * feat(scan): add capability check in validation * fix(scanner): format error * chore: change format
This commit is contained in:
		
							
								
								
									
										181
									
								
								scanner/base.go
									
									
									
									
									
								
							
							
						
						
									
										181
									
								
								scanner/base.go
									
									
									
									
									
								
							@@ -10,6 +10,7 @@ import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -32,6 +33,8 @@ import (
 | 
			
		||||
	_ "github.com/aquasecurity/fanal/analyzer/library/pipenv"
 | 
			
		||||
	_ "github.com/aquasecurity/fanal/analyzer/library/poetry"
 | 
			
		||||
	_ "github.com/aquasecurity/fanal/analyzer/library/yarn"
 | 
			
		||||
 | 
			
		||||
	nmap "github.com/Ullaakut/nmap/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type base struct {
 | 
			
		||||
@@ -836,26 +839,196 @@ func (l *base) detectScanDest() map[string][]string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) execPortsScan(scanDestIPPorts map[string][]string) ([]string, error) {
 | 
			
		||||
	if l.getServerInfo().PortScan.IsUseExternalScanner {
 | 
			
		||||
		listenIPPorts, err := l.execExternalPortScan(scanDestIPPorts)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []string{}, err
 | 
			
		||||
		}
 | 
			
		||||
		return listenIPPorts, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	listenIPPorts, err := l.execNativePortScan(scanDestIPPorts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []string{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return listenIPPorts, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) execNativePortScan(scanDestIPPorts map[string][]string) ([]string, error) {
 | 
			
		||||
	l.log.Info("Using Port Scanner: Vuls built-in Scanner")
 | 
			
		||||
 | 
			
		||||
	listenIPPorts := []string{}
 | 
			
		||||
 | 
			
		||||
	for ip, ports := range scanDestIPPorts {
 | 
			
		||||
		if !isLocalExec(l.ServerInfo.Port, l.ServerInfo.Host) && net.ParseIP(ip).IsLoopback() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, port := range ports {
 | 
			
		||||
			scanDest := ip + ":" + port
 | 
			
		||||
			conn, err := net.DialTimeout("tcp", scanDest, time.Duration(1)*time.Second)
 | 
			
		||||
			isOpen, err := nativeScanPort(scanDest)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
				return []string{}, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if isOpen {
 | 
			
		||||
				listenIPPorts = append(listenIPPorts, scanDest)
 | 
			
		||||
			}
 | 
			
		||||
			conn.Close()
 | 
			
		||||
			listenIPPorts = append(listenIPPorts, scanDest)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return listenIPPorts, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func nativeScanPort(scanDest string) (bool, error) {
 | 
			
		||||
	conn, err := net.DialTimeout("tcp", scanDest, time.Duration(1)*time.Second)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if strings.Contains(err.Error(), "i/o timeout") || strings.Contains(err.Error(), "connection refused") {
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}
 | 
			
		||||
		if strings.Contains(err.Error(), "too many open files") {
 | 
			
		||||
			time.Sleep(time.Duration(1) * time.Second)
 | 
			
		||||
			return nativeScanPort(scanDest)
 | 
			
		||||
		}
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	conn.Close()
 | 
			
		||||
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) execExternalPortScan(scanDestIPPorts map[string][]string) ([]string, error) {
 | 
			
		||||
	portScanConf := l.getServerInfo().PortScan
 | 
			
		||||
	l.log.Infof("Using Port Scanner: External Scanner(PATH: %s)", portScanConf.ScannerBinPath)
 | 
			
		||||
	l.log.Infof("External Scanner Apply Options: Scan Techniques: %s, HasPrivileged: %t, Source Port: %s",
 | 
			
		||||
		strings.Join(portScanConf.ScanTechniques, ","), portScanConf.HasPrivileged, portScanConf.SourcePort)
 | 
			
		||||
	baseCmd := formatNmapOptionsToString(portScanConf)
 | 
			
		||||
 | 
			
		||||
	listenIPPorts := []string{}
 | 
			
		||||
 | 
			
		||||
	for ip, ports := range scanDestIPPorts {
 | 
			
		||||
		if !isLocalExec(l.ServerInfo.Port, l.ServerInfo.Host) && net.ParseIP(ip).IsLoopback() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
 | 
			
		||||
		defer cancel()
 | 
			
		||||
 | 
			
		||||
		scanner, err := nmap.NewScanner(nmap.WithBinaryPath(portScanConf.ScannerBinPath))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []string{}, xerrors.Errorf("unable to create nmap scanner: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		scanTechnique, err := l.setScanTechniques()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []string{}, err
 | 
			
		||||
		}
 | 
			
		||||
		scanner.AddOptions(scanTechnique)
 | 
			
		||||
 | 
			
		||||
		if portScanConf.HasPrivileged {
 | 
			
		||||
			scanner.AddOptions(nmap.WithPrivileged())
 | 
			
		||||
		} else {
 | 
			
		||||
			scanner.AddOptions(nmap.WithUnprivileged())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if portScanConf.SourcePort != "" {
 | 
			
		||||
			port, err := strconv.ParseUint(portScanConf.SourcePort, 10, 16)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return []string{}, xerrors.Errorf("failed to strconv.ParseUint(%s, 10, 16) = %w", portScanConf.SourcePort, err)
 | 
			
		||||
			}
 | 
			
		||||
			scanner.AddOptions(nmap.WithSourcePort(uint16(port)))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cmd := []string{baseCmd}
 | 
			
		||||
		if strings.Contains(ip, ":") {
 | 
			
		||||
			scanner.AddOptions(nmap.WithTargets(ip[1:len(ip)-1]), nmap.WithPorts(ports...), nmap.WithIPv6Scanning())
 | 
			
		||||
			cmd = append(cmd, "-p", strings.Join(ports, ","), ip[1:len(ip)-1])
 | 
			
		||||
		} else {
 | 
			
		||||
			scanner.AddOptions(nmap.WithTargets(ip), nmap.WithPorts(ports...))
 | 
			
		||||
			cmd = append(cmd, "-p", strings.Join(ports, ","), ip)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l.log.Debugf("Executing... %s", strings.Replace(strings.Join(cmd, " "), "\n", "", -1))
 | 
			
		||||
		result, warnings, err := scanner.Run()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []string{}, xerrors.Errorf("unable to run nmap scan: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if warnings != nil {
 | 
			
		||||
			l.log.Warnf("nmap scan warnings: %s", warnings)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, host := range result.Hosts {
 | 
			
		||||
			if len(host.Ports) == 0 || len(host.Addresses) == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, port := range host.Ports {
 | 
			
		||||
				if strings.Contains(string(port.Status()), string(nmap.Open)) {
 | 
			
		||||
					scanDest := fmt.Sprintf("%s:%d", ip, port.ID)
 | 
			
		||||
					listenIPPorts = append(listenIPPorts, scanDest)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return listenIPPorts, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatNmapOptionsToString(conf *config.PortScanConf) string {
 | 
			
		||||
	cmd := []string{conf.ScannerBinPath}
 | 
			
		||||
	if len(conf.ScanTechniques) != 0 {
 | 
			
		||||
		for _, technique := range conf.ScanTechniques {
 | 
			
		||||
			cmd = append(cmd, "-"+technique)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if conf.SourcePort != "" {
 | 
			
		||||
		cmd = append(cmd, "--source-port "+conf.SourcePort)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if conf.HasPrivileged {
 | 
			
		||||
		cmd = append(cmd, "--privileged")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return strings.Join(cmd, " ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setScanTechniques() (func(*nmap.Scanner), error) {
 | 
			
		||||
	scanTechniques := l.getServerInfo().PortScan.GetScanTechniques()
 | 
			
		||||
 | 
			
		||||
	if len(scanTechniques) == 0 {
 | 
			
		||||
		if l.getServerInfo().PortScan.HasPrivileged {
 | 
			
		||||
			return nmap.WithSYNScan(), nil
 | 
			
		||||
		}
 | 
			
		||||
		return nmap.WithConnectScan(), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, technique := range scanTechniques {
 | 
			
		||||
		switch technique {
 | 
			
		||||
		case config.TCPSYN:
 | 
			
		||||
			return nmap.WithSYNScan(), nil
 | 
			
		||||
		case config.TCPConnect:
 | 
			
		||||
			return nmap.WithConnectScan(), nil
 | 
			
		||||
		case config.TCPACK:
 | 
			
		||||
			return nmap.WithACKScan(), nil
 | 
			
		||||
		case config.TCPWindow:
 | 
			
		||||
			return nmap.WithWindowScan(), nil
 | 
			
		||||
		case config.TCPMaimon:
 | 
			
		||||
			return nmap.WithMaimonScan(), nil
 | 
			
		||||
		case config.TCPNull:
 | 
			
		||||
			return nmap.WithTCPNullScan(), nil
 | 
			
		||||
		case config.TCPFIN:
 | 
			
		||||
			return nmap.WithTCPFINScan(), nil
 | 
			
		||||
		case config.TCPXmas:
 | 
			
		||||
			return nmap.WithTCPXmasScan(), nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil, xerrors.Errorf("Failed to setScanTechniques. There is an unsupported option in ScanTechniques.")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) updatePortStatus(listenIPPorts []string) {
 | 
			
		||||
	for name, p := range l.osPackages.Packages {
 | 
			
		||||
		if p.AffectedProcs == nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -467,7 +467,7 @@ func Test_updatePortStatus(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_matchListenPorts(t *testing.T) {
 | 
			
		||||
func Test_findPortScanSuccessOn(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		listenIPPorts    []string
 | 
			
		||||
		searchListenPort models.PortStat
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user