From 0b55f948285d37b25a362e87eef1226404cf469e Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Wed, 13 Jan 2021 08:46:27 +0900 Subject: [PATCH] Improve implementation around config (#1122) * refactor config * fix saas config * feat(config): scanmodule for each server in config.toml * feat(config): enable to specify containersOnly in config.toml * add new keys of config.toml to discover.go * fix summary output, logging --- README.md | 2 +- config/chatworkconf.go | 32 ++ config/config.go | 725 +++---------------------------------- config/exploitconf.go | 53 +++ config/gocvedictconf.go | 53 +++ config/gostconf.go | 53 +++ config/govaldictconf.go | 54 +++ config/httpconf.go | 38 ++ config/metasploitconf.go | 53 +++ config/os.go | 4 + config/saasconf.go | 34 ++ config/scanmode.go | 110 ++++++ config/scanmodule.go | 97 +++++ config/scanmodule_test.go | 65 ++++ config/slackconf.go | 51 +++ config/smtpconf.go | 65 ++++ config/syslogconf.go | 129 +++++++ config/telegramconf.go | 32 ++ config/tomlloader.go | 323 +++++++---------- github/github.go | 5 +- go.mod | 1 - go.sum | 2 - models/cvecontents.go | 8 +- models/library.go | 7 + models/scanresults.go | 36 +- models/scanresults_test.go | 3 +- models/vulninfos.go | 10 +- report/report.go | 21 +- report/syslog.go | 2 +- report/util.go | 6 + saas/uuid.go | 83 +---- scan/alpine.go | 2 +- scan/base.go | 30 +- scan/debian.go | 2 +- scan/freebsd.go | 2 +- scan/redhatbase.go | 2 +- scan/serverapi.go | 58 +-- scan/suse.go | 1 + subcmds/configtest.go | 3 - subcmds/discover.go | 10 +- subcmds/report.go | 5 - subcmds/scan.go | 13 - util/util.go | 1 + wordpress/wordpress.go | 32 +- 44 files changed, 1238 insertions(+), 1080 deletions(-) create mode 100644 config/chatworkconf.go create mode 100644 config/exploitconf.go create mode 100644 config/gocvedictconf.go create mode 100644 config/gostconf.go create mode 100644 config/govaldictconf.go create mode 100644 config/httpconf.go create mode 100644 config/metasploitconf.go create mode 100644 config/saasconf.go create mode 100644 config/scanmode.go create mode 100644 config/scanmodule.go create mode 100644 config/scanmodule_test.go create mode 100644 config/slackconf.go create mode 100644 config/smtpconf.go create mode 100644 config/syslogconf.go create mode 100644 config/telegramconf.go diff --git a/README.md b/README.md index 52475f75a0..8a5552de5e 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Vuls is a tool created to solve the problems listed above. It has the following - [RustSec Advisory Database](https://github.com/RustSec/advisory-db) - WordPress - - [WPVulnDB](https://wpvulndb.com/api) + - [wpscan](https://wpscan.com/api) ### Scan mode diff --git a/config/chatworkconf.go b/config/chatworkconf.go new file mode 100644 index 0000000000..2d0155c87e --- /dev/null +++ b/config/chatworkconf.go @@ -0,0 +1,32 @@ +package config + +import ( + "github.com/asaskevich/govalidator" + "golang.org/x/xerrors" +) + +// ChatWorkConf is ChatWork config +type ChatWorkConf struct { + APIToken string `json:"-"` + Room string `json:"-"` +} + +// Validate validates configuration +func (c *ChatWorkConf) Validate() (errs []error) { + if !Conf.ToChatWork { + return + } + if len(c.Room) == 0 { + errs = append(errs, xerrors.New("chatWorkConf.room must not be empty")) + } + + if len(c.APIToken) == 0 { + errs = append(errs, xerrors.New("chatWorkConf.ApiToken must not be empty")) + } + + _, err := govalidator.ValidateStruct(c) + if err != nil { + errs = append(errs, err) + } + return +} diff --git a/config/config.go b/config/config.go index 96d2ae8989..252f517117 100644 --- a/config/config.go +++ b/config/config.go @@ -1,16 +1,13 @@ package config import ( - "errors" "fmt" "os" - "path/filepath" "runtime" "strconv" "strings" - syslog "github.com/RackSec/srslog" - valid "github.com/asaskevich/govalidator" + "github.com/asaskevich/govalidator" log "github.com/sirupsen/logrus" "golang.org/x/xerrors" ) @@ -46,10 +43,6 @@ type Config struct { IgnoreUnfixed bool `json:"ignoreUnfixed,omitempty"` IgnoreGitHubDismissed bool `json:"ignore_git_hub_dismissed,omitempty"` - ContainersOnly bool `json:"containersOnly,omitempty"` - LibsOnly bool `json:"libsOnly,omitempty"` - WordPressOnly bool `json:"wordpressOnly,omitempty"` - CacheDBPath string `json:"cacheDBPath,omitempty"` TrivyCacheDBDir string `json:"trivyCacheDBDir,omitempty"` @@ -63,13 +56,14 @@ type Config struct { EMail SMTPConf `json:"-"` HTTP HTTPConf `json:"-"` Syslog SyslogConf `json:"-"` - AWS AWS `json:"-"` - Azure Azure `json:"-"` + AWS AWSConf `json:"-"` + Azure AzureConf `json:"-"` ChatWork ChatWorkConf `json:"-"` Telegram TelegramConf `json:"-"` + WpScan WpScanConf `json:"WpScan,omitempty"` + Saas SaasConf `json:"-"` - UUID bool `json:"uuid,omitempty"` DetectIPS bool `json:"detectIps,omitempty"` RefreshCve bool `json:"refreshCve,omitempty"` @@ -91,7 +85,6 @@ type Config struct { FormatCsvList bool `json:"formatCsvList,omitempty"` GZIP bool `json:"gzip,omitempty"` Diff bool `json:"diff,omitempty"` - WpIgnoreInactive bool `json:"wpIgnoreInactive,omitempty"` } // ValidateOnConfigtest validates @@ -102,7 +95,7 @@ func (c Config) ValidateOnConfigtest() bool { errs = append(errs, xerrors.New("-ssh-native-insecure is needed on windows")) } - _, err := valid.ValidateStruct(c) + _, err := govalidator.ValidateStruct(c) if err != nil { errs = append(errs, err) } @@ -123,21 +116,21 @@ func (c Config) ValidateOnScan() bool { } if len(c.ResultsDir) != 0 { - if ok, _ := valid.IsFilePath(c.ResultsDir); !ok { + 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 len(c.CacheDBPath) != 0 { - if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok { + if ok, _ := govalidator.IsFilePath(c.CacheDBPath); !ok { errs = append(errs, xerrors.Errorf( "Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath)) } } - _, err := valid.ValidateStruct(c) + _, err := govalidator.ValidateStruct(c) if err != nil { errs = append(errs, err) } @@ -200,13 +193,13 @@ func (c Config) ValidateOnReport() bool { errs := []error{} if len(c.ResultsDir) != 0 { - if ok, _ := valid.IsFilePath(c.ResultsDir); !ok { + 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 := valid.ValidateStruct(c) + _, err := govalidator.ValidateStruct(c) if err != nil { errs = append(errs, err) } @@ -247,7 +240,7 @@ func (c Config) ValidateOnTui() bool { errs := []error{} if len(c.ResultsDir) != 0 { - if ok, _ := valid.IsFilePath(c.ResultsDir); !ok { + 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)) } @@ -284,7 +277,7 @@ func validateDB(dictionaryDBName, dbType, dbPath, dbURL string) error { return xerrors.Errorf("To use SQLite3, specify -%s-type=sqlite3 and -%s-path. To use as http server mode, specify -%s-type=http and -%s-url", dictionaryDBName, dictionaryDBName, dictionaryDBName, dictionaryDBName) } - if ok, _ := valid.IsFilePath(dbPath); !ok { + if ok, _ := govalidator.IsFilePath(dbPath); !ok { return xerrors.Errorf("SQLite3 path must be a *Absolute* file path. -%s-path: %s", dictionaryDBName, dbPath) } @@ -315,577 +308,8 @@ func validateDB(dictionaryDBName, dbType, dbPath, dbURL string) error { return nil } -// SMTPConf is smtp config -type SMTPConf struct { - SMTPAddr string `toml:"smtpAddr,omitempty" json:"-"` - SMTPPort string `toml:"smtpPort,omitempty" valid:"port" json:"-"` - User string `toml:"user,omitempty" json:"-"` - Password string `toml:"password,omitempty" json:"-"` - From string `toml:"from,omitempty" json:"-"` - To []string `toml:"to,omitempty" json:"-"` - Cc []string `toml:"cc,omitempty" json:"-"` - SubjectPrefix string `toml:"subjectPrefix,omitempty" json:"-"` -} - -func checkEmails(emails []string) (errs []error) { - for _, addr := range emails { - if len(addr) == 0 { - return - } - if ok := valid.IsEmail(addr); !ok { - errs = append(errs, xerrors.Errorf("Invalid email address. email: %s", addr)) - } - } - return -} - -// Validate SMTP configuration -func (c *SMTPConf) Validate() (errs []error) { - if !Conf.ToEmail { - return - } - // Check Emails fromat - emails := []string{} - emails = append(emails, c.From) - emails = append(emails, c.To...) - emails = append(emails, c.Cc...) - - if emailErrs := checkEmails(emails); 0 < len(emailErrs) { - errs = append(errs, emailErrs...) - } - - if len(c.SMTPAddr) == 0 { - errs = append(errs, xerrors.New("email.smtpAddr must not be empty")) - } - if len(c.SMTPPort) == 0 { - errs = append(errs, xerrors.New("email.smtpPort must not be empty")) - } - if len(c.To) == 0 { - errs = append(errs, xerrors.New("email.To required at least one address")) - } - if len(c.From) == 0 { - errs = append(errs, xerrors.New("email.From required at least one address")) - } - - _, err := valid.ValidateStruct(c) - if err != nil { - errs = append(errs, err) - } - return -} - -// SlackConf is slack config -type SlackConf struct { - HookURL string `valid:"url" json:"-" toml:"hookURL,omitempty"` - LegacyToken string `json:"-" toml:"legacyToken,omitempty"` - Channel string `json:"-" toml:"channel,omitempty"` - IconEmoji string `json:"-" toml:"iconEmoji,omitempty"` - AuthUser string `json:"-" toml:"authUser,omitempty"` - NotifyUsers []string `toml:"notifyUsers,omitempty" json:"-"` - Text string `json:"-"` -} - -// Validate validates configuration -func (c *SlackConf) Validate() (errs []error) { - if !Conf.ToSlack { - return - } - - if len(c.HookURL) == 0 && len(c.LegacyToken) == 0 { - errs = append(errs, xerrors.New("slack.hookURL or slack.LegacyToken must not be empty")) - } - - if len(c.Channel) == 0 { - errs = append(errs, xerrors.New("slack.channel must not be empty")) - } else { - if !(strings.HasPrefix(c.Channel, "#") || - c.Channel == "${servername}") { - errs = append(errs, xerrors.Errorf( - "channel's prefix must be '#', channel: %s", c.Channel)) - } - } - - if len(c.AuthUser) == 0 { - errs = append(errs, xerrors.New("slack.authUser must not be empty")) - } - - _, err := valid.ValidateStruct(c) - if err != nil { - errs = append(errs, err) - } - - return -} - -// ChatWorkConf is ChatWork config -type ChatWorkConf struct { - APIToken string `json:"-"` - Room string `json:"-"` -} - -// Validate validates configuration -func (c *ChatWorkConf) Validate() (errs []error) { - if !Conf.ToChatWork { - return - } - if len(c.Room) == 0 { - errs = append(errs, xerrors.New("chatWorkConf.room must not be empty")) - } - - if len(c.APIToken) == 0 { - errs = append(errs, xerrors.New("chatWorkConf.ApiToken must not be empty")) - } - - _, err := valid.ValidateStruct(c) - if err != nil { - errs = append(errs, err) - } - return -} - -// TelegramConf is Telegram config -type TelegramConf struct { - Token string `json:"-"` - ChatID string `json:"-"` -} - -// Validate validates configuration -func (c *TelegramConf) Validate() (errs []error) { - if !Conf.ToTelegram { - return - } - if len(c.ChatID) == 0 { - errs = append(errs, xerrors.New("TelegramConf.ChatID must not be empty")) - } - - if len(c.Token) == 0 { - errs = append(errs, xerrors.New("TelegramConf.Token must not be empty")) - } - - _, err := valid.ValidateStruct(c) - if err != nil { - errs = append(errs, err) - } - return -} - -// SaasConf is FutureVuls config -type SaasConf struct { - GroupID int64 `json:"-"` - Token string `json:"-"` - URL string `json:"-"` -} - -// Validate validates configuration -func (c *SaasConf) Validate() (errs []error) { - if c.GroupID == 0 { - errs = append(errs, xerrors.New("GroupID must not be empty")) - } - - if len(c.Token) == 0 { - errs = append(errs, xerrors.New("Token must not be empty")) - } - - if len(c.URL) == 0 { - errs = append(errs, xerrors.New("URL must not be empty")) - } - - _, err := valid.ValidateStruct(c) - if err != nil { - errs = append(errs, err) - } - return -} - -// SyslogConf is syslog config -type SyslogConf struct { - Protocol string `json:"-"` - Host string `valid:"host" json:"-"` - Port string `valid:"port" json:"-"` - Severity string `json:"-"` - Facility string `json:"-"` - Tag string `json:"-"` - Verbose bool `json:"-"` -} - -// Validate validates configuration -func (c *SyslogConf) Validate() (errs []error) { - if !Conf.ToSyslog { - return nil - } - // If protocol is empty, it will connect to the local syslog server. - if len(c.Protocol) > 0 && c.Protocol != "tcp" && c.Protocol != "udp" { - errs = append(errs, errors.New(`syslog.protocol must be "tcp" or "udp"`)) - } - - // Default port: 514 - if c.Port == "" { - c.Port = "514" - } - - if _, err := c.GetSeverity(); err != nil { - errs = append(errs, err) - } - - if _, err := c.GetFacility(); err != nil { - errs = append(errs, err) - } - - if _, err := valid.ValidateStruct(c); err != nil { - errs = append(errs, err) - } - return errs -} - -// GetSeverity gets severity -func (c *SyslogConf) GetSeverity() (syslog.Priority, error) { - if c.Severity == "" { - return syslog.LOG_INFO, nil - } - - switch c.Severity { - case "emerg": - return syslog.LOG_EMERG, nil - case "alert": - return syslog.LOG_ALERT, nil - case "crit": - return syslog.LOG_CRIT, nil - case "err": - return syslog.LOG_ERR, nil - case "warning": - return syslog.LOG_WARNING, nil - case "notice": - return syslog.LOG_NOTICE, nil - case "info": - return syslog.LOG_INFO, nil - case "debug": - return syslog.LOG_DEBUG, nil - default: - return -1, xerrors.Errorf("Invalid severity: %s", c.Severity) - } -} - -// GetFacility gets facility -func (c *SyslogConf) GetFacility() (syslog.Priority, error) { - if c.Facility == "" { - return syslog.LOG_AUTH, nil - } - - switch c.Facility { - case "kern": - return syslog.LOG_KERN, nil - case "user": - return syslog.LOG_USER, nil - case "mail": - return syslog.LOG_MAIL, nil - case "daemon": - return syslog.LOG_DAEMON, nil - case "auth": - return syslog.LOG_AUTH, nil - case "syslog": - return syslog.LOG_SYSLOG, nil - case "lpr": - return syslog.LOG_LPR, nil - case "news": - return syslog.LOG_NEWS, nil - case "uucp": - return syslog.LOG_UUCP, nil - case "cron": - return syslog.LOG_CRON, nil - case "authpriv": - return syslog.LOG_AUTHPRIV, nil - case "ftp": - return syslog.LOG_FTP, nil - case "local0": - return syslog.LOG_LOCAL0, nil - case "local1": - return syslog.LOG_LOCAL1, nil - case "local2": - return syslog.LOG_LOCAL2, nil - case "local3": - return syslog.LOG_LOCAL3, nil - case "local4": - return syslog.LOG_LOCAL4, nil - case "local5": - return syslog.LOG_LOCAL5, nil - case "local6": - return syslog.LOG_LOCAL6, nil - case "local7": - return syslog.LOG_LOCAL7, nil - default: - return -1, xerrors.Errorf("Invalid facility: %s", c.Facility) - } -} - -// HTTPConf is HTTP config -type HTTPConf struct { - URL string `valid:"url" json:"-"` -} - -// Validate validates configuration -func (c *HTTPConf) Validate() (errs []error) { - if !Conf.ToHTTP { - return nil - } - - if _, err := valid.ValidateStruct(c); err != nil { - errs = append(errs, err) - } - return errs -} - -const httpKey = "VULS_HTTP_URL" - -// Init set options with the following priority. -// 1. Environment variable -// 2. config.toml -func (c *HTTPConf) Init(toml HTTPConf) { - if os.Getenv(httpKey) != "" { - c.URL = os.Getenv(httpKey) - } - if toml.URL != "" { - c.URL = toml.URL - } -} - -// GoCveDictConf is go-cve-dictionary config -type GoCveDictConf struct { - // DB type of CVE dictionary (sqlite3, mysql, postgres or redis) - Type string - - // http://cve-dictionary.com:1323 or DB connection string - URL string `json:"-"` - - // /path/to/cve.sqlite3 - SQLite3Path string `json:"-"` -} - -func (cnf *GoCveDictConf) setDefault() { - if cnf.Type == "" { - cnf.Type = "sqlite3" - } - if cnf.URL == "" && cnf.SQLite3Path == "" { - wd, _ := os.Getwd() - cnf.SQLite3Path = filepath.Join(wd, "cve.sqlite3") - } -} - -const cveDBType = "CVEDB_TYPE" -const cveDBURL = "CVEDB_URL" -const cveDBPATH = "CVEDB_SQLITE3_PATH" - -// Init set options with the following priority. -// 1. Environment variable -// 2. config.toml -func (cnf *GoCveDictConf) Init() { - if os.Getenv(cveDBType) != "" { - cnf.Type = os.Getenv(cveDBType) - } - if os.Getenv(cveDBURL) != "" { - cnf.URL = os.Getenv(cveDBURL) - } - if os.Getenv(cveDBPATH) != "" { - cnf.SQLite3Path = os.Getenv(cveDBPATH) - } - cnf.setDefault() -} - -// IsFetchViaHTTP returns wether fetch via http -func (cnf *GoCveDictConf) IsFetchViaHTTP() bool { - return Conf.CveDict.Type == "http" -} - -// GovalDictConf is goval-dictionary config -type GovalDictConf struct { - - // DB type of OVAL dictionary (sqlite3, mysql, postgres or redis) - Type string - - // http://goval-dictionary.com:1324 or DB connection string - URL string `json:"-"` - - // /path/to/oval.sqlite3 - SQLite3Path string `json:"-"` -} - -func (cnf *GovalDictConf) setDefault() { - if cnf.Type == "" { - cnf.Type = "sqlite3" - } - if cnf.URL == "" && cnf.SQLite3Path == "" { - wd, _ := os.Getwd() - cnf.SQLite3Path = filepath.Join(wd, "oval.sqlite3") - } -} - -const govalType = "OVALDB_TYPE" -const govalURL = "OVALDB_URL" -const govalPATH = "OVALDB_SQLITE3_PATH" - -// Init set options with the following priority. -// 1. Environment variable -// 2. config.toml -func (cnf *GovalDictConf) Init() { - if os.Getenv(govalType) != "" { - cnf.Type = os.Getenv(govalType) - } - if os.Getenv(govalURL) != "" { - cnf.URL = os.Getenv(govalURL) - } - if os.Getenv(govalPATH) != "" { - cnf.SQLite3Path = os.Getenv(govalPATH) - } - cnf.setDefault() -} - -// IsFetchViaHTTP returns wether fetch via http -func (cnf *GovalDictConf) IsFetchViaHTTP() bool { - return Conf.OvalDict.Type == "http" -} - -// GostConf is gost config -type GostConf struct { - // DB type for gost dictionary (sqlite3, mysql, postgres or redis) - Type string - - // http://gost-dictionary.com:1324 or DB connection string - URL string `json:"-"` - - // /path/to/gost.sqlite3 - SQLite3Path string `json:"-"` -} - -func (cnf *GostConf) setDefault() { - if cnf.Type == "" { - cnf.Type = "sqlite3" - } - if cnf.URL == "" && cnf.SQLite3Path == "" { - wd, _ := os.Getwd() - cnf.SQLite3Path = filepath.Join(wd, "gost.sqlite3") - } -} - -const gostDBType = "GOSTDB_TYPE" -const gostDBURL = "GOSTDB_URL" -const gostDBPATH = "GOSTDB_SQLITE3_PATH" - -// Init set options with the following priority. -// 1. Environment variable -// 2. config.toml -func (cnf *GostConf) Init() { - if os.Getenv(gostDBType) != "" { - cnf.Type = os.Getenv(gostDBType) - } - if os.Getenv(gostDBURL) != "" { - cnf.URL = os.Getenv(gostDBURL) - } - if os.Getenv(gostDBPATH) != "" { - cnf.SQLite3Path = os.Getenv(gostDBPATH) - } - cnf.setDefault() -} - -// IsFetchViaHTTP returns wether fetch via http -func (cnf *GostConf) IsFetchViaHTTP() bool { - return Conf.Gost.Type == "http" -} - -// ExploitConf is exploit config -type ExploitConf struct { - // DB type for exploit dictionary (sqlite3, mysql, postgres or redis) - Type string - - // http://exploit-dictionary.com:1324 or DB connection string - URL string `json:"-"` - - // /path/to/exploit.sqlite3 - SQLite3Path string `json:"-"` -} - -func (cnf *ExploitConf) setDefault() { - if cnf.Type == "" { - cnf.Type = "sqlite3" - } - if cnf.URL == "" && cnf.SQLite3Path == "" { - wd, _ := os.Getwd() - cnf.SQLite3Path = filepath.Join(wd, "go-exploitdb.sqlite3") - } -} - -const exploitDBType = "EXPLOITDB_TYPE" -const exploitDBURL = "EXPLOITDB_URL" -const exploitDBPATH = "EXPLOITDB_SQLITE3_PATH" - -// Init set options with the following priority. -// 1. Environment variable -// 2. config.toml -func (cnf *ExploitConf) Init() { - if os.Getenv(exploitDBType) != "" { - cnf.Type = os.Getenv(exploitDBType) - } - if os.Getenv(exploitDBURL) != "" { - cnf.URL = os.Getenv(exploitDBURL) - } - if os.Getenv(exploitDBPATH) != "" { - cnf.SQLite3Path = os.Getenv(exploitDBPATH) - } - cnf.setDefault() -} - -// IsFetchViaHTTP returns wether fetch via http -func (cnf *ExploitConf) IsFetchViaHTTP() bool { - return Conf.Exploit.Type == "http" -} - -// MetasploitConf is metasploit config -type MetasploitConf struct { - // DB type for metasploit dictionary (sqlite3, mysql, postgres or redis) - Type string - - // http://metasploit-dictionary.com:1324 or DB connection string - URL string `json:"-"` - - // /path/to/metasploit.sqlite3 - SQLite3Path string `json:"-"` -} - -func (cnf *MetasploitConf) setDefault() { - if cnf.Type == "" { - cnf.Type = "sqlite3" - } - if cnf.URL == "" && cnf.SQLite3Path == "" { - wd, _ := os.Getwd() - cnf.SQLite3Path = filepath.Join(wd, "go-msfdb.sqlite3") - } -} - -const metasploitDBType = "METASPLOITDB_TYPE" -const metasploitDBURL = "METASPLOITDB_URL" -const metasploitDBPATH = "METASPLOITDB_SQLITE3_PATH" - -// Init set options with the following priority. -// 1. Environment variable -// 2. config.toml -func (cnf *MetasploitConf) Init() { - if os.Getenv(metasploitDBType) != "" { - cnf.Type = os.Getenv(metasploitDBType) - } - if os.Getenv(metasploitDBURL) != "" { - cnf.URL = os.Getenv(metasploitDBURL) - } - if os.Getenv(metasploitDBPATH) != "" { - cnf.SQLite3Path = os.Getenv(metasploitDBPATH) - } - cnf.setDefault() -} - -// IsFetchViaHTTP returns wether fetch via http -func (cnf *MetasploitConf) IsFetchViaHTTP() bool { - return Conf.Metasploit.Type == "http" -} - -// AWS is aws config -type AWS struct { +// AWSConf is aws config +type AWSConf struct { // AWS profile to use Profile string `json:"profile"` @@ -902,8 +326,8 @@ type AWS struct { S3ServerSideEncryption string `json:"s3ServerSideEncryption"` } -// Azure is azure config -type Azure struct { +// AzureConf is azure config +type AzureConf struct { // Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified AccountName string `json:"accountName"` @@ -914,6 +338,12 @@ type Azure struct { ContainerName string `json:"containerName"` } +// 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 { ServerName string `toml:"-" json:"serverName,omitempty"` @@ -926,11 +356,13 @@ type ServerInfo struct { KeyPassword string `json:"-" toml:"-"` 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" json:"containers,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 @@ -941,34 +373,38 @@ type ServerInfo struct { Lockfiles []string `toml:"lockfiles,omitempty" json:"lockfiles,omitempty"` // ie) path/to/package-lock.json FindLock bool `toml:"findLock,omitempty" json:"findLock,omitempty"` Type string `toml:"type,omitempty" json:"type,omitempty"` // "pseudo" or "" - WordPress WordPressConf `toml:"wordpress,omitempty" json:"wordpress,omitempty"` IgnoredJSONKeys []string `toml:"ignoredJSONKeys,omitempty" json:"ignoredJSONKeys,omitempty"` + IPv4Addrs []string `toml:"-" json:"ipv4Addrs,omitempty"` + IPv6Addrs []string `toml:"-" json:"ipv6Addrs,omitempty"` + IPSIdentifiers map[IPS]string `toml:"-" json:"ipsIdentifiers,omitempty"` + WordPress *WordPressConf `toml:"wordpress,omitempty" json:"wordpress,omitempty"` // internal use - IPv4Addrs []string `toml:"-" json:"ipv4Addrs,omitempty"` - IPv6Addrs []string `toml:"-" json:"ipv6Addrs,omitempty"` - IPSIdentifiers map[IPS]string `toml:"-" json:"ipsIdentifiers,omitempty"` - LogMsgAnsiColor string `toml:"-" json:"-"` // DebugLog Color - Container Container `toml:"-" json:"-"` - Distro Distro `toml:"-" json:"-"` - Mode ScanMode `toml:"-" json:"-"` + 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"` + 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" json:"osUser,omitempty"` - DocRoot string `toml:"docRoot" json:"docRoot,omitempty"` - CmdPath string `toml:"cmdPath" json:"cmdPath,omitempty"` - WPVulnDBToken string `toml:"wpVulnDBToken" json:"-"` - IgnoreInactive bool `json:"ignoreInactive,omitempty"` + 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 @@ -976,79 +412,6 @@ type GitHubConf struct { Token string `json:"-"` } -// ScanMode has a type of scan mode. fast, fast-root, deep and offline -type ScanMode struct { - flag byte -} - -// Set mode -func (s *ScanMode) Set(f byte) { - s.flag |= f -} - -// IsFast return whether scan mode is fast -func (s ScanMode) IsFast() bool { - return s.flag&Fast == Fast -} - -// IsFastRoot return whether scan mode is fastroot -func (s ScanMode) IsFastRoot() bool { - return s.flag&FastRoot == FastRoot -} - -// IsDeep return whether scan mode is deep -func (s ScanMode) IsDeep() bool { - return s.flag&Deep == Deep -} - -// IsOffline return whether scan mode is offline -func (s ScanMode) IsOffline() bool { - return s.flag&Offline == Offline -} - -func (s ScanMode) validate() error { - numTrue := 0 - for _, b := range []bool{s.IsFast(), s.IsFastRoot(), s.IsDeep()} { - if b { - numTrue++ - } - } - if numTrue == 0 { - s.Set(Fast) - } else if s.IsDeep() && s.IsOffline() { - return xerrors.New("Don't specify both of -deep and offline") - } else if numTrue != 1 { - return xerrors.New("Specify only one of -fast, -fast-root or -deep") - } - return nil -} - -func (s ScanMode) String() string { - ss := "" - if s.IsFast() { - ss = "fast" - } else if s.IsFastRoot() { - ss = "fast-root" - } else if s.IsDeep() { - ss = "deep" - } - if s.IsOffline() { - ss += " offline" - } - return ss + " mode" -} - -const ( - // Fast is fast scan mode - Fast = byte(1 << iota) - // FastRoot is fast-root scan mode - FastRoot - // Deep is deep scan mode - Deep - // Offline is offline scan mode - Offline -) - // GetServerName returns ServerName if this serverInfo is about host. // If this serverInfo is about a container, returns containerID@ServerName func (s ServerInfo) GetServerName() string { diff --git a/config/exploitconf.go b/config/exploitconf.go new file mode 100644 index 0000000000..cfccd7b474 --- /dev/null +++ b/config/exploitconf.go @@ -0,0 +1,53 @@ +package config + +import ( + "os" + "path/filepath" +) + +// ExploitConf is exploit config +type ExploitConf struct { + // DB type for exploit dictionary (sqlite3, mysql, postgres or redis) + Type string + + // http://exploit-dictionary.com:1324 or DB connection string + URL string `json:"-"` + + // /path/to/exploit.sqlite3 + SQLite3Path string `json:"-"` +} + +func (cnf *ExploitConf) setDefault() { + if cnf.Type == "" { + cnf.Type = "sqlite3" + } + if cnf.URL == "" && cnf.SQLite3Path == "" { + wd, _ := os.Getwd() + cnf.SQLite3Path = filepath.Join(wd, "go-exploitdb.sqlite3") + } +} + +const exploitDBType = "EXPLOITDB_TYPE" +const exploitDBURL = "EXPLOITDB_URL" +const exploitDBPATH = "EXPLOITDB_SQLITE3_PATH" + +// Init set options with the following priority. +// 1. Environment variable +// 2. config.toml +func (cnf *ExploitConf) Init() { + if os.Getenv(exploitDBType) != "" { + cnf.Type = os.Getenv(exploitDBType) + } + if os.Getenv(exploitDBURL) != "" { + cnf.URL = os.Getenv(exploitDBURL) + } + if os.Getenv(exploitDBPATH) != "" { + cnf.SQLite3Path = os.Getenv(exploitDBPATH) + } + cnf.setDefault() +} + +// IsFetchViaHTTP returns wether fetch via http +func (cnf *ExploitConf) IsFetchViaHTTP() bool { + return Conf.Exploit.Type == "http" +} diff --git a/config/gocvedictconf.go b/config/gocvedictconf.go new file mode 100644 index 0000000000..005f8418fc --- /dev/null +++ b/config/gocvedictconf.go @@ -0,0 +1,53 @@ +package config + +import ( + "os" + "path/filepath" +) + +// GoCveDictConf is go-cve-dictionary config +type GoCveDictConf struct { + // DB type of CVE dictionary (sqlite3, mysql, postgres or redis) + Type string + + // http://cve-dictionary.com:1323 or DB connection string + URL string `json:"-"` + + // /path/to/cve.sqlite3 + SQLite3Path string `json:"-"` +} + +func (cnf *GoCveDictConf) setDefault() { + if cnf.Type == "" { + cnf.Type = "sqlite3" + } + if cnf.URL == "" && cnf.SQLite3Path == "" { + wd, _ := os.Getwd() + cnf.SQLite3Path = filepath.Join(wd, "cve.sqlite3") + } +} + +const cveDBType = "CVEDB_TYPE" +const cveDBURL = "CVEDB_URL" +const cveDBPATH = "CVEDB_SQLITE3_PATH" + +// Init set options with the following priority. +// 1. Environment variable +// 2. config.toml +func (cnf *GoCveDictConf) Init() { + if os.Getenv(cveDBType) != "" { + cnf.Type = os.Getenv(cveDBType) + } + if os.Getenv(cveDBURL) != "" { + cnf.URL = os.Getenv(cveDBURL) + } + if os.Getenv(cveDBPATH) != "" { + cnf.SQLite3Path = os.Getenv(cveDBPATH) + } + cnf.setDefault() +} + +// IsFetchViaHTTP returns wether fetch via http +func (cnf *GoCveDictConf) IsFetchViaHTTP() bool { + return Conf.CveDict.Type == "http" +} diff --git a/config/gostconf.go b/config/gostconf.go new file mode 100644 index 0000000000..b364f5b2f0 --- /dev/null +++ b/config/gostconf.go @@ -0,0 +1,53 @@ +package config + +import ( + "os" + "path/filepath" +) + +// GostConf is gost config +type GostConf struct { + // DB type for gost dictionary (sqlite3, mysql, postgres or redis) + Type string + + // http://gost-dictionary.com:1324 or DB connection string + URL string `json:"-"` + + // /path/to/gost.sqlite3 + SQLite3Path string `json:"-"` +} + +func (cnf *GostConf) setDefault() { + if cnf.Type == "" { + cnf.Type = "sqlite3" + } + if cnf.URL == "" && cnf.SQLite3Path == "" { + wd, _ := os.Getwd() + cnf.SQLite3Path = filepath.Join(wd, "gost.sqlite3") + } +} + +const gostDBType = "GOSTDB_TYPE" +const gostDBURL = "GOSTDB_URL" +const gostDBPATH = "GOSTDB_SQLITE3_PATH" + +// Init set options with the following priority. +// 1. Environment variable +// 2. config.toml +func (cnf *GostConf) Init() { + if os.Getenv(gostDBType) != "" { + cnf.Type = os.Getenv(gostDBType) + } + if os.Getenv(gostDBURL) != "" { + cnf.URL = os.Getenv(gostDBURL) + } + if os.Getenv(gostDBPATH) != "" { + cnf.SQLite3Path = os.Getenv(gostDBPATH) + } + cnf.setDefault() +} + +// IsFetchViaHTTP returns wether fetch via http +func (cnf *GostConf) IsFetchViaHTTP() bool { + return Conf.Gost.Type == "http" +} diff --git a/config/govaldictconf.go b/config/govaldictconf.go new file mode 100644 index 0000000000..d2a775a5c9 --- /dev/null +++ b/config/govaldictconf.go @@ -0,0 +1,54 @@ +package config + +import ( + "os" + "path/filepath" +) + +// GovalDictConf is goval-dictionary config +type GovalDictConf struct { + + // DB type of OVAL dictionary (sqlite3, mysql, postgres or redis) + Type string + + // http://goval-dictionary.com:1324 or DB connection string + URL string `json:"-"` + + // /path/to/oval.sqlite3 + SQLite3Path string `json:"-"` +} + +func (cnf *GovalDictConf) setDefault() { + if cnf.Type == "" { + cnf.Type = "sqlite3" + } + if cnf.URL == "" && cnf.SQLite3Path == "" { + wd, _ := os.Getwd() + cnf.SQLite3Path = filepath.Join(wd, "oval.sqlite3") + } +} + +const govalType = "OVALDB_TYPE" +const govalURL = "OVALDB_URL" +const govalPATH = "OVALDB_SQLITE3_PATH" + +// Init set options with the following priority. +// 1. Environment variable +// 2. config.toml +func (cnf *GovalDictConf) Init() { + if os.Getenv(govalType) != "" { + cnf.Type = os.Getenv(govalType) + } + if os.Getenv(govalURL) != "" { + cnf.URL = os.Getenv(govalURL) + } + if os.Getenv(govalPATH) != "" { + cnf.SQLite3Path = os.Getenv(govalPATH) + } + cnf.setDefault() +} + +// IsFetchViaHTTP returns wether fetch via http +func (cnf *GovalDictConf) IsFetchViaHTTP() bool { + return Conf.OvalDict.Type == "http" +} diff --git a/config/httpconf.go b/config/httpconf.go new file mode 100644 index 0000000000..ca1636fa41 --- /dev/null +++ b/config/httpconf.go @@ -0,0 +1,38 @@ +package config + +import ( + "os" + + "github.com/asaskevich/govalidator" +) + +// HTTPConf is HTTP config +type HTTPConf struct { + URL string `valid:"url" json:"-"` +} + +// Validate validates configuration +func (c *HTTPConf) Validate() (errs []error) { + if !Conf.ToHTTP { + return nil + } + + if _, err := govalidator.ValidateStruct(c); err != nil { + errs = append(errs, err) + } + return errs +} + +const httpKey = "VULS_HTTP_URL" + +// Init set options with the following priority. +// 1. Environment variable +// 2. config.toml +func (c *HTTPConf) Init(toml HTTPConf) { + if os.Getenv(httpKey) != "" { + c.URL = os.Getenv(httpKey) + } + if toml.URL != "" { + c.URL = toml.URL + } +} diff --git a/config/metasploitconf.go b/config/metasploitconf.go new file mode 100644 index 0000000000..414f5ce50c --- /dev/null +++ b/config/metasploitconf.go @@ -0,0 +1,53 @@ +package config + +import ( + "os" + "path/filepath" +) + +// MetasploitConf is metasploit config +type MetasploitConf struct { + // DB type for metasploit dictionary (sqlite3, mysql, postgres or redis) + Type string + + // http://metasploit-dictionary.com:1324 or DB connection string + URL string `json:"-"` + + // /path/to/metasploit.sqlite3 + SQLite3Path string `json:"-"` +} + +func (cnf *MetasploitConf) setDefault() { + if cnf.Type == "" { + cnf.Type = "sqlite3" + } + if cnf.URL == "" && cnf.SQLite3Path == "" { + wd, _ := os.Getwd() + cnf.SQLite3Path = filepath.Join(wd, "go-msfdb.sqlite3") + } +} + +const metasploitDBType = "METASPLOITDB_TYPE" +const metasploitDBURL = "METASPLOITDB_URL" +const metasploitDBPATH = "METASPLOITDB_SQLITE3_PATH" + +// Init set options with the following priority. +// 1. Environment variable +// 2. config.toml +func (cnf *MetasploitConf) Init() { + if os.Getenv(metasploitDBType) != "" { + cnf.Type = os.Getenv(metasploitDBType) + } + if os.Getenv(metasploitDBURL) != "" { + cnf.URL = os.Getenv(metasploitDBURL) + } + if os.Getenv(metasploitDBPATH) != "" { + cnf.SQLite3Path = os.Getenv(metasploitDBPATH) + } + cnf.setDefault() +} + +// IsFetchViaHTTP returns wether fetch via http +func (cnf *MetasploitConf) IsFetchViaHTTP() bool { + return Conf.Metasploit.Type == "http" +} diff --git a/config/os.go b/config/os.go index 10f34468b3..4d8e063198 100644 --- a/config/os.go +++ b/config/os.go @@ -59,18 +59,21 @@ const ( ServerTypePseudo = "pseudo" ) +// EOL has End-of-Life information type EOL struct { StandardSupportUntil time.Time ExtendedSupportUntil time.Time Ended bool } +// IsStandardSupportEnded checks now is under standard support func (e EOL) IsStandardSupportEnded(now time.Time) bool { return e.Ended || !e.ExtendedSupportUntil.IsZero() && e.StandardSupportUntil.IsZero() || !e.StandardSupportUntil.IsZero() && now.After(e.StandardSupportUntil) } +// IsExtendedSuppportEnded checks now is under extended support func (e EOL) IsExtendedSuppportEnded(now time.Time) bool { if e.Ended { return true @@ -82,6 +85,7 @@ func (e EOL) IsExtendedSuppportEnded(now time.Time) bool { e.ExtendedSupportUntil.IsZero() && now.After(e.StandardSupportUntil) } +// GetEOL return EOL information for the OS-release passed by args // https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/redhat/redhat.go#L20 func GetEOL(family, release string) (eol EOL, found bool) { switch family { diff --git a/config/saasconf.go b/config/saasconf.go new file mode 100644 index 0000000000..c1b5daf171 --- /dev/null +++ b/config/saasconf.go @@ -0,0 +1,34 @@ +package config + +import ( + "github.com/asaskevich/govalidator" + "golang.org/x/xerrors" +) + +// SaasConf is FutureVuls config +type SaasConf struct { + GroupID int64 `json:"-"` + Token string `json:"-"` + URL string `json:"-"` +} + +// Validate validates configuration +func (c *SaasConf) Validate() (errs []error) { + if c.GroupID == 0 { + errs = append(errs, xerrors.New("GroupID must not be empty")) + } + + if len(c.Token) == 0 { + errs = append(errs, xerrors.New("Token must not be empty")) + } + + if len(c.URL) == 0 { + errs = append(errs, xerrors.New("URL must not be empty")) + } + + _, err := govalidator.ValidateStruct(c) + if err != nil { + errs = append(errs, err) + } + return +} diff --git a/config/scanmode.go b/config/scanmode.go new file mode 100644 index 0000000000..2e5a5a35e8 --- /dev/null +++ b/config/scanmode.go @@ -0,0 +1,110 @@ +package config + +import ( + "strings" + + "golang.org/x/xerrors" +) + +// ScanMode has a type of scan mode. fast, fast-root, deep and offline +type ScanMode struct { + flag byte +} + +const ( + // Fast is fast scan mode + Fast = byte(1 << iota) + // FastRoot is scanmode + FastRoot + // Deep is scanmode + Deep + // Offline is scanmode + Offline + + fastStr = "fast" + fastRootStr = "fast-root" + deepStr = "deep" + offlineStr = "offline" +) + +// Set mode +func (s *ScanMode) Set(f byte) { + s.flag |= f +} + +// IsFast return whether scan mode is fast +func (s ScanMode) IsFast() bool { + return s.flag&Fast == Fast +} + +// IsFastRoot return whether scan mode is fastroot +func (s ScanMode) IsFastRoot() bool { + return s.flag&FastRoot == FastRoot +} + +// IsDeep return whether scan mode is deep +func (s ScanMode) IsDeep() bool { + return s.flag&Deep == Deep +} + +// IsOffline return whether scan mode is offline +func (s ScanMode) IsOffline() bool { + return s.flag&Offline == Offline +} + +func (s *ScanMode) ensure() error { + numTrue := 0 + for _, b := range []bool{s.IsFast(), s.IsFastRoot(), s.IsDeep()} { + if b { + numTrue++ + } + } + if numTrue == 0 { + s.Set(Fast) + } else if s.IsDeep() && s.IsOffline() { + return xerrors.New("Don't specify both of deep and offline") + } else if numTrue != 1 { + return xerrors.New("Specify only one of offline, fast, fast-root or deep") + } + return nil +} + +func (s ScanMode) String() string { + ss := "" + if s.IsFast() { + ss = fastStr + } else if s.IsFastRoot() { + ss = fastRootStr + } else if s.IsDeep() { + ss = deepStr + } + if s.IsOffline() { + ss += " " + offlineStr + } + return ss + " mode" +} + +func setScanMode(server *ServerInfo, d ServerInfo) error { + if len(server.ScanMode) == 0 { + server.ScanMode = Conf.Default.ScanMode + } + for _, m := range server.ScanMode { + switch strings.ToLower(m) { + case fastStr: + server.Mode.Set(Fast) + case fastRootStr: + server.Mode.Set(FastRoot) + case deepStr: + server.Mode.Set(Deep) + case offlineStr: + server.Mode.Set(Offline) + default: + return xerrors.Errorf("scanMode: %s of %s is invalid. Specify -fast, -fast-root, -deep or offline", + m, server.ServerName) + } + } + if err := server.Mode.ensure(); err != nil { + return xerrors.Errorf("%s in %s", err, server.ServerName) + } + return nil +} diff --git a/config/scanmodule.go b/config/scanmodule.go new file mode 100644 index 0000000000..db5bb50a83 --- /dev/null +++ b/config/scanmodule.go @@ -0,0 +1,97 @@ +package config + +import ( + "strings" + + "golang.org/x/xerrors" +) + +// ScanModule has a type of scan module +type ScanModule struct { + flag byte +} + +const ( + // OSPkg is scanmodule + OSPkg = byte(1 << iota) + // WordPress is scanmodule + WordPress + // Lockfile is scanmodule + Lockfile + // Port is scanmodule + Port + + osPkgStr = "ospkg" + wordPressStr = "wordpress" + lockfileStr = "lockfile" + portStr = "port" +) + +var allModules = []string{osPkgStr, wordPressStr, lockfileStr, portStr} + +// Set module +func (s *ScanModule) Set(f byte) { + s.flag |= f +} + +// IsScanOSPkg return whether scanning os pkg +func (s ScanModule) IsScanOSPkg() bool { + return s.flag&OSPkg == OSPkg +} + +// IsScanWordPress return whether scanning wordpress +func (s ScanModule) IsScanWordPress() bool { + return s.flag&WordPress == WordPress +} + +// IsScanLockFile whether scanning lock file +func (s ScanModule) IsScanLockFile() bool { + return s.flag&Lockfile == Lockfile +} + +// IsScanPort whether scanning listening ports +func (s ScanModule) IsScanPort() bool { + return s.flag&Port == Port +} + +// IsZero return the struct value are all false +func (s ScanModule) IsZero() bool { + return !(s.IsScanOSPkg() || s.IsScanWordPress() || s.IsScanLockFile() || s.IsScanPort()) +} + +func (s *ScanModule) ensure() error { + if s.IsZero() { + s.Set(OSPkg) + s.Set(WordPress) + s.Set(Lockfile) + s.Set(Port) + } else if !s.IsScanOSPkg() && s.IsScanPort() { + return xerrors.New("When specifying the Port, Specify OSPkg as well") + } + return nil +} + +func setScanModules(server *ServerInfo, d ServerInfo) error { + if len(server.ScanModules) == 0 { + server.ScanModules = d.ScanModules + } + for _, m := range server.ScanModules { + switch strings.ToLower(m) { + case osPkgStr: + server.Module.Set(OSPkg) + case wordPressStr: + server.Module.Set(WordPress) + case lockfileStr: + server.Module.Set(Lockfile) + case portStr: + server.Module.Set(Port) + default: + return xerrors.Errorf("scanMode: %s of %s is invalid. Specify %s", + m, server.ServerName, allModules) + } + } + if err := server.Module.ensure(); err != nil { + return xerrors.Errorf("%s in %s", err, server.ServerName) + } + return nil +} diff --git a/config/scanmodule_test.go b/config/scanmodule_test.go new file mode 100644 index 0000000000..80648458ae --- /dev/null +++ b/config/scanmodule_test.go @@ -0,0 +1,65 @@ +package config + +import ( + "testing" +) + +func TestScanModule_IsZero(t *testing.T) { + tests := []struct { + name string + modes []byte + want bool + }{ + { + name: "not zero", + modes: []byte{OSPkg}, + want: false, + }, + { + name: "zero", + modes: []byte{}, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := ScanModule{} + for _, b := range tt.modes { + s.Set(b) + } + if got := s.IsZero(); got != tt.want { + t.Errorf("ScanModule.IsZero() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestScanModule_validate(t *testing.T) { + tests := []struct { + name string + modes []byte + wantErr bool + }{ + { + name: "valid", + modes: []byte{}, + wantErr: false, + }, + { + name: "err", + modes: []byte{WordPress, Lockfile, Port}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := ScanModule{} + for _, b := range tt.modes { + s.Set(b) + } + if err := s.ensure(); (err != nil) != tt.wantErr { + t.Errorf("ScanModule.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/config/slackconf.go b/config/slackconf.go new file mode 100644 index 0000000000..affa9ce5f3 --- /dev/null +++ b/config/slackconf.go @@ -0,0 +1,51 @@ +package config + +import ( + "strings" + + "github.com/asaskevich/govalidator" + "golang.org/x/xerrors" +) + +// SlackConf is slack config +type SlackConf struct { + HookURL string `valid:"url" json:"-" toml:"hookURL,omitempty"` + LegacyToken string `json:"-" toml:"legacyToken,omitempty"` + Channel string `json:"-" toml:"channel,omitempty"` + IconEmoji string `json:"-" toml:"iconEmoji,omitempty"` + AuthUser string `json:"-" toml:"authUser,omitempty"` + NotifyUsers []string `toml:"notifyUsers,omitempty" json:"-"` + Text string `json:"-"` +} + +// Validate validates configuration +func (c *SlackConf) Validate() (errs []error) { + if !Conf.ToSlack { + return + } + + if len(c.HookURL) == 0 && len(c.LegacyToken) == 0 { + errs = append(errs, xerrors.New("slack.hookURL or slack.LegacyToken must not be empty")) + } + + if len(c.Channel) == 0 { + errs = append(errs, xerrors.New("slack.channel must not be empty")) + } else { + if !(strings.HasPrefix(c.Channel, "#") || + c.Channel == "${servername}") { + errs = append(errs, xerrors.Errorf( + "channel's prefix must be '#', channel: %s", c.Channel)) + } + } + + if len(c.AuthUser) == 0 { + errs = append(errs, xerrors.New("slack.authUser must not be empty")) + } + + _, err := govalidator.ValidateStruct(c) + if err != nil { + errs = append(errs, err) + } + + return +} diff --git a/config/smtpconf.go b/config/smtpconf.go new file mode 100644 index 0000000000..43e8533260 --- /dev/null +++ b/config/smtpconf.go @@ -0,0 +1,65 @@ +package config + +import ( + "github.com/asaskevich/govalidator" + "golang.org/x/xerrors" +) + +// SMTPConf is smtp config +type SMTPConf struct { + SMTPAddr string `toml:"smtpAddr,omitempty" json:"-"` + SMTPPort string `toml:"smtpPort,omitempty" valid:"port" json:"-"` + User string `toml:"user,omitempty" json:"-"` + Password string `toml:"password,omitempty" json:"-"` + From string `toml:"from,omitempty" json:"-"` + To []string `toml:"to,omitempty" json:"-"` + Cc []string `toml:"cc,omitempty" json:"-"` + SubjectPrefix string `toml:"subjectPrefix,omitempty" json:"-"` +} + +func checkEmails(emails []string) (errs []error) { + for _, addr := range emails { + if len(addr) == 0 { + return + } + if ok := govalidator.IsEmail(addr); !ok { + errs = append(errs, xerrors.Errorf("Invalid email address. email: %s", addr)) + } + } + return +} + +// Validate SMTP configuration +func (c *SMTPConf) Validate() (errs []error) { + if !Conf.ToEmail { + return + } + // Check Emails fromat + emails := []string{} + emails = append(emails, c.From) + emails = append(emails, c.To...) + emails = append(emails, c.Cc...) + + if emailErrs := checkEmails(emails); 0 < len(emailErrs) { + errs = append(errs, emailErrs...) + } + + if len(c.SMTPAddr) == 0 { + errs = append(errs, xerrors.New("email.smtpAddr must not be empty")) + } + if len(c.SMTPPort) == 0 { + errs = append(errs, xerrors.New("email.smtpPort must not be empty")) + } + if len(c.To) == 0 { + errs = append(errs, xerrors.New("email.To required at least one address")) + } + if len(c.From) == 0 { + errs = append(errs, xerrors.New("email.From required at least one address")) + } + + _, err := govalidator.ValidateStruct(c) + if err != nil { + errs = append(errs, err) + } + return +} diff --git a/config/syslogconf.go b/config/syslogconf.go new file mode 100644 index 0000000000..6e8fc614f2 --- /dev/null +++ b/config/syslogconf.go @@ -0,0 +1,129 @@ +package config + +import ( + "errors" + "log/syslog" + + "github.com/asaskevich/govalidator" + "golang.org/x/xerrors" +) + +// SyslogConf is syslog config +type SyslogConf struct { + Protocol string `json:"-"` + Host string `valid:"host" json:"-"` + Port string `valid:"port" json:"-"` + Severity string `json:"-"` + Facility string `json:"-"` + Tag string `json:"-"` + Verbose bool `json:"-"` +} + +// Validate validates configuration +func (c *SyslogConf) Validate() (errs []error) { + if !Conf.ToSyslog { + return nil + } + // If protocol is empty, it will connect to the local syslog server. + if len(c.Protocol) > 0 && c.Protocol != "tcp" && c.Protocol != "udp" { + errs = append(errs, errors.New(`syslog.protocol must be "tcp" or "udp"`)) + } + + // Default port: 514 + if c.Port == "" { + c.Port = "514" + } + + if _, err := c.GetSeverity(); err != nil { + errs = append(errs, err) + } + + if _, err := c.GetFacility(); err != nil { + errs = append(errs, err) + } + + if _, err := govalidator.ValidateStruct(c); err != nil { + errs = append(errs, err) + } + return errs +} + +// GetSeverity gets severity +func (c *SyslogConf) GetSeverity() (syslog.Priority, error) { + if c.Severity == "" { + return syslog.LOG_INFO, nil + } + + switch c.Severity { + case "emerg": + return syslog.LOG_EMERG, nil + case "alert": + return syslog.LOG_ALERT, nil + case "crit": + return syslog.LOG_CRIT, nil + case "err": + return syslog.LOG_ERR, nil + case "warning": + return syslog.LOG_WARNING, nil + case "notice": + return syslog.LOG_NOTICE, nil + case "info": + return syslog.LOG_INFO, nil + case "debug": + return syslog.LOG_DEBUG, nil + default: + return -1, xerrors.Errorf("Invalid severity: %s", c.Severity) + } +} + +// GetFacility gets facility +func (c *SyslogConf) GetFacility() (syslog.Priority, error) { + if c.Facility == "" { + return syslog.LOG_AUTH, nil + } + + switch c.Facility { + case "kern": + return syslog.LOG_KERN, nil + case "user": + return syslog.LOG_USER, nil + case "mail": + return syslog.LOG_MAIL, nil + case "daemon": + return syslog.LOG_DAEMON, nil + case "auth": + return syslog.LOG_AUTH, nil + case "syslog": + return syslog.LOG_SYSLOG, nil + case "lpr": + return syslog.LOG_LPR, nil + case "news": + return syslog.LOG_NEWS, nil + case "uucp": + return syslog.LOG_UUCP, nil + case "cron": + return syslog.LOG_CRON, nil + case "authpriv": + return syslog.LOG_AUTHPRIV, nil + case "ftp": + return syslog.LOG_FTP, nil + case "local0": + return syslog.LOG_LOCAL0, nil + case "local1": + return syslog.LOG_LOCAL1, nil + case "local2": + return syslog.LOG_LOCAL2, nil + case "local3": + return syslog.LOG_LOCAL3, nil + case "local4": + return syslog.LOG_LOCAL4, nil + case "local5": + return syslog.LOG_LOCAL5, nil + case "local6": + return syslog.LOG_LOCAL6, nil + case "local7": + return syslog.LOG_LOCAL7, nil + default: + return -1, xerrors.Errorf("Invalid facility: %s", c.Facility) + } +} diff --git a/config/telegramconf.go b/config/telegramconf.go new file mode 100644 index 0000000000..d15f660bb6 --- /dev/null +++ b/config/telegramconf.go @@ -0,0 +1,32 @@ +package config + +import ( + "github.com/asaskevich/govalidator" + "golang.org/x/xerrors" +) + +// TelegramConf is Telegram config +type TelegramConf struct { + Token string `json:"-"` + ChatID string `json:"-"` +} + +// Validate validates configuration +func (c *TelegramConf) Validate() (errs []error) { + if !Conf.ToTelegram { + return + } + if len(c.ChatID) == 0 { + errs = append(errs, xerrors.New("TelegramConf.ChatID must not be empty")) + } + + if len(c.Token) == 0 { + errs = append(errs, xerrors.New("TelegramConf.Token must not be empty")) + } + + _, err := govalidator.ValidateStruct(c) + if err != nil { + errs = append(errs, err) + } + return +} diff --git a/config/tomlloader.go b/config/tomlloader.go index 2b4b4e2f06..d559f8834d 100644 --- a/config/tomlloader.go +++ b/config/tomlloader.go @@ -15,270 +15,213 @@ type TOMLLoader struct { // Load load the configuration TOML file specified by path arg. func (c TOMLLoader) Load(pathToToml, keyPass string) error { - var conf Config - if _, err := toml.DecodeFile(pathToToml, &conf); err != nil { + if _, err := toml.DecodeFile(pathToToml, &Conf); err != nil { return err } - Conf.EMail = conf.EMail - Conf.Slack = conf.Slack - Conf.ChatWork = conf.ChatWork - Conf.Telegram = conf.Telegram - Conf.Saas = conf.Saas - Conf.Syslog = conf.Syslog - Conf.HTTP = conf.HTTP - Conf.AWS = conf.AWS - Conf.Azure = conf.Azure - - Conf.CveDict = conf.CveDict - Conf.OvalDict = conf.OvalDict - Conf.Gost = conf.Gost - Conf.Exploit = conf.Exploit - Conf.Metasploit = conf.Metasploit - - d := conf.Default - Conf.Default = d - servers := make(map[string]ServerInfo) - if keyPass != "" { - d.KeyPassword = keyPass + Conf.Default.KeyPassword = keyPass } + Conf.CveDict.Init() + Conf.OvalDict.Init() + Conf.Gost.Init() + Conf.Exploit.Init() + Conf.Metasploit.Init() + index := 0 - for serverName, v := range conf.Servers { - if 0 < len(v.KeyPassword) { - return xerrors.Errorf("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE: %s", serverName) + for name, server := range Conf.Servers { + server.ServerName = name + if 0 < len(server.KeyPassword) { + return xerrors.Errorf("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE: %s", name) } - s := ServerInfo{ServerName: serverName} - if v.Type != ServerTypePseudo { - s.Host = v.Host - if len(s.Host) == 0 { - return xerrors.Errorf("%s is invalid. host is empty", serverName) - } - - s.JumpServer = v.JumpServer - if len(s.JumpServer) == 0 { - s.JumpServer = d.JumpServer - } - - switch { - case v.Port != "": - s.Port = v.Port - case d.Port != "": - s.Port = d.Port - default: - s.Port = "22" - } - - switch { - case v.User != "": - s.User = v.User - case d.User != "": - s.User = d.User - default: - if s.Port != "local" { - return xerrors.Errorf("%s is invalid. User is empty", serverName) - } - } - - s.SSHConfigPath = v.SSHConfigPath - if len(s.SSHConfigPath) == 0 { - s.SSHConfigPath = d.SSHConfigPath - } - - s.KeyPath = v.KeyPath - if len(s.KeyPath) == 0 { - s.KeyPath = d.KeyPath - } - s.KeyPassword = v.KeyPassword - if len(s.KeyPassword) == 0 { - s.KeyPassword = d.KeyPassword - } + if err := setDefaultIfEmpty(&server, Conf.Default); err != nil { + return xerrors.Errorf("Failed to set default value to config. server: %s, err: %w", name, err) } - s.ScanMode = v.ScanMode - if len(s.ScanMode) == 0 { - s.ScanMode = d.ScanMode - if len(s.ScanMode) == 0 { - s.ScanMode = []string{"fast"} - } - } - for _, m := range s.ScanMode { - switch m { - case "fast": - s.Mode.Set(Fast) - case "fast-root": - s.Mode.Set(FastRoot) - case "deep": - s.Mode.Set(Deep) - case "offline": - s.Mode.Set(Offline) - default: - return xerrors.Errorf("scanMode: %s of %s is invalid. Specify -fast, -fast-root, -deep or offline", m, serverName) - } - } - if err := s.Mode.validate(); err != nil { - return xerrors.Errorf("%s in %s", err, serverName) + if err := setScanMode(&server, Conf.Default); err != nil { + return xerrors.Errorf("Failed to set ScanMode: %w", err) } - s.CpeNames = v.CpeNames - if len(s.CpeNames) == 0 { - s.CpeNames = d.CpeNames + if err := setScanModules(&server, Conf.Default); err != nil { + return xerrors.Errorf("Failed to set ScanModule: %w", err) } - s.Lockfiles = v.Lockfiles - if len(s.Lockfiles) == 0 { - s.Lockfiles = d.Lockfiles + if len(server.CpeNames) == 0 { + server.CpeNames = Conf.Default.CpeNames } - - s.FindLock = v.FindLock - - for i, n := range s.CpeNames { + for i, n := range server.CpeNames { uri, err := toCpeURI(n) if err != nil { - return xerrors.Errorf("Failed to parse CPENames %s in %s, err: %w", n, serverName, err) + return xerrors.Errorf("Failed to parse CPENames %s in %s, err: %w", n, name, err) } - s.CpeNames[i] = uri - } - - s.ContainersIncluded = v.ContainersIncluded - if len(s.ContainersIncluded) == 0 { - s.ContainersIncluded = d.ContainersIncluded - } - - s.ContainersExcluded = v.ContainersExcluded - if len(s.ContainersExcluded) == 0 { - s.ContainersExcluded = d.ContainersExcluded - } - - s.ContainerType = v.ContainerType - if len(s.ContainerType) == 0 { - s.ContainerType = d.ContainerType - } - - s.Containers = v.Containers - for contName, cont := range s.Containers { - cont.IgnoreCves = append(cont.IgnoreCves, d.IgnoreCves...) - s.Containers[contName] = cont + server.CpeNames[i] = uri } - s.OwaspDCXMLPath = v.OwaspDCXMLPath - if len(s.OwaspDCXMLPath) == 0 { - s.OwaspDCXMLPath = d.OwaspDCXMLPath - } - - s.Memo = v.Memo - if s.Memo == "" { - s.Memo = d.Memo - } - - s.IgnoreCves = v.IgnoreCves - for _, cve := range d.IgnoreCves { + for _, cve := range Conf.Default.IgnoreCves { found := false - for _, c := range s.IgnoreCves { + for _, c := range server.IgnoreCves { if cve == c { found = true break } } if !found { - s.IgnoreCves = append(s.IgnoreCves, cve) + server.IgnoreCves = append(server.IgnoreCves, cve) } } - s.IgnorePkgsRegexp = v.IgnorePkgsRegexp - for _, pkg := range d.IgnorePkgsRegexp { + for _, pkg := range Conf.Default.IgnorePkgsRegexp { found := false - for _, p := range s.IgnorePkgsRegexp { + for _, p := range server.IgnorePkgsRegexp { if pkg == p { found = true break } } if !found { - s.IgnorePkgsRegexp = append(s.IgnorePkgsRegexp, pkg) + server.IgnorePkgsRegexp = append(server.IgnorePkgsRegexp, pkg) } } - for _, reg := range s.IgnorePkgsRegexp { + for _, reg := range server.IgnorePkgsRegexp { _, err := regexp.Compile(reg) if err != nil { - return xerrors.Errorf("Failed to parse %s in %s. err: %w", reg, serverName, err) + return xerrors.Errorf("Failed to parse %s in %s. err: %w", reg, name, err) } } - for contName, cont := range s.Containers { + for contName, cont := range server.Containers { for _, reg := range cont.IgnorePkgsRegexp { _, err := regexp.Compile(reg) if err != nil { return xerrors.Errorf("Failed to parse %s in %s@%s. err: %w", - reg, contName, serverName, err) + reg, contName, name, err) } } } - opt := map[string]interface{}{} - for k, v := range d.Optional { - opt[k] = v - } - for k, v := range v.Optional { - opt[k] = v + for ownerRepo, githubSetting := range server.GitHubRepos { + if ss := strings.Split(ownerRepo, "/"); len(ss) != 2 { + return xerrors.Errorf("Failed to parse GitHub owner/repo: %s in %s", + ownerRepo, name) + } + if githubSetting.Token == "" { + return xerrors.Errorf("GitHub owner/repo: %s in %s token is empty", + ownerRepo, name) + } } - s.Optional = opt - s.Enablerepo = v.Enablerepo - if len(s.Enablerepo) == 0 { - s.Enablerepo = d.Enablerepo + if len(server.Enablerepo) == 0 { + server.Enablerepo = Conf.Default.Enablerepo } - if len(s.Enablerepo) != 0 { - for _, repo := range s.Enablerepo { + if len(server.Enablerepo) != 0 { + for _, repo := range server.Enablerepo { switch repo { case "base", "updates": // nop default: return xerrors.Errorf( - "For now, enablerepo have to be base or updates: %s, servername: %s", - s.Enablerepo, serverName) + "For now, enablerepo have to be base or updates: %s", + server.Enablerepo) } } } - s.GitHubRepos = v.GitHubRepos - for ownerRepo, githubSetting := range s.GitHubRepos { - if ss := strings.Split(ownerRepo, "/"); len(ss) != 2 { - return xerrors.Errorf("Failed to parse GitHub owner/repo: %s in %s", - ownerRepo, serverName) + server.LogMsgAnsiColor = Colors[index%len(Colors)] + index++ + + Conf.Servers[name] = server + } + return nil +} + +func setDefaultIfEmpty(server *ServerInfo, d ServerInfo) error { + if server.Type != ServerTypePseudo { + if len(server.Host) == 0 { + return xerrors.Errorf("server.host is empty") + } + + if len(server.JumpServer) == 0 { + server.JumpServer = Conf.Default.JumpServer + } + + if server.Port == "" { + if Conf.Default.Port != "" { + server.Port = Conf.Default.Port + } else { + server.Port = "22" } - if githubSetting.Token == "" { - return xerrors.Errorf("GitHub owner/repo: %s in %s token is empty", - ownerRepo, serverName) + } + + if server.User == "" { + if Conf.Default.User != "" { + server.User = Conf.Default.User + } + if server.Port != "local" { + return xerrors.Errorf("server.user is empty") } } - s.UUIDs = v.UUIDs - s.Type = v.Type + if server.SSHConfigPath == "" { + server.SSHConfigPath = Conf.Default.SSHConfigPath + } - s.WordPress.WPVulnDBToken = v.WordPress.WPVulnDBToken - s.WordPress.CmdPath = v.WordPress.CmdPath - s.WordPress.DocRoot = v.WordPress.DocRoot - s.WordPress.OSUser = v.WordPress.OSUser - s.WordPress.IgnoreInactive = v.WordPress.IgnoreInactive + if server.KeyPath == "" { + server.KeyPath = Conf.Default.KeyPath + } - s.IgnoredJSONKeys = v.IgnoredJSONKeys - if len(s.IgnoredJSONKeys) == 0 { - s.IgnoredJSONKeys = d.IgnoredJSONKeys + if server.KeyPassword == "" { + server.KeyPassword = Conf.Default.KeyPassword } + } - s.LogMsgAnsiColor = Colors[index%len(Colors)] - index++ + if len(server.Lockfiles) == 0 { + server.Lockfiles = Conf.Default.Lockfiles + } - servers[serverName] = s + if len(server.ContainersIncluded) == 0 { + server.ContainersIncluded = Conf.Default.ContainersIncluded } - Conf.Servers = servers - Conf.CveDict.Init() - Conf.OvalDict.Init() - Conf.Gost.Init() - Conf.Exploit.Init() - Conf.Metasploit.Init() + if len(server.ContainersExcluded) == 0 { + server.ContainersExcluded = Conf.Default.ContainersExcluded + } + + if server.ContainerType == "" { + server.ContainerType = Conf.Default.ContainerType + } + + for contName, cont := range server.Containers { + cont.IgnoreCves = append(cont.IgnoreCves, Conf.Default.IgnoreCves...) + server.Containers[contName] = cont + } + + if server.OwaspDCXMLPath == "" { + server.OwaspDCXMLPath = Conf.Default.OwaspDCXMLPath + } + + if server.Memo == "" { + server.Memo = Conf.Default.Memo + } + + // TODO set default WordPress + if server.WordPress == nil { + server.WordPress = &WordPressConf{} + } + //TODO set nil in config re-generate in saas subcmd + + if len(server.IgnoredJSONKeys) == 0 { + server.IgnoredJSONKeys = Conf.Default.IgnoredJSONKeys + } + + opt := map[string]interface{}{} + for k, v := range Conf.Default.Optional { + opt[k] = v + } + for k, v := range server.Optional { + opt[k] = v + } + server.Optional = opt + return nil } diff --git a/github/github.go b/github/github.go index 1eb24e64a3..1019a992ff 100644 --- a/github/github.go +++ b/github/github.go @@ -15,9 +15,10 @@ import ( "golang.org/x/oauth2" ) -// FillGitHubSecurityAlerts access to owner/repo on GitHub and fetch security alerts of the repository via GitHub API v4 GraphQL and then set to the given ScanResult. +// DetectGitHubSecurityAlerts access to owner/repo on GitHub and fetch security alerts of the repository via GitHub API v4 GraphQL and then set to the given ScanResult. // https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/ -func FillGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (nCVEs int, err error) { +//TODO move to report +func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (nCVEs int, err error) { src := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: token}, ) diff --git a/go.mod b/go.mod index 861b89529d..d6b7e9ffc3 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/Azure/go-autorest/autorest v0.11.15 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.10 // indirect github.com/BurntSushi/toml v0.3.1 - github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 github.com/aquasecurity/fanal v0.0.0-20210106083348-3f85e04a8048 github.com/aquasecurity/trivy v0.15.0 github.com/aquasecurity/trivy-db v0.0.0-20210106051232-62e6657ad501 diff --git a/go.sum b/go.sum index d58da146fd..e0463946f7 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,6 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 h1:vX+gnvBc56EbWYrmlhYbFYRaeikAke1GL84N4BEYOFE= -github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91/go.mod h1:cDLGBht23g0XQdLjzn6xOGXDkLK182YfINAaZEQLCHQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= diff --git a/models/cvecontents.go b/models/cvecontents.go index 191ae0baa5..a14fb775c1 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -247,7 +247,7 @@ func NewCveContentType(name string) CveContentType { case "microsoft": return Microsoft case "wordpress": - return WPVulnDB + return WpScan case "amazon": return Amazon case "trivy": @@ -291,8 +291,8 @@ const ( // Microsoft is Microsoft Microsoft CveContentType = "microsoft" - // WPVulnDB is WordPress - WPVulnDB CveContentType = "wpvulndb" + // WpScan is WordPress + WpScan CveContentType = "wpscan" // Trivy is Trivy Trivy CveContentType = "trivy" @@ -315,7 +315,7 @@ var AllCveContetTypes = CveContentTypes{ Amazon, SUSE, DebianSecurityTracker, - WPVulnDB, + WpScan, Trivy, } diff --git a/models/library.go b/models/library.go index 8f84da8adc..82a36193da 100644 --- a/models/library.go +++ b/models/library.go @@ -30,6 +30,13 @@ func (lss LibraryScanners) Find(path, name string) map[string]types.Library { return filtered } +func (lss LibraryScanners) Total() (total int) { + for _, lib := range lss { + total += len(lib.Libs) + } + return +} + // LibraryScanner has libraries information type LibraryScanner struct { Path string diff --git a/models/scanresults.go b/models/scanresults.go index 45beaed6cf..5b9bb78f5a 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -145,11 +145,12 @@ func (r ScanResult) FilterByCvssOver(over float64) ScanResult { // FilterIgnoreCves is filter function. func (r ScanResult) FilterIgnoreCves() ScanResult { - ignoreCves := []string{} if len(r.Container.Name) == 0 { + //TODO pass by args ignoreCves = config.Conf.Servers[r.ServerName].IgnoreCves } else { + //TODO pass by args if s, ok := config.Conf.Servers[r.ServerName]; ok { if con, ok := s.Containers[r.Container.Name]; ok { ignoreCves = con.IgnoreCves @@ -176,8 +177,8 @@ func (r ScanResult) FilterIgnoreCves() ScanResult { } // FilterUnfixed is filter function. -func (r ScanResult) FilterUnfixed() ScanResult { - if !config.Conf.IgnoreUnfixed { +func (r ScanResult) FilterUnfixed(ignoreUnfixed bool) ScanResult { + if !ignoreUnfixed { return r } filtered := r.ScannedCves.Find(func(v VulnInfo) bool { @@ -199,6 +200,7 @@ func (r ScanResult) FilterUnfixed() ScanResult { func (r ScanResult) FilterIgnorePkgs() ScanResult { var ignorePkgsRegexps []string if len(r.Container.Name) == 0 { + //TODO pass by args ignorePkgsRegexps = config.Conf.Servers[r.ServerName].IgnorePkgsRegexp } else { if s, ok := config.Conf.Servers[r.ServerName]; ok { @@ -251,8 +253,8 @@ func (r ScanResult) FilterIgnorePkgs() ScanResult { } // FilterInactiveWordPressLibs is filter function. -func (r ScanResult) FilterInactiveWordPressLibs() ScanResult { - if !config.Conf.Servers[r.ServerName].WordPress.IgnoreInactive { +func (r ScanResult) FilterInactiveWordPressLibs(detectInactive bool) ScanResult { + if detectInactive { return r } @@ -348,16 +350,23 @@ func (r ScanResult) FormatTextReportHeader() string { buf.WriteString("=") } - return fmt.Sprintf("%s\n%s\n%s, %s, %s, %s, %s, %s\n", + pkgs := r.FormatUpdatablePacksSummary() + if 0 < len(r.WordPressPackages) { + pkgs = fmt.Sprintf("%s, %d WordPress pkgs", pkgs, len(r.WordPressPackages)) + } + if 0 < len(r.LibraryScanners) { + pkgs = fmt.Sprintf("%s, %d libs", pkgs, r.LibraryScanners.Total()) + } + + return fmt.Sprintf("%s\n%s\n%s, %s, %s, %s, %s\n%s\n", r.ServerInfo(), buf.String(), r.ScannedCves.FormatCveSummary(), r.ScannedCves.FormatFixedStatus(r.Packages), - r.FormatUpdatablePacksSummary(), r.FormatExploitCveSummary(), r.FormatMetasploitCveSummary(), r.FormatAlertSummary(), - ) + pkgs) } // FormatUpdatablePacksSummary returns a summary of updatable packages @@ -388,7 +397,7 @@ func (r ScanResult) FormatExploitCveSummary() string { nExploitCve++ } } - return fmt.Sprintf("%d exploits", nExploitCve) + return fmt.Sprintf("%d poc", nExploitCve) } // FormatMetasploitCveSummary returns a summary of exploit cve @@ -399,7 +408,7 @@ func (r ScanResult) FormatMetasploitCveSummary() string { nMetasploitCve++ } } - return fmt.Sprintf("%d modules", nMetasploitCve) + return fmt.Sprintf("%d exploits", nMetasploitCve) } // FormatAlertSummary returns a summary of CERT alerts @@ -423,6 +432,7 @@ func (r ScanResult) isDisplayUpdatableNum() bool { } var mode config.ScanMode + //TODO pass by args s, _ := config.Conf.Servers[r.ServerName] mode = s.Mode @@ -455,10 +465,8 @@ func (r ScanResult) IsContainer() bool { // IsDeepScanMode checks if the scan mode is deep scan mode. func (r ScanResult) IsDeepScanMode() bool { for _, s := range r.Config.Scan.Servers { - for _, m := range s.ScanMode { - if m == "deep" { - return true - } + if ok := s.Mode.IsDeep(); ok { + return true } } return false diff --git a/models/scanresults_test.go b/models/scanresults_test.go index 22124ff290..8f825d157f 100644 --- a/models/scanresults_test.go +++ b/models/scanresults_test.go @@ -392,8 +392,7 @@ func TestFilterUnfixed(t *testing.T) { }, } for i, tt := range tests { - config.Conf.IgnoreUnfixed = true - actual := tt.in.FilterUnfixed() + actual := tt.in.FilterUnfixed(true) if !reflect.DeepEqual(tt.out.ScannedCves, actual.ScannedCves) { o := pp.Sprintf("%v", tt.out.ScannedCves) a := pp.Sprintf("%v", actual.ScannedCves) diff --git a/models/vulninfos.go b/models/vulninfos.go index e319996f8c..ab9f57dfd0 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -306,7 +306,7 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) { }) } - if v, ok := v.CveContents[WPVulnDB]; ok { + if v, ok := v.CveContents[WpScan]; ok { values = append(values, CveContentStr{ Type: "WPVDB", Value: v.Title, @@ -810,8 +810,8 @@ const ( // GitHubMatchStr is a String representation of GitHubMatch GitHubMatchStr = "GitHubMatch" - // WPVulnDBMatchStr is a String representation of WordPress VulnDB scanning - WPVulnDBMatchStr = "WPVulnDBMatch" + // WpScanMatchStr is a String representation of WordPress VulnDB scanning + WpScanMatchStr = "WpScanMatch" // FailedToGetChangelog is a String representation of FailedToGetChangelog FailedToGetChangelog = "FailedToGetChangelog" @@ -851,6 +851,6 @@ var ( // GitHubMatch is a ranking how confident the CVE-ID was detected correctly GitHubMatch = Confidence{97, GitHubMatchStr, 2} - // WPVulnDBMatch is a ranking how confident the CVE-ID was detected correctly - WPVulnDBMatch = Confidence{100, WPVulnDBMatchStr, 0} + // WpScanMatch is a ranking how confident the CVE-ID was detected correctly + WpScanMatch = Confidence{100, WpScanMatchStr, 0} ) diff --git a/report/report.go b/report/report.go index 3ccb21cae8..3f25854091 100644 --- a/report/report.go +++ b/report/report.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/future-architect/vuls/config" c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/contrib/owasp-dependency-check/parser" "github.com/future-architect/vuls/cwe" @@ -90,8 +91,7 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode return nil, xerrors.Errorf("Failed to detect GitHub Cves: %w", err) } - wpConf := c.Conf.Servers[r.ServerName].WordPress - if err := DetectWordPressCves(&r, &wpConf); err != nil { + if err := DetectWordPressCves(&r, &config.Conf.WpScan); err != nil { return nil, xerrors.Errorf("Failed to detect WordPress Cves: %w", err) } @@ -142,9 +142,9 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode for i, r := range rs { r = r.FilterByCvssOver(c.Conf.CvssScoreOver) r = r.FilterIgnoreCves() - r = r.FilterUnfixed() + r = r.FilterUnfixed(c.Conf.IgnoreUnfixed) r = r.FilterIgnorePkgs() - r = r.FilterInactiveWordPressLibs() + r = r.FilterInactiveWordPressLibs(c.Conf.WpScan.DetectInactive) if c.Conf.IgnoreUnscoredCves { r.ScannedCves = r.ScannedCves.FindScoredVulns() } @@ -215,7 +215,7 @@ func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]c.GitHubConf) return xerrors.Errorf("Failed to parse GitHub owner/repo: %s", ownerRepo) } owner, repo := ss[0], ss[1] - n, err := github.FillGitHubSecurityAlerts(r, owner, repo, setting.Token) + n, err := github.DetectGitHubSecurityAlerts(r, owner, repo, setting.Token) if err != nil { return xerrors.Errorf("Failed to access GitHub Security Alerts: %w", err) } @@ -226,15 +226,16 @@ func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]c.GitHubConf) } // DetectWordPressCves detects CVEs of WordPress -func DetectWordPressCves(r *models.ScanResult, wpCnf *c.WordPressConf) error { - if wpCnf.WPVulnDBToken == "" { +func DetectWordPressCves(r *models.ScanResult, wpCnf *c.WpScanConf) error { + if len(r.Packages) == 0 { return nil } - n, err := wordpress.FillWordPress(r, wpCnf.WPVulnDBToken) + util.Log.Infof("Detect WordPress CVE. pkgs: %d ", len(r.WordPressPackages)) + n, err := wordpress.DetectWordPressCves(r, wpCnf) if err != nil { - return xerrors.Errorf("Failed to detect CVE with wpscan.com: %w", err) + return xerrors.Errorf("Failed to detect WordPress CVE: %w", err) } - util.Log.Infof("%s: %d CVEs detected with wpscan.com", r.FormatServerName(), n) + util.Log.Infof("%s: found %d WordPress CVEs", r.FormatServerName(), n) return nil } diff --git a/report/syslog.go b/report/syslog.go index 738f8fc090..4e02bef54f 100644 --- a/report/syslog.go +++ b/report/syslog.go @@ -2,9 +2,9 @@ package report import ( "fmt" + "log/syslog" "strings" - syslog "github.com/RackSec/srslog" "golang.org/x/xerrors" "github.com/future-architect/vuls/config" diff --git a/report/util.go b/report/util.go index fbc8f63dff..ec7abdaee5 100644 --- a/report/util.go +++ b/report/util.go @@ -42,6 +42,12 @@ func formatScanSummary(rs ...models.ScanResult) string { fmt.Sprintf("%s%s", r.Family, r.Release), r.FormatUpdatablePacksSummary(), } + if 0 < len(r.WordPressPackages) { + cols = append(cols, fmt.Sprintf("%d WordPress pkgs", len(r.WordPressPackages))) + } + if 0 < len(r.LibraryScanners) { + cols = append(cols, fmt.Sprintf("%d libs", r.LibraryScanners.Total())) + } } else { cols = []interface{}{ r.FormatServerName(), diff --git a/saas/uuid.go b/saas/uuid.go index 4935e91a84..c44051f119 100644 --- a/saas/uuid.go +++ b/saas/uuid.go @@ -101,7 +101,6 @@ func EnsureUUIDs(configPath string, results models.ScanResults) (err error) { return err } server.UUIDs[name] = serverUUID - server = cleanForTOMLEncoding(server, c.Conf.Default) c.Conf.Servers[r.ServerName] = server if r.IsContainer() { @@ -116,84 +115,16 @@ func EnsureUUIDs(configPath string, results models.ScanResults) (err error) { server = cleanForTOMLEncoding(server, c.Conf.Default) c.Conf.Servers[name] = server } - - email := &c.Conf.EMail - if email.SMTPAddr == "" { - email = nil - } - - slack := &c.Conf.Slack - if slack.HookURL == "" { - slack = nil - } - - cveDict := &c.Conf.CveDict - ovalDict := &c.Conf.OvalDict - gost := &c.Conf.Gost - exploit := &c.Conf.Exploit - metasploit := &c.Conf.Metasploit - http := &c.Conf.HTTP - if http.URL == "" { - http = nil - } - - syslog := &c.Conf.Syslog - if syslog.Host == "" { - syslog = nil - } - - aws := &c.Conf.AWS - if aws.S3Bucket == "" { - aws = nil - } - - azure := &c.Conf.Azure - if azure.AccountName == "" { - azure = nil - } - - chatWork := &c.Conf.ChatWork - if chatWork.APIToken == "" { - chatWork = nil - } - - saas := &c.Conf.Saas - if saas.GroupID == 0 { - saas = nil + if c.Conf.Default.WordPress != nil && c.Conf.Default.WordPress.IsZero() { + c.Conf.Default.WordPress = nil } c := struct { - CveDict *c.GoCveDictConf `toml:"cveDict"` - OvalDict *c.GovalDictConf `toml:"ovalDict"` - Gost *c.GostConf `toml:"gost"` - Exploit *c.ExploitConf `toml:"exploit"` - Metasploit *c.MetasploitConf `toml:"metasploit"` - Slack *c.SlackConf `toml:"slack"` - Email *c.SMTPConf `toml:"email"` - HTTP *c.HTTPConf `toml:"http"` - Syslog *c.SyslogConf `toml:"syslog"` - AWS *c.AWS `toml:"aws"` - Azure *c.Azure `toml:"azure"` - ChatWork *c.ChatWorkConf `toml:"chatWork"` - Saas *c.SaasConf `toml:"saas"` - + Saas *c.SaasConf `toml:"saas"` Default c.ServerInfo `toml:"default"` Servers map[string]c.ServerInfo `toml:"servers"` }{ - CveDict: cveDict, - OvalDict: ovalDict, - Gost: gost, - Exploit: exploit, - Metasploit: metasploit, - Slack: slack, - Email: email, - HTTP: http, - Syslog: syslog, - AWS: aws, - Azure: azure, - ChatWork: chatWork, - Saas: saas, - + Saas: &c.Conf.Saas, Default: c.Conf.Default, Servers: c.Conf.Servers, } @@ -276,5 +207,11 @@ func cleanForTOMLEncoding(server c.ServerInfo, def c.ServerInfo) c.ServerInfo { } } + if server.WordPress != nil { + if server.WordPress.IsZero() || reflect.DeepEqual(server.WordPress, def.WordPress) { + server.WordPress = nil + } + } + return server } diff --git a/scan/alpine.go b/scan/alpine.go index 0d40bcecb2..218d021c6b 100644 --- a/scan/alpine.go +++ b/scan/alpine.go @@ -73,7 +73,6 @@ func (o *alpine) apkUpdate() error { } func (o *alpine) preCure() error { - o.log.Infof("Scanning in %s", o.getServerInfo().Mode) if err := o.detectIPAddr(); err != nil { o.log.Warnf("Failed to detect IP addresses: %s", err) o.warns = append(o.warns, err) @@ -92,6 +91,7 @@ func (o *alpine) detectIPAddr() (err error) { } func (o *alpine) scanPackages() error { + o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode) if err := o.apkUpdate(); err != nil { return err } diff --git a/scan/base.go b/scan/base.go index e938f3f62e..e6660f035d 100644 --- a/scan/base.go +++ b/scan/base.go @@ -526,7 +526,6 @@ func (l *base) parseSystemctlStatus(stdout string) string { } func (l *base) scanLibraries() (err error) { - // image already detected libraries if len(l.LibraryScanners) != 0 { return nil } @@ -536,6 +535,8 @@ func (l *base) scanLibraries() (err error) { return nil } + l.log.Info("Scanning Lockfile...") + libFilemap := map[string][]byte{} detectFiles := l.ServerInfo.Lockfiles @@ -621,34 +622,14 @@ func (d *DummyFileInfo) IsDir() bool { return false } func (d *DummyFileInfo) Sys() interface{} { return nil } func (l *base) scanWordPress() (err error) { - wpOpts := []string{l.ServerInfo.WordPress.OSUser, - l.ServerInfo.WordPress.DocRoot, - l.ServerInfo.WordPress.CmdPath, - l.ServerInfo.WordPress.WPVulnDBToken, - } - var isScanWp, hasEmptyOpt bool - for _, opt := range wpOpts { - if opt != "" { - isScanWp = true - break - } else { - hasEmptyOpt = true - } - } - if !isScanWp { + if l.ServerInfo.WordPress.IsZero() { return nil } - - if hasEmptyOpt { - return xerrors.Errorf("%s has empty WordPress opts: %s", - l.getServerInfo().GetServerName(), wpOpts) - } - + l.log.Info("Scanning WordPress...") cmd := fmt.Sprintf("sudo -u %s -i -- %s cli version --allow-root", l.ServerInfo.WordPress.OSUser, l.ServerInfo.WordPress.CmdPath) if r := exec(l.ServerInfo, cmd, noSudo); !r.isSuccess() { - l.ServerInfo.WordPress.WPVulnDBToken = "secret" return xerrors.Errorf("Failed to exec `%s`. Check the OS user, command path of wp-cli, DocRoot and permission: %#v", cmd, l.ServerInfo.WordPress) } @@ -743,6 +724,7 @@ func (l *base) detectWpPlugins() ([]models.WpPackage, error) { } func (l *base) scanPorts() (err error) { + l.log.Info("Scanning listen port...") dest := l.detectScanDest() open, err := l.execPortsScan(dest) if err != nil { @@ -838,7 +820,7 @@ func (l *base) findPortTestSuccessOn(listenIPPorts []string, searchListenPort mo for _, ipPort := range listenIPPorts { ipPort, err := models.NewPortStat(ipPort) if err != nil { - util.Log.Warnf("Failed to find: %+v", err) + l.log.Warnf("Failed to find: %+v", err) continue } if searchListenPort.BindAddress == "*" { diff --git a/scan/debian.go b/scan/debian.go index 61b8e59977..aeaede5476 100644 --- a/scan/debian.go +++ b/scan/debian.go @@ -241,7 +241,6 @@ func (o *debian) checkDeps() error { } func (o *debian) preCure() error { - o.log.Infof("Scanning in %s", o.getServerInfo().Mode) if err := o.detectIPAddr(); err != nil { o.log.Warnf("Failed to detect IP addresses: %s", err) o.warns = append(o.warns, err) @@ -277,6 +276,7 @@ func (o *debian) detectIPAddr() (err error) { } func (o *debian) scanPackages() error { + o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode) // collect the running kernel information release, version, err := o.runningKernel() if err != nil { diff --git a/scan/freebsd.go b/scan/freebsd.go index fee916c451..8abfc51ae5 100644 --- a/scan/freebsd.go +++ b/scan/freebsd.go @@ -69,7 +69,6 @@ func (o *bsd) checkDeps() error { } func (o *bsd) preCure() error { - o.log.Infof("Scanning in %s", o.getServerInfo().Mode) if err := o.detectIPAddr(); err != nil { o.log.Debugf("Failed to detect IP addresses: %s", err) } @@ -115,6 +114,7 @@ func (l *base) parseIfconfig(stdout string) (ipv4Addrs []string, ipv6Addrs []str } func (o *bsd) scanPackages() error { + o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode) // collect the running kernel information release, version, err := o.runningKernel() if err != nil { diff --git a/scan/redhatbase.go b/scan/redhatbase.go index eec046eb43..17d8c9ac02 100644 --- a/scan/redhatbase.go +++ b/scan/redhatbase.go @@ -193,12 +193,12 @@ func (o *redhatBase) postScan() error { } func (o *redhatBase) detectIPAddr() (err error) { - o.log.Infof("Scanning in %s", o.getServerInfo().Mode) o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs, err = o.ip() return err } func (o *redhatBase) scanPackages() error { + o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode) installed, err := o.scanInstalledPackages() if err != nil { return xerrors.Errorf("Failed to scan installed packages: %w", err) diff --git a/scan/serverapi.go b/scan/serverapi.go index 2605ec61f4..647decea78 100644 --- a/scan/serverapi.go +++ b/scan/serverapi.go @@ -173,25 +173,20 @@ func PrintSSHableServerNames() bool { // InitServers detect the kind of OS distribution of target servers func InitServers(timeoutSec int) error { - // use global servers, errServers when scan containers - servers, errServers = detectServerOSes(timeoutSec) - if len(servers) == 0 { - return xerrors.New("No scannable base servers") + hosts, errHosts := detectServerOSes(timeoutSec) + if len(hosts) == 0 { + return xerrors.New("No scannable host OS") } + containers, errContainers := detectContainerOSes(hosts, timeoutSec) - // scan additional servers - var actives, inactives []osTypeInterface - oks, errs := detectContainerOSes(timeoutSec) - actives = append(actives, oks...) - inactives = append(inactives, errs...) - - if config.Conf.ContainersOnly { - servers = actives - errServers = inactives - } else { - servers = append(servers, actives...) - errServers = append(errServers, inactives...) + // set to pkg global variable + for _, host := range hosts { + if !host.getServerInfo().ContainersOnly { + servers = append(servers, host) + } } + servers = append(servers, containers...) + errServers = append(errHosts, errContainers...) if len(servers) == 0 { return xerrors.New("No scannable servers") @@ -260,11 +255,11 @@ func detectServerOSes(timeoutSec int) (servers, errServers []osTypeInterface) { return } -func detectContainerOSes(timeoutSec int) (actives, inactives []osTypeInterface) { +func detectContainerOSes(hosts []osTypeInterface, timeoutSec int) (actives, inactives []osTypeInterface) { util.Log.Info("Detecting OS of containers... ") - osTypesChan := make(chan []osTypeInterface, len(servers)) + osTypesChan := make(chan []osTypeInterface, len(hosts)) defer close(osTypesChan) - for _, s := range servers { + for _, s := range hosts { go func(s osTypeInterface) { defer func() { if p := recover(); p != nil { @@ -277,7 +272,7 @@ func detectContainerOSes(timeoutSec int) (actives, inactives []osTypeInterface) } timeout := time.After(time.Duration(timeoutSec) * time.Second) - for i := 0; i < len(servers); i++ { + for i := 0; i < len(hosts); i++ { select { case res := <-osTypesChan: for _, osi := range res { @@ -495,7 +490,6 @@ func Scan(timeoutSec int) error { } }() - util.Log.Info("Scanning OS packages...") scannedAt := time.Now() dir, err := EnsureResultDir(scannedAt) if err != nil { @@ -631,7 +625,7 @@ func setupChangelogCache() error { // GetScanResults returns ScanResults from func GetScanResults(scannedAt time.Time, timeoutSec int) (results models.ScanResults, err error) { parallelExec(func(o osTypeInterface) (err error) { - if !(config.Conf.LibsOnly || config.Conf.WordPressOnly) { + if o.getServerInfo().Module.IsScanOSPkg() { if err = o.preCure(); err != nil { return err } @@ -642,14 +636,20 @@ func GetScanResults(scannedAt time.Time, timeoutSec int) (results models.ScanRes return err } } - if err = o.scanWordPress(); err != nil { - return xerrors.Errorf("Failed to scan WordPress: %w", err) + if o.getServerInfo().Module.IsScanPort() { + if err = o.scanPorts(); err != nil { + return xerrors.Errorf("Failed to scan Ports: %w", err) + } } - if err = o.scanLibraries(); err != nil { - return xerrors.Errorf("Failed to scan Library: %w", err) + if o.getServerInfo().Module.IsScanWordPress() { + if err = o.scanWordPress(); err != nil { + return xerrors.Errorf("Failed to scan WordPress: %w", err) + } } - if err = o.scanPorts(); err != nil { - return xerrors.Errorf("Failed to scan Ports: %w", err) + if o.getServerInfo().Module.IsScanLockFile() { + if err = o.scanLibraries(); err != nil { + return xerrors.Errorf("Failed to scan Library: %w", err) + } } return nil }, timeoutSec) @@ -662,6 +662,7 @@ func GetScanResults(scannedAt time.Time, timeoutSec int) (results models.ScanRes for _, s := range append(servers, errServers...) { r := s.convertToModel() + checkEOL(&r) r.ScannedAt = scannedAt r.ScannedVersion = config.Version r.ScannedRevision = config.Revision @@ -669,7 +670,6 @@ func GetScanResults(scannedAt time.Time, timeoutSec int) (results models.ScanRes r.ScannedIPv4Addrs = ipv4s r.ScannedIPv6Addrs = ipv6s r.Config.Scan = config.Conf - checkEOL(&r) results = append(results, r) if 0 < len(r.Warnings) { diff --git a/scan/suse.go b/scan/suse.go index 6a56afd637..ed8b39fb53 100644 --- a/scan/suse.go +++ b/scan/suse.go @@ -115,6 +115,7 @@ func (o *suse) checkIfSudoNoPasswd() error { } func (o *suse) scanPackages() error { + o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode) installed, err := o.scanInstalledPackages() if err != nil { o.log.Errorf("Failed to scan installed packages: %s", err) diff --git a/subcmds/configtest.go b/subcmds/configtest.go index 798f17c41e..f7db155866 100644 --- a/subcmds/configtest.go +++ b/subcmds/configtest.go @@ -67,9 +67,6 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) { f.BoolVar(&c.Conf.SSHNative, "ssh-native-insecure", false, "Use Native Go implementation of SSH. Default: Use the external command") - f.BoolVar(&c.Conf.ContainersOnly, "containers-only", false, - "Containers only. Default: Test both of hosts and containers") - f.BoolVar(&c.Conf.Vvv, "vvv", false, "ssh -vvv") } diff --git a/subcmds/discover.go b/subcmds/discover.go index 4c15dceb5e..419733a763 100644 --- a/subcmds/discover.go +++ b/subcmds/discover.go @@ -159,17 +159,23 @@ sqlite3Path = "/path/to/go-msfdb.sqlite3" #chatID = "xxxxxxxxxxx" #token = "xxxxxxxxxxxxxxxxxx" +#[wpscan] +#token = "xxxxxxxxxxx" +#detectInactive = false + # https://vuls.io/docs/en/usage-settings.html#default-section [default] #port = "22" #user = "username" #keyPath = "/home/username/.ssh/id_rsa" #scanMode = ["fast", "fast-root", "deep", "offline"] +#scanModules = ["ospkg", "wordpress", "lockfile", "port"] #cpeNames = [ # "cpe:/a:rubyonrails:ruby_on_rails:4.2.1", #] #owaspDCXMLPath = "/tmp/dependency-check-report.xml" #ignoreCves = ["CVE-2014-6271"] +#containersOnly = false #containerType = "docker" #or "lxd" or "lxc" default: docker #containersIncluded = ["${running}"] #containersExcluded = ["container_name_a"] @@ -185,11 +191,13 @@ host = "{{$ip}}" #sshConfigPath = "/home/username/.ssh/config" #keyPath = "/home/username/.ssh/id_rsa" #scanMode = ["fast", "fast-root", "deep", "offline"] +#scanModules = ["ospkg", "wordpress", "lockfile", "port"] #type = "pseudo" #memo = "DB Server" #cpeNames = [ "cpe:/a:rubyonrails:ruby_on_rails:4.2.1" ] #owaspDCXMLPath = "/path/to/dependency-check-report.xml" #ignoreCves = ["CVE-2014-0160"] +#containersOnly = false #containerType = "docker" #or "lxd" or "lxc" default: docker #containersIncluded = ["${running}"] #containersExcluded = ["container_name_a"] @@ -206,8 +214,6 @@ host = "{{$ip}}" #cmdPath = "/usr/local/bin/wp" #osUser = "wordpress" #docRoot = "/path/to/DocumentRoot/" -#wpVulnDBToken = "xxxxTokenxxxx" -#ignoreInactive = true #[servers.{{index $names $i}}.optional] #key = "value1" diff --git a/subcmds/report.go b/subcmds/report.go index 687aba6d2d..3b1c3721fa 100644 --- a/subcmds/report.go +++ b/subcmds/report.go @@ -44,7 +44,6 @@ func (*ReportCmd) Usage() string { [-refresh-cve] [-cvss-over=7] [-diff] - [-wp-ignore-inactive] [-ignore-unscored-cves] [-ignore-unfixed] [-ignore-github-dismissed] @@ -63,7 +62,6 @@ func (*ReportCmd) Usage() string { [-format-list] [-format-full-text] [-gzip] - [-uuid] [-http-proxy=http://192.168.0.1:8080] [-debug] [-debug-sql] @@ -104,9 +102,6 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) { f.BoolVar(&c.Conf.Diff, "diff", false, "Difference between previous result and current result ") - f.BoolVar(&c.Conf.WpIgnoreInactive, "wp-ignore-inactive", false, - "ignore inactive on wordpress's plugin and theme") - f.BoolVar(&c.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false, "Don't report the unscored CVEs") diff --git a/subcmds/scan.go b/subcmds/scan.go index acd1b10af2..40bd15a0b0 100644 --- a/subcmds/scan.go +++ b/subcmds/scan.go @@ -39,9 +39,6 @@ func (*ScanCmd) Usage() string { [-log-dir=/path/to/log] [-cachedb-path=/path/to/cache.db] [-ssh-native-insecure] - [-containers-only] - [-libs-only] - [-wordpress-only] [-http-proxy=http://192.168.0.1:8080] [-ask-key-password] [-timeout=300] @@ -79,15 +76,6 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) { f.BoolVar(&c.Conf.SSHNative, "ssh-native-insecure", false, "Use Native Go implementation of SSH. Default: Use the external command") - f.BoolVar(&c.Conf.ContainersOnly, "containers-only", false, - "Scan running containers only. Default: Scan both of hosts and running containers") - - f.BoolVar(&c.Conf.LibsOnly, "libs-only", false, - "Scan libraries (lock files) specified in config.toml only.") - - f.BoolVar(&c.Conf.WordPressOnly, "wordpress-only", false, - "Scan WordPress only.") - f.StringVar(&c.Conf.HTTPProxy, "http-proxy", "", "http://proxy-url:port (default: empty)") @@ -200,7 +188,6 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) util.Log.Info("Detecting IPS identifiers... ") scan.DetectIPSs(p.timeoutSec) - util.Log.Info("Scanning... ") if err := scan.Scan(p.scanTimeoutSec); err != nil { util.Log.Errorf("Failed to scan. err: %+v", err) return subcommands.ExitFailure diff --git a/util/util.go b/util/util.go index fe149997a1..26afb3fc02 100644 --- a/util/util.go +++ b/util/util.go @@ -164,6 +164,7 @@ func Distinct(ss []string) (distincted []string) { return } +// Major return the major version of the given semantic version func Major(version string) string { if version == "" { return "" diff --git a/wordpress/wordpress.go b/wordpress/wordpress.go index c95b60736d..479e552655 100644 --- a/wordpress/wordpress.go +++ b/wordpress/wordpress.go @@ -16,7 +16,7 @@ import ( "golang.org/x/xerrors" ) -//WpCveInfos is for wpvulndb's json +//WpCveInfos is for wpscan json type WpCveInfos struct { ReleaseDate string `json:"release_date"` ChangelogURL string `json:"changelog_url"` @@ -28,7 +28,7 @@ type WpCveInfos struct { Error string `json:"error"` } -//WpCveInfo is for wpvulndb's json +//WpCveInfo is for wpscan json type WpCveInfo struct { ID string `json:"id"` Title string `json:"title"` @@ -40,35 +40,39 @@ type WpCveInfo struct { FixedIn string `json:"fixed_in"` } -//References is for wpvulndb's json +//References is for wpscan json type References struct { URL []string `json:"url"` Cve []string `json:"cve"` Secunia []string `json:"secunia"` } -// FillWordPress access to wpvulndb and fetch scurity alerts and then set to the given ScanResult. +// DetectWordPressCves access to wpscan and fetch scurity alerts and then set to the given ScanResult. // https://wpscan.com/ -func FillWordPress(r *models.ScanResult, token string) (int, error) { +// TODO move to report +func DetectWordPressCves(r *models.ScanResult, cnf *c.WpScanConf) (int, error) { + if len(r.WordPressPackages) == 0 { + return 0, nil + } // Core ver := strings.Replace(r.WordPressPackages.CoreVersion(), ".", "", -1) if ver == "" { return 0, xerrors.New("Failed to get WordPress core version") } url := fmt.Sprintf("https://wpscan.com/api/v3/wordpresses/%s", ver) - wpVinfos, err := wpscan(url, ver, token) + wpVinfos, err := wpscan(url, ver, cnf.Token) if err != nil { return 0, err } // Themes themes := r.WordPressPackages.Themes() - if c.Conf.WpIgnoreInactive { + if !cnf.DetectInactive { themes = removeInactives(themes) } for _, p := range themes { url := fmt.Sprintf("https://wpscan.com/api/v3/themes/%s", p.Name) - candidates, err := wpscan(url, p.Name, token) + candidates, err := wpscan(url, p.Name, cnf.Token) if err != nil { return 0, err } @@ -78,12 +82,12 @@ func FillWordPress(r *models.ScanResult, token string) (int, error) { // Plugins plugins := r.WordPressPackages.Plugins() - if c.Conf.WpIgnoreInactive { + if !cnf.DetectInactive { plugins = removeInactives(plugins) } for _, p := range plugins { url := fmt.Sprintf("https://wpscan.com/api/v3/plugins/%s", p.Name) - candidates, err := wpscan(url, p.Name, token) + candidates, err := wpscan(url, p.Name, cnf.Token) if err != nil { return 0, err } @@ -93,7 +97,7 @@ func FillWordPress(r *models.ScanResult, token string) (int, error) { for _, wpVinfo := range wpVinfos { if vinfo, ok := r.ScannedCves[wpVinfo.CveID]; ok { - vinfo.CveContents[models.WPVulnDB] = wpVinfo.CveContents[models.WPVulnDB] + vinfo.CveContents[models.WpScan] = wpVinfo.CveContents[models.WpScan] vinfo.VulnType = wpVinfo.VulnType vinfo.Confidences = append(vinfo.Confidences, wpVinfo.Confidences...) vinfo.WpPackageFixStats = append(vinfo.WpPackageFixStats, wpVinfo.WpPackageFixStats...) @@ -192,7 +196,7 @@ func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnI CveID: cveID, CveContents: models.NewCveContents( models.CveContent{ - Type: models.WPVulnDB, + Type: models.WpScan, CveID: cveID, Title: vulnerability.Title, References: refs, @@ -200,7 +204,7 @@ func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnI ), VulnType: vulnerability.VulnType, Confidences: []models.Confidence{ - models.WPVulnDBMatch, + models.WpScanMatch, }, WpPackageFixStats: []models.WpPackageFixStatus{{ Name: pkgName, @@ -233,7 +237,7 @@ loop: if resp.StatusCode == 200 { return string(body), nil } else if resp.StatusCode == 404 { - // This package is not in WPVulnDB + // This package is not in wpscan return "", nil } else if resp.StatusCode == 429 && retry <= 3 { // 429 Too Many Requests