commit f75d57b53ae3329b2347946b39feed738de86335 Author: Kalle Carlbark Date: Mon Sep 9 15:14:49 2019 +0200 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1969b47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +getvulns diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d77ba6 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# getvulns 🚨 +Utility to fetch and list current security advisories of network vendor products and os versions. + +To be able to use this utility you need to go to +1. Access the Cisco API console at: [https://apiconsole.cisco.com](https://apiconsole.cisco.com) +2. Login with your CCO credentials +3. Register your application and obtain your client credentials. +4. Once you register your application and obtain your client ID and client secret you need to add these to your environment variables like so: + +```bash +$ export CLIENT_ID="0lkajsdklsajdlkj" +$ export CLIENT_SECRET="2lk3rlncxk5l6l" +``` + +# Usage + +```bash +$ ./getvulns -h +Usage of ./getvulns: + -ios string + fetch Security Advisories for IOS version + -iosxe string + fetch Security Advisories for IOS-XE version + -product string + fetch Security Advisories Cisco Products, ex Cisco Adaptive Security Appliance + -v be verbose +$ +``` + +# Building +```bash +$ go get -u -v +$ go build || go run main.go +``` + +# Screenshot +![1](assets/img/screenshot.png) diff --git a/assets/img/screenshot.png b/assets/img/screenshot.png new file mode 100644 index 0000000..8f1e8c6 Binary files /dev/null and b/assets/img/screenshot.png differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..4405bf5 --- /dev/null +++ b/main.go @@ -0,0 +1,265 @@ +package main + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "flag" + "fmt" + "github.com/fatih/color" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "strings" + "time" +) + +const oauth2Url = "https://cloudsso.cisco.com/as/token.oauth2" + +type oauthToken struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` +} + +type ciscoVulns struct { + Advisories []struct { + AdvisoryID string `json:"advisoryId"` + AdvisoryTitle string `json:"advisoryTitle"` + BugIDs []string `json:"bugIDs"` + IpsSignatures []string `json:"ipsSignatures"` + Cves []string `json:"cves"` + CvrfURL string `json:"cvrfUrl"` + OvalURL string `json:"ovalUrl"` + CvssBaseScore string `json:"cvssBaseScore"` + Cwe []string `json:"cwe"` + IosRelease []string `json:"iosRelease"` + FirstFixed []string `json:"firstFixed"` + FirstPublished string `json:"firstPublished"` + LastUpdated string `json:"lastUpdated"` + ProductNames []string `json:"productNames"` + PublicationURL string `json:"publicationUrl"` + Sir string `json:"sir"` + Summary string `json:"summary"` + } `json:"advisories"` + SirCriticalCount int + SirHighCount int + SirMediumCount int + SirLowCount int +} + +var verboseFlag = flag.Bool("v", false, "be verbose") +var scriptFlag = flag.Bool("s", false, "scriptble output") + +func main() { + var t oauthToken + var c ciscoVulns + + var iosXeFlag = flag.String("iosxe", "", "fetch Security Advisories for IOS-XE version") + var iosFlag = flag.String("ios", "", "fetch Security Advisories for IOS version") + var productFlag = flag.String("product", "", "fetch Security Advisories Cisco Products, ex Cisco Adaptive Security Appliance") + + flag.Parse() + + if *iosXeFlag == "" && *iosFlag == "" && *productFlag == "" { + flag.PrintDefaults() + return + } + + /* Colors */ + redstr := color.New(color.FgRed).SprintFunc() + red := color.New(color.FgRed) + yellow := color.New(color.FgYellow).SprintFunc() + boldRed := red.Add(color.Bold).SprintFunc() + + if *iosXeFlag != "" { + err := t.fetchToken() + if err != nil { + fmt.Println(err) + return + } + c.fetchVulnVersion(t, "iosxe", *iosXeFlag) + c.populateSirCount() + if *scriptFlag == true { + fmt.Printf("Summary Version: %s Critical: %d: High: %d Medium: %d Low: %d\n", *iosXeFlag, c.SirCriticalCount, c.SirHighCount, c.SirMediumCount, c.SirLowCount) + } else { + /* Clear terminal */ + fmt.Printf("\033[H\033[2J") + fmt.Printf("--- Vulnerabilities for IOS-XE %s ---\n", *iosXeFlag) + fmt.Printf("Critical: %s High: %s Medium: %s Low: %d\n\n", boldRed(c.SirCriticalCount), redstr(c.SirHighCount), yellow(c.SirMediumCount), c.SirLowCount) + } + } + + if *iosFlag != "" { + err := t.fetchToken() + if err != nil { + fmt.Println(err) + return + } + c.fetchVulnVersion(t, "ios", *iosFlag) + c.populateSirCount() + /* Clear terminal */ + if *scriptFlag == true { + fmt.Printf("Summary Version: %s Critical: %d: High: %d Medium: %d Low: %d\n", *iosFlag, c.SirCriticalCount, c.SirHighCount, c.SirMediumCount, c.SirLowCount) + } else { + fmt.Printf("\033[H\033[2J") + fmt.Printf("--- Vulnerabilities for IOS %s ---\n", *iosFlag) + fmt.Printf("Critical: %s High: %s Medium: %s Low: %d\n\n", boldRed(c.SirCriticalCount), redstr(c.SirHighCount), yellow(c.SirMediumCount), c.SirLowCount) + } + } + + if *productFlag != "" { + fmt.Println("Not yet implemented.") + } + + if *scriptFlag == true { + for _, vuln := range c.Advisories { + fmt.Printf("%s:%s:%s:%s\n", vuln.AdvisoryTitle, vuln.CvssBaseScore, vuln.Sir, vuln.PublicationURL) + } + } else { + for _, vuln := range c.Advisories { + switch vuln.Sir { + case "Critical": + fmt.Printf("Title: %s\nCVE Score: %s\nSeverity: %s\nUrl: %s\n\n", vuln.AdvisoryTitle, vuln.CvssBaseScore, boldRed("!!! "+vuln.Sir+" !!!"), vuln.PublicationURL) + case "High": + fmt.Printf("Title: %s\nCVE Score: %s\nSeverity: %s\nUrl: %s\n\n", vuln.AdvisoryTitle, vuln.CvssBaseScore, redstr(vuln.Sir), vuln.PublicationURL) + case "Medium": + fmt.Printf("Title: %s\nCVE Score: %s\nSeverity: %s\nUrl: %s\n\n", vuln.AdvisoryTitle, vuln.CvssBaseScore, yellow(vuln.Sir), vuln.PublicationURL) + default: + fmt.Printf("Title: %s\nCVE Score: %s\nSeverity: %s\nUrl: %s\n\n", vuln.AdvisoryTitle, vuln.CvssBaseScore, vuln.Sir, vuln.PublicationURL) + } + } + } +} + +func (c *ciscoVulns) populateSirCount() { + for _, vuln := range c.Advisories { + switch vuln.Sir { + case "Critical": + c.SirCriticalCount++ + case "High": + c.SirHighCount++ + case "Medium": + c.SirMediumCount++ + case "Low": + c.SirLowCount++ + } + } +} + +func (c *ciscoVulns) fetchVulnVersion(token oauthToken, os string, version string) error { + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + timeout := time.Duration(60 * time.Second) + client := &http.Client{ + Timeout: timeout, + Transport: transport, + } + + var urlStr bytes.Buffer + switch os { + case "iosxe": + urlStr.WriteString("https://api.cisco.com/security/advisories/iosxe?version=") + case "ios": + urlStr.WriteString("https://api.cisco.com/security/advisories/ios?version=") + } + + urlStr.WriteString(version) + + url, err := url.Parse(urlStr.String()) + + request, err := http.NewRequest("GET", url.String(), nil) + if err != nil { + log.Println(err) + } + request.Header.Add("Accept", "application/json") + request.Header.Add("Authorization", "Bearer "+token.AccessToken) + + if *verboseFlag == true { + fmt.Printf("Connecting to api.cisco.com\n") + } + response, err := client.Do(request) + if err != nil { + log.Println(err) + return err + } + if response.StatusCode == http.StatusUnauthorized { + log.Println("Invalid credentials") + return errors.New("Invalid credentials") + } + if *verboseFlag == true { + fmt.Printf("Authentication ok.\nReading response\n") + } + data, err := ioutil.ReadAll(response.Body) + if err != nil { + log.Println(err) + return err + } + err = json.Unmarshal(data, &c) + if err != nil { + return err + } + return nil +} + +func (t *oauthToken) fetchToken() error { + clientId := os.Getenv("CLIENT_ID") + clientSecret := os.Getenv("CLIENT_SECRET") + + if clientId == "" || clientSecret == "" { + return errors.New("You have not set correct environment variables (CLIENT_ID or CLIENT_SECRET).\n\n$ export CLIENT_ID=\"kajsdkljadslkj\"\n$ export CLIENT_SECRET=\"kljadslksjdlj\"") + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + timeout := time.Duration(60 * time.Second) + client := &http.Client{ + Timeout: timeout, + Transport: transport, + } + + form := url.Values{} + form.Add("client_id", clientId) + form.Add("client_secret", clientSecret) + form.Add("grant_type", "client_credentials") + + request, err := http.NewRequest("POST", oauth2Url, strings.NewReader(form.Encode())) + if err != nil { + return err + } + + request.Header.Add("Content-Type", "application/x-www-form-urlencoded") + if *verboseFlag == true { + fmt.Printf("Connecting to cloudsso.cisco.com.\n") + } + response, err := client.Do(request) + if err != nil { + return err + } + if response.StatusCode == http.StatusUnauthorized { + log.Println("Authentication failed: " + oauth2Url) + return errors.New("Authentication failed: " + oauth2Url) + } + if *verboseFlag == true { + fmt.Printf("Authenticated ok.\nAcquiring token.\n") + } + + data, err := ioutil.ReadAll(response.Body) + if err != nil { + return err + } + + err = json.Unmarshal(data, &t) + if err != nil { + return err + } + + return nil +}