diff --git a/input.go b/input.go index 88305602..d3640004 100644 --- a/input.go +++ b/input.go @@ -5,29 +5,31 @@ import ( "fmt" "io" "net" + "strconv" "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. +// returns the specified ipnet, domain, tag and port or an error. // -// ZGrab2 input files have three fields: -// IP, DOMAIN, TAG +// ZGrab2 input files have four fields: +// +// IP, DOMAIN, TAG, PORT // // 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 +// name or both, as well as an optional tag used to determine which // scanners will be invoked. // +// Port number has been added to the end of the line for compatibility reasons. // 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) { +func ParseCSVTarget(fields []string) (ipnet *net.IPNet, domain string, tag string, port string, err error) { for i := range fields { fields[i] = strings.TrimSpace(fields[i]) } @@ -47,7 +49,11 @@ func ParseCSVTarget(fields []string) (ipnet *net.IPNet, domain string, tag strin if len(fields) > 2 { tag = fields[2] } + // Use string for port to allow empty port if len(fields) > 3 { + port = fields[3] + } + if len(fields) > 4 { err = fmt.Errorf("too many fields: %q", fields) return } @@ -102,24 +108,41 @@ func GetTargetsCSV(source io.Reader, ch chan<- ScanTarget) error { if len(fields) == 0 { continue } - ipnet, domain, tag, err := ParseCSVTarget(fields) + ipnet, domain, tag, port, err := ParseCSVTarget(fields) if err != nil { log.Errorf("parse error, skipping: %v", err) continue } var ip net.IP + var port_uint uint + if port != "" { + port_int, err := strconv.Atoi(port) + port_uint = uint(port_int) + if err != nil { + log.Errorf("parse error, skipping: %v", err) + continue + } + } 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} + if port == "" { + ch <- ScanTarget{IP: duplicateIP(ip), Domain: domain, Tag: tag} + } else { + ch <- ScanTarget{IP: duplicateIP(ip), Domain: domain, Tag: tag, Port: &port_uint} + } } continue } else { ip = ipnet.IP } } - ch <- ScanTarget{IP: ip, Domain: domain, Tag: tag} + if port == "" { + ch <- ScanTarget{IP: ip, Domain: domain, Tag: tag} + } else { + ch <- ScanTarget{IP: ip, Domain: domain, Tag: tag, Port: &port_uint} + } } return nil } diff --git a/input_test.go b/input_test.go index 892cef16..b9d9d9a5 100644 --- a/input_test.go +++ b/input_test.go @@ -39,8 +39,27 @@ func TestParseCSVTarget(t *testing.T) { ipnet *net.IPNet domain string tag string + port string success bool }{ + // IP DOMAIN TAG PORT + { + fields: []string{"10.0.0.1", "example.com", "tag", "443"}, + ipnet: parseIP("10.0.0.1"), + domain: "example.com", + tag: "tag", + port: "443", + success: true, + }, + // IP DOMAIN TAG PORT + { + fields: []string{"10.0.0.1", "example.com", "tag"}, + ipnet: parseIP("10.0.0.1"), + domain: "example.com", + tag: "tag", + port: "", + success: true, + }, // IP DOMAIN TAG { fields: []string{"10.0.0.1", "example.com", "tag"}, @@ -129,14 +148,14 @@ func TestParseCSVTarget(t *testing.T) { } for _, test := range tests { - ipnet, domain, tag, err := ParseCSVTarget(test.fields) + ipnet, domain, tag, port, err := ParseCSVTarget(test.fields) if (err == nil) != test.success { t.Errorf("wrong error status (got err=%v, success should be %v): %q", err, test.success, test.fields) return } if err == nil { - if ipnetString(ipnet) != ipnetString(test.ipnet) || domain != test.domain || tag != test.tag { - t.Errorf("wrong result (got %v,%v,%v; expected %v,%v,%v): %q", ipnetString(ipnet), domain, tag, ipnetString(test.ipnet), test.domain, test.tag, test.fields) + if ipnetString(ipnet) != ipnetString(test.ipnet) || domain != test.domain || tag != test.tag || port != test.port { + t.Errorf("wrong result (got %v,%v,%v,%v ; expected %v,%v,%v,%v): %q", ipnetString(ipnet), domain, tag, port, ipnetString(test.ipnet), test.domain, test.tag, test.port, test.fields) return } } @@ -150,8 +169,11 @@ func TestGetTargetsCSV(t *testing.T) { 10.0.0.1 ,example.com example.com -2.2.2.2/30,, tag` - +2.2.2.2/30,, tag +10.0.0.1,example.com,tag,443 +10.0.0.1,,,443 +` + port := uint(443) expected := []ScanTarget{ ScanTarget{IP: net.ParseIP("10.0.0.1"), Domain: "example.com", Tag: "tag"}, ScanTarget{IP: net.ParseIP("10.0.0.1"), Domain: "example.com"}, @@ -162,6 +184,8 @@ example.com ScanTarget{IP: net.ParseIP("2.2.2.1"), Tag: "tag"}, ScanTarget{IP: net.ParseIP("2.2.2.2"), Tag: "tag"}, ScanTarget{IP: net.ParseIP("2.2.2.3"), Tag: "tag"}, + ScanTarget{IP: net.ParseIP("10.0.0.1"), Domain: "example.com", Tag: "tag", Port: &port}, + ScanTarget{IP: net.ParseIP("10.0.0.1"), Port: &port}, } ch := make(chan ScanTarget, 0) diff --git a/processing.go b/processing.go index 3bade603..bcde63f0 100644 --- a/processing.go +++ b/processing.go @@ -13,6 +13,7 @@ import ( // Grab contains all scan responses for a single host type Grab struct { IP string `json:"ip,omitempty"` + Port uint `json:"port,omitempty"` Domain string `json:"domain,omitempty"` Data map[string]ScanResponse `json:"data,omitempty"` } @@ -117,12 +118,16 @@ func (target *ScanTarget) OpenUDP(flags *BaseFlags, udp *UDPFlags) (net.Conn, er // scan responses. func BuildGrabFromInputResponse(t *ScanTarget, responses map[string]ScanResponse) *Grab { var ipstr string - + var port uint if t.IP != nil { ipstr = t.IP.String() } + if t.Port != nil { + port = *t.Port + } return &Grab{ IP: ipstr, + Port: port, Domain: t.Domain, Data: responses, }