From 3bb649f17544c81bfe928a021cd3654fce7e1dff Mon Sep 17 00:00:00 2001 From: Jeff Cody Date: Tue, 28 Jan 2020 17:27:24 -0500 Subject: [PATCH 01/16] Add custom fake resolver This adds a custom resolver, that will always resolve to the specified ip address. The intended usage is for when doing name-based scans, but have a specified IP address as well. This will provide a resolver that can be added to a Dialer, that will cause all DNS lookups to match the specified IP address. --- fake_resolver.go | 166 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 fake_resolver.go diff --git a/fake_resolver.go b/fake_resolver.go new file mode 100644 index 00000000..a72a34e7 --- /dev/null +++ b/fake_resolver.go @@ -0,0 +1,166 @@ +// Fake DNS lookups, so that they always returned the specified +// IP +// +// Inspired by the golang net/dnsclient_unix_test.go code +package zgrab2 + +import ( + "context" + "errors" + "fmt" + "golang.org/x/net/dns/dnsmessage" + "net" + "time" +) + +// For a given IP, create a new Resolver that wraps a fake +// DNS server. This resolver will always return an IP that +// is represented by "ipstr", for DNS queries of the same +// IP type. Otherwise, it will return a DNS lookup error. +func NewFakeResolver(ipstr string) (*net.Resolver, error) { + ip := net.ParseIP(ipstr) + if len(ip) < 4 { + return nil, fmt.Errorf("Fake resolver can't use non-IP '%s'", ipstr) + } + fDNS := FakeDNSServer{ + IP: ip, + } + return &net.Resolver{ + PreferGo: true, // Needed to force the use of the Go internal resolver + Dial: fDNS.DialContext, + }, nil +} + +type FakeDNSServer struct { + // Any domain name will resolve to this IP. It can be either ipv4 or ipv6 + IP net.IP +} + +// For a given DNS query, return the hard-coded IP that is part of +// FakeDNSServer. +// +// It will work with either ipv4 or ipv6 addresses; if a TypeA question +// is received, we will only return the IP if what we have to return is +// ipv4. The same for TypeAAAA and ipv6. +func (f *FakeDNSServer) fakeDNS(s string, dmsg dnsmessage.Message) (r dnsmessage.Message, err error) { + + r = dnsmessage.Message{ + Header: dnsmessage.Header{ + ID: dmsg.ID, + Response: true, + }, + Questions: dmsg.Questions, + } + ipv6 := f.IP.To16() + ipv4 := f.IP.To4() + switch t := dmsg.Questions[0].Type; { + case t == dnsmessage.TypeA && ipv4 != nil: + var ip [4]byte + copy(ip[:], []byte(ipv4)) + r.Answers = []dnsmessage.Resource{ + { + Header: dnsmessage.ResourceHeader{ + Name: dmsg.Questions[0].Name, + Type: dnsmessage.TypeA, + Class: dnsmessage.ClassINET, + Length: 4, + }, + Body: &dnsmessage.AResource{ + A: ip, + }, + }, + } + case t == dnsmessage.TypeAAAA && ipv4 == nil: + var ip [16]byte + copy(ip[:], []byte(ipv6)) + r.Answers = []dnsmessage.Resource{ + { + Header: dnsmessage.ResourceHeader{ + Name: dmsg.Questions[0].Name, + Type: dnsmessage.TypeAAAA, + Class: dnsmessage.ClassINET, + Length: 16, + }, + Body: &dnsmessage.AAAAResource{ + AAAA: ip, + }, + }, + } + default: + r.Header.RCode = dnsmessage.RCodeNameError + } + + return r, nil +} + +// This merely wraps a custom net.Conn, that is only good for DNS +// messages +func (f *FakeDNSServer) DialContext(ctx context.Context, network, + address string) (net.Conn, error) { + + conn := &fakeDNSPacketConn{ + fakeDNSConn: fakeDNSConn{ + server: f, + network: network, + address: address, + }, + } + return conn, nil +} + +type fakeDNSConn struct { + net.Conn + server *FakeDNSServer + network string + address string + dmsg dnsmessage.Message +} + +func (fc *fakeDNSConn) Read(b []byte) (int, error) { + resp, err := fc.server.fakeDNS(fc.address, fc.dmsg) + if err != nil { + return 0, err + } + + bb := make([]byte, 2, 514) + bb, err = resp.AppendPack(bb) + if err != nil { + return 0, fmt.Errorf("cannot marshal DNS message: %v", err) + } + + bb = bb[2:] + if len(b) < len(bb) { + return 0, errors.New("read would fragment DNS message") + } + + copy(b, bb) + return len(bb), nil +} + +func (fc *fakeDNSConn) Write(b []byte) (int, error) { + if fc.dmsg.Unpack(b) != nil { + return 0, fmt.Errorf("cannot unmarshal DNS message fake %s (%d)", fc.network, len(b)) + } + return len(b), nil +} + +func (fc *fakeDNSConn) SetDeadline(deadline time.Time) error { + return nil +} + +func (fc *fakeDNSConn) Close() error { + return nil +} + +type fakeDNSPacketConn struct { + net.PacketConn + fakeDNSConn +} + +func (f *fakeDNSPacketConn) SetDeadline(deadline time.Time) error { + return nil +} + +func (f *fakeDNSPacketConn) Close() error { + return f.fakeDNSConn.Close() +} From 82da88e34df24d31386a5f4c829c67a31db238f6 Mon Sep 17 00:00:00 2001 From: Jeff Cody Date: Fri, 31 Jan 2020 14:40:36 -0500 Subject: [PATCH 02/16] Let HTTP scanner use the custom resolver If both an IP address and a domain are specified for a scan, have the HTTP scanner use a fake resolver in the DialContext, so that we always scan the intended IP and Domain name pair. However, make sure redirects still function as normal, so only use our fake resolver if the domain name matches the original targeted domain name. In addition, the custom resolver is only used if the network specified is one that supports domain names. --- modules/http/scanner.go | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/modules/http/scanner.go b/modules/http/scanner.go index 4292ca9f..e8033a70 100644 --- a/modules/http/scanner.go +++ b/modules/http/scanner.go @@ -158,12 +158,40 @@ func (scan *scan) withDeadlineContext(ctx context.Context) context.Context { // Dial a connection using the configured timeouts, as well as the global deadline, and on success, // add the connection to the list of connections to be cleaned up. -func (scan *scan) dialContext(ctx context.Context, net string, addr string) (net.Conn, error) { +func (scan *scan) dialContext(ctx context.Context, network string, addr string) (net.Conn, error) { dialer := zgrab2.GetTimeoutConnectionDialer(scan.scanner.config.Timeout) + switch network { + case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": + // If the scan is for a specific IP, and a domain name is provided, we + // don't want to just let the http library resolve the domain. Create + // a fake resolver that we will use, that always returns the IP we are + // given to scan. + if scan.target.IP != nil && scan.target.Domain != "" { + host, _, err := net.SplitHostPort(addr) + if err != nil { + log.Errorf("http/scanner.go dialContext: unable to split host:port '%s'", addr) + log.Errorf("No fake resolver, IP address may be incorrect: %s", err) + } else { + // In the case of redirects, we don't want to blindly use the + // IP we were given to scan, however. Only use the fake + // resolver if the domain originally specified for the scan + // target matches the current address being looked up in this + // DialContext. + if host == scan.target.Domain { + resolver, err := zgrab2.NewFakeResolver(scan.target.IP.String()) + if err != nil { + return nil, err + } + dialer.Dialer.Resolver = resolver + } + } + } + } + timeoutContext, _ := context.WithTimeout(context.Background(), scan.scanner.config.Timeout) - conn, err := dialer.DialContext(scan.withDeadlineContext(timeoutContext), net, addr) + conn, err := dialer.DialContext(scan.withDeadlineContext(timeoutContext), network, addr) if err != nil { return nil, err } @@ -210,7 +238,8 @@ func redirectsToLocalhost(host string) bool { return false } -// Taken from zgrab/zlib/grabber.go -- get a CheckRedirect callback that uses the redirectToLocalhost and MaxRedirects config +// Taken from zgrab/zlib/grabber.go -- get a CheckRedirect callback that uses +// the redirectToLocalhost and MaxRedirects config func (scan *scan) getCheckRedirect() func(*http.Request, *http.Response, []*http.Request) error { return func(req *http.Request, res *http.Response, via []*http.Request) error { if !scan.scanner.config.FollowLocalhostRedirects && redirectsToLocalhost(req.URL.Hostname()) { From 0b6845eed84febe73c2cc882b6beb20021efcd32 Mon Sep 17 00:00:00 2001 From: Jeff Cody Date: Fri, 31 Jan 2020 23:06:38 -0500 Subject: [PATCH 03/16] Update header comment for fake_resolver.go --- fake_resolver.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fake_resolver.go b/fake_resolver.go index a72a34e7..00578db4 100644 --- a/fake_resolver.go +++ b/fake_resolver.go @@ -1,7 +1,3 @@ -// Fake DNS lookups, so that they always returned the specified -// IP -// -// Inspired by the golang net/dnsclient_unix_test.go code package zgrab2 import ( @@ -13,6 +9,9 @@ import ( "time" ) +// Fake DNS Resolver, to force a DNS lookup to return a pinned address +// Inspired by the golang net/dnsclient_unix_test.go code +// // For a given IP, create a new Resolver that wraps a fake // DNS server. This resolver will always return an IP that // is represented by "ipstr", for DNS queries of the same From c3ef8dcd8bf0049172464ccb941164aa23611254 Mon Sep 17 00:00:00 2001 From: Meteorite Date: Tue, 4 Feb 2020 03:45:08 +0300 Subject: [PATCH 04/16] add option to stop scanning host after first successful protocol (#242) --- config.go | 1 + multiple.go | 1 + processing.go | 3 +++ 3 files changed, 5 insertions(+) diff --git a/config.go b/config.go index e91ca62b..cd0e08ad 100644 --- a/config.go +++ b/config.go @@ -46,6 +46,7 @@ func SetOutputFunc(f OutputResultsFunc) { func init() { config.Multiple.ContinueOnError = true // set default for multiple value + config.Multiple.BreakOnSuccess = false // set default for multiple value } var config Config diff --git a/multiple.go b/multiple.go index f07993c8..25cfcc48 100644 --- a/multiple.go +++ b/multiple.go @@ -6,6 +6,7 @@ import "errors" type MultipleCommand struct { ConfigFileName string `short:"c" long:"config-file" default:"-" description:"Config filename, use - for stdin"` ContinueOnError bool `long:"continue-on-error" description:"If proceeding protocols error, do not run following protocols (default: true)"` + BreakOnSuccess bool `long:"break-on-success" description:"If proceeding protocols succeed, do not run following protocols (default: false)"` } // Validate the options sent to MultipleCommand diff --git a/processing.go b/processing.go index 30859fc9..a61c2356 100644 --- a/processing.go +++ b/processing.go @@ -135,6 +135,9 @@ func grabTarget(input ScanTarget, m *Monitor) []byte { if res.Error != nil && !config.Multiple.ContinueOnError { break } + if res.Status == SCAN_SUCCESS && config.Multiple.BreakOnSuccess { + break + } } var ipstr string From f5b169cbd4125004251dddbf66967dd4a3824398 Mon Sep 17 00:00:00 2001 From: Julian Kornberger Date: Tue, 4 Feb 2020 01:52:37 +0100 Subject: [PATCH 05/16] Trim SMTP response (#237) this avoids to have \r\n in the error message --- modules/smtp/scanner.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/smtp/scanner.go b/modules/smtp/scanner.go index 3c9cc821..9ff5d7d0 100644 --- a/modules/smtp/scanner.go +++ b/modules/smtp/scanner.go @@ -29,6 +29,7 @@ import ( "errors" "fmt" "strconv" + "strings" log "github.com/sirupsen/logrus" "github.com/zmap/zgrab2" @@ -265,7 +266,7 @@ func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, inter return zgrab2.TryGetScanStatus(err), result, err } if code < 200 || code >= 300 { - return zgrab2.SCAN_APPLICATION_ERROR, result, fmt.Errorf("SMTP error code %d returned from STARTTLS command (%s)", code, ret) + return zgrab2.SCAN_APPLICATION_ERROR, result, fmt.Errorf("SMTP error code %d returned from STARTTLS command (%s)", code, strings.TrimSpace(ret)) } tlsConn, err := scanner.config.TLSFlags.GetTLSConnection(conn.Conn) if err != nil { From 320f7aa1447a62211b5ebed81852182db0471b1b Mon Sep 17 00:00:00 2001 From: David Adrian Date: Tue, 18 Feb 2020 10:23:43 -0500 Subject: [PATCH 06/16] Add ModuleSet object (#247) Eventually this can be used to replace AddCommand, allowing the use of a non-global config object. https://github.com/zmap/zgrab2/pull/247 --- bin/default_modules.go | 65 ++++++++++++++++++++++++++++++++++++++++++ bin/doc.go | 6 ++++ module_set.go | 30 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 bin/default_modules.go create mode 100644 bin/doc.go create mode 100644 module_set.go diff --git a/bin/default_modules.go b/bin/default_modules.go new file mode 100644 index 00000000..bead8170 --- /dev/null +++ b/bin/default_modules.go @@ -0,0 +1,65 @@ +package bin + +import ( + "github.com/zmap/zgrab2" + "github.com/zmap/zgrab2/modules" + "github.com/zmap/zgrab2/modules/bacnet" + "github.com/zmap/zgrab2/modules/banner" + "github.com/zmap/zgrab2/modules/dnp3" + "github.com/zmap/zgrab2/modules/fox" + "github.com/zmap/zgrab2/modules/ftp" + "github.com/zmap/zgrab2/modules/http" + "github.com/zmap/zgrab2/modules/imap" + "github.com/zmap/zgrab2/modules/ipp" + "github.com/zmap/zgrab2/modules/modbus" + "github.com/zmap/zgrab2/modules/mongodb" + "github.com/zmap/zgrab2/modules/mssql" + "github.com/zmap/zgrab2/modules/mysql" + "github.com/zmap/zgrab2/modules/ntp" + "github.com/zmap/zgrab2/modules/oracle" + "github.com/zmap/zgrab2/modules/pop3" + "github.com/zmap/zgrab2/modules/postgres" + "github.com/zmap/zgrab2/modules/redis" + "github.com/zmap/zgrab2/modules/siemens" + "github.com/zmap/zgrab2/modules/smb" + "github.com/zmap/zgrab2/modules/smtp" + "github.com/zmap/zgrab2/modules/telnet" +) + +var defaultModules zgrab2.ModuleSet + +func init() { + defaultModules = map[string]zgrab2.ScanModule{ + "bacnet": &bacnet.Module{}, + "banner": &banner.Module{}, + "dnp3": &dnp3.Module{}, + "fox": &fox.Module{}, + "ftp": &ftp.Module{}, + "http": &http.Module{}, + "imap": &imap.Module{}, + "ipp": &ipp.Module{}, + "modbus": &modbus.Module{}, + "mongodb": &mongodb.Module{}, + "mssql": &mssql.Module{}, + "mysql": &mysql.Module{}, + "ntp": &ntp.Module{}, + "oracle": &oracle.Module{}, + "pop3": &pop3.Module{}, + "postgres": &postgres.Module{}, + "redis": &redis.Module{}, + "siemens": &siemens.Module{}, + "smb": &smb.Module{}, + "smtp": &smtp.Module{}, + "ssh": &modules.SSHModule{}, + "telnet": &telnet.Module{}, + "tls": &modules.TLSModule{}, + } +} + +// NewModuleSetWithDefaults returns a newly allocated ModuleSet containing all +// ScanModules implemented by the ZGrab2 framework. +func NewModuleSetWithDefaults() zgrab2.ModuleSet { + out := zgrab2.ModuleSet{} + defaultModules.CopyInto(out) + return out +} diff --git a/bin/doc.go b/bin/doc.go new file mode 100644 index 00000000..d9b4b6c0 --- /dev/null +++ b/bin/doc.go @@ -0,0 +1,6 @@ +// Package bin contains functions useful for creating a binary version of +// ZGrab2. +// +// This package can import "github.com/zmap/zgrab2", and should be imported by +// targets within "github.com/zmap/zgrab2/cmd" +package bin diff --git a/module_set.go b/module_set.go new file mode 100644 index 00000000..75d3b8b8 --- /dev/null +++ b/module_set.go @@ -0,0 +1,30 @@ +package zgrab2 + +// ModuleSet is a container holding named scan modules. It is a wrapper around a +// map. +type ModuleSet map[string]ScanModule + +// AddModule adds m to the ModuleSet, accessible via the given name. If the name +// is already in the ModuleSet, it is overwritten. +func (s ModuleSet) AddModule(name string, m ScanModule) { + s[name] = m +} + +// RemoveModule removes the module at the specified name. If the name is not in +// the module set, nothing happens. +func (s ModuleSet) RemoveModule(name string) { + delete(s, name) +} + +// CopyInto copies the modules in s to destination. The sets will be unique, but +// the underlying ScanModule instances will be the same. +func (s ModuleSet) CopyInto(destination ModuleSet) { + for name, m := range s { + destination[name] = m + } +} + +// NewModuleSet returns an empty ModuleSet. +func NewModuleSet() ModuleSet { + return make(ModuleSet) +} From 11425ac78b64e39d95050b042c9505fa28c24641 Mon Sep 17 00:00:00 2001 From: David Adrian Date: Tue, 18 Feb 2020 10:24:04 -0500 Subject: [PATCH 07/16] Add Description() to ScanModule (#248) This abstracts more of the help text into the ScanModule definition, removing some more of the need for `zgrab2.AddCommand()` https://github.com/zmap/zgrab2/pull/248 --- module.go | 4 ++ modules/bacnet/scanner.go | 7 ++- modules/banner/scanner.go | 7 ++- modules/dnp3/scanner.go | 7 ++- modules/fox/scanner.go | 7 ++- modules/ftp/scanner.go | 7 ++- modules/http/scanner.go | 7 ++- modules/imap/scanner.go | 10 +++- modules/ipp/scanner.go | 36 +++++++------ modules/modbus/scanner.go | 7 ++- modules/mongodb/scanner.go | 100 +++++++++++++++++++----------------- modules/mssql/scanner.go | 10 +++- modules/mysql/scanner.go | 7 ++- modules/ntp/scanner.go | 7 ++- modules/oracle/scanner.go | 7 ++- modules/pop3/scanner.go | 10 +++- modules/postgres/scanner.go | 8 ++- modules/redis/scanner.go | 7 ++- modules/siemens/scanner.go | 10 +++- modules/smb/scanner.go | 7 ++- modules/smtp/scanner.go | 7 ++- modules/ssh.go | 7 ++- modules/telnet/scanner.go | 7 ++- modules/tls.go | 7 ++- 24 files changed, 213 insertions(+), 87 deletions(-) diff --git a/module.go b/module.go index 4b8010ac..efce70c1 100644 --- a/module.go +++ b/module.go @@ -48,6 +48,10 @@ type ScanModule interface { // NewScanner is called by the framework for each time an individual scan is specified in the config or on // the command-line. The framework will then call scanner.Init(name, flags). NewScanner() Scanner + + // Description returns a string suitable for use as an overview of this + // module within usage text. + Description() string } // ScanFlags is an interface which must be implemented by all types sent to diff --git a/modules/bacnet/scanner.go b/modules/bacnet/scanner.go index 586d2939..23bc2be3 100644 --- a/modules/bacnet/scanner.go +++ b/modules/bacnet/scanner.go @@ -32,7 +32,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("bacnet", "bacnet", "Probe for bacnet", 0xBAC0, &module) + _, err := zgrab2.AddCommand("bacnet", "bacnet", module.Description(), 0xBAC0, &module) if err != nil { log.Fatal(err) } @@ -48,6 +48,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns text uses in the help for this module. +func (module *Module) Description() string { + return "Probe for devices that speak Bacnet, commonly used for HVAC control." +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/banner/scanner.go b/modules/banner/scanner.go index a1af4a53..7418f41c 100644 --- a/modules/banner/scanner.go +++ b/modules/banner/scanner.go @@ -42,7 +42,7 @@ type Results struct { // RegisterModule is called by modules/banner.go to register the scanner. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("banner", "Banner", "Grab banner by sending probe and match with regexp", 80, &module) + _, err := zgrab2.AddCommand("banner", "Banner", module.Description(), 80, &module) if err != nil { log.Fatal(err) } @@ -83,6 +83,11 @@ func (f *Flags) Validate(args []string) error { return nil } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Fetch a raw banner by sending a static probe and checking the result against a regular expression" +} + // Help returns the module's help string. func (f *Flags) Help() string { return "" diff --git a/modules/dnp3/scanner.go b/modules/dnp3/scanner.go index 5d73fab2..93ec5353 100644 --- a/modules/dnp3/scanner.go +++ b/modules/dnp3/scanner.go @@ -30,7 +30,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("dnp3", "dnp3", "Probe for dnp3", 20000, &module) + _, err := zgrab2.AddCommand("dnp3", "dnp3", module.Description(), 20000, &module) if err != nil { log.Fatal(err) } @@ -46,6 +46,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Probe for DNP3, a SCADA protocol" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/fox/scanner.go b/modules/fox/scanner.go index b64630be..910aa40c 100644 --- a/modules/fox/scanner.go +++ b/modules/fox/scanner.go @@ -30,7 +30,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("fox", "fox", "Probe for Tridium Fox", 1911, &module) + _, err := zgrab2.AddCommand("fox", "fox", module.Description(), 1911, &module) if err != nil { log.Fatal(err) } @@ -46,6 +46,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Probe for Tridium Fox" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/ftp/scanner.go b/modules/ftp/scanner.go index 2070fca4..f27be773 100644 --- a/modules/ftp/scanner.go +++ b/modules/ftp/scanner.go @@ -71,7 +71,7 @@ type Connection struct { // RegisterModule registers the ftp zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("ftp", "FTP", "Grab an FTP banner", 21, &module) + _, err := zgrab2.AddCommand("ftp", "FTP", module.Description(), 21, &module) if err != nil { log.Fatal(err) } @@ -88,6 +88,11 @@ func (m *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (m *Module) Description() string { + return "Grab an FTP banner" +} + // Validate does nothing in this module. func (f *Flags) Validate(args []string) error { return nil diff --git a/modules/http/scanner.go b/modules/http/scanner.go index e8033a70..0025a40a 100644 --- a/modules/http/scanner.go +++ b/modules/http/scanner.go @@ -98,6 +98,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Send an HTTP request and read the response, optionally following redirects." +} + // Validate performs any needed validation on the arguments func (flags *Flags) Validate(args []string) error { return nil @@ -401,7 +406,7 @@ func (scanner *Scanner) Scan(t zgrab2.ScanTarget) (zgrab2.ScanStatus, interface{ func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("http", "HTTP Banner Grab", "Grab a banner over HTTP", 80, &module) + _, err := zgrab2.AddCommand("http", "HTTP Banner Grab", module.Description(), 80, &module) if err != nil { log.Fatal(err) } diff --git a/modules/imap/scanner.go b/modules/imap/scanner.go index 02c13c5b..145e8563 100644 --- a/modules/imap/scanner.go +++ b/modules/imap/scanner.go @@ -25,9 +25,10 @@ package imap import ( "fmt" + "strings" + log "github.com/sirupsen/logrus" "github.com/zmap/zgrab2" - "strings" ) // ScanResults instances are returned by the module's Scan function. @@ -76,7 +77,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("imap", "imap", "Probe for IMAP", 143, &module) + _, err := zgrab2.AddCommand("imap", "imap", module.Description(), 143, &module) if err != nil { log.Fatal(err) } @@ -92,6 +93,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Fetch an IMAP banner, optionally over TLS" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/ipp/scanner.go b/modules/ipp/scanner.go index 9da9a396..d04825dc 100644 --- a/modules/ipp/scanner.go +++ b/modules/ipp/scanner.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "encoding/binary" "errors" + //"fmt" "io" "io/ioutil" @@ -40,7 +41,7 @@ var ( // TODO: Explain this error ErrVersionNotSupported = errors.New("IPP version not supported") - Versions = []version{{Major: 2, Minor: 1}, {Major: 2, Minor: 0}, {Major: 1, Minor: 1}, {Major: 1, Minor: 0}} + Versions = []version{{Major: 2, Minor: 1}, {Major: 2, Minor: 0}, {Major: 1, Minor: 1}, {Major: 1, Minor: 0}} AttributesCharset = []byte{0x47, 0x00, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74} ) @@ -70,9 +71,9 @@ type ScanResults struct { CUPSVersion string `json:"cups_version,omitempty"` Attributes []*Attribute `json:"attributes,omitempty"` - AttributeCUPSVersion string `json:"attr_cups_version,omitempty"` - AttributeIPPVersions []string `json:"attr_ipp_versions,omitempty"` - AttributePrinterURIs []string `json:"attr_printer_uris,omitempty"` + AttributeCUPSVersion string `json:"attr_cups_version,omitempty"` + AttributeIPPVersions []string `json:"attr_ipp_versions,omitempty"` + AttributePrinterURIs []string `json:"attr_printer_uris,omitempty"` TLSLog *zgrab2.TLSLog `json:"tls,omitempty"` } @@ -117,7 +118,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("ipp", "ipp", "Probe for ipp", 631, &module) + _, err := zgrab2.AddCommand("ipp", "ipp", module.Description(), 631, &module) if err != nil { log.Fatal(err) } @@ -133,6 +134,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Probe for printers via IPP" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. @@ -206,13 +212,13 @@ type Value struct { } type Attribute struct { - Name string `json:"name,omitempty"` - Values []Value `json:"values,omitempty"` - ValueTag byte `json:"tag,omitempty"` + Name string `json:"name,omitempty"` + Values []Value `json:"values,omitempty"` + ValueTag byte `json:"tag,omitempty"` } func shouldReturnAttrs(length, soFar, size, upperBound int) (bool, error) { - if soFar + length > size { + if soFar+length > size { // Size should never exceed upperBound in practice because of truncation, but this is more general if size >= upperBound { return true, nil @@ -260,9 +266,9 @@ func readAllAttributes(body []byte, scanner *Scanner) ([]*Attribute, error) { buf := bytes.NewBuffer(body) // Each field of this struct is exported to avoid binary.Read panicking var start struct { - Version int16 + Version int16 StatusCode int16 - ReqID int32 + ReqID int32 } // Read in pre-attribute part of body to ignore it if err := binary.Read(buf, binary.BigEndian, &start); err != nil { @@ -299,7 +305,7 @@ func readAllAttributes(body []byte, scanner *Scanner) ([]*Attribute, error) { } bytesRead += 2 // If reading the name would entail reading past body, check whether body was truncated - if should, err := shouldReturnAttrs(int(nameLength), bytesRead, len(body), scanner.config.MaxSize * 1024); should { + if should, err := shouldReturnAttrs(int(nameLength), bytesRead, len(body), scanner.config.MaxSize*1024); should { // If body was truncated, return all attributes so far without error // Otherwise, return a protocol error because name-length should indicate the // length of the following name when obeying the protocol's encoding @@ -311,7 +317,7 @@ func readAllAttributes(body []byte, scanner *Scanner) ([]*Attribute, error) { // an additional value for the former, so we read and append another value for that attr if tag == lastTag && nameLength == 0 { attr = attrs[len(attrs)-1] - // Otherwise, create a new attribute and read in its name + // Otherwise, create a new attribute and read in its name } else { attr = &Attribute{ValueTag: tag} attrs = append(attrs, attr) @@ -332,7 +338,7 @@ func readAllAttributes(body []byte, scanner *Scanner) ([]*Attribute, error) { } bytesRead += 2 // If reading the name would entail reading past body, check whether body was truncated - if should, err := shouldReturnAttrs(int(length), bytesRead, len(body), scanner.config.MaxSize * 1024); should { + if should, err := shouldReturnAttrs(int(length), bytesRead, len(body), scanner.config.MaxSize*1024); should { // If body was truncated, return all attributes so far without error // Otherwise, return a protocol error because name-length should indicate the // length of the following name when obeying the protocol's encoding @@ -366,7 +372,7 @@ func (scanner *Scanner) tryReadAttributes(resp *http.Response, scan *scan) *zgra // Therefore, an HTTP Status Code other than 200 indicates the response is not a well-formed IPP response. // RFC 8010 Section 3.4.3 Source: https://tools.ietf.org/html/rfc8010#section-3.4.3 if resp.StatusCode != 200 { - return zgrab2.NewScanError(zgrab2.SCAN_APPLICATION_ERROR, errors.New("Response returned with status " + resp.Status)) + return zgrab2.NewScanError(zgrab2.SCAN_APPLICATION_ERROR, errors.New("Response returned with status "+resp.Status)) } // Reject successful responses which specify non-IPP MIME mediatype (ie: text/html) diff --git a/modules/modbus/scanner.go b/modules/modbus/scanner.go index cb8a0230..2f7033e0 100644 --- a/modules/modbus/scanner.go +++ b/modules/modbus/scanner.go @@ -52,7 +52,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("modbus", "modbus", "Probe for modbus", 502, &module) + _, err := zgrab2.AddCommand("modbus", "modbus", module.Description(), 502, &module) if err != nil { log.Fatal(err) } @@ -68,6 +68,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Probe for Modbus devices, usually PLCs as part of a SCADA system" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/mongodb/scanner.go b/modules/mongodb/scanner.go index 69d5b719..fef87504 100644 --- a/modules/mongodb/scanner.go +++ b/modules/mongodb/scanner.go @@ -1,11 +1,12 @@ package mongodb import ( - "fmt" - "encoding/hex" "encoding/binary" - "github.com/zmap/zgrab2" + "encoding/hex" + "fmt" + log "github.com/sirupsen/logrus" + "github.com/zmap/zgrab2" "gopkg.in/mgo.v2/bson" ) @@ -20,10 +21,10 @@ type Flags struct { // Scanner implements the zgrab2.Scanner interface type Scanner struct { - config *Flags - isMasterMsg []byte - buildInfoCommandMsg []byte - buildInfoOpMsg []byte + config *Flags + isMasterMsg []byte + buildInfoCommandMsg []byte + buildInfoOpMsg []byte } // scan holds the state for the scan of an individual target @@ -42,7 +43,7 @@ func (scan *scan) Close() { // getCommandMsg returns a mongodb message containing the specified BSON-encoded command. // metdata and commandArgs expected to be BSON byte arrays. -func getCommandMsg(database string, commandName string, metadata []byte, commandArgs []byte) ([]byte) { +func getCommandMsg(database string, commandName string, metadata []byte, commandArgs []byte) []byte { dblen := len(database) + 1 cnlen := len(commandName) + 1 mdlen := len(metadata) @@ -67,8 +68,8 @@ func getCommandMsg(database string, commandName string, metadata []byte, command // getIsMasterMsg returns a mongodb message containing isMaster command. // https://docs.mongodb.com/manual/reference/command/isMaster/ -func getIsMasterMsg() ([]byte) { - query, err := bson.Marshal(bson.M{ "isMaster": 1 }) +func getIsMasterMsg() []byte { + query, err := bson.Marshal(bson.M{"isMaster": 1}) if err != nil { // programmer error log.Fatalf("Invalid BSON: %v", err) @@ -78,8 +79,8 @@ func getIsMasterMsg() ([]byte) { } // getBuildInfoCommandMsg returns a mongodb message containing a command to retrieve MongoDB build info. -func getBuildInfoCommandMsg() ([]byte) { - metaData, err := bson.Marshal(bson.M{ "buildInfo": 1 }) +func getBuildInfoCommandMsg() []byte { + metaData, err := bson.Marshal(bson.M{"buildInfo": 1}) if err != nil { // programmer error log.Fatalf("Invalid BSON: %v", err) @@ -96,7 +97,7 @@ func getBuildInfoCommandMsg() ([]byte) { // getOpQuery returns a mongodb OP_QUERY message containing the specified BSON-encoded query. // query expected to be BSON byte array. -func getOpQuery(collname string, query []byte) ([]byte) { +func getOpQuery(collname string, query []byte) []byte { flagslen := 4 collname_len := len(collname) + 1 nskiplen := 4 @@ -119,7 +120,7 @@ func getOpQuery(collname string, query []byte) ([]byte) { // getOpMsg returns a mongodb OP_MSG message containing the specified BSON-encoded command. // section expected to be BSON byte array. -func getOpMsg(section []byte) ([]byte) { +func getOpMsg(section []byte) []byte { flagslen := 4 slen := len(section) msglen := MSGHEADER_LEN + flagslen + slen @@ -134,14 +135,14 @@ func getOpMsg(section []byte) ([]byte) { } // getBuildInfoOpMsg returns a mongodb "OP" message containing query to retrieve MongoDB build info. -func getBuildInfoOpMsg() ([]byte) { +func getBuildInfoOpMsg() []byte { // gleaned from tshark - section_payload, err := bson.Marshal(bson.M{ "buildinfo": 1, "$db": "admin" }) + section_payload, err := bson.Marshal(bson.M{"buildinfo": 1, "$db": "admin"}) if err != nil { // programmer error log.Fatalf("Invalid BSON: %v", err) } - section := make([]byte, len(section_payload) + 1) + section := make([]byte, len(section_payload)+1) copy(section[1:], section_payload) op_msg := getOpMsg(section) return op_msg @@ -149,39 +150,39 @@ func getBuildInfoOpMsg() ([]byte) { // BuildEnvironment_t holds build environment information returned by scan. type BuildEnvironment_t struct { - Distmod string `bson:"distmod,omitempty" json:"dist_mod,omitempty"` - Distarch string `bson:"distarch,omitempty" json:"dist_arch,omitempty"` - Cc string `bson:"cc,omitempty" json:"cc,omitempty"` - CcFlags string `bson:"ccflags,omitempty" json:"cc_flags,omitempty"` - Cxx string `bson:"cxx,omitempty" json:"cxx,omitempty"` - CxxFlags string `bson:"cxxflags,omitempty" json:"cxx_flags,omitempty"` - LinkFlags string `bson:"linkflags,omitempty" json:"link_flags,omitempty"` + Distmod string `bson:"distmod,omitempty" json:"dist_mod,omitempty"` + Distarch string `bson:"distarch,omitempty" json:"dist_arch,omitempty"` + Cc string `bson:"cc,omitempty" json:"cc,omitempty"` + CcFlags string `bson:"ccflags,omitempty" json:"cc_flags,omitempty"` + Cxx string `bson:"cxx,omitempty" json:"cxx,omitempty"` + CxxFlags string `bson:"cxxflags,omitempty" json:"cxx_flags,omitempty"` + LinkFlags string `bson:"linkflags,omitempty" json:"link_flags,omitempty"` TargetArch string `bson:"target_arch,omitempty" json:"target_arch,omitempty"` - TargetOS string `bson:"target_os,omitempty" json:"target_os,omitempty"` + TargetOS string `bson:"target_os,omitempty" json:"target_os,omitempty"` } // BuildInfo_t holds the data returned by the the buildInfo query type BuildInfo_t struct { - Version string `bson:"version,omitempty" json:"version,omitempty"` - GitVersion string `bson:"gitVersion,omitempty" json:"git_version,omitempty"` + Version string `bson:"version,omitempty" json:"version,omitempty"` + GitVersion string `bson:"gitVersion,omitempty" json:"git_version,omitempty"` BuildEnvironment BuildEnvironment_t `bson:"buildEnvironment,omitempty" json:"build_environment,omitempty"` } // IsMaster_t holds the data returned by an isMaster query type IsMaster_t struct { - IsMaster bool `bson:"ismaster" json:"is_master"` - MaxWireVersion int32 `bson:"maxWireVersion,omitempty" json:"max_wire_version,omitempty"` - MinWireVersion int32 `bson:"minWireVersion,omitempty" json:"min_wire_version,omitempty"` - MaxBsonObjectSize int32 `bson:"maxBsonObjectSize,omitempty" json:"max_bson_object_size,omitempty"` - MaxWriteBatchSize int32 `bson:"maxWriteBatchSize,omitempty" json:"max_write_batch_size,omitempty"` + IsMaster bool `bson:"ismaster" json:"is_master"` + MaxWireVersion int32 `bson:"maxWireVersion,omitempty" json:"max_wire_version,omitempty"` + MinWireVersion int32 `bson:"minWireVersion,omitempty" json:"min_wire_version,omitempty"` + MaxBsonObjectSize int32 `bson:"maxBsonObjectSize,omitempty" json:"max_bson_object_size,omitempty"` + MaxWriteBatchSize int32 `bson:"maxWriteBatchSize,omitempty" json:"max_write_batch_size,omitempty"` LogicalSessionTimeoutMinutes int32 `bson:"logicalSessionTimeoutMinutes,omitempty" json:"logical_session_timeout_minutes,omitempty"` - MaxMessageSizeBytes int32 `bson:"maxMessageSizeBytes,omitempty" json:"max_message_size_bytes,omitempty"` - ReadOnly bool `bson:"readOnly" json:"read_only"` + MaxMessageSizeBytes int32 `bson:"maxMessageSizeBytes,omitempty" json:"max_message_size_bytes,omitempty"` + ReadOnly bool `bson:"readOnly" json:"read_only"` } // Result holds the data returned by a scan type Result struct { - IsMaster *IsMaster_t `json:"is_master,omitempty"` + IsMaster *IsMaster_t `json:"is_master,omitempty"` BuildInfo *BuildInfo_t `json:"build_info,omitempty"` } @@ -235,6 +236,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Perform a handshake with a MongoDB server" +} + // StartScan opens a connection to the target and sets up a scan instance for it. func (scanner *Scanner) StartScan(target *zgrab2.ScanTarget) (*scan, error) { conn, err := target.Open(&scanner.config.BaseFlags) @@ -265,25 +271,25 @@ func getIsMaster(conn *Connection) (*IsMaster_t, error) { return nil, err } - if len(msg) < doc_offset + 4 { + if len(msg) < doc_offset+4 { err = fmt.Errorf("Server truncated message - no query reply (%d bytes: %s)", len(msg), hex.EncodeToString(msg)) return nil, err } - respFlags := binary.LittleEndian.Uint32(msg[MSGHEADER_LEN:MSGHEADER_LEN + 4]) - if respFlags & QUERY_RESP_FAILED != 0 { + respFlags := binary.LittleEndian.Uint32(msg[MSGHEADER_LEN : MSGHEADER_LEN+4]) + if respFlags&QUERY_RESP_FAILED != 0 { err = fmt.Errorf("isMaster query failed") return nil, err } - doclen := int(binary.LittleEndian.Uint32(msg[doc_offset:doc_offset + 4])) + doclen := int(binary.LittleEndian.Uint32(msg[doc_offset : doc_offset+4])) if len(msg[doc_offset:]) < doclen { err = fmt.Errorf("Server truncated BSON reply doc (%d bytes: %s)", - len(msg[doc_offset:]), hex.EncodeToString(msg)) + len(msg[doc_offset:]), hex.EncodeToString(msg)) return nil, err } err = bson.Unmarshal(msg[doc_offset:], &document) if err != nil { err = fmt.Errorf("Server sent invalid BSON reply doc (%d bytes: %s)", - len(msg[doc_offset:]), hex.EncodeToString(msg)) + len(msg[doc_offset:]), hex.EncodeToString(msg)) return nil, err } return document, nil @@ -309,7 +315,7 @@ func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, inter // Gleaned from wireshark - if "MaxWireVersion" is less than 7, then // "build info" command should be sent in an OP_COMMAND with the query sent - // and response retrieved at "metadata" offset. At 7 and above, should + // and response retrieved at "metadata" offset. At 7 and above, should // be sent as an OP_MSG in the "section" field, and response is at "body" offset if result.IsMaster.MaxWireVersion < 7 { query = scanner.buildInfoCommandMsg @@ -327,15 +333,15 @@ func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, inter return zgrab2.TryGetScanStatus(err), &result, err } - if len(msg) < MSGHEADER_LEN + resplen_offset { + if len(msg) < MSGHEADER_LEN+resplen_offset { err = fmt.Errorf("Server truncated message - no metadata doc (%d bytes: %s)", len(msg), hex.EncodeToString(msg)) return zgrab2.SCAN_PROTOCOL_ERROR, &result, err } - responselen := int(binary.LittleEndian.Uint32(msg[MSGHEADER_LEN:MSGHEADER_LEN + resplen_offset])) + responselen := int(binary.LittleEndian.Uint32(msg[MSGHEADER_LEN : MSGHEADER_LEN+resplen_offset])) if len(msg[MSGHEADER_LEN:]) < responselen { - err = fmt.Errorf("Server truncated BSON response doc (%d bytes: %s)", - len(msg[MSGHEADER_LEN:]), hex.EncodeToString(msg)) + err = fmt.Errorf("Server truncated BSON response doc (%d bytes: %s)", + len(msg[MSGHEADER_LEN:]), hex.EncodeToString(msg)) return zgrab2.SCAN_PROTOCOL_ERROR, &result, err } bson.Unmarshal(msg[MSGHEADER_LEN+resp_offset:], &result.BuildInfo) @@ -346,7 +352,7 @@ func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, inter // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("mongodb", "mongodb", "Probe for mongodb", 27017, &module) + _, err := zgrab2.AddCommand("mongodb", "mongodb", module.Description(), 27017, &module) if err != nil { log.Fatal(err) } diff --git a/modules/mssql/scanner.go b/modules/mssql/scanner.go index db5f97fe..708fa96c 100644 --- a/modules/mssql/scanner.go +++ b/modules/mssql/scanner.go @@ -12,9 +12,10 @@ package mssql import ( + "strings" + log "github.com/sirupsen/logrus" "github.com/zmap/zgrab2" - "strings" ) // ScanResults contains detailed information about each step of the @@ -68,6 +69,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Perform a handshake for MSSQL databases" +} + // Validate does nothing in this module. func (flags *Flags) Validate(args []string) error { return nil @@ -172,7 +178,7 @@ func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, inter // RegisterModule is called by modules/mssql.go's init() func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("mssql", "MSSQL", "Grab a mssql handshake", 1433, &module) + _, err := zgrab2.AddCommand("mssql", "MSSQL", module.Description(), 1433, &module) if err != nil { log.Fatal(err) } diff --git a/modules/mysql/scanner.go b/modules/mysql/scanner.go index fc8171c9..6a45543f 100644 --- a/modules/mysql/scanner.go +++ b/modules/mysql/scanner.go @@ -149,7 +149,7 @@ type Scanner struct { // RegisterModule is called by modules/mysql.go to register the scanner. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("mysql", "MySQL", "Grab a MySQL handshake", 3306, &module) + _, err := zgrab2.AddCommand("mysql", "MySQL", module.Description(), 3306, &module) if err != nil { log.Fatal(err) } @@ -165,6 +165,11 @@ func (m *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (m *Module) Description() string { + return "Perform a handshake with a MySQL database" +} + // Validate validates the flags and returns nil on success. func (f *Flags) Validate(args []string) error { return nil diff --git a/modules/ntp/scanner.go b/modules/ntp/scanner.go index b52fafc8..083af17e 100644 --- a/modules/ntp/scanner.go +++ b/modules/ntp/scanner.go @@ -815,7 +815,7 @@ type Scanner struct { // RegisterModule registers the module with zgrab2 func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("ntp", "NTP", "Scan for NTP", 123, &module) + _, err := zgrab2.AddCommand("ntp", "NTP", module.Description(), 123, &module) if err != nil { log.Fatal(err) } @@ -831,6 +831,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Scan for NTP" +} + // Validate checks that the flags are valid func (cfg *Flags) Validate(args []string) error { return nil diff --git a/modules/oracle/scanner.go b/modules/oracle/scanner.go index 1ab212dd..752e5125 100644 --- a/modules/oracle/scanner.go +++ b/modules/oracle/scanner.go @@ -106,7 +106,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("oracle", "oracle", "Probe for oracle", 1521, &module) + _, err := zgrab2.AddCommand("oracle", "oracle", module.Description(), 1521, &module) if err != nil { log.Fatal(err) } @@ -122,6 +122,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Perform a handshake with Oracle database servers" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/pop3/scanner.go b/modules/pop3/scanner.go index 47c94908..cd1ed269 100644 --- a/modules/pop3/scanner.go +++ b/modules/pop3/scanner.go @@ -28,9 +28,10 @@ package pop3 import ( "fmt" + "strings" + log "github.com/sirupsen/logrus" "github.com/zmap/zgrab2" - "strings" ) // ScanResults instances are returned by the module's Scan function. @@ -91,7 +92,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("pop3", "pop3", "Probe for pop3", 110, &module) + _, err := zgrab2.AddCommand("pop3", "pop3", module.Description(), 110, &module) if err != nil { log.Fatal(err) } @@ -107,6 +108,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Fetch POP3 banners, optionally over TLS" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/postgres/scanner.go b/modules/postgres/scanner.go index 783d84b6..8e0becae 100644 --- a/modules/postgres/scanner.go +++ b/modules/postgres/scanner.go @@ -16,6 +16,7 @@ import ( "strings" "encoding/json" + log "github.com/sirupsen/logrus" "github.com/zmap/zgrab2" ) @@ -279,6 +280,11 @@ func (m *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (m *Module) Description() string { + return "Perform a handshake with a PostgreSQL server" +} + // Validate checks the arguments; on success, returns nil. func (f *Flags) Validate(args []string) error { return nil @@ -533,7 +539,7 @@ func (s *Scanner) Scan(t zgrab2.ScanTarget) (status zgrab2.ScanStatus, result in // the postgres module with the zgrab2 framework. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("postgres", "Postgres", "Grab a Postgres handshake", 5432, &module) + _, err := zgrab2.AddCommand("postgres", "Postgres", module.Description(), 5432, &module) if err != nil { log.Fatal(err) } diff --git a/modules/redis/scanner.go b/modules/redis/scanner.go index d115f87a..567576bb 100644 --- a/modules/redis/scanner.go +++ b/modules/redis/scanner.go @@ -159,7 +159,7 @@ type Result struct { // RegisterModule registers the zgrab2 module func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("redis", "redis", "Probe for redis", 6379, &module) + _, err := zgrab2.AddCommand("redis", "redis", module.Description(), 6379, &module) if err != nil { log.Fatal(err) } @@ -175,6 +175,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Probe for Redis" +} + // Validate checks that the flags are valid func (flags *Flags) Validate(args []string) error { return nil diff --git a/modules/siemens/scanner.go b/modules/siemens/scanner.go index e4f4673a..03112d0a 100644 --- a/modules/siemens/scanner.go +++ b/modules/siemens/scanner.go @@ -4,9 +4,10 @@ package siemens import ( + "net" + log "github.com/sirupsen/logrus" "github.com/zmap/zgrab2" - "net" ) // Flags holds the command-line configuration for the siemens scan module. @@ -29,7 +30,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("siemens", "siemens", "Probe for Siemens S7", 102, &module) + _, err := zgrab2.AddCommand("siemens", "siemens", module.Description(), 102, &module) if err != nil { log.Fatal(err) } @@ -45,6 +46,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Probe for Siemens S7 devices" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/smb/scanner.go b/modules/smb/scanner.go index f0cf32bc..851185b5 100644 --- a/modules/smb/scanner.go +++ b/modules/smb/scanner.go @@ -32,7 +32,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("smb", "smb", "Probe for smb", 445, &module) + _, err := zgrab2.AddCommand("smb", "smb", module.Description(), 445, &module) if err != nil { log.Fatal(err) } @@ -48,6 +48,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Probe for SMB servers (Windows filesharing / SAMBA)" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/smtp/scanner.go b/modules/smtp/scanner.go index 9ff5d7d0..281c6f7e 100644 --- a/modules/smtp/scanner.go +++ b/modules/smtp/scanner.go @@ -108,7 +108,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("smtp", "smtp", "Probe for smtp", 25, &module) + _, err := zgrab2.AddCommand("smtp", "smtp", module.Description(), 25, &module) if err != nil { log.Fatal(err) } @@ -124,6 +124,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Fetch an SMTP server banner, optionally over TLS" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/ssh.go b/modules/ssh.go index 860211b5..5248c38c 100644 --- a/modules/ssh.go +++ b/modules/ssh.go @@ -33,7 +33,7 @@ type SSHScanner struct { func init() { var sshModule SSHModule - cmd, err := zgrab2.AddCommand("ssh", "SSH Banner Grab", "Grab a banner over SSH", 22, &sshModule) + cmd, err := zgrab2.AddCommand("ssh", "SSH Banner Grab", sshModule.Description(), 22, &sshModule) if err != nil { log.Fatal(err) } @@ -51,6 +51,11 @@ func (m *SSHModule) NewScanner() zgrab2.Scanner { return new(SSHScanner) } +// Description returns an overview of this module. +func (m *SSHModule) Description() string { + return "Fetch an SSH server banner and collect key exchange information" +} + func (f *SSHFlags) Validate(args []string) error { return nil } diff --git a/modules/telnet/scanner.go b/modules/telnet/scanner.go index e4274094..b5f68ee4 100644 --- a/modules/telnet/scanner.go +++ b/modules/telnet/scanner.go @@ -36,7 +36,7 @@ type Scanner struct { // RegisterModule registers the zgrab2 module. func RegisterModule() { var module Module - _, err := zgrab2.AddCommand("telnet", "telnet", "Probe for telnet", 23, &module) + _, err := zgrab2.AddCommand("telnet", "telnet", module.Description(), 23, &module) if err != nil { log.Fatal(err) } @@ -52,6 +52,11 @@ func (module *Module) NewScanner() zgrab2.Scanner { return new(Scanner) } +// Description returns an overview of this module. +func (module *Module) Description() string { + return "Fetch a telnet banner" +} + // Validate checks that the flags are valid. // On success, returns nil. // On failure, returns an error instance describing the error. diff --git a/modules/tls.go b/modules/tls.go index 5dc6ccd9..7f8c48d4 100644 --- a/modules/tls.go +++ b/modules/tls.go @@ -19,7 +19,7 @@ type TLSScanner struct { func init() { var tlsModule TLSModule - _, err := zgrab2.AddCommand("tls", "TLS Banner Grab", "Grab banner over TLS", 443, &tlsModule) + _, err := zgrab2.AddCommand("tls", "TLS Banner Grab", tlsModule.Description(), 443, &tlsModule) if err != nil { log.Fatal(err) } @@ -33,6 +33,11 @@ func (m *TLSModule) NewScanner() zgrab2.Scanner { return new(TLSScanner) } +// Description returns an overview of this module. +func (m *TLSModule) Description() string { + return "Perform a TLS handshake" +} + func (f *TLSFlags) Validate(args []string) error { return nil } From 6848e3ae605a3c318f6d2150f947c181342bea60 Mon Sep 17 00:00:00 2001 From: David Adrian Date: Tue, 18 Feb 2020 13:43:19 -0500 Subject: [PATCH 08/16] Remove reference to global config in monitor.go This updates MakeMonitor() to take the channel size as a parameter, instead of reading it from the global `config` object. Unfortunately, the caller of MakeMonitor() doesn't actually have access to the global, since it's in a different package (bin vs the root package). Luckily, there doesn't appear to be a reason to have a buffer in this channel. This updates the caller to pass a hardcoded size of 1. --- bin/bin.go | 2 +- monitor.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/bin.go b/bin/bin.go index 693cc7f8..eab13bd5 100644 --- a/bin/bin.go +++ b/bin/bin.go @@ -132,7 +132,7 @@ func ZGrab2Main() { s.Init(flag) zgrab2.RegisterScan(moduleType, s) } - monitor := zgrab2.MakeMonitor() + monitor := zgrab2.MakeMonitor(1) monitor.Callback = func(_ string) { dumpHeapProfile() } diff --git a/monitor.go b/monitor.go index c67cbe0d..554e9f03 100644 --- a/monitor.go +++ b/monitor.go @@ -6,7 +6,7 @@ type Monitor struct { states map[string]*State statusesChan chan moduleStatus // Callback is invoked after each scan. - Callback func(string) + Callback func(string) } // State contains the respective number of successes and failures @@ -36,9 +36,9 @@ func (m *Monitor) GetStatuses() map[string]*State { // MakeMonitor returns a Monitor object that can be used to collect and send // the status of a running scan -func MakeMonitor() *Monitor { +func MakeMonitor(statusChanSize int) *Monitor { m := new(Monitor) - m.statusesChan = make(chan moduleStatus, config.Senders*4) + m.statusesChan = make(chan moduleStatus, statusChanSize) m.states = make(map[string]*State, 10) go func() { for s := range m.statusesChan { From 178d984996c518848e8c1133b6ce52ffaa621579 Mon Sep 17 00:00:00 2001 From: David Adrian Date: Tue, 18 Feb 2020 14:54:03 -0500 Subject: [PATCH 09/16] Fix synchronization for Monitor The goroutine running the monitor isn't actually closed. This PR updates the API to allow that Goroutine to properly block program exit. This can be leveraged as we continue to make the configuration non-global. --- bin/bin.go | 6 +++++- monitor.go | 13 ++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/bin/bin.go b/bin/bin.go index eab13bd5..dc490461 100644 --- a/bin/bin.go +++ b/bin/bin.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" "runtime/pprof" + "sync" "time" "fmt" @@ -132,7 +133,8 @@ func ZGrab2Main() { s.Init(flag) zgrab2.RegisterScan(moduleType, s) } - monitor := zgrab2.MakeMonitor(1) + wg := sync.WaitGroup{} + monitor := zgrab2.MakeMonitor(1, &wg) monitor.Callback = func(_ string) { dumpHeapProfile() } @@ -141,6 +143,8 @@ func ZGrab2Main() { zgrab2.Process(monitor) end := time.Now() log.Infof("finished grab at %s", end.Format(time.RFC3339)) + monitor.Stop() + wg.Wait() s := Summary{ StatusesPerModule: monitor.GetStatuses(), StartTime: start.Format(time.RFC3339), diff --git a/monitor.go b/monitor.go index 554e9f03..654a0fac 100644 --- a/monitor.go +++ b/monitor.go @@ -1,5 +1,7 @@ package zgrab2 +import "sync" + // Monitor is a collection of states per scans and a channel to communicate // those scans to the monitor type Monitor struct { @@ -34,13 +36,22 @@ func (m *Monitor) GetStatuses() map[string]*State { return m.states } +// Stop indicates the monitor is done and the internal channel should be closed. +// This function does not block, but will allow a call to Wait() on the +// WaitGroup passed to MakeMonitor to return. +func (m *Monitor) Stop() { + close(m.statusesChan) +} + // MakeMonitor returns a Monitor object that can be used to collect and send // the status of a running scan -func MakeMonitor(statusChanSize int) *Monitor { +func MakeMonitor(statusChanSize int, wg *sync.WaitGroup) *Monitor { m := new(Monitor) m.statusesChan = make(chan moduleStatus, statusChanSize) m.states = make(map[string]*State, 10) + wg.Add(1) go func() { + defer wg.Done() for s := range m.statusesChan { if m.states[s.name] == nil { m.states[s.name] = new(State) From b2bf9cbc413d26a2a3601af90ad8e487dfc1a5ee Mon Sep 17 00:00:00 2001 From: David Adrian Date: Tue, 18 Feb 2020 15:46:36 -0500 Subject: [PATCH 10/16] Remove dependency on config in output.go (#250) Refactor the output handlers to take the necessary writers as arguments. https://github.com/zmap/zgrab2/pull/250 --- config.go | 3 ++- output.go | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/config.go b/config.go index cd0e08ad..f70cdc7a 100644 --- a/config.go +++ b/config.go @@ -89,7 +89,8 @@ func validateFrameworkConfiguration() { log.Fatal(err) } } - SetOutputFunc(OutputResultsFile) + outputFunc := OutputResultsWriterFunc(config.outputFile) + SetOutputFunc(outputFunc) if config.MetaFileName == "-" { config.metaFile = os.Stderr diff --git a/output.go b/output.go index 5b6e0bca..5d234fc8 100644 --- a/output.go +++ b/output.go @@ -3,6 +3,7 @@ package zgrab2 import ( "bufio" "fmt" + "io" ) // FlagMap is a function that maps a single-bit bitmask (i.e. a number of the @@ -133,16 +134,23 @@ func WidenMapKeys(input map[int]string) map[uint64]string { // results or error. type OutputResultsFunc func(results <-chan []byte) error -// OutputResultsFile is an OutputResultsFunc that write results to -// a filename provided on the command line. -func OutputResultsFile(results <-chan []byte) error { - out := bufio.NewWriter(config.outputFile) - defer out.Flush() +// OutputResultsWriterFunc returns an OutputResultsFunc that wraps an io.Writer +// in a buffered writer, and uses OutputResults. +func OutputResultsWriterFunc(w io.Writer) OutputResultsFunc { + buf := bufio.NewWriter(w) + return func(result <-chan []byte) error { + defer buf.Flush() + return OutputResults(buf, result) + } +} + +// OutputResults writes results to a buffered Writer from a channel. +func OutputResults(w *bufio.Writer, results <-chan []byte) error { for result := range results { - if _, err := out.Write(result); err != nil { + if _, err := w.Write(result); err != nil { return err } - if err := out.WriteByte('\n'); err != nil { + if err := w.WriteByte('\n'); err != nil { return err } } From 909643c22742e8feae63b6e588ca73dada1f6947 Mon Sep 17 00:00:00 2001 From: Jeff Cody Date: Wed, 19 Feb 2020 14:31:12 -0500 Subject: [PATCH 11/16] HTTP Module: Keep scanner.config immutable during scans (#245) The Scanner.config struct is a configuration for all instances of Scanner. Scanner.Scan() is called concurrently by multiple worker goroutines; while Scanner is dereferenced before the call, the config struct is a pointer, and so modifications to it will affect all other running scans done with that Scanner. Make sure we treat it as immutable during anything invoked by Scanner.Scan() in the http module. https://github.com/zmap/zgrab2/pull/245 --- modules/http/scanner.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/http/scanner.go b/modules/http/scanner.go index 0025a40a..f4947e22 100644 --- a/modules/http/scanner.go +++ b/modules/http/scanner.go @@ -294,7 +294,7 @@ func getHTTPURL(https bool, host string, port uint16, endpoint string) string { } // NewHTTPScan gets a new Scan instance for the given target -func (scanner *Scanner) newHTTPScan(t *zgrab2.ScanTarget) *scan { +func (scanner *Scanner) newHTTPScan(t *zgrab2.ScanTarget, useHTTPS bool) *scan { ret := scan{ scanner: scanner, target: t, @@ -325,7 +325,7 @@ func (scanner *Scanner) newHTTPScan(t *zgrab2.ScanTarget) *scan { } else { port = uint16(scanner.config.BaseFlags.Port) } - ret.url = getHTTPURL(scanner.config.UseHTTPS, host, port, scanner.config.Endpoint) + ret.url = getHTTPURL(useHTTPS, host, port, scanner.config.Endpoint) return &ret } @@ -381,14 +381,13 @@ func (scan *scan) Grab() *zgrab2.ScanError { // the target. If the scanner is configured to follow redirects, this may entail // multiple TCP connections to hosts other than target. func (scanner *Scanner) Scan(t zgrab2.ScanTarget) (zgrab2.ScanStatus, interface{}, error) { - scan := scanner.newHTTPScan(&t) + scan := scanner.newHTTPScan(&t, scanner.config.UseHTTPS) defer scan.Cleanup() err := scan.Grab() if err != nil { if scanner.config.RetryHTTPS && !scanner.config.UseHTTPS { scan.Cleanup() - scanner.config.UseHTTPS = true - retry := scanner.newHTTPScan(&t) + retry := scanner.newHTTPScan(&t, true) defer retry.Cleanup() retryError := retry.Grab() if retryError != nil { From 0bd36c52679e0718e534ab195869c8cd842def73 Mon Sep 17 00:00:00 2001 From: Jeff Cody Date: Fri, 28 Feb 2020 13:56:18 -0500 Subject: [PATCH 12/16] modules/http: Add option to suppress errors when max redirects exceeded (#253) If the --max-redirects value is exceeded, we return SCAN_APPLICATION_ERROR with "Too many redirect" as the error message. Add an option to suppress this error, and return success even if we exceed the maximum specified number of redirects. --- modules/http/scanner.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/http/scanner.go b/modules/http/scanner.go index f4947e22..65d9605b 100644 --- a/modules/http/scanner.go +++ b/modules/http/scanner.go @@ -53,6 +53,9 @@ type Flags struct { // UseHTTPS causes the first request to be over TLS, without requiring a // redirect to HTTPS. It does not change the port used for the connection. UseHTTPS bool `long:"use-https" description:"Perform an HTTPS connection on the initial host"` + + // RedirectsSucceed causes the ErrTooManRedirects error to be suppressed + RedirectsSucceed bool `long:"redirects-succeed" description:"Redirects are always a success, even if max-redirects is exceeded"` } // A Results object is returned by the HTTP module's Scanner.Scan() @@ -354,6 +357,9 @@ func (scan *scan) Grab() *zgrab2.ScanError { case ErrRedirLocalhost: break case ErrTooManyRedirects: + if scan.scanner.config.RedirectsSucceed { + return nil + } return zgrab2.NewScanError(zgrab2.SCAN_APPLICATION_ERROR, err) default: return zgrab2.DetectScanError(err) From 351f826d8a95183f55afdcb1a759b138e2fcc9a9 Mon Sep 17 00:00:00 2001 From: David Adrian Date: Tue, 17 Mar 2020 11:50:54 -0400 Subject: [PATCH 13/16] Extract the creation and Marshal of a Grab object (#256) This allows these steps to be reusable outside of the context of the grabTarget function. https://github.com/zmap/zgrab2/pull/256 --- processing.go | 63 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/processing.go b/processing.go index a61c2356..7ac14234 100644 --- a/processing.go +++ b/processing.go @@ -113,6 +113,41 @@ func (target *ScanTarget) OpenUDP(flags *BaseFlags, udp *UDPFlags) (net.Conn, er return NewTimeoutConnection(nil, conn, flags.Timeout, 0, 0, flags.BytesReadLimit), nil } +// BuildGrabFromInputResponse constructs a Grab object for a target, given the +// scan responses. +func BuildGrabFromInputResponse(t *ScanTarget, responses map[string]ScanResponse) *Grab { + var ipstr string + + if t.IP != nil { + ipstr = t.IP.String() + } + return &Grab{ + IP: ipstr, + Domain: t.Domain, + Data: responses, + } +} + +// EncodeGrab serializes a Grab to JSON, handling the debug fields if necessary. +func EncodeGrab(raw *Grab, includeDebug bool) ([]byte, error) { + var outputData interface{} + if includeDebug { + outputData = raw + } else { + // If the caller doesn't explicitly request debug data, strip it out. + // TODO: Migrate this to the ZMap fork of sheriff, once it's more + // stable. + processor := output.Processor{Verbose: false} + stripped, err := processor.Process(raw) + if err != nil { + log.Debugf("Error processing results: %v", err) + stripped = raw + } + outputData = stripped + } + return json.Marshal(outputData) +} + // grabTarget calls handler for each action func grabTarget(input ScanTarget, m *Monitor) []byte { moduleResult := make(map[string]ScanResponse) @@ -140,32 +175,8 @@ func grabTarget(input ScanTarget, m *Monitor) []byte { } } - var ipstr string - if input.IP == nil { - ipstr = "" - } else { - s := input.IP.String() - ipstr = s - } - - raw := Grab{IP: ipstr, Domain: input.Domain, Data: moduleResult} - - var outputData interface{} = raw - - if !includeDebugOutput() { - // If the caller doesn't explicitly request debug data, strip it out. - // Take advantage of the fact that we can skip the (expensive) call to - // process if debug output is included (TODO: until Process does anything else) - processor := output.Processor{Verbose: false} - stripped, err := processor.Process(raw) - if err != nil { - log.Debugf("Error processing results: %v", err) - stripped = raw - } - outputData = stripped - } - - result, err := json.Marshal(outputData) + raw := BuildGrabFromInputResponse(&input, moduleResult) + result, err := EncodeGrab(raw, includeDebugOutput()) if err != nil { log.Fatalf("unable to marshal data: %s", err) } From 7922a73983410157a9feaf1706d6219dcf15e31e Mon Sep 17 00:00:00 2001 From: David Adrian Date: Thu, 19 Mar 2020 14:41:21 -0400 Subject: [PATCH 14/16] Add VSCode to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c70091b3..32b2b0d6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ venv/* zgrab2_schemas.egg-info/* build/* dist/* + +.vscode/* From 8395d72fee29742ef0d313c60012b0b99635bec6 Mon Sep 17 00:00:00 2001 From: bwireman Date: Sat, 21 Mar 2020 10:51:04 -0400 Subject: [PATCH 15/16] use DetermineEncoding To try and translate page to UTF8 if possible --- modules/http/scanner.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/modules/http/scanner.go b/modules/http/scanner.go index 65d9605b..265ad6ff 100644 --- a/modules/http/scanner.go +++ b/modules/http/scanner.go @@ -20,6 +20,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/zmap/zgrab2" "github.com/zmap/zgrab2/lib/http" + "golang.org/x/net/html/charset" ) var ( @@ -373,7 +374,22 @@ func (scan *scan) Grab() *zgrab2.ScanError { readLen = resp.ContentLength } io.CopyN(buf, resp.Body, readLen) - scan.results.Response.BodyText = buf.String() + bufAsString := buf.String() + + // do best effort attempt to determine the response's encoding + // ignore the certainty and just go with it + encoder, _, _ := charset.DetermineEncoding(buf.Bytes(), resp.Header.Get("content_type")) + decoder := encoder.NewDecoder() + + decoded, decErr := decoder.String(bufAsString) + + // if the decoder errors out just use the buffer as a string + if decErr == nil { + scan.results.Response.BodyText = decoded + } else { + scan.results.Response.BodyText = bufAsString + } + if len(scan.results.Response.BodyText) > 0 { m := sha256.New() m.Write(buf.Bytes()) From d2e503881f63a43bc1cbfa6f38364ec5edef4e43 Mon Sep 17 00:00:00 2001 From: Benjamin Wireman Date: Tue, 31 Mar 2020 15:56:44 -0400 Subject: [PATCH 16/16] Allow for programaitc access of ssh result types (#260) https://github.com/zmap/zgrab2/pull/260 --- lib/ssh/cipher.go | 6 ++--- lib/ssh/cipher_test.go | 4 ++-- lib/ssh/common.go | 46 +++++++++++++++++++-------------------- lib/ssh/handshake.go | 22 +++++++++---------- lib/ssh/handshake_test.go | 2 +- lib/ssh/log.go | 6 ++--- lib/ssh/messages.go | 6 ++--- lib/ssh/messages_test.go | 10 ++++----- lib/ssh/transport.go | 10 ++++----- 9 files changed, 56 insertions(+), 56 deletions(-) diff --git a/lib/ssh/cipher.go b/lib/ssh/cipher.go index 34d3917c..b77c1061 100644 --- a/lib/ssh/cipher.go +++ b/lib/ssh/cipher.go @@ -372,7 +372,7 @@ type cbcCipher struct { oracleCamouflage uint32 } -func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) { +func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) { cbc := &cbcCipher{ mac: macModes[algs.MAC].new(macKey), decrypter: cipher.NewCBCDecrypter(c, iv), @@ -386,7 +386,7 @@ func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorith return cbc, nil } -func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) { +func newAESCBCCipher(iv, key, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) { c, err := aes.NewCipher(key) if err != nil { return nil, err @@ -400,7 +400,7 @@ func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCi return cbc, nil } -func newTripleDESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) { +func newTripleDESCBCCipher(iv, key, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) { c, err := des.NewTripleDESCipher(key) if err != nil { return nil, err diff --git a/lib/ssh/cipher_test.go b/lib/ssh/cipher_test.go index e4bb1d92..2f4bd8a9 100644 --- a/lib/ssh/cipher_test.go +++ b/lib/ssh/cipher_test.go @@ -27,7 +27,7 @@ func TestPacketCiphers(t *testing.T) { for cipher := range cipherModes { kr := &kexResult{Hash: crypto.SHA1} - algs := directionAlgorithms{ + algs := DirectionAlgorithms{ Cipher: cipher, MAC: "hmac-sha1", Compression: "none", @@ -68,7 +68,7 @@ func TestCBCOracleCounterMeasure(t *testing.T) { defer delete(cipherModes, aes128cbcID) kr := &kexResult{Hash: crypto.SHA1} - algs := directionAlgorithms{ + algs := DirectionAlgorithms{ Cipher: aes128cbcID, MAC: "hmac-sha1", Compression: "none", diff --git a/lib/ssh/common.go b/lib/ssh/common.go index 5c232803..1077fd84 100644 --- a/lib/ssh/common.go +++ b/lib/ssh/common.go @@ -119,74 +119,74 @@ func findCommon(what string, client []string, server []string) (common string, e return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server) } -type directionAlgorithms struct { +type DirectionAlgorithms struct { Cipher string `json:"cipher"` MAC string `json:"mac"` Compression string `json:"compression"` } -type algorithms struct { - kex string - hostKey string - w directionAlgorithms - r directionAlgorithms +type Algorithms struct { + Kex string + HostKey string + W DirectionAlgorithms + R DirectionAlgorithms } -func (alg *algorithms) MarshalJSON() ([]byte, error) { +func (alg *Algorithms) MarshalJSON() ([]byte, error) { aux := struct { Kex string `json:"dh_kex_algorithm"` HostKey string `json:"host_key_algorithm"` - W directionAlgorithms `json:"client_to_server_alg_group"` - R directionAlgorithms `json:"server_to_client_alg_group"` + W DirectionAlgorithms `json:"client_to_server_alg_group"` + R DirectionAlgorithms `json:"server_to_client_alg_group"` }{ - Kex: alg.kex, - HostKey: alg.hostKey, - W: alg.w, - R: alg.r, + Kex: alg.Kex, + HostKey: alg.HostKey, + W: alg.W, + R: alg.R, } return json.Marshal(aux) } -func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms, err error) { - result := &algorithms{} +func findAgreedAlgorithms(clientKexInit, serverKexInit *KexInitMsg) (algs *Algorithms, err error) { + result := &Algorithms{} - result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos) + result.Kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos) if err != nil { return } - result.hostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos) + result.HostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos) if err != nil { return } - result.w.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer) + result.W.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer) if err != nil { return } - result.r.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient) + result.R.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient) if err != nil { return } - result.w.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer) + result.W.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer) if err != nil { return } - result.r.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient) + result.R.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient) if err != nil { return } - result.w.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer) + result.W.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer) if err != nil { return } - result.r.Compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient) + result.R.Compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient) if err != nil { return } diff --git a/lib/ssh/handshake.go b/lib/ssh/handshake.go index d66526bd..fa8e2299 100644 --- a/lib/ssh/handshake.go +++ b/lib/ssh/handshake.go @@ -28,7 +28,7 @@ type keyingTransport interface { // prepareKeyChange sets up a key change. The key change for a // direction will be effected if a msgNewKeys message is sent // or received. - prepareKeyChange(*algorithms, *kexResult) error + prepareKeyChange(*Algorithms, *kexResult) error } // handshakeTransport implements rekeying on top of a keyingTransport @@ -68,7 +68,7 @@ type handshakeTransport struct { mu sync.Mutex cond *sync.Cond sentInitPacket []byte - sentInitMsg *kexInitMsg + sentInitMsg *KexInitMsg writtenSinceKex uint64 writeError error @@ -264,7 +264,7 @@ func (t *handshakeTransport) requestKeyChange() error { // sendKexInitLocked sends a key change message. t.mu must be locked // while this happens. -func (t *handshakeTransport) sendKexInitLocked(isFirst keyChangeCategory) (*kexInitMsg, []byte, error) { +func (t *handshakeTransport) sendKexInitLocked(isFirst keyChangeCategory) (*KexInitMsg, []byte, error) { // kexInits may be sent either in response to the other side, // or because our side wants to initiate a key change, so we // may have already sent a kexInit. In that case, don't send a @@ -273,7 +273,7 @@ func (t *handshakeTransport) sendKexInitLocked(isFirst keyChangeCategory) (*kexI return t.sentInitMsg, t.sentInitPacket, nil } - msg := &kexInitMsg{ + msg := &KexInitMsg{ KexAlgos: t.config.KeyExchanges, CiphersClientServer: t.config.Ciphers, CiphersServerClient: t.config.Ciphers, @@ -352,7 +352,7 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro } } - otherInit := &kexInitMsg{} + otherInit := &KexInitMsg{} if err := Unmarshal(otherInitPacket, otherInit); err != nil { return err } @@ -403,12 +403,12 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro } } - kex, ok := kexAlgoMap[algs.kex] + kex, ok := kexAlgoMap[algs.Kex] if !ok { - return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex) + return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.Kex) } - kex = kex.GetNew(algs.kex) + kex = kex.GetNew(algs.Kex) if t.config.ConnLog != nil { t.config.ConnLog.DHKeyExchange = kex @@ -447,10 +447,10 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro return nil } -func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) { +func (t *handshakeTransport) server(kex kexAlgorithm, algs *Algorithms, magics *handshakeMagics) (*kexResult, error) { var hostKey Signer for _, k := range t.hostKeys { - if algs.hostKey == k.PublicKey().Type() { + if algs.HostKey == k.PublicKey().Type() { hostKey = k } } @@ -459,7 +459,7 @@ func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics * return r, err } -func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) { +func (t *handshakeTransport) client(kex kexAlgorithm, algs *Algorithms, magics *handshakeMagics) (*kexResult, error) { result, err := kex.Client(t.conn, t.config.Rand, magics, t.config) if err != nil { return nil, err diff --git a/lib/ssh/handshake_test.go b/lib/ssh/handshake_test.go index da53d3a0..16c3779e 100644 --- a/lib/ssh/handshake_test.go +++ b/lib/ssh/handshake_test.go @@ -354,7 +354,7 @@ type errorKeyingTransport struct { readLeft, writeLeft int } -func (n *errorKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error { +func (n *errorKeyingTransport) prepareKeyChange(*Algorithms, *kexResult) error { return nil } func (n *errorKeyingTransport) getSessionID() []byte { diff --git a/lib/ssh/log.go b/lib/ssh/log.go index 58209b38..a8981867 100644 --- a/lib/ssh/log.go +++ b/lib/ssh/log.go @@ -20,9 +20,9 @@ type HandshakeLog struct { Banner string `json:"banner,omitempty"` ServerID *EndpointId `json:"server_id,omitempty"` ClientID *EndpointId `json:"client_id,omitempty"` - ServerKex *kexInitMsg `json:"server_key_exchange,omitempty"` - ClientKex *kexInitMsg `json:"client_key_exchange,omitempty"` - AlgorithmSelection *algorithms `json:"algorithm_selection,omitempty"` + ServerKex *KexInitMsg `json:"server_key_exchange,omitempty"` + ClientKex *KexInitMsg `json:"client_key_exchange,omitempty"` + AlgorithmSelection *Algorithms `json:"algorithm_selection,omitempty"` DHKeyExchange kexAlgorithm `json:"key_exchange,omitempty"` UserAuth []string `json:"userauth,omitempty"` Crypto *kexResult `json:"crypto,omitempty"` diff --git a/lib/ssh/messages.go b/lib/ssh/messages.go index d46efec9..a3267448 100644 --- a/lib/ssh/messages.go +++ b/lib/ssh/messages.go @@ -54,7 +54,7 @@ func (d *disconnectMsg) Error() string { // See RFC 4253, section 7.1. const msgKexInit = 20 -type kexInitMsg struct { +type KexInitMsg struct { Cookie [16]byte `sshtype:"20"` KexAlgos []string ServerHostKeyAlgos []string @@ -86,7 +86,7 @@ type JsonKexInitMsg struct { Reserved uint32 `json:"reserved"` } -func (kex *kexInitMsg) MarshalJSON() ([]byte, error) { +func (kex *KexInitMsg) MarshalJSON() ([]byte, error) { temp := JsonKexInitMsg{ Cookie: kex.Cookie[:], KexAlgos: kex.KexAlgos, @@ -753,7 +753,7 @@ func decode(packet []byte) (interface{}, error) { case msgServiceAccept: msg = new(serviceAcceptMsg) case msgKexInit: - msg = new(kexInitMsg) + msg = new(KexInitMsg) case msgKexDHInit: msg = new(kexDHInitMsg) case msgKexDHReply: diff --git a/lib/ssh/messages_test.go b/lib/ssh/messages_test.go index e7907641..ae7a7347 100644 --- a/lib/ssh/messages_test.go +++ b/lib/ssh/messages_test.go @@ -166,7 +166,7 @@ func TestUnmarshalShortKexInitPacket(t *testing.T) { // This used to panic. // Issue 11348 packet := []byte{0x14, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xff, 0xff, 0xff, 0xff} - kim := &kexInitMsg{} + kim := &KexInitMsg{} if err := Unmarshal(packet, kim); err == nil { t.Error("truncated packet unmarshaled without error") } @@ -228,8 +228,8 @@ func randomInt(rand *rand.Rand) *big.Int { return new(big.Int).SetInt64(int64(int32(rand.Uint32()))) } -func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value { - ki := &kexInitMsg{} +func (*KexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value { + ki := &KexInitMsg{} randomBytes(ki.Cookie[:], rand) ki.KexAlgos = randomNameList(rand) ki.ServerHostKeyAlgos = randomNameList(rand) @@ -254,7 +254,7 @@ func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value { } var ( - _kexInitMsg = new(kexInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface() + _kexInitMsg = new(KexInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface() _kexDHInitMsg = new(kexDHInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface() _kexInit = Marshal(_kexInitMsg) @@ -268,7 +268,7 @@ func BenchmarkMarshalKexInitMsg(b *testing.B) { } func BenchmarkUnmarshalKexInitMsg(b *testing.B) { - m := new(kexInitMsg) + m := new(KexInitMsg) for i := 0; i < b.N; i++ { Unmarshal(_kexInit, m) } diff --git a/lib/ssh/transport.go b/lib/ssh/transport.go index 62fba629..2683ad99 100644 --- a/lib/ssh/transport.go +++ b/lib/ssh/transport.go @@ -68,14 +68,14 @@ type connectionState struct { // prepareKeyChange sets up key material for a keychange. The key changes in // both directions are triggered by reading and writing a msgNewKey packet // respectively. -func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error { - if ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult); err != nil { +func (t *transport) prepareKeyChange(algs *Algorithms, kexResult *kexResult) error { + if ciph, err := newPacketCipher(t.reader.dir, algs.R, kexResult); err != nil { return err } else { t.reader.pendingKeyChange <- ciph } - if ciph, err := newPacketCipher(t.writer.dir, algs.w, kexResult); err != nil { + if ciph, err := newPacketCipher(t.writer.dir, algs.W, kexResult); err != nil { return err } else { t.writer.pendingKeyChange <- ciph @@ -192,7 +192,7 @@ var ( ) // generateKeys generates key material for IV, MAC and encryption. -func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, key, macKey []byte) { +func generateKeys(d direction, algs DirectionAlgorithms, kex *kexResult) (iv, key, macKey []byte) { cipherMode := cipherModes[algs.Cipher] macMode := macModes[algs.MAC] @@ -209,7 +209,7 @@ func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, ke // setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as // described in RFC 4253, section 6.4. direction should either be serverKeys // (to setup server->client keys) or clientKeys (for client->server keys). -func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) { +func newPacketCipher(d direction, algs DirectionAlgorithms, kex *kexResult) (packetCipher, error) { iv, key, macKey := generateKeys(d, algs, kex) if algs.Cipher == gcmCipherID {