use masscan for k8scan

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-07-13 20:17:36 -04:00
parent cabc5dad9c
commit cf4dd75b5c
No known key found for this signature in database
GPG Key ID: 18F3685C0022BFF3

View File

@ -11,7 +11,9 @@ import (
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"sync"
@ -36,8 +38,11 @@ var (
cidr string
defaultPorts = intSlice{80, 443, 8001, 9001}
ports intSlice
defaultPorts = intSlice{80, 443, 8001, 9001}
originalPorts string
ports intSlice
useMasscan bool
mailgunDomain string
mailgunAPIKey string
@ -62,6 +67,8 @@ func (i *intSlice) String() (out string) {
}
func (i *intSlice) Set(value string) error {
originalPorts = value
// Set the default if nothing was given.
if len(value) <= 0 {
*i = defaultPorts
@ -110,6 +117,8 @@ func init() {
flag.StringVar(&cidr, "cidr", defaultCIDR, "IP CIDR to scan")
flag.Var(&ports, "ports", fmt.Sprintf("Ports to scan (ex. 80-443 or 80,443,8080 or 1-20,22,80-443) (default %q)", defaultPorts.String()))
flag.BoolVar(&useMasscan, "masscan", true, "Use masscan binary for scanning (this is faster than using pure golang)")
flag.StringVar(&mailgunAPIKey, "mailgun-api-key", "", "Mailgun API Key to use for sending email (optional)")
flag.StringVar(&mailgunDomain, "mailgun-domain", "", "Mailgun Domain to use for sending email (optional)")
flag.StringVar(&emailRecipient, "email-recipient", "", "Recipient for email notifications (optional)")
@ -122,10 +131,15 @@ func init() {
flag.Parse()
// set log level
// Set the log level.
if debug {
logrus.SetLevel(logrus.DebugLevel)
}
// Set the default ports.
if len(ports) <= 0 {
ports = defaultPorts
}
}
func main() {
@ -145,29 +159,56 @@ func main() {
log.SetFlags(0)
log.SetOutput(ioutil.Discard)
logrus.Infof("Scanning for Kubernetes Dashboards and API Servers on %s over port range %#v", cidr, ports)
logrus.Infof("Scanning for Kubernetes Dashboards and API Servers on %s over port range %s", cidr, originalPorts)
if len(mailgunDomain) > 0 && len(mailgunAPIKey) > 0 && len(emailRecipient) > 0 {
logrus.Infof("Using Mailgun Domain %s, API Key %s to send emails to %s", mailgunDomain, mailgunAPIKey, emailRecipient)
}
logrus.Infof("This may take a bit...")
startTime := time.Now()
var (
startTime = time.Now()
wg sync.WaitGroup
)
ip, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
logrus.Fatal(err)
}
if useMasscan {
m, err := doMasscan()
if err != nil {
logrus.Fatal(err)
}
var wg sync.WaitGroup
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
for _, port := range ports {
wg.Add(1)
go func(ip string, port int) {
defer wg.Done()
for _, result := range m {
for _, port := range result.Ports {
wg.Add(1)
go func(ip string, port int) {
defer wg.Done()
scanIP(ip, port)
scanIP(ip, port)
}(ip.String(), port)
}(result.IP, port.Port)
}
}
} else {
ip, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
logrus.Fatal(err)
}
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
for _, port := range ports {
wg.Add(1)
go func(ip string, port int) {
defer wg.Done()
// Check if the port is open.
ok := portOpen(ip, port)
if !ok {
return
}
scanIP(ip, port)
}(ip.String(), port)
}
}
}
@ -178,12 +219,6 @@ func main() {
}
func scanIP(ip string, port int) {
// Check if the port is open.
ok := portOpen(ip, port)
if !ok {
return
}
// Check if it's a kubernetes dashboard.
ok, uri := isKubernetesDashboard(ip, port)
if !ok {
@ -286,6 +321,58 @@ type OrganizationJSON struct {
Reference string `json:"$,omitempty"`
}
// MasscanResult holds the masscan results data struct.
// Looks like:
// [
// {
// "ip": "104.198.238.41",
// "timestamp": "1531524211",
// "ports": [
// {
// "port": 22,
// "proto": "tcp",
// "status": "open",
// "reason": "syn-ack",
// "ttl": 56
// }
// ]
// },
// ...
// ]
type MasscanResult struct {
IP string `json:"ip,omitempty"`
Timestamp MasscanTime `json:"timestamp,omitempty"`
Ports []MasscanPort `json:"ports,omitempty"`
}
// MasscanPort defines the data struct for a masscan port.
type MasscanPort struct {
Port int `json:"port,omitempty"`
Protocol string `json:"proto,omitempty"`
Status string `json:"status,omitempty"`
Reason string `json:"reason,omitempty"`
TTL int `json:"ttl,omitempty"`
}
// MasscanTime is the time format returned by masscan.
type MasscanTime struct {
time.Time
}
// UnmarshalJSON sets MasscanTime correctly from a string.
func (t *MasscanTime) UnmarshalJSON(b []byte) error {
s := strings.Trim(strings.TrimSpace(string(b)), `"`)
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return err
}
*t = MasscanTime{time.Unix(i, 0)}
return nil
}
func getIPInfo(ip string) (b ARINResponse, err error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(arinAPIEndpoint, ip), nil)
if err != nil {
@ -346,3 +433,52 @@ ARIN: %s
return nil
}
func doMasscan() ([]MasscanResult, error) {
// Create a temporary directory for the output.
dir, err := ioutil.TempDir(os.TempDir(), "masscan")
if err != nil {
return nil, fmt.Errorf("creating temporary directory failed: %v", err)
}
defer os.RemoveAll(dir)
file := filepath.Join(dir, "scan.json")
cmd := exec.Command("masscan",
fmt.Sprintf("-p%s", ports.String()),
cidr,
"--output-format", "json",
"--output-file", file,
"--rate", "1000000",
"--exclude", "255.255.255.255",
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
logrus.Infof("Running masscan command: `%s`", strings.Join(cmd.Args, " "))
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("running masscan failed: %v", err)
}
b, err := cleanMasscanOutputFile(file)
if err != nil {
return nil, fmt.Errorf("cleaning up masscan file failed: %v", err)
}
m := []MasscanResult{}
if err := json.Unmarshal(b, &m); err != nil {
return nil, fmt.Errorf("unmarshal json failed: %v", err)
}
logrus.Debugf("masscan result: %#v", m)
return m, nil
}
func cleanMasscanOutputFile(file string) ([]byte, error) {
b, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
return []byte(strings.TrimSuffix(strings.TrimSpace(string(b)), ",\n]") + "]"), nil
}