From 304f4b0b4b0f90917e767240775599ea3b77f379 Mon Sep 17 00:00:00 2001 From: MaineK00n Date: Wed, 24 Apr 2024 09:45:23 +0900 Subject: [PATCH] feat(commands): add search command --- README.md | 133 +++++++++++++++++++++++++++++ commands/search.go | 203 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 336 insertions(+) create mode 100644 commands/search.go diff --git a/README.md b/README.md index 8c223a7..d55cca4 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,7 @@ Available Commands: completion generate the autocompletion script for the specified shell fetch Fetch Vulnerability dictionary help Help about any command + search Search for Vulnerability in the dictionary server Start CVE dictionary HTTP Server version Show version @@ -321,6 +322,138 @@ Global Flags: ---- +### Usage: Search Command + +```bash +$ go-cve-dictionary search --help +Search for Vulnerability in the dictionary + +Usage: + go-cve-dictionary search [command] + +Available Commands: + cpe Search for Vulnerability in the dictionary by CPE + cve Search for Vulnerability in the dictionary by CVEID + +Flags: + -h, --help help for search + +Global Flags: + --config string config file (default is $HOME/.go-cve-dictionary.yaml) + --dbpath string /path/to/sqlite3 or SQL connection string (default "$PWD/cve.sqlite3") + --dbtype string Database type to store data in (sqlite3, mysql, postgres or redis supported) (default "sqlite3") + --debug debug mode (default: false) + --debug-sql SQL debug mode + --http-proxy string http://proxy-url:port (default: empty) + --log-dir string /path/to/log (default "/var/log/go-cve-dictionary") + --log-json output log as JSON + --log-to-file output log to file + +Use "go-cve-dictionary search [command] --help" for more information about a command. +``` + +#### Search All CVE IDs +```bash +$ go-cve-dictionary search cve +[ + "CVE-2023-38624", + "CVE-2024-20750", + "CVE-2024-21101", + "CVE-2023-27427", + "CVE-2023-30445", +... +``` + +#### Search by CVE ID(s) +```bash +$ go-cve-dictionary search cve CVE-2024-3400 +{ + "CveID": "CVE-2024-3400", + "Nvds": [ + { + "CveID": "CVE-2024-3400", + "Descriptions": [ + { + "Lang": "en", + "Value": "A command injection as a result of arbitrary file creation vulnerability in the GlobalProtect feature of Palo Alto Networks PAN-OS software for specific PAN-OS versions and distinct feature configurations may enable an unauthenticated attacker to execute arbitrary code with root privileges on the firewall.\n\nCloud NGFW, Panorama appliances, and Prisma Access are not impacted by this vulnerability." + }, +... + +$ go-cve-dictionary search cve CVE-2023-48783 CVE-2024-3400 +{ + "CVE-2023-48783": { + "CveID": "CVE-2023-48783", + "Nvds": [ + { + "CveID": "CVE-2023-48783", + ... + } + ], + "Jvns": [], + "Fortinets": [ + { + "AdvisoryID": "FG-IR-23-408", + "CveID": "CVE-2023-48783", + ... + } + ] + }, + "CVE-2024-3400": { + "CveID": "CVE-2024-3400", + "Nvds": [ + { + "CveID": "CVE-2024-3400", +... +``` + +#### Search by CPE +```bash +$ go-cve-dictionary search cpe "cpe:/a:fortinet:fortiportal" +[ + { + "CveID": "CVE-2017-7337", + "Nvds": [], + "Jvns": [], + "Fortinets": [ + { + "AdvisoryID": "FG-IR-17-114", + "CveID": "CVE-2017-7337", + "Title": "FortiPortal Multiple Vulnerabilities", +... +``` + +#### Search CVE IDs by CPE +```bash +$ go-cve-dictionary search cpe --cveid-only "cpe:/a:fortinet:fortiportal" +{ + "Fortinet": [ + "CVE-2023-46712", + "CVE-2023-48791", + "CVE-2017-7339", + "CVE-2017-7343", + "CVE-2022-27490", + "CVE-2017-7342", + "CVE-2017-7731", + "CVE-2023-41842", + "CVE-2023-48783", + "CVE-2024-21761", + "CVE-2017-7337", + "CVE-2017-7338", + "CVE-2017-7340" + ], + "JVN": [], + "NVD": [ + "CVE-2023-46712", + "CVE-2023-48791", + "CVE-2023-41842", + "CVE-2023-48783", + "CVE-2024-21761" + ] +} +``` + +---- + ### Usage: Use MySQL as a DB storage back-end - fetch nvd diff --git a/commands/search.go b/commands/search.go new file mode 100644 index 0000000..0ad9078 --- /dev/null +++ b/commands/search.go @@ -0,0 +1,203 @@ +package commands + +import ( + "encoding/json" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/vulsio/go-cve-dictionary/db" + log "github.com/vulsio/go-cve-dictionary/log" + "github.com/vulsio/go-cve-dictionary/models" + "golang.org/x/xerrors" +) + +var searchCmd = &cobra.Command{ + Use: "search", + Short: "Search for Vulnerability in the dictionary", + Long: "Search for Vulnerability in the dictionary", +} + +var searchCVECmd = &cobra.Command{ + Use: "cve", + Short: "Search for Vulnerability in the dictionary by CVEID", + Long: "Search for Vulnerability in the dictionary by CVEID", + RunE: searchCVE, +} + +var searchCPECmd = &cobra.Command{ + Use: "cpe", + Short: "Search for Vulnerability in the dictionary by CPE", + Long: "Search for Vulnerability in the dictionary by CPE", + Args: cobra.ExactArgs(1), + RunE: searchCPE, +} + +func searchCVE(_ *cobra.Command, args []string) error { + if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { + return xerrors.Errorf("Failed to SetLogger. err: %w", err) + } + + driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) + if err != nil { + if xerrors.Is(err, db.ErrDBLocked) { + return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) + } + return xerrors.Errorf("Failed to open DB. err: %w", err) + } + + fetchMeta, err := driver.GetFetchMeta() + if err != nil { + return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) + } + if fetchMeta.OutDated() { + return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) + } + + count := 0 + nvdCount, err := driver.CountNvd() + if err != nil { + log.Errorf("Failed to count NVD table: %s", err) + return err + } + count += nvdCount + + jvnCount, err := driver.CountJvn() + if err != nil { + log.Errorf("Failed to count JVN table: %s", err) + return err + } + count += jvnCount + + fortinetCount, err := driver.CountFortinet() + if err != nil { + log.Errorf("Failed to count Fortinet table: %s", err) + return err + } + count += fortinetCount + + if count == 0 { + log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, JVN, Fortinet") + log.Infof("") + log.Infof(" go-cve-dictionary fetch nvd") + log.Infof(" go-cve-dictionary fetch jvn") + log.Infof(" go-cve-dictionary fetch fortinet") + log.Infof("") + return nil + } + + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + switch len(args) { + case 0: + cveids, err := driver.GetCveIDs() + if err != nil { + return xerrors.Errorf("Failed to get All CVEIDs. err: %w", err) + } + if err := enc.Encode(cveids); err != nil { + return xerrors.Errorf("Failed to encode All CVEIDs. err: %w", err) + } + case 1: + d, err := driver.Get(args[0]) + if err != nil { + return xerrors.Errorf("Failed to get CVEDetail by CVEID. err: %w", err) + } + if err := enc.Encode(d); err != nil { + return xerrors.Errorf("Failed to encode CVEDetail by CVEID. err: %w", err) + } + default: + ds, err := driver.GetMulti(args) + if err != nil { + return xerrors.Errorf("Failed to get CVEDetails by CVEIDs. err: %w", err) + } + if err := enc.Encode(ds); err != nil { + return xerrors.Errorf("Failed to encode CVEDetails by CVEIDs. err: %w", err) + } + } + + return nil +} + +func searchCPE(_ *cobra.Command, args []string) error { + if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { + return xerrors.Errorf("Failed to SetLogger. err: %w", err) + } + + driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) + if err != nil { + if xerrors.Is(err, db.ErrDBLocked) { + return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) + } + return xerrors.Errorf("Failed to open DB. err: %w", err) + } + + fetchMeta, err := driver.GetFetchMeta() + if err != nil { + return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) + } + if fetchMeta.OutDated() { + return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) + } + + count := 0 + nvdCount, err := driver.CountNvd() + if err != nil { + log.Errorf("Failed to count NVD table: %s", err) + return err + } + count += nvdCount + + jvnCount, err := driver.CountJvn() + if err != nil { + log.Errorf("Failed to count JVN table: %s", err) + return err + } + count += jvnCount + + fortinetCount, err := driver.CountFortinet() + if err != nil { + log.Errorf("Failed to count Fortinet table: %s", err) + return err + } + count += fortinetCount + + if count == 0 { + log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, JVN, Fortinet") + log.Infof("") + log.Infof(" go-cve-dictionary fetch nvd") + log.Infof(" go-cve-dictionary fetch jvn") + log.Infof(" go-cve-dictionary fetch fortinet") + log.Infof("") + return nil + } + + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + if viper.GetBool("cveid-only") { + nvds, jvns, fortinets, err := driver.GetCveIDsByCpeURI(args[0]) + if err != nil { + return xerrors.Errorf("Failed to Get CVEIDs by CPE URI. err: %w", err) + } + if err := enc.Encode(map[string][]string{"NVD": nvds, "JVN": jvns, "Fortinet": fortinets}); err != nil { + return xerrors.Errorf("Failed to encode CVEIDs by CPE URI. err: %w", err) + } + return nil + } + cveDetails, err := driver.GetByCpeURI(args[0]) + if err != nil { + return xerrors.Errorf("Failed to Get CVEDetails by CPE URI. err: %w", err) + } + if err := enc.Encode(cveDetails); err != nil { + return xerrors.Errorf("Failed to encode CVEDetails by CPE URI. err: %w", err) + } + + return nil +} + +func init() { + RootCmd.AddCommand(searchCmd) + searchCmd.AddCommand(searchCVECmd, searchCPECmd) + + searchCPECmd.PersistentFlags().Bool("cveid-only", false, "show only CVEID in search results") + _ = viper.BindPFlag("cveid-only", searchCPECmd.PersistentFlags().Lookup("cveid-only")) +}