diff --git a/Gopkg.lock b/Gopkg.lock index a01cb516..a022bfe2 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -506,6 +506,18 @@ pruneopts = "UT" revision = "9ac6cf4d929b2fa8fd2d2e6dec5bb0feb4f4911d" +[[projects]] + branch = "master" + digest = "1:c72d41e2be29143a802361f175f9eafe81ecd35119b80b7673bb3e997b086687" + name = "github.com/mozqnet/go-exploitdb" + packages = [ + "db", + "models", + "util", + ] + pruneopts = "UT" + revision = "b359807ea9b24f7ce80d1bfa02ffca5ed428ffb5" + [[projects]] digest = "1:95d38d218bf2290987c6b0e885a9f0f2d3d3239235acaddca01c3fe36e5e5566" name = "github.com/nlopes/slack" @@ -820,6 +832,8 @@ "github.com/kotakanbe/goval-dictionary/models", "github.com/kotakanbe/logrus-prefixed-formatter", "github.com/mitchellh/go-homedir", + "github.com/mozqnet/go-exploitdb/db", + "github.com/mozqnet/go-exploitdb/models", "github.com/nlopes/slack", "github.com/olekukonko/tablewriter", "github.com/parnurzeal/gorequest", diff --git a/README.md b/README.md index 59b300f5..a99eb9f7 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Vuls uses Multiple vulnerability databases - [Debian Security Bug Tracker](https://security-tracker.debian.org/tracker/) - Commands(yum, zypper, pkg-audit) - RHSA/ALAS/ELSA/FreeBSD-SA +- [Exploit Database](https://www.exploit-db.com/) - Changelog ## Fast scan and Deep scan diff --git a/commands/discover.go b/commands/discover.go index f87210d4..1af12c97 100644 --- a/commands/discover.go +++ b/commands/discover.go @@ -91,24 +91,27 @@ func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface func printConfigToml(ips []string) (err error) { const tomlTemplate = ` -# TODO Doc Link +# https://vuls.io/docs/en/usage-settings.html [cveDict] type = "sqlite3" sqlite3Path = "/path/to/cve.sqlite3" #url = "" -# TODO Doc Link [ovalDict] type = "sqlite3" sqlite3Path = "/path/to/oval.sqlite3" #url = "" -# TODO Doc Link [gost] type = "sqlite3" sqlite3Path = "/path/to/gost.sqlite3" #url = "" +[exploit] +type = "sqlite3" +sqlite3Path = "/path/to/go-exploitdb.sqlite3" +#url = "" + # https://vuls.io/docs/en/usage-settings.html#slack-section #[slack] #hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz" diff --git a/commands/report.go b/commands/report.go index 3e752300..f631e0d3 100644 --- a/commands/report.go +++ b/commands/report.go @@ -24,6 +24,7 @@ import ( "path/filepath" c "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/exploit" "github.com/future-architect/vuls/gost" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/oval" @@ -36,11 +37,12 @@ import ( // ReportCmd is subcommand for reporting type ReportCmd struct { - configPath string - cveDict c.GoCveDictConf - ovalDict c.GovalDictConf - gostConf c.GostConf - httpConf c.HTTPConf + configPath string + cveDict c.GoCveDictConf + ovalDict c.GovalDictConf + gostConf c.GostConf + exploitConf c.ExploitConf + httpConf c.HTTPConf } // Name return subcommand name @@ -93,6 +95,9 @@ func (*ReportCmd) Usage() string { [-gostdb-type=sqlite3|mysql|redis] [-gostdb-sqlite3-path=/path/to/gost.sqlite3] [-gostdb-url=http://127.0.0.1:1325 or DB connection string] + [-exploitdb-type=sqlite3|mysql|redis] + [-exploitdb-sqlite3-path=/path/to/exploitdb.sqlite3] + [-exploitdb-url=http://127.0.0.1:1325 or DB connection string] [-http="http://vuls-report-server"] [RFC3339 datetime format under results dir] @@ -183,6 +188,12 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) { f.StringVar(&p.gostConf.URL, "gostdb-url", "", "http://gost.com:1325 or DB connection string") + f.StringVar(&p.exploitConf.Type, "exploitdb-type", "", + "DB type of exploit (sqlite3, mysql, postgres or redis)") + f.StringVar(&p.exploitConf.SQLite3Path, "exploitdb-sqlite3-path", "", "/path/to/sqlite3") + f.StringVar(&p.exploitConf.URL, "exploitdb-url", "", + "http://exploit.com:1326 or DB connection string") + f.StringVar(&p.httpConf.URL, "http", "", "-to-http http://vuls-report") } @@ -200,6 +211,7 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} c.Conf.CveDict.Overwrite(p.cveDict) c.Conf.OvalDict.Overwrite(p.ovalDict) c.Conf.Gost.Overwrite(p.gostConf) + c.Conf.Exploit.Overwrite(p.exploitConf) c.Conf.HTTP.Overwrite(p.httpConf) var dir string @@ -378,10 +390,25 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} util.Log.Infof("gost: %s", c.Conf.Gost.SQLite3Path) } } + + if c.Conf.Exploit.URL != "" { + util.Log.Infof("exploit: %s", c.Conf.Exploit.URL) + err := exploit.CheckHTTPHealth() + if err != nil { + util.Log.Errorf("exploit HTTP server is not running. err: %s", err) + util.Log.Errorf("Run exploit as server mode before reporting or run with -exploitdb-sqlite3-path option instead of -exploitdb-url") + return subcommands.ExitFailure + } + } else { + if c.Conf.Exploit.Type == "sqlite3" { + util.Log.Infof("exploit: %s", c.Conf.Exploit.SQLite3Path) + } + } dbclient, locked, err := report.NewDBClient(report.DBClientConf{ CveDictCnf: c.Conf.CveDict, OvalDictCnf: c.Conf.OvalDict, GostCnf: c.Conf.Gost, + ExploitCnf: c.Conf.Exploit, DebugSQL: c.Conf.DebugSQL, }) if locked { diff --git a/commands/tui.go b/commands/tui.go index 81a3fd8a..37fcd408 100644 --- a/commands/tui.go +++ b/commands/tui.go @@ -24,7 +24,10 @@ import ( "path/filepath" c "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/exploit" + "github.com/future-architect/vuls/gost" "github.com/future-architect/vuls/models" + "github.com/future-architect/vuls/oval" "github.com/future-architect/vuls/report" "github.com/future-architect/vuls/util" "github.com/google/subcommands" @@ -33,10 +36,11 @@ import ( // TuiCmd is Subcommand of host discovery mode type TuiCmd struct { - configPath string - cvelDict c.GoCveDictConf - ovalDict c.GovalDictConf - gostConf c.GostConf + configPath string + cvelDict c.GoCveDictConf + ovalDict c.GovalDictConf + gostConf c.GostConf + exploitConf c.ExploitConf } // Name return subcommand name @@ -124,6 +128,13 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) { f.StringVar(&p.gostConf.SQLite3Path, "gostdb-path", "", "/path/to/sqlite3") f.StringVar(&p.gostConf.URL, "gostdb-url", "", "http://gost.com:1325 or DB connection string") + + f.StringVar(&p.exploitConf.Type, "exploitdb-type", "", + "DB type of exploit (sqlite3, mysql, postgres or redis)") + f.StringVar(&p.exploitConf.SQLite3Path, "exploitdb-sqlite3-path", "", "/path/to/sqlite3") + f.StringVar(&p.exploitConf.URL, "exploitdb-url", "", + "http://exploit.com:1326 or DB connection string") + } // Execute execute @@ -142,11 +153,7 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s c.Conf.CveDict.Overwrite(p.cvelDict) c.Conf.OvalDict.Overwrite(p.ovalDict) c.Conf.Gost.Overwrite(p.gostConf) - - util.Log.Info("Validating config...") - if !c.Conf.ValidateOnTui() { - return subcommands.ExitUsageError - } + c.Conf.Exploit.Overwrite(p.exploitConf) var dir string var err error @@ -159,6 +166,12 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s util.Log.Errorf("Failed to read from JSON: %s", err) return subcommands.ExitFailure } + + util.Log.Info("Validating config...") + if !c.Conf.ValidateOnTui() { + return subcommands.ExitUsageError + } + var res models.ScanResults if res, err = report.LoadScanResults(dir); err != nil { util.Log.Error(err) @@ -166,10 +179,65 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s } util.Log.Infof("Loaded: %s", dir) + if err := report.CveClient.CheckHealth(); err != nil { + util.Log.Errorf("CVE HTTP server is not running. err: %s", err) + util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with -cvedb-sqlite3-path option instead of -cvedb-url") + return subcommands.ExitFailure + } + if c.Conf.CveDict.URL != "" { + util.Log.Infof("cve-dictionary: %s", c.Conf.CveDict.URL) + } else { + if c.Conf.CveDict.Type == "sqlite3" { + util.Log.Infof("cve-dictionary: %s", c.Conf.CveDict.SQLite3Path) + } + } + + if c.Conf.OvalDict.URL != "" { + util.Log.Infof("oval-dictionary: %s", c.Conf.OvalDict.URL) + err := oval.Base{}.CheckHTTPHealth() + if err != nil { + util.Log.Errorf("OVAL HTTP server is not running. err: %s", err) + util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with -ovaldb-sqlite3-path option instead of -ovaldb-url") + return subcommands.ExitFailure + } + } else { + if c.Conf.OvalDict.Type == "sqlite3" { + util.Log.Infof("oval-dictionary: %s", c.Conf.OvalDict.SQLite3Path) + } + } + + if c.Conf.Gost.URL != "" { + util.Log.Infof("gost: %s", c.Conf.Gost.URL) + err := gost.Base{}.CheckHTTPHealth() + if err != nil { + util.Log.Errorf("gost HTTP server is not running. err: %s", err) + util.Log.Errorf("Run gost as server mode before reporting or run with -gostdb-sqlite3-path option instead of -gostdb-url") + return subcommands.ExitFailure + } + } else { + if c.Conf.Gost.Type == "sqlite3" { + util.Log.Infof("gost: %s", c.Conf.Gost.SQLite3Path) + } + } + + if c.Conf.Exploit.URL != "" { + util.Log.Infof("exploit: %s", c.Conf.Exploit.URL) + err := exploit.CheckHTTPHealth() + if err != nil { + util.Log.Errorf("exploit HTTP server is not running. err: %s", err) + util.Log.Errorf("Run exploit as server mode before reporting or run with -exploitdb-sqlite3-path option instead of -exploitdb-url") + return subcommands.ExitFailure + } + } else { + if c.Conf.Exploit.Type == "sqlite3" { + util.Log.Infof("exploit: %s", c.Conf.Exploit.SQLite3Path) + } + } dbclient, locked, err := report.NewDBClient(report.DBClientConf{ CveDictCnf: c.Conf.CveDict, OvalDictCnf: c.Conf.OvalDict, GostCnf: c.Conf.Gost, + ExploitCnf: c.Conf.Exploit, DebugSQL: c.Conf.DebugSQL, }) if locked { diff --git a/config/config.go b/config/config.go index 091e6ca6..adc8f1d0 100644 --- a/config/config.go +++ b/config/config.go @@ -122,6 +122,7 @@ type Config struct { CveDict GoCveDictConf `json:"cveDict"` OvalDict GovalDictConf `json:"ovalDict"` Gost GostConf `json:"gost"` + Exploit ExploitConf `json:"exploit"` Slack SlackConf `json:"-"` EMail SMTPConf `json:"-"` @@ -889,6 +890,59 @@ func (cnf *GostConf) Overwrite(cmdOpt GostConf) { cnf.setDefault() } +// 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" + +// Overwrite set options with the following priority. +// 1. Command line option +// 2. Environment variable +// 3. config.toml +func (cnf *ExploitConf) Overwrite(cmdOpt ExploitConf) { + 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) + } + + if cmdOpt.Type != "" { + cnf.Type = cmdOpt.Type + } + if cmdOpt.URL != "" { + cnf.URL = cmdOpt.URL + } + if cmdOpt.SQLite3Path != "" { + cnf.SQLite3Path = cmdOpt.SQLite3Path + } + cnf.setDefault() +} + // AWS is aws config type AWS struct { // AWS profile to use diff --git a/config/tomlloader.go b/config/tomlloader.go index 99e1b349..917f4b95 100644 --- a/config/tomlloader.go +++ b/config/tomlloader.go @@ -51,6 +51,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { Conf.CveDict = conf.CveDict Conf.OvalDict = conf.OvalDict Conf.Gost = conf.Gost + Conf.Exploit = conf.Exploit d := conf.Default Conf.Default = d diff --git a/exploit/exploit.go b/exploit/exploit.go new file mode 100644 index 00000000..a5722171 --- /dev/null +++ b/exploit/exploit.go @@ -0,0 +1,119 @@ +/* Vuls - Vulnerability Scanner +Copyright (C) 2016 Future Architect, Inc. Japan. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package exploit + +import ( + "fmt" + "net/http" + + cnf "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/models" + "github.com/mozqnet/go-exploitdb/db" + exploitmodels "github.com/mozqnet/go-exploitdb/models" + "github.com/parnurzeal/gorequest" +) + +// FillWithExploit fills exploit information that has in Exploit +func FillWithExploit(driver db.DB, r *models.ScanResult) (nExploitCve int, err error) { + if isFetchViaHTTP() { + // TODO + return 0, fmt.Errorf("We are not yet supporting data acquisition in exploitdb server mode") + } + + if driver == nil { + return 0, nil + } + for cveID, vuln := range r.ScannedCves { + es := driver.GetExploitByCveID(cveID) + if len(es) == 0 { + continue + } + exploits := ConvertToModel(es) + vuln.Exploits = exploits + r.ScannedCves[cveID] = vuln + nExploitCve++ + } + return nExploitCve, nil +} + +// ConvertToModel converts gost model to vuls model +func ConvertToModel(es []*exploitmodels.Exploit) (exploits []models.Exploit) { + for _, e := range es { + var documentURL, paperURL, shellURL *string + var description string + if e.Document != nil { + documentURL = &e.Document.DocumentURL + description = e.Document.Description + } + if e.ShellCode != nil { + shellURL = &e.ShellCode.ShellCodeURL + description = e.ShellCode.Description + } + if e.Paper != nil { + paperURL = &e.Paper.PaperURL + description = e.Paper.Description + } + exploit := models.Exploit{ + ExploitType: models.ExploitDB, + ID: e.ExploitDBID, + URL: e.ExploitDBURL, + Description: description, + DocumentURL: documentURL, + ShellCodeURL: shellURL, + PaperURL: paperURL, + } + exploits = append(exploits, exploit) + } + return exploits +} + +// CheckHTTPHealth do health check +func CheckHTTPHealth() error { + if !isFetchViaHTTP() { + return nil + } + + url := fmt.Sprintf("%s/health", cnf.Conf.Exploit.URL) + var errs []error + var resp *http.Response + resp, _, errs = gorequest.New().Get(url).End() + // resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End() + // resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End() + if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { + return fmt.Errorf("Failed to connect to exploit server. url: %s, errs: %v", + url, errs) + } + return nil +} + +// CheckIfExploitFetched checks if oval entries are in DB by family, release. +func CheckIfExploitFetched(driver db.DB, osFamily string) (fetched bool, err error) { + //TODO + return true, nil +} + +// CheckIfExploitFresh checks if oval entries are fresh enough +func CheckIfExploitFresh(driver db.DB, osFamily string) (ok bool, err error) { + //TODO + return true, nil +} + +func isFetchViaHTTP() bool { + // Default value of OvalDBType is sqlite3 + return cnf.Conf.Exploit.URL != "" && cnf.Conf.Exploit.Type == "sqlite3" +} diff --git a/exploit/exploit_test.go b/exploit/exploit_test.go new file mode 100644 index 00000000..129ff1e0 --- /dev/null +++ b/exploit/exploit_test.go @@ -0,0 +1,8 @@ +package exploit + +import ( + "testing" +) + +func TestSetPackageStates(t *testing.T) { +} diff --git a/exploit/util.go b/exploit/util.go new file mode 100644 index 00000000..904cfd18 --- /dev/null +++ b/exploit/util.go @@ -0,0 +1,133 @@ +/* Vuls - Vulnerability Scanner +Copyright (C) 2016 Future Architect, Inc. Japan. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package exploit + +import ( + "fmt" + "net/http" + "time" + + "github.com/cenkalti/backoff" + "github.com/future-architect/vuls/util" + "github.com/parnurzeal/gorequest" +) + +type response struct { + request request + json string +} + +func getCvesViaHTTP(cveIDs []string, urlPrefix string) ( + responses []response, err error) { + nReq := len(cveIDs) + reqChan := make(chan request, nReq) + resChan := make(chan response, nReq) + errChan := make(chan error, nReq) + defer close(reqChan) + defer close(resChan) + defer close(errChan) + + go func() { + for _, cveID := range cveIDs { + reqChan <- request{ + cveID: cveID, + } + } + }() + + concurrency := 10 + tasks := util.GenWorkers(concurrency) + for i := 0; i < nReq; i++ { + tasks <- func() { + select { + case req := <-reqChan: + url, err := util.URLPathJoin( + urlPrefix, + req.cveID, + ) + if err != nil { + errChan <- err + } else { + util.Log.Debugf("HTTP Request to %s", url) + httpGet(url, req, resChan, errChan) + } + } + } + } + + timeout := time.After(2 * 60 * time.Second) + var errs []error + for i := 0; i < nReq; i++ { + select { + case res := <-resChan: + responses = append(responses, res) + case err := <-errChan: + errs = append(errs, err) + case <-timeout: + return nil, fmt.Errorf("Timeout Fetching OVAL") + } + } + if len(errs) != 0 { + return nil, fmt.Errorf("Failed to fetch OVAL. err: %v", errs) + } + return +} + +type request struct { + osMajorVersion string + packName string + isSrcPack bool + cveID string +} + +func httpGet(url string, req request, resChan chan<- response, errChan chan<- error) { + var body string + var errs []error + var resp *http.Response + count, retryMax := 0, 3 + f := func() (err error) { + // resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End() + resp, body, errs = gorequest.New().Get(url).End() + if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { + count++ + if count == retryMax { + return nil + } + return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", + errs, url, resp) + } + return nil + } + notify := func(err error, t time.Duration) { + util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err) + } + err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify) + if err != nil { + errChan <- fmt.Errorf("HTTP Error %s", err) + return + } + if count == retryMax { + errChan <- fmt.Errorf("HRetry count exceeded") + return + } + + resChan <- response{ + request: req, + json: body, + } +} diff --git a/models/scanresults.go b/models/scanresults.go index f5b511c5..38321966 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -309,12 +309,13 @@ func (r ScanResult) FormatTextReportHeadedr() string { buf.WriteString("=") } - return fmt.Sprintf("%s\n%s\n%s, %s, %s\n", + return fmt.Sprintf("%s\n%s\n%s, %s, %s, %s\n", r.ServerInfo(), buf.String(), r.ScannedCves.FormatCveSummary(), r.ScannedCves.FormatFixedStatus(r.Packages), r.FormatUpdatablePacksSummary(), + r.FormatExploitCveSummary(), ) } @@ -338,6 +339,17 @@ func (r ScanResult) FormatUpdatablePacksSummary() string { nUpdatable) } +// FormatExploitCveSummary returns a summary of exploit cve +func (r ScanResult) FormatExploitCveSummary() string { + nExploitCve := 0 + for _, vuln := range r.ScannedCves { + if 0 < len(vuln.Exploits) { + nExploitCve++ + } + } + return fmt.Sprintf("%d cves with exploit", nExploitCve) +} + func (r ScanResult) isDisplayUpdatableNum() bool { var mode config.ScanMode s, _ := config.Conf.Servers[r.ServerName] diff --git a/models/vulninfos.go b/models/vulninfos.go index 3984070c..17602151 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -166,6 +166,7 @@ type VulnInfo struct { DistroAdvisories []DistroAdvisory `json:"distroAdvisories,omitempty"` // for Aamazon, RHEL, FreeBSD CpeURIs []string `json:"cpeURIs,omitempty"` // CpeURIs related to this CVE defined in config.toml CveContents CveContents `json:"cveContents"` + Exploits []Exploit `json:"exploits"` } // Titles returns tilte (TUI) @@ -713,6 +714,26 @@ func (p DistroAdvisory) Format() string { return strings.Join(buf, "\n") } +// ExploitType is exploit type +type ExploitType string + +const ( + // ExploitDB : https://www.exploit-db.com/ + ExploitDB ExploitType = "exploitdb" +) + +// Exploit : +type Exploit struct { + ExploitType ExploitType `json:"exploitType"` + ID string `json:"id"` + URL string `json:"url"` + Description string `json:"description"` + DocumentURL *string `json:"documentURL,omitempty"` + PaperURL *string `json:"paperURL,omitempty"` + ShellCodeURL *string `json:"shellCodeURL,omitempty"` + BinaryURL *string `json:"binaryURL,omitempty"` +} + // Confidences is a list of Confidence type Confidences []Confidence diff --git a/report/db_client.go b/report/db_client.go index ad7b4188..09659ec0 100644 --- a/report/db_client.go +++ b/report/db_client.go @@ -9,13 +9,15 @@ import ( gostdb "github.com/knqyf263/gost/db" cvedb "github.com/kotakanbe/go-cve-dictionary/db" ovaldb "github.com/kotakanbe/goval-dictionary/db" + exploitdb "github.com/mozqnet/go-exploitdb/db" ) // DBClient is a dictionarie's db client for reporting type DBClient struct { - CveDB cvedb.DB - OvalDB ovaldb.DB - GostDB gostdb.DB + CveDB cvedb.DB + OvalDB ovaldb.DB + GostDB gostdb.DB + ExploitDB exploitdb.DB } // DBClientConf has a configuration of Vulnerability DBs @@ -23,6 +25,7 @@ type DBClientConf struct { CveDictCnf config.GoCveDictConf OvalDictCnf config.GovalDictConf GostCnf config.GostConf + ExploitCnf config.ExploitConf DebugSQL bool } @@ -38,6 +41,10 @@ func (c DBClientConf) isGostViaHTTP() bool { return c.GostCnf.URL != "" && c.GostCnf.Type == "sqlite3" } +func (c DBClientConf) isExploitViaHTTP() bool { + return c.ExploitCnf.URL != "" && c.ExploitCnf.Type == "sqlite3" +} + // NewDBClient returns db clients func NewDBClient(cnf DBClientConf) (dbclient *DBClient, locked bool, err error) { cveDriver, locked, err := NewCveDB(cnf) @@ -63,10 +70,20 @@ func NewDBClient(cnf DBClientConf) (dbclient *DBClient, locked bool, err error) cnf.GostCnf.SQLite3Path, err) } + exploitdb, locked, err := NewExploitDB(cnf) + if locked { + return nil, true, fmt.Errorf("exploitDB is locked: %s", + cnf.ExploitCnf.SQLite3Path) + } else if err != nil { + util.Log.Warnf("Unable to use exploitDB: %s, err: %s", + cnf.ExploitCnf.SQLite3Path, err) + } + return &DBClient{ - CveDB: cveDriver, - OvalDB: ovaldb, - GostDB: gostdb, + CveDB: cveDriver, + OvalDB: ovaldb, + GostDB: gostdb, + ExploitDB: exploitdb, }, false, nil } @@ -143,6 +160,32 @@ func NewGostDB(cnf DBClientConf) (driver gostdb.DB, locked bool, err error) { return driver, false, nil } +// NewExploitDB returns db client for Exploit +func NewExploitDB(cnf DBClientConf) (driver exploitdb.DB, locked bool, err error) { + if cnf.isExploitViaHTTP() { + return nil, false, nil + } + path := cnf.ExploitCnf.URL + if cnf.ExploitCnf.Type == "sqlite3" { + path = cnf.ExploitCnf.SQLite3Path + + if _, err := os.Stat(path); os.IsNotExist(err) { + util.Log.Warnf("--exploitdb-path=%s is not found. It's recommended to use exploit to improve scanning accuracy. To use exploit db database, see https://github.com/mozqnet/go-exploitdb", path) + return nil, false, nil + } + } + + util.Log.Debugf("Open exploit db (%s): %s", cnf.ExploitCnf.Type, path) + if driver, locked, err = exploitdb.NewDB(cnf.ExploitCnf.Type, path, cnf.DebugSQL); err != nil { + if locked { + util.Log.Errorf("exploitDB is locked: %s", err) + return nil, true, err + } + return nil, false, err + } + return driver, false, nil +} + // CloseDB close dbs func (d DBClient) CloseDB() { if d.CveDB != nil { diff --git a/report/report.go b/report/report.go index 3a436125..a816b1e9 100644 --- a/report/report.go +++ b/report/report.go @@ -32,6 +32,7 @@ import ( c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/contrib/owasp-dependency-check/parser" "github.com/future-architect/vuls/cwe" + "github.com/future-architect/vuls/exploit" "github.com/future-architect/vuls/gost" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/oval" @@ -40,6 +41,7 @@ import ( gostdb "github.com/knqyf263/gost/db" cvedb "github.com/kotakanbe/go-cve-dictionary/db" ovaldb "github.com/kotakanbe/goval-dictionary/db" + exploitdb "github.com/mozqnet/go-exploitdb/db" ) const ( @@ -176,6 +178,14 @@ func FillCveInfo(dbclient DBClient, r *models.ScanResult, cpeURIs []string) erro return fmt.Errorf("Failed to fill with CVE: %s", err) } + util.Log.Infof("Fill Exploit information with Exploit-DB") + nExploitCve, err := FillWithExploit(dbclient.ExploitDB, r) + if err != nil { + return fmt.Errorf("Failed to fill with exploit: %s", err) + } + util.Log.Infof("%s: %d Exploits are detected with exploit", + r.FormatServerName(), nExploitCve) + fillCweDict(r) return nil } @@ -292,6 +302,14 @@ func FillWithGost(driver gostdb.DB, r *models.ScanResult) (nCVEs int, err error) return gostClient.FillWithGost(driver, r) } +// FillWithExploit fills Exploits with exploit dataabase +// https://github.com/mozqnet/go-exploitdb +func FillWithExploit(driver exploitdb.DB, r *models.ScanResult) (nExploitCve int, err error) { + // TODO chekc if fetched + // TODO chekc if fresh enough + return exploit.FillWithExploit(driver, r) +} + func fillVulnByCpeURIs(driver cvedb.DB, r *models.ScanResult, cpeURIs []string) (nCVEs int, err error) { for _, name := range cpeURIs { details, err := CveClient.FetchCveDetailsByCpeName(driver, name) @@ -454,6 +472,7 @@ func EnsureUUIDs(configPath string, results models.ScanResults) error { cveDict := &c.Conf.CveDict ovalDict := &c.Conf.OvalDict gost := &c.Conf.Gost + exploit := &c.Conf.Exploit http := &c.Conf.HTTP if http.URL == "" { http = nil @@ -498,6 +517,7 @@ func EnsureUUIDs(configPath string, results models.ScanResults) error { CveDict *c.GoCveDictConf `toml:"cveDict"` OvalDict *c.GovalDictConf `toml:"ovalDict"` Gost *c.GostConf `toml:"gost"` + Exploit *c.ExploitConf `toml:"exploit"` Slack *c.SlackConf `toml:"slack"` Email *c.SMTPConf `toml:"email"` HTTP *c.HTTPConf `toml:"http"` @@ -515,6 +535,7 @@ func EnsureUUIDs(configPath string, results models.ScanResults) error { CveDict: cveDict, OvalDict: ovalDict, Gost: gost, + Exploit: exploit, Slack: slack, Email: email, HTTP: http, diff --git a/report/tui.go b/report/tui.go index 48fe9857..b9b8fbfb 100644 --- a/report/tui.go +++ b/report/tui.go @@ -743,6 +743,16 @@ func setChangelogLayout(g *gocui.Gui) error { lines = append(lines, adv.Format()) } + if len(vinfo.Exploits) != 0 { + lines = append(lines, "\n", + "Exploit Codes", + "=============", + ) + for _, exploit := range vinfo.Exploits { + lines = append(lines, fmt.Sprintf("* [%s](%s)", exploit.Description, exploit.URL)) + } + } + if currentScanResult.IsDeepScanMode() { lines = append(lines, "\n", "ChangeLogs", @@ -770,6 +780,7 @@ func setChangelogLayout(g *gocui.Gui) error { type dataForTmpl struct { CveID string Cvsses string + Exploits []models.Exploit Summary string Mitigation string Confidences models.Confidences @@ -877,6 +888,7 @@ const mdTemplate = ` CVSS Scores ----------- {{.Cvsses }} + Summary ----------- {{.Summary }} diff --git a/report/util.go b/report/util.go index 5d9e7af5..8e94af2b 100644 --- a/report/util.go +++ b/report/util.go @@ -50,6 +50,7 @@ func formatScanSummary(rs ...models.ScanResult) string { r.FormatServerName(), fmt.Sprintf("%s%s", r.Family, r.Release), r.FormatUpdatablePacksSummary(), + r.FormatExploitCveSummary(), } } else { cols = []interface{}{ @@ -76,6 +77,7 @@ func formatOneLineSummary(rs ...models.ScanResult) string { r.ScannedCves.FormatCveSummary(), r.ScannedCves.FormatFixedStatus(r.Packages), r.FormatUpdatablePacksSummary(), + r.FormatExploitCveSummary(), } } else { cols = []interface{}{ @@ -123,6 +125,7 @@ No CVE-IDs are found in updatable packages. fmt.Sprintf("%7s", vinfo.PatchStatus(r.Packages)), // packname, fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID), + fmt.Sprintf("%t", 0 < len(vinfo.Exploits)), }) } @@ -137,6 +140,7 @@ No CVE-IDs are found in updatable packages. "Fixed", // "Pkg", "NVD", + "Exploit", }) table.SetBorder(true) table.AppendBulk(data) @@ -250,6 +254,9 @@ No CVE-IDs are found in updatable packages. for _, url := range cweURLs { data = append(data, []string{"CWE", url}) } + for _, exploit := range vuln.Exploits { + data = append(data, []string{string(exploit.ExploitType), exploit.URL}) + } for _, url := range top10URLs { data = append(data, []string{"OWASP Top10", url}) }