Display exploit codes information for each detected CVE-IDs (#729)
* add exploit * bug fix while loading config in TUI, display in format-full-text * fix readme
This commit is contained in:
committed by
Kota Kanbe
parent
678e72a8b6
commit
9865eab2c0
14
Gopkg.lock
generated
14
Gopkg.lock
generated
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
119
exploit/exploit.go
Normal file
119
exploit/exploit.go
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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"
|
||||
}
|
||||
8
exploit/exploit_test.go
Normal file
8
exploit/exploit_test.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package exploit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetPackageStates(t *testing.T) {
|
||||
}
|
||||
133
exploit/util.go
Normal file
133
exploit/util.go
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user