zgrab2/input.go

132 lines
3.3 KiB
Go

package zgrab2
import (
"encoding/csv"
"fmt"
"io"
"net"
"strings"
log "github.com/sirupsen/logrus"
)
// ParseCSVTarget takes a record from a CSV-format input file and
// returns the specified ipnet, domain, and tag, or an error.
//
// ZGrab2 input files have three fields:
// IP, DOMAIN, TAG
//
// Each line specifies a target to scan by its IP address, domain
// name, or both, as well as an optional tag used to determine which
// scanners will be invoked.
//
// A CIDR block may be provided in the IP field, in which case the
// framework expands the record into targets for every address in the
// block.
//
// Trailing empty fields may be omitted.
// Comment lines begin with #, and empty lines are ignored.
//
func ParseCSVTarget(fields []string) (ipnet *net.IPNet, domain string, tag string, err error) {
for i := range fields {
fields[i] = strings.TrimSpace(fields[i])
}
if len(fields) > 0 && fields[0] != "" {
if ip := net.ParseIP(fields[0]); ip != nil {
ipnet = &net.IPNet{IP: ip}
} else if _, cidr, er := net.ParseCIDR(fields[0]); er == nil {
ipnet = cidr
} else if len(fields) != 1 {
err = fmt.Errorf("can't parse %q as an IP address or CIDR block", fields[0])
return
}
}
if len(fields) > 1 {
domain = fields[1]
}
if len(fields) > 2 {
tag = fields[2]
}
if len(fields) > 3 {
err = fmt.Errorf("too many fields: %q", fields)
return
}
// For legacy reasons, we also allow targets of the form:
// DOMAIN
if ipnet == nil && len(fields) == 1 {
domain = fields[0]
}
if ipnet == nil && domain == "" {
err = fmt.Errorf("record doesn't specify an address, network, or domain: %v", fields)
return
}
return
}
func incrementIP(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}
func duplicateIP(ip net.IP) net.IP {
dup := make(net.IP, len(ip))
copy(dup, ip)
return dup
}
// InputTargetsCSV is an InputTargetsFunc that calls GetTargetsCSV with
// the CSV file provided on the command line.
func InputTargetsCSV(ch chan<- ScanTarget) error {
return GetTargetsCSV(config.inputFile, ch)
}
// GetTargetsCSV reads targets from a CSV source, generates ScanTargets,
// and delivers them to the provided channel.
func GetTargetsCSV(source io.Reader, ch chan<- ScanTarget) error {
csvreader := csv.NewReader(source)
csvreader.Comment = '#'
csvreader.FieldsPerRecord = -1 // variable
for {
fields, err := csvreader.Read()
if err == io.EOF {
break
} else if err != nil {
return err
}
if len(fields) == 0 {
continue
}
ipnet, domain, tag, err := ParseCSVTarget(fields)
if err != nil {
log.Errorf("parse error, skipping: %v", err)
continue
}
var ip net.IP
if ipnet != nil {
if ipnet.Mask != nil {
// expand CIDR block into one target for each IP
for ip = ipnet.IP.Mask(ipnet.Mask); ipnet.Contains(ip); incrementIP(ip) {
ch <- ScanTarget{IP: duplicateIP(ip), Domain: domain, Tag: tag}
}
continue
} else {
ip = ipnet.IP
}
}
ch <- ScanTarget{IP: ip, Domain: domain, Tag: tag}
}
return nil
}
// InputTargetsFunc is a function type for target input functions.
//
// A function of this type generates ScanTargets on the provided
// channel. It returns nil if there are no further inputs or error.
type InputTargetsFunc func(ch chan<- ScanTarget) error