diff --git a/Makefile b/Makefile
index 03c472c..d195149 100644
--- a/Makefile
+++ b/Makefile
@@ -2,10 +2,10 @@ install:
go install cmd/checkip.go
run: install
- checkip 140.82.114.4
+ checkip 91.228.166.47
+ checkip 209.141.33.65
checkip 218.92.0.158
- checkip 92.118.160.17
- checkip -j 92.118.160.17 | jq -r '.[] | select(.Type=="Sec") | "\(.Name) => \(.IsMalicious)"'
+ checkip -j 218.92.0.158 | jq -r '.[] | select(.Type=="Sec" or .Type=="InfoSec") | "\(.Name) => \(.IsMalicious)"'
PLATFORMS := linux/amd64 darwin/amd64 linux/arm windows/amd64
diff --git a/README.md b/README.md
index 03be9d8..0a6db55 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
# checkip
`checkip` is a CLI tool and library that checks an IP address using various
-public services.
+public services. It provides generic and security information.
-
+
## Installation and configuration
diff --git a/abuseipdb.go b/abuseipdb.go
index 8cc9c35..bcaded1 100644
--- a/abuseipdb.go
+++ b/abuseipdb.go
@@ -70,3 +70,7 @@ func (a *AbuseIPDB) Check(ipaddr net.IP) error {
func (a *AbuseIPDB) IsMalicious() bool {
return a.Data.TotalReports > 0 && !a.Data.IsWhitelisted && a.Data.AbuseConfidenceScore > 25
}
+
+func (a *AbuseIPDB) Info() string {
+ return fmt.Sprintf("domain: %s, usage type: %s", na(a.Data.Domain), na(a.Data.UsageType))
+}
diff --git a/as.go b/as.go
index c6aa682..78cde15 100644
--- a/as.go
+++ b/as.go
@@ -41,7 +41,7 @@ func (a *AS) Check(ipaddr net.IP) error {
// Info returns interesting information from the check.
func (a *AS) Info() string {
- return a.Description
+ return fmt.Sprintf("AS description: %s", na(a.Description))
}
// search searches the ippadrr in tsvFile and if found fills in AS data.
diff --git a/checkip.go b/checkip.go
index 270d2c0..b85ac9c 100644
--- a/checkip.go
+++ b/checkip.go
@@ -35,6 +35,11 @@ type SecChecker interface {
Checker
}
+type InfoSecChecker interface {
+ InfoChecker
+ SecChecker
+}
+
// Result holds the result of a check.
type Result struct {
Name string
@@ -61,6 +66,9 @@ func Run(checkers []Checker, ipaddr net.IP) []Result {
errMsg = redactSecrets(err.Error())
}
switch v := c.(type) {
+ case InfoSecChecker:
+ r := Result{Name: v.String(), Type: "InfoSec", Data: v, Info: v.Info(), IsMalicious: v.IsMalicious(), Err: err, ErrMsg: errMsg}
+ res = append(res, r)
case InfoChecker:
r := Result{Name: v.String(), Type: "Info", Data: v, Info: v.Info(), Err: err, ErrMsg: errMsg}
res = append(res, r)
@@ -76,11 +84,6 @@ func Run(checkers []Checker, ipaddr net.IP) []Result {
return res
}
-func redactSecrets(s string) string {
- key := regexp.MustCompile(`(key|pass|password)=\w+`)
- return key.ReplaceAllString(s, "${1}=REDACTED")
-}
-
type byName []Result
func (x byName) Len() int { return len(x) }
@@ -96,14 +99,15 @@ func Print(results []Result) error {
if r.Err != nil {
log.Print(r.ErrMsg)
}
- if r.Type == "Info" {
+ if r.Type == "Info" || r.Type == "InfoSec" {
fmt.Printf("%-15s %s\n", r.Name, r.Info)
- continue
}
- if r.IsMalicious {
- malicious++
+ if r.Type == "Sec" || r.Type == "InfoSec" {
+ totalSec++
+ if r.IsMalicious {
+ malicious++
+ }
}
- totalSec++
}
probabilityMalicious := malicious / totalSec
@@ -128,3 +132,25 @@ func PrintJSON(results []Result) error {
enc := json.NewEncoder(os.Stdout)
return enc.Encode(&results)
}
+
+func redactSecrets(s string) string {
+ key := regexp.MustCompile(`(key|pass|password)=\w+`)
+ return key.ReplaceAllString(s, "${1}=REDACTED")
+}
+
+func na(s string) string {
+ if s == "" {
+ return "n/a"
+ }
+ return s
+}
+
+func nonEmpty(strings ...string) []string {
+ var ss []string
+ for _, s := range strings {
+ if s != "" {
+ ss = append(ss, s)
+ }
+ }
+ return ss
+}
diff --git a/checkip.png b/checkip.png
index c1ac511..1f3e744 100644
Binary files a/checkip.png and b/checkip.png differ
diff --git a/cmd/checkip.go b/cmd/checkip.go
index df148f2..12e4871 100755
--- a/cmd/checkip.go
+++ b/cmd/checkip.go
@@ -35,9 +35,7 @@ func main() {
&checkip.Blocklist{},
&checkip.CINSArmy{},
&checkip.DNS{},
- &checkip.ET{},
&checkip.Geo{},
- &checkip.IP{},
&checkip.IPsum{},
&checkip.OTX{},
&checkip.Shodan{},
diff --git a/dns.go b/dns.go
index 597b53e..ab57ba5 100644
--- a/dns.go
+++ b/dns.go
@@ -1,6 +1,7 @@
package checkip
import (
+ "fmt"
"net"
"strings"
)
@@ -23,5 +24,9 @@ func (d *DNS) Check(ipaddr net.IP) error {
// Info returns interesting information from the check.
func (d *DNS) Info() string {
- return strings.Join(d.Names, ", ")
+ msg := "DNS name"
+ if len(d.Names) > 1 {
+ msg += "s"
+ }
+ return fmt.Sprintf("%s: %s", msg, na(strings.Join(d.Names, ", ")))
}
diff --git a/et.go b/et.go
deleted file mode 100644
index 9674e78..0000000
--- a/et.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package checkip
-
-import (
- "bufio"
- "fmt"
- "net"
- "os"
-)
-
-// ET (Emerging Threats) holds information about an IP address from
-// rules.emergingthreats.net
-type ET struct {
- CompromisedIP bool
- CountIPs int
-}
-
-func (e *ET) String() 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/
-func (e *ET) Check(ipaddr net.IP) error {
- file := "/var/tmp/et.txt"
- url := "https://rules.emergingthreats.net/blockrules/compromised-ips.txt"
-
- if err := updateFile(file, url, ""); err != nil {
- return fmt.Errorf("can't update %s from %s: %v", file, url, err)
- }
-
- if err := e.search(ipaddr, file); err != nil {
- return fmt.Errorf("searching %s in %s: %v", ipaddr, file, err)
- }
-
- return nil
-}
-
-func (e *ET) IsMalicious() bool {
- return e.CompromisedIP
-}
-
-// search searches the ippadrr in filename fills in ET data.
-func (e *ET) search(ipaddr net.IP, filename string) error {
- file, err := os.Open(filename)
- if err != nil {
- return err
- }
-
- s := bufio.NewScanner(file)
- for s.Scan() {
- line := s.Text()
- e.CountIPs++
- if line == ipaddr.String() {
- e.CompromisedIP = true
- }
- }
- if s.Err() != nil {
- return err
- }
-
- return nil
-}
diff --git a/geo.go b/geo.go
index 3f0ca41..1562ac9 100644
--- a/geo.go
+++ b/geo.go
@@ -49,14 +49,5 @@ func (g *Geo) Check(ip net.IP) error {
// Info returns interesting information from the check.
func (g *Geo) Info() string {
- if g.City == "" {
- g.City = "city unknown"
- }
- if g.Country == "" {
- g.Country = "country unknown"
- }
- if g.IsoCode == "" {
- g.IsoCode = "ISO code unknown"
- }
- return fmt.Sprintf("%s, %s (%s)", g.City, g.Country, g.IsoCode)
+ return fmt.Sprintf("city: %s, country: %s, ISO code: %s", na(g.City), na(g.Country), na(g.IsoCode))
}
diff --git a/ip.go b/ip.go
deleted file mode 100644
index ae62e20..0000000
--- a/ip.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package checkip
-
-import (
- "fmt"
- "net"
- "strconv"
- "strings"
-)
-
-// IP holds information from net.IP.
-type IP struct {
- Private bool
- DefaultMask net.IPMask
-}
-
-func (i *IP) String() string { return "net.IP" }
-
-// Check fills in IP data.
-func (i *IP) Check(ipaddr net.IP) error {
- i.Private = ipaddr.IsPrivate()
- i.DefaultMask = ipaddr.DefaultMask()
- return nil
-}
-
-// Info returns interesting information from the check.
-func (i *IP) Info() string {
- private := "RFC 1918 private"
- if !i.Private {
- private = "not " + private
- }
- var mask []string
- for _, b := range i.DefaultMask {
- mask = append(mask, strconv.Itoa(int(b)))
- }
- return fmt.Sprintf("%s, default mask %s", private, strings.Join(mask, "."))
-}
diff --git a/shodan.go b/shodan.go
index f8f6afe..068c2c0 100644
--- a/shodan.go
+++ b/shodan.go
@@ -13,7 +13,7 @@ import (
type Shodan struct {
Org string `json:"org"`
Data data `json:"data"`
- Os string `json:"os"`
+ OS string `json:"os"`
Ports []int `json:"ports"`
}
@@ -61,11 +61,6 @@ func (x byPort) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
// Info returns interesting information from the check.
func (s *Shodan) Info() string {
- os := "OS unknown"
- if s.Os != "" {
- os = s.Os
- }
-
var portInfo []string
sort.Sort(byPort(s.Data))
for _, d := range s.Data {
@@ -82,7 +77,8 @@ func (s *Shodan) Info() string {
if product == "" && version == "" {
portInfo = append(portInfo, fmt.Sprintf("%s/%d", d.Transport, d.Port))
} else {
- portInfo = append(portInfo, fmt.Sprintf("%s/%d (%s, %s)", d.Transport, d.Port, product, version))
+ ss := nonEmpty(product, version)
+ portInfo = append(portInfo, fmt.Sprintf("%s/%d (%s)", d.Transport, d.Port, strings.Join(ss, ", ")))
}
}
@@ -94,5 +90,5 @@ func (s *Shodan) Info() string {
portStr += ":"
}
- return fmt.Sprintf("%s, %d open %s %s", os, len(portInfo), portStr, strings.Join(portInfo, ", "))
+ return fmt.Sprintf("OS: %s, %d open %s %s", na(s.OS), len(portInfo), portStr, strings.Join(portInfo, ", "))
}
diff --git a/virustotal.go b/virustotal.go
index a1da7c0..84edb63 100644
--- a/virustotal.go
+++ b/virustotal.go
@@ -5,13 +5,16 @@ import (
"fmt"
"net"
"net/http"
+ "strings"
)
// VirusTotal holds information about an IP address from virustotal.com.
type VirusTotal struct {
Data struct {
Attributes struct {
- Reputation int `json:"reputation"`
+ Reputation int `json:"reputation"`
+ Network string `json:"network"`
+ ASowner string `json:"as_owner"`
LastAnalysisStats struct {
Harmless int `json:"harmless"`
Malicious int `json:"malicious"`
@@ -19,6 +22,11 @@ type VirusTotal struct {
Timeout int `json:"timeout"`
Undetected int `json:"undetected"`
} `json:"last_analysis_stats"`
+ LastHTTPScert struct {
+ Extensions struct {
+ SAN []string `json:"subject_alternative_name"`
+ } `json:"extensions"`
+ } `json:"last_https_certificate"`
} `json:"attributes"`
} `json:"data"`
}
@@ -58,3 +66,7 @@ func (vt *VirusTotal) IsMalicious() bool {
// https://developers.virustotal.com/reference#ip-object
return vt.Data.Attributes.Reputation < 0
}
+
+func (vt *VirusTotal) Info() string {
+ return fmt.Sprintf("AS onwer: %s, network: %s, SAN: %s", na(vt.Data.Attributes.ASowner), na(vt.Data.Attributes.Network), na(strings.Join(vt.Data.Attributes.LastHTTPScert.Extensions.SAN, ", ")))
+}