265 lines
7.3 KiB
Go
265 lines
7.3 KiB
Go
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
|
|
}
|