* 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
223 lines
6.5 KiB
Go
223 lines
6.5 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/asaskevich/govalidator"
|
|
"golang.org/x/xerrors"
|
|
)
|
|
|
|
// PortScanConf is the setting for using an external port scanner
|
|
type PortScanConf struct {
|
|
IsUseExternalScanner bool `toml:"-" json:"-"`
|
|
|
|
// Path to external scanner
|
|
ScannerBinPath string `toml:"scannerBinPath,omitempty" json:"scannerBinPath,omitempty"`
|
|
|
|
// set user has privileged
|
|
HasPrivileged bool `toml:"hasPrivileged,omitempty" json:"hasPrivileged,omitempty"`
|
|
|
|
// set the ScanTechniques for ScannerBinPath
|
|
ScanTechniques []string `toml:"scanTechniques,omitempty" json:"scanTechniques,omitempty"`
|
|
|
|
// set the FIREWALL/IDS EVASION AND SPOOFING(Use given port number)
|
|
SourcePort string `toml:"sourcePort,omitempty" json:"sourcePort,omitempty"`
|
|
}
|
|
|
|
// ScanTechnique is implemented to represent the supported ScanTechniques in an Enum.
|
|
type ScanTechnique int
|
|
|
|
const (
|
|
// NotSupportTechnique is a ScanTechnique that is currently not supported.
|
|
NotSupportTechnique ScanTechnique = iota
|
|
// TCPSYN is SYN scan
|
|
TCPSYN
|
|
// TCPConnect is TCP connect scan
|
|
TCPConnect
|
|
// TCPACK is ACK scan
|
|
TCPACK
|
|
// TCPWindow is Window scan
|
|
TCPWindow
|
|
// TCPMaimon is Maimon scan
|
|
TCPMaimon
|
|
// TCPNull is Null scan
|
|
TCPNull
|
|
// TCPFIN is FIN scan
|
|
TCPFIN
|
|
// TCPXmas is Xmas scan
|
|
TCPXmas
|
|
)
|
|
|
|
var scanTechniqueMap = map[ScanTechnique]string{
|
|
TCPSYN: "sS",
|
|
TCPConnect: "sT",
|
|
TCPACK: "sA",
|
|
TCPWindow: "sW",
|
|
TCPMaimon: "sM",
|
|
TCPNull: "sN",
|
|
TCPFIN: "sF",
|
|
TCPXmas: "sX",
|
|
}
|
|
|
|
func (s ScanTechnique) String() string {
|
|
switch s {
|
|
case TCPSYN:
|
|
return "TCPSYN"
|
|
case TCPConnect:
|
|
return "TCPConnect"
|
|
case TCPACK:
|
|
return "TCPACK"
|
|
case TCPWindow:
|
|
return "TCPWindow"
|
|
case TCPMaimon:
|
|
return "TCPMaimon"
|
|
case TCPNull:
|
|
return "TCPNull"
|
|
case TCPFIN:
|
|
return "TCPFIN"
|
|
case TCPXmas:
|
|
return "TCPXmas"
|
|
default:
|
|
return "NotSupportTechnique"
|
|
}
|
|
}
|
|
|
|
// GetScanTechniques converts ScanTechniques loaded from config.toml to []scanTechniques.
|
|
func (c *PortScanConf) GetScanTechniques() []ScanTechnique {
|
|
if len(c.ScanTechniques) == 0 {
|
|
return []ScanTechnique{}
|
|
}
|
|
|
|
scanTechniques := []ScanTechnique{}
|
|
for _, technique := range c.ScanTechniques {
|
|
findScanTechniqueFlag := false
|
|
for key, value := range scanTechniqueMap {
|
|
if strings.EqualFold(value, technique) {
|
|
scanTechniques = append(scanTechniques, key)
|
|
findScanTechniqueFlag = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !findScanTechniqueFlag {
|
|
scanTechniques = append(scanTechniques, NotSupportTechnique)
|
|
}
|
|
}
|
|
|
|
if len(scanTechniques) == 0 {
|
|
return []ScanTechnique{NotSupportTechnique}
|
|
}
|
|
return scanTechniques
|
|
}
|
|
|
|
// Validate validates configuration
|
|
func (c *PortScanConf) Validate() (errs []error) {
|
|
if !c.IsUseExternalScanner {
|
|
if c.IsZero() {
|
|
return
|
|
}
|
|
errs = append(errs, xerrors.New("To enable the PortScan option, ScannerBinPath must be set."))
|
|
}
|
|
|
|
if _, err := os.Stat(c.ScannerBinPath); err != nil {
|
|
errs = append(errs, xerrors.Errorf(
|
|
"scanner is not found. ScannerBinPath: %s not exists", c.ScannerBinPath))
|
|
}
|
|
|
|
scanTechniques := c.GetScanTechniques()
|
|
for _, scanTechnique := range scanTechniques {
|
|
if scanTechnique == NotSupportTechnique {
|
|
errs = append(errs, xerrors.New("There is an unsupported option in ScanTechniques."))
|
|
}
|
|
}
|
|
|
|
// It does not currently support multiple ScanTechniques.
|
|
// But if it supports UDP scanning, it will need to accept multiple ScanTechniques.
|
|
if len(scanTechniques) > 1 {
|
|
errs = append(errs, xerrors.New("Currently multiple ScanTechniques are not supported."))
|
|
}
|
|
|
|
if c.HasPrivileged {
|
|
if os.Geteuid() != 0 {
|
|
output, err := exec.Command("getcap", c.ScannerBinPath).Output()
|
|
if err != nil {
|
|
errs = append(errs, xerrors.Errorf("Failed to check capability of %s. error message: %w", c.ScannerBinPath, err))
|
|
} else {
|
|
parseOutput := strings.SplitN(string(output), "=", 2)
|
|
if len(parseOutput) != 2 {
|
|
errs = append(errs, xerrors.Errorf("Failed to parse getcap outputs. please execute this command: `$ getcap %s`. If the following string (`/usr/bin/nmap = ... `) is not displayed, you need to set the capability with the following command. `$ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip %s`", c.ScannerBinPath, c.ScannerBinPath))
|
|
} else {
|
|
parseCapability := strings.Split(strings.TrimSpace(parseOutput[1]), "+")
|
|
capabilities := strings.Split(parseCapability[0], ",")
|
|
for _, needCap := range []string{"cap_net_bind_service", "cap_net_admin", "cap_net_raw"} {
|
|
existCapFlag := false
|
|
for _, cap := range capabilities {
|
|
if needCap == cap {
|
|
existCapFlag = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if existCapFlag {
|
|
continue
|
|
}
|
|
|
|
errs = append(errs, xerrors.Errorf("Not enough capability to execute. needs: ['cap_net_bind_service', 'cap_net_admin', 'cap_net_raw'], actual: %s. To fix this, run the following command. `$ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip %s`", capabilities, c.ScannerBinPath))
|
|
break
|
|
}
|
|
|
|
if parseCapability[1] != "eip" {
|
|
errs = append(errs, xerrors.Errorf("Capability(`cap_net_bind_service,cap_net_admin,cap_net_raw`) must belong to the following capability set(need: eip, actual: %s). To fix this, run the following command. `$ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip %s`", parseCapability[1], c.ScannerBinPath))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !c.HasPrivileged {
|
|
for _, scanTechnique := range scanTechniques {
|
|
if scanTechnique != TCPConnect && scanTechnique != NotSupportTechnique {
|
|
errs = append(errs, xerrors.New("If not privileged, only TCPConnect Scan(-sT) can be used."))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.SourcePort != "" {
|
|
for _, scanTechnique := range scanTechniques {
|
|
if scanTechnique == TCPConnect {
|
|
errs = append(errs, xerrors.New("SourcePort Option(-g/--source-port) is incompatible with the default TCPConnect Scan(-sT)."))
|
|
break
|
|
}
|
|
}
|
|
|
|
portNumber, err := strconv.Atoi(c.SourcePort)
|
|
if err != nil {
|
|
errs = append(errs, xerrors.Errorf("SourcePort conversion failed. %w", err))
|
|
} else {
|
|
if portNumber < 0 || 65535 < portNumber {
|
|
errs = append(errs, xerrors.Errorf("SourcePort(%s) must be between 0 and 65535.", c.SourcePort))
|
|
}
|
|
|
|
if portNumber == 0 {
|
|
errs = append(errs, xerrors.New("SourcePort(0) may not work on all systems."))
|
|
}
|
|
}
|
|
}
|
|
|
|
_, err := govalidator.ValidateStruct(c)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// IsZero return whether this struct is not specified in config.toml
|
|
func (c PortScanConf) IsZero() bool {
|
|
return c.ScannerBinPath == "" && !c.HasPrivileged && len(c.ScanTechniques) == 0 && c.SourcePort == ""
|
|
}
|