From 72c7a9d815aded3c081006b2c591b01b918161d2 Mon Sep 17 00:00:00 2001 From: Jozef Reisinger Date: Fri, 5 Nov 2021 15:41:39 +0100 Subject: [PATCH] improve JSON output and add Name() --- Makefile | 1 + abuseipdb.go | 2 ++ as.go | 4 ++- checkip.go | 96 +++++++++++++++++++++++++++++--------------------- cins.go | 2 ++ cmd/checkip.go | 7 ++-- dns.go | 5 +-- et.go | 2 ++ geo.go | 4 ++- ip.go | 4 ++- ipsum.go | 2 ++ otx.go | 2 ++ shodan.go | 4 ++- threatcrowd.go | 2 ++ virustotal.go | 2 ++ 15 files changed, 88 insertions(+), 51 deletions(-) diff --git a/Makefile b/Makefile index 90c0e5e..03c472c 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ run: install checkip 140.82.114.4 checkip 218.92.0.158 checkip 92.118.160.17 + checkip -j 92.118.160.17 | jq -r '.[] | select(.Type=="Sec") | "\(.Name) => \(.IsMalicious)"' PLATFORMS := linux/amd64 darwin/amd64 linux/arm windows/amd64 diff --git a/abuseipdb.go b/abuseipdb.go index ac7e50f..dde72a4 100644 --- a/abuseipdb.go +++ b/abuseipdb.go @@ -19,6 +19,8 @@ type AbuseIPDB struct { } `json:"data"` } +func (a *AbuseIPDB) Name() string { return "abuseipdb.com" } + // Check fills in AbuseIPDB data for a given IP address. It gets the data from // api.abuseipdb.com/api/v2/check (docs.abuseipdb.com/#check-endpoint). func (a *AbuseIPDB) Check(ipaddr net.IP) error { diff --git a/as.go b/as.go index 1a72959..6a1db8d 100644 --- a/as.go +++ b/as.go @@ -19,6 +19,8 @@ type AS struct { CountryCode string } +func (a *AS) Name() string { return "iptoasn.com" } + // Check fills in AS data for a given IP address. The data is taken from a TSV // file ip2asn-combined downloaded from iptoasn.com. The file is created or // updated as needed. @@ -39,7 +41,7 @@ func (a *AS) Check(ipaddr net.IP) error { // Info returns interesting information from the check. func (a *AS) Info() string { - return fmt.Sprintf("AS description\t%s", a.Description) + return a.Description } // search searches the ippadrr in tsvFile and if found fills in AS data. diff --git a/checkip.go b/checkip.go index d45b85f..3a03af6 100644 --- a/checkip.go +++ b/checkip.go @@ -9,15 +9,16 @@ import ( "fmt" "net" "os" - "regexp" + "sort" "sync" "github.com/logrusorgru/aurora" ) -// Checker runs a check of an IP address. +// Checker runs a check of an IP address. It also returns its name. type Checker interface { Check(ip net.IP) error + Name() string } // InfoChecker finds information about an IP address. @@ -33,72 +34,87 @@ type SecChecker interface { } // Run runs checkers concurrently checking the ipaddr. -func Run(checkers []Checker, ipaddr net.IP) Result { - var res Result +func Run(checkers []Checker, ipaddr net.IP) []Result { + var res []Result var wg sync.WaitGroup - for _, c := range checkers { + for _, chk := range checkers { wg.Add(1) go func(c Checker) { defer wg.Done() - if err := c.Check(ipaddr); err != nil { - res.Errors = append(res.Errors, redactSecrets(err.Error())) + err := c.Check(ipaddr) + switch v := c.(type) { + case InfoChecker: + r := Result{Name: v.Name(), Type: "Info", Data: v, Info: v.Info(), Err: err} + res = append(res, r) + case SecChecker: + r := Result{Name: c.Name(), Type: "Sec", Data: v, IsMalicious: v.IsMalicious(), Err: err} + res = append(res, r) } - }(c) - } - wg.Wait() - - var total, malicious int - for _, c := range checkers { - switch ip := c.(type) { - case InfoChecker: - res.Infos = append(res.Infos, ip.Info()) - case SecChecker: - total++ - if ip.IsMalicious() { - malicious++ - } - } + }(chk) } - res.ProbabilityMalicious = float64(malicious) / float64(total) + wg.Wait() return res } -func redactSecrets(s string) string { - key := regexp.MustCompile(`(key|pass|password)=\w+`) - return key.ReplaceAllString(s, "${1}=REDACTED") -} +// func redactSecrets(s string) string { +// key := regexp.MustCompile(`(key|pass|password)=\w+`) +// return key.ReplaceAllString(s, "${1}=REDACTED") +// } -// Result holds the result of running a check. +// Result holds the result of a check. type Result struct { - Infos []string - ProbabilityMalicious float64 - Errors []string + Name string + Type string + Data Checker + Info string + IsMalicious bool + Err error } -func (res Result) Print() error { - for _, i := range res.Infos { - fmt.Println(i) +type byName []Result + +func (x byName) Len() int { return len(x) } +func (x byName) Less(i, j int) bool { return x[i].Name < x[j].Name } +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +// Print prints condensed results to stdout. +func Print(results []Result) error { + sort.Sort(byName(results)) + + var malicious, total float64 + for _, r := range results { + if r.Type == "Info" { + fmt.Printf("%-15s %s\n", r.Name, r.Info) + continue + } + if r.IsMalicious { + malicious++ + } + total++ } + probabilityMalicious := malicious / total var msg string - switch { - case res.ProbabilityMalicious < 0.15: + case probabilityMalicious < 0.15: msg = fmt.Sprint(aurora.Green("Malicious")) - case res.ProbabilityMalicious < 0.50: + case probabilityMalicious < 0.50: msg = fmt.Sprint(aurora.Yellow("Malicious")) default: msg = fmt.Sprint(aurora.Red("Malicious")) } - _, err := fmt.Printf("%s\t%.0f%%\n", msg, res.ProbabilityMalicious*100) + _, err := fmt.Printf("%s\t%.0f%%\n", msg, probabilityMalicious*100) return err } -func (res Result) PrintJSON() error { +// PrintJSON prints all data from results in JSON format to stdout. +func PrintJSON(results []Result) error { + sort.Sort(byName(results)) + enc := json.NewEncoder(os.Stdout) - return enc.Encode(&res) + return enc.Encode(&results) } diff --git a/cins.go b/cins.go index a042f49..779aa54 100644 --- a/cins.go +++ b/cins.go @@ -15,6 +15,8 @@ type CINSArmy struct { CountIPs int } +func (c *CINSArmy) Name() string { return "cinsscore.com" } + // Check fills in the CINSArmy data. func (c *CINSArmy) Check(ipaddr net.IP) error { file := "/var/tmp/cins.txt" diff --git a/cmd/checkip.go b/cmd/checkip.go index 41dc55d..beaa521 100755 --- a/cmd/checkip.go +++ b/cmd/checkip.go @@ -46,11 +46,8 @@ func main() { results := checkip.Run(checkers, ipaddr) if *j { - results.PrintJSON() + checkip.PrintJSON(results) } else { - for _, e := range results.Errors { - log.Println(e) - } - results.Print() + checkip.Print(results) } } diff --git a/dns.go b/dns.go index 771031e..cd7e138 100644 --- a/dns.go +++ b/dns.go @@ -1,7 +1,6 @@ package checkip import ( - "fmt" "net" "strings" ) @@ -11,6 +10,8 @@ type DNS struct { Names []string } +func (d *DNS) Name() string { return "net.LookupAddr" } + // Check does a reverse lookup for a given IP address. func (d *DNS) Check(ipaddr net.IP) error { // NOTE: We are ignoring error. It says: "nodename nor servname @@ -22,5 +23,5 @@ func (d *DNS) Check(ipaddr net.IP) error { // Info returns interesting information from the check. func (d *DNS) Info() string { - return fmt.Sprintf("DNS names\t%s", strings.Join(d.Names, ", ")) + return strings.Join(d.Names, ", ") } diff --git a/et.go b/et.go index da232b9..0288ca6 100644 --- a/et.go +++ b/et.go @@ -14,6 +14,8 @@ type ET struct { CountIPs int } +func (e *ET) Name() string { return "rules.emergingthreats.net" } + // Check checks whether the ippaddr is not among compromised IP addresses from // The Emerging Threats Intelligence feed (ET). I found ET mentioned at // https://logz.io/blog/open-source-threat-intelligence-feeds/ diff --git a/geo.go b/geo.go index 58c6d4a..c271fa3 100644 --- a/geo.go +++ b/geo.go @@ -12,6 +12,8 @@ type Geo struct { City, Country, IsoCode string } +func (g *Geo) Name() string { return "maxmind.com" } + // Check fills in the geolocation data. The data is taken from // GeoLite2-City.mmdb file that gets downloaded and regularly updated. func (g *Geo) Check(ip net.IP) error { @@ -56,5 +58,5 @@ func (g *Geo) Info() string { if g.IsoCode == "" { g.IsoCode = "ISO code unknown" } - return fmt.Sprintf("Geolocation\t%s, %s (%s)", g.City, g.Country, g.IsoCode) + return fmt.Sprintf("%s, %s (%s)", g.City, g.Country, g.IsoCode) } diff --git a/ip.go b/ip.go index 353e1d1..7cfe0d6 100644 --- a/ip.go +++ b/ip.go @@ -13,6 +13,8 @@ type IP struct { DefaultMask net.IPMask } +func (i *IP) Name() string { return "net.IP" } + // Check fills in IP data. func (i *IP) Check(ipaddr net.IP) error { i.Private = ipaddr.IsPrivate() @@ -30,5 +32,5 @@ func (i *IP) Info() string { for _, b := range i.DefaultMask { mask = append(mask, strconv.Itoa(int(b))) } - return fmt.Sprintf("IP address\t%s, default mask %s", private, strings.Join(mask, ".")) + return fmt.Sprintf("%s, default mask %s", private, strings.Join(mask, ".")) } diff --git a/ipsum.go b/ipsum.go index c020076..1840894 100644 --- a/ipsum.go +++ b/ipsum.go @@ -14,6 +14,8 @@ type IPsum struct { NumOfBlacklists int } +func (ip *IPsum) Name() string { return "github.com/stamparm/ipsum" } + // Check checks how many blackists the IP address is found on. func (ip *IPsum) Check(ipaddr net.IP) error { file := "/var/tmp/ipsum.txt" diff --git a/otx.go b/otx.go index d1d2bb3..35b6ce9 100644 --- a/otx.go +++ b/otx.go @@ -14,6 +14,8 @@ type OTX struct { } `json:"pulse_info"` } +func (otx *OTX) Name() string { return "otx.alienvault.com" } + // Check gets data from https://otx.alienvault.com/api. func (otx *OTX) Check(ipaddr net.IP) error { apiurl := fmt.Sprintf("https://otx.alienvault.com/api/v1/indicators/IPv4/%s/", ipaddr.String()) diff --git a/shodan.go b/shodan.go index d405c31..01b21c3 100644 --- a/shodan.go +++ b/shodan.go @@ -24,6 +24,8 @@ type data []struct { Transport string `json:"transport"` // tcp, udp } +func (s *Shodan) Name() string { return "shodan.io" } + // Check fills in Shodan data for a given IP address. Its get the data from // https://api.shodan.io. func (s *Shodan) Check(ipaddr net.IP) error { @@ -92,5 +94,5 @@ func (s *Shodan) Info() string { portStr += ":" } - return fmt.Sprintf("OS and ports\t%s, %d open %s %s", os, len(portInfo), portStr, strings.Join(portInfo, ", ")) + return fmt.Sprintf("%s, %d open %s %s", os, len(portInfo), portStr, strings.Join(portInfo, ", ")) } diff --git a/threatcrowd.go b/threatcrowd.go index 85c9996..1904942 100644 --- a/threatcrowd.go +++ b/threatcrowd.go @@ -12,6 +12,8 @@ type ThreatCrowd struct { Votes int `json:"votes"` } +func (t *ThreatCrowd) Name() string { return "threatcrowd.org" } + // Check retrieves information from // https://www.threatcrowd.org/searchApi/v2/ip/report. func (t *ThreatCrowd) Check(ipaddr net.IP) error { diff --git a/virustotal.go b/virustotal.go index 9ef4063..9ef56a6 100644 --- a/virustotal.go +++ b/virustotal.go @@ -23,6 +23,8 @@ type VirusTotal struct { } `json:"data"` } +func (vt *VirusTotal) Name() string { return "virustotal.com" } + // Check fills in data about ippaddr from https://www.virustotal.com/api func (vt *VirusTotal) Check(ipaddr net.IP) error { apiKey, err := getConfigValue("VIRUSTOTAL_API_KEY")