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 }