From 4b736d3aa00e45879afc6b2cd8b772a83096862e Mon Sep 17 00:00:00 2001 From: RicYaben Date: Thu, 17 Oct 2024 15:12:12 +0200 Subject: [PATCH] implements xmpp, coap and opcua --- docker-runner/base-apt-update.sh | 0 docker-runner/docker-run.sh | 0 docker-runner/entrypoint.sh | 0 go.mod | 2 + go.sum | 4 + integration_tests/.template/cleanup.sh | 0 .../.template/container/entrypoint.sh | 0 integration_tests/.template/setup.sh | 0 integration_tests/.template/test.sh | 0 integration_tests/cleanup.sh | 0 integration_tests/ftp/cleanup.sh | 0 integration_tests/ftp/container/entrypoint.sh | 0 integration_tests/ftp/setup.sh | 0 integration_tests/ftp/test.sh | 0 integration_tests/http/cleanup.sh | 0 integration_tests/http/setup.sh | 0 integration_tests/http/test.sh | 0 integration_tests/ipp/cleanup.sh | 0 .../ipp/container-cups-tls/Dockerfile | 0 .../ipp/container-cups-tls/entrypoint.sh | 0 .../ipp/container-cups/Dockerfile | 0 .../ipp/container-cups/entrypoint.sh | 0 integration_tests/ipp/setup.sh | 0 integration_tests/ipp/test.sh | 0 integration_tests/mongodb/cleanup.sh | 0 integration_tests/mongodb/setup.sh | 0 integration_tests/mongodb/test.sh | 0 integration_tests/mssql/cleanup.sh | 0 integration_tests/mssql/setup.sh | 0 integration_tests/mssql/test.sh | 0 integration_tests/mysql/cleanup.sh | 0 integration_tests/mysql/setup.sh | 0 integration_tests/mysql/test.sh | 0 integration_tests/new.sh | 0 integration_tests/ntp/cleanup.sh | 0 integration_tests/ntp/setup.sh | 0 integration_tests/ntp/test.sh | 0 integration_tests/pop3/cleanup.sh | 0 .../pop3/container/entrypoint.sh | 0 integration_tests/pop3/setup.sh | 0 integration_tests/pop3/test.sh | 0 integration_tests/postgres/cleanup.sh | 0 integration_tests/postgres/container/build.sh | 0 .../postgres/container/setup_nossl.sh | 0 .../postgres/container/setup_ssl.sh | 0 integration_tests/postgres/setup.sh | 0 integration_tests/postgres/test.sh | 0 integration_tests/redis/cleanup.sh | 0 integration_tests/redis/setup.sh | 0 integration_tests/redis/test.sh | 0 integration_tests/setup.sh | 0 integration_tests/smtp/cleanup.sh | 0 .../smtp/container/entrypoint.sh | 0 integration_tests/smtp/setup.sh | 0 integration_tests/smtp/test.sh | 0 integration_tests/ssh/cleanup.sh | 0 integration_tests/ssh/setup.sh | 0 integration_tests/ssh/test.sh | 0 integration_tests/telnet/cleanup.sh | 0 .../telnet/container/entrypoint.sh | 0 integration_tests/telnet/setup.sh | 0 integration_tests/telnet/test.sh | 0 integration_tests/test.sh | 0 modules/coap/scanner.go | 3 +- modules/opcua.go | 7 + modules/opcua/browse.go | 234 +++++++++++++++ modules/opcua/opcua_test.go | 66 +++++ modules/opcua/scanner.go | 273 ++++++++++++++++++ modules/rtsp/{rtsp.go => scanner.go} | 0 modules/webproxy/scanner.go | 1 - modules/xmpp.go | 7 + modules/xmpp/scanner.go | 145 ++++++++++ 72 files changed, 739 insertions(+), 3 deletions(-) mode change 100755 => 100644 docker-runner/base-apt-update.sh mode change 100755 => 100644 docker-runner/docker-run.sh mode change 100755 => 100644 docker-runner/entrypoint.sh mode change 100755 => 100644 integration_tests/.template/cleanup.sh mode change 100755 => 100644 integration_tests/.template/container/entrypoint.sh mode change 100755 => 100644 integration_tests/.template/setup.sh mode change 100755 => 100644 integration_tests/.template/test.sh mode change 100755 => 100644 integration_tests/cleanup.sh mode change 100755 => 100644 integration_tests/ftp/cleanup.sh mode change 100755 => 100644 integration_tests/ftp/container/entrypoint.sh mode change 100755 => 100644 integration_tests/ftp/setup.sh mode change 100755 => 100644 integration_tests/ftp/test.sh mode change 100755 => 100644 integration_tests/http/cleanup.sh mode change 100755 => 100644 integration_tests/http/setup.sh mode change 100755 => 100644 integration_tests/http/test.sh mode change 100755 => 100644 integration_tests/ipp/cleanup.sh mode change 100755 => 100644 integration_tests/ipp/container-cups-tls/Dockerfile mode change 100755 => 100644 integration_tests/ipp/container-cups-tls/entrypoint.sh mode change 100755 => 100644 integration_tests/ipp/container-cups/Dockerfile mode change 100755 => 100644 integration_tests/ipp/container-cups/entrypoint.sh mode change 100755 => 100644 integration_tests/ipp/setup.sh mode change 100755 => 100644 integration_tests/ipp/test.sh mode change 100755 => 100644 integration_tests/mongodb/cleanup.sh mode change 100755 => 100644 integration_tests/mongodb/setup.sh mode change 100755 => 100644 integration_tests/mongodb/test.sh mode change 100755 => 100644 integration_tests/mssql/cleanup.sh mode change 100755 => 100644 integration_tests/mssql/setup.sh mode change 100755 => 100644 integration_tests/mssql/test.sh mode change 100755 => 100644 integration_tests/mysql/cleanup.sh mode change 100755 => 100644 integration_tests/mysql/setup.sh mode change 100755 => 100644 integration_tests/mysql/test.sh mode change 100755 => 100644 integration_tests/new.sh mode change 100755 => 100644 integration_tests/ntp/cleanup.sh mode change 100755 => 100644 integration_tests/ntp/setup.sh mode change 100755 => 100644 integration_tests/ntp/test.sh mode change 100755 => 100644 integration_tests/pop3/cleanup.sh mode change 100755 => 100644 integration_tests/pop3/container/entrypoint.sh mode change 100755 => 100644 integration_tests/pop3/setup.sh mode change 100755 => 100644 integration_tests/pop3/test.sh mode change 100755 => 100644 integration_tests/postgres/cleanup.sh mode change 100755 => 100644 integration_tests/postgres/container/build.sh mode change 100755 => 100644 integration_tests/postgres/container/setup_nossl.sh mode change 100755 => 100644 integration_tests/postgres/container/setup_ssl.sh mode change 100755 => 100644 integration_tests/postgres/setup.sh mode change 100755 => 100644 integration_tests/postgres/test.sh mode change 100755 => 100644 integration_tests/redis/cleanup.sh mode change 100755 => 100644 integration_tests/redis/setup.sh mode change 100755 => 100644 integration_tests/redis/test.sh mode change 100755 => 100644 integration_tests/setup.sh mode change 100755 => 100644 integration_tests/smtp/cleanup.sh mode change 100755 => 100644 integration_tests/smtp/container/entrypoint.sh mode change 100755 => 100644 integration_tests/smtp/setup.sh mode change 100755 => 100644 integration_tests/smtp/test.sh mode change 100755 => 100644 integration_tests/ssh/cleanup.sh mode change 100755 => 100644 integration_tests/ssh/setup.sh mode change 100755 => 100644 integration_tests/ssh/test.sh mode change 100755 => 100644 integration_tests/telnet/cleanup.sh mode change 100755 => 100644 integration_tests/telnet/container/entrypoint.sh mode change 100755 => 100644 integration_tests/telnet/setup.sh mode change 100755 => 100644 integration_tests/telnet/test.sh mode change 100755 => 100644 integration_tests/test.sh create mode 100644 modules/opcua.go create mode 100644 modules/opcua/browse.go create mode 100644 modules/opcua/opcua_test.go create mode 100644 modules/opcua/scanner.go rename modules/rtsp/{rtsp.go => scanner.go} (100%) create mode 100644 modules/xmpp.go create mode 100644 modules/xmpp/scanner.go diff --git a/docker-runner/base-apt-update.sh b/docker-runner/base-apt-update.sh old mode 100755 new mode 100644 diff --git a/docker-runner/docker-run.sh b/docker-runner/docker-run.sh old mode 100755 new mode 100644 diff --git a/docker-runner/entrypoint.sh b/docker-runner/entrypoint.sh old mode 100755 new mode 100644 diff --git a/go.mod b/go.mod index f20e2de0..6d44e382 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require golang.org/x/exp v0.0.0-20240707233637-46b078467d37 require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect golang.org/x/sync v0.7.0 // indirect ) @@ -34,6 +35,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/gopcua/opcua v0.5.3 github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect diff --git a/go.sum b/go.sum index a6a7719d..c6479f77 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/gopcua/opcua v0.5.3 h1:K5QQhjK9KQxQW8doHL/Cd8oljUeXWnJJsNgP7mOGIhw= +github.com/gopcua/opcua v0.5.3/go.mod h1:nrVl4/Rs3SDQRhNQ50EbAiI5JSpDrTG6Frx3s4HLnw4= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A= @@ -47,6 +49,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= diff --git a/integration_tests/.template/cleanup.sh b/integration_tests/.template/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/.template/container/entrypoint.sh b/integration_tests/.template/container/entrypoint.sh old mode 100755 new mode 100644 diff --git a/integration_tests/.template/setup.sh b/integration_tests/.template/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/.template/test.sh b/integration_tests/.template/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/cleanup.sh b/integration_tests/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ftp/cleanup.sh b/integration_tests/ftp/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ftp/container/entrypoint.sh b/integration_tests/ftp/container/entrypoint.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ftp/setup.sh b/integration_tests/ftp/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ftp/test.sh b/integration_tests/ftp/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/http/cleanup.sh b/integration_tests/http/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/http/setup.sh b/integration_tests/http/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/http/test.sh b/integration_tests/http/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ipp/cleanup.sh b/integration_tests/ipp/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ipp/container-cups-tls/Dockerfile b/integration_tests/ipp/container-cups-tls/Dockerfile old mode 100755 new mode 100644 diff --git a/integration_tests/ipp/container-cups-tls/entrypoint.sh b/integration_tests/ipp/container-cups-tls/entrypoint.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ipp/container-cups/Dockerfile b/integration_tests/ipp/container-cups/Dockerfile old mode 100755 new mode 100644 diff --git a/integration_tests/ipp/container-cups/entrypoint.sh b/integration_tests/ipp/container-cups/entrypoint.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ipp/setup.sh b/integration_tests/ipp/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ipp/test.sh b/integration_tests/ipp/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/mongodb/cleanup.sh b/integration_tests/mongodb/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/mongodb/setup.sh b/integration_tests/mongodb/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/mongodb/test.sh b/integration_tests/mongodb/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/mssql/cleanup.sh b/integration_tests/mssql/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/mssql/setup.sh b/integration_tests/mssql/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/mssql/test.sh b/integration_tests/mssql/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/mysql/cleanup.sh b/integration_tests/mysql/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/mysql/setup.sh b/integration_tests/mysql/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/mysql/test.sh b/integration_tests/mysql/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/new.sh b/integration_tests/new.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ntp/cleanup.sh b/integration_tests/ntp/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ntp/setup.sh b/integration_tests/ntp/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ntp/test.sh b/integration_tests/ntp/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/pop3/cleanup.sh b/integration_tests/pop3/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/pop3/container/entrypoint.sh b/integration_tests/pop3/container/entrypoint.sh old mode 100755 new mode 100644 diff --git a/integration_tests/pop3/setup.sh b/integration_tests/pop3/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/pop3/test.sh b/integration_tests/pop3/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/postgres/cleanup.sh b/integration_tests/postgres/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/postgres/container/build.sh b/integration_tests/postgres/container/build.sh old mode 100755 new mode 100644 diff --git a/integration_tests/postgres/container/setup_nossl.sh b/integration_tests/postgres/container/setup_nossl.sh old mode 100755 new mode 100644 diff --git a/integration_tests/postgres/container/setup_ssl.sh b/integration_tests/postgres/container/setup_ssl.sh old mode 100755 new mode 100644 diff --git a/integration_tests/postgres/setup.sh b/integration_tests/postgres/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/postgres/test.sh b/integration_tests/postgres/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/redis/cleanup.sh b/integration_tests/redis/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/redis/setup.sh b/integration_tests/redis/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/redis/test.sh b/integration_tests/redis/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/setup.sh b/integration_tests/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/smtp/cleanup.sh b/integration_tests/smtp/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/smtp/container/entrypoint.sh b/integration_tests/smtp/container/entrypoint.sh old mode 100755 new mode 100644 diff --git a/integration_tests/smtp/setup.sh b/integration_tests/smtp/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/smtp/test.sh b/integration_tests/smtp/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ssh/cleanup.sh b/integration_tests/ssh/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ssh/setup.sh b/integration_tests/ssh/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/ssh/test.sh b/integration_tests/ssh/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/telnet/cleanup.sh b/integration_tests/telnet/cleanup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/telnet/container/entrypoint.sh b/integration_tests/telnet/container/entrypoint.sh old mode 100755 new mode 100644 diff --git a/integration_tests/telnet/setup.sh b/integration_tests/telnet/setup.sh old mode 100755 new mode 100644 diff --git a/integration_tests/telnet/test.sh b/integration_tests/telnet/test.sh old mode 100755 new mode 100644 diff --git a/integration_tests/test.sh b/integration_tests/test.sh old mode 100755 new mode 100644 diff --git a/modules/coap/scanner.go b/modules/coap/scanner.go index fc3ce34b..f86dcc33 100644 --- a/modules/coap/scanner.go +++ b/modules/coap/scanner.go @@ -143,8 +143,7 @@ func (scanner *Scanner) newCoAPscan(t *zgrab2.ScanTarget) *scan { func (scanner *Scanner) Scan(t zgrab2.ScanTarget) (zgrab2.ScanStatus, interface{}, error) { scan := scanner.newCoAPscan(&t) - err := scan.Grab() - if err != nil { + if err := scan.Grab(); err != nil { return err.Unpack(&scan.results) } return zgrab2.SCAN_SUCCESS, &scan.results, nil diff --git a/modules/opcua.go b/modules/opcua.go new file mode 100644 index 00000000..23978dca --- /dev/null +++ b/modules/opcua.go @@ -0,0 +1,7 @@ +package modules + +import "github.com/zmap/zgrab2/modules/opcua" + +func init() { + opcua.RegisterModule() +} diff --git a/modules/opcua/browse.go b/modules/opcua/browse.go new file mode 100644 index 00000000..4a235bfe --- /dev/null +++ b/modules/opcua/browse.go @@ -0,0 +1,234 @@ +package opcua + +import ( + "context" + + "github.com/gopcua/opcua" + "github.com/gopcua/opcua/id" + "github.com/gopcua/opcua/ua" + "github.com/pkg/errors" + "github.com/zmap/zgrab2" +) + +type NodeDef struct { + NodeID *ua.NodeID `json:"nodeID"` + NodeClass ua.NodeClass `json:"nodeClass"` + BrowseName string `json:"browseName"` + Description string `json:"description"` + AccessLevel ua.AccessLevelType `json:"accessLevel"` + Path string `json:"path"` + DataType string `json:"dataType"` + Writable bool `json:"writable"` +} + +func (n *NodeDef) SetNodeClass(v *ua.DataValue) error { + switch err := v.Status; err { + case ua.StatusOK: + n.NodeClass = ua.NodeClass(v.Value.Int()) + return nil + default: + return err + } +} + +func (n *NodeDef) SetBrowseName(v *ua.DataValue) error { + switch err := v.Status; err { + case ua.StatusOK: + n.BrowseName = v.Value.String() + return nil + default: + return err + } +} + +func (n *NodeDef) SetDescription(v *ua.DataValue) error { + switch err := v.Status; err { + case ua.StatusOK: + if v.Value != nil { + n.Description = v.Value.String() + } + return nil + case ua.StatusBadAttributeIDInvalid: + return nil + default: + return err + } +} + +func (n *NodeDef) SetAccessLevel(v *ua.DataValue) error { + switch err := v.Status; err { + case ua.StatusOK: + n.AccessLevel = ua.AccessLevelType(v.Value.Int()) + n.Writable = n.AccessLevel&ua.AccessLevelTypeCurrentWrite == ua.AccessLevelTypeCurrentWrite + return nil + case ua.StatusBadAttributeIDInvalid: + return nil + default: + return err + } +} + +func (n *NodeDef) SetDataType(dt *ua.DataValue) error { + switch err := dt.Status; err { + case ua.StatusOK: + switch v := dt.Value.NodeID().IntID(); v { + case id.DateTime: + n.DataType = "time.Time" + case id.Boolean: + n.DataType = "bool" + case id.SByte: + n.DataType = "int8" + case id.Int16: + n.DataType = "int16" + case id.Int32: + n.DataType = "int32" + case id.Byte: + n.DataType = "byte" + case id.UInt16: + n.DataType = "uint16" + case id.UInt32: + n.DataType = "uint32" + case id.UtcTime: + n.DataType = "time.Time" + case id.String: + n.DataType = "string" + case id.Float: + n.DataType = "float32" + case id.Double: + n.DataType = "float64" + default: + n.DataType = dt.Value.NodeID().String() + } + return nil + case ua.StatusBadAttributeIDInvalid: + return nil + default: + return err + } +} + +type EndpointResult struct { + Error *zgrab2.ScanError `json:"error,omitempty"` + EndpointDescription *ua.EndpointDescription + Authenticated []string `json:"authentication"` + Endpoint map[string]interface{} `json:"endpoint"` + Nodes []*NodeDef `json:"nodes"` + Namespaces []string `json:"namespaces"` +} + +func newEndpoint(ep *ua.EndpointDescription) *EndpointResult { + return &EndpointResult{ + EndpointDescription: ep, + Authenticated: []string{}, + } +} + +type Results struct { + Endpoints []*EndpointResult `json:"endpoints,omitempty"` +} + +type browser struct { + level uint + maxLevel uint + ctx context.Context +} + +func newBrowser(level uint, ctx context.Context) *browser { + b := &browser{ + ctx: ctx, + maxLevel: 10, + } + b.setLevel(level) + + return b +} + +func (b *browser) setLevel(level uint) { + if level > b.maxLevel { + panic(errors.Errorf("failed to set browser level. The maximum level is %d, but attempted to set %d", b.maxLevel, level)) + } + b.level = level +} + +func (b *browser) Node(n *opcua.Node) (*NodeDef, error) { + var def = &NodeDef{ + NodeID: n.ID, + } + + // NOTE: We do not need to know anything else about the node + // If you are modifying this, please be aware of possible + // ethical issues. Reading values from nodes will not offer + // more insights. + attrs, err := n.Attributes( + b.ctx, + ua.AttributeIDNodeClass, + ua.AttributeIDBrowseName, + ua.AttributeIDDescription, + ua.AttributeIDAccessLevel, + ua.AttributeIDDataType, + ) + if err != nil { + return def, err + } + + for i, f := range []func(v *ua.DataValue) error{ + def.SetNodeClass, + def.SetBrowseName, + def.SetDescription, + def.SetAccessLevel, + def.SetDataType, + } { + if err := f(attrs[i]); err != nil { + return def, err + } + } + + return def, nil +} + +func (b *browser) getChildren(refType uint32, n *opcua.Node, path string, level int) ([]*NodeDef, error) { + refs, err := n.ReferencedNodes(b.ctx, refType, ua.BrowseDirectionForward, ua.NodeClassAll, true) + if err != nil { + return nil, errors.Errorf("References: %d: %s", refType, err) + } + + var nodes []*NodeDef + for _, rn := range refs { + children, err := b.browse(rn, path, level+1) + if err != nil { + return nil, errors.Errorf("browse children: %s", err) + } + nodes = append(nodes, children...) + } + return nodes, nil +} + +func (b *browser) browse(n *opcua.Node, path string, level int) ([]*NodeDef, error) { + if level > int(b.level) { + return nil, nil + } + + def, err := b.Node(n) + if err != nil { + return nil, err + } + def.Path = join(path, def.BrowseName) + + var nodes []*NodeDef + nodes = append(nodes, def) + for _, refType := range []uint32{id.HasComponent, id.Organizes, id.HasProperty} { + nChilds, err := b.getChildren(refType, n, def.Path, level) + if err != nil { + return nil, err + } + nodes = append(nodes, nChilds...) + } + return nodes, nil +} + +func join(a, b string) string { + if a == "" { + return b + } + return a + "." + b +} diff --git a/modules/opcua/opcua_test.go b/modules/opcua/opcua_test.go new file mode 100644 index 00000000..6ca1e8fb --- /dev/null +++ b/modules/opcua/opcua_test.go @@ -0,0 +1,66 @@ +package opcua + +import ( + "testing" + + "github.com/zmap/zgrab2" +) + +type opcuaTester struct { + address string + port uint + uri string + certHost string + endpoint string + level uint +} + +func (cfg *opcuaTester) getScanner() (*Scanner, error) { + var module Module + flags := module.NewFlags().(*Flags) + flags.Uri = cfg.uri + flags.CertHost = cfg.certHost + flags.Endpoint = cfg.endpoint + flags.BrowseDepth = cfg.level + + scanner := module.NewScanner() + if err := scanner.Init(flags); err != nil { + return nil, err + } + return scanner.(*Scanner), nil +} + +func (cfg *opcuaTester) runTest(t *testing.T, testName string) { + target := zgrab2.ScanTarget{ + Port: &cfg.port, + Domain: cfg.address, + } + + scanner, err := cfg.getScanner() + if err != nil { + t.Fatalf("[%s] Unexpected error: %v", testName, err) + } + + st, res, err := scanner.Scan(target) + if err != nil { + t.Fatalf("[%s] error while scanning: %v, %v", testName, err, st) + } + t.Logf("%+v", res) +} + +var tests = map[string]*opcuaTester{ + "demo": { + address: "opcua.demo-this.com", + port: 51210, + uri: "urn:opcua:scanner", + certHost: "localhost", + endpoint: "UA/SampleServer", + level: 2, + }, +} + +func TestOPCUA(t *testing.T) { + for tname, cfg := range tests { + cfg.runTest(t, tname) + } +} diff --git a/modules/opcua/scanner.go b/modules/opcua/scanner.go new file mode 100644 index 00000000..7391782b --- /dev/null +++ b/modules/opcua/scanner.go @@ -0,0 +1,273 @@ +package opcua + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "fmt" + "math/big" + "net" + "strings" + "time" + + "github.com/gopcua/opcua" + "github.com/gopcua/opcua/ua" + log "github.com/sirupsen/logrus" + "github.com/zmap/zgrab2" +) + +type Flags struct { + zgrab2.BaseFlags + + Uri string `json:"uri" default:"urn:opcua:zgrab" description:"Client URI for anonymous authentication"` + CertHost string `json:"cert-host" default:"localhost" description:"Host certificate holder"` + Endpoint string `json:"endpoint" description:"Path endpoint in the server"` + BrowseDepth uint `json:"browse-depth" description:"Browse nesting level, one level is enough to prove access and retrival. Default: 0; Max: 10; Recommended: 1;"` + + UseHTTP bool `json:"use-http" description:"use HTTP"` + RetryHTTP bool `json:"retry-http" description:"retry over HTTP"` + UseTLS bool `json:"use-tls" description:"Use HTTPS"` + RetryTLS bool `json:"retry-tls" description:"retry to connect with TLS"` +} + +// Module implements the zgrab2.Module interface. +type Module struct { +} + +type Scanner struct { + config *Flags + LocalKey *rsa.PrivateKey + + certificate []byte + endpoint string +} + +// GetName implements zgrab2.Scanner. +func (scanner *Scanner) GetName() string { + return scanner.config.Name +} + +// GetTrigger implements zgrab2.Scanner. +func (scanner *Scanner) GetTrigger() string { + return scanner.config.Trigger +} + +// Init implements zgrab2.Scanner. +func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error { + f, _ := flags.(*Flags) + scanner.config = f + scanner.endpoint = f.Endpoint + + certPEM, keyPEM, err := generateSelfSignedCert(f.CertHost) + if err != nil { + return fmt.Errorf("failed to generate self-signed certificate: %v", err) + } + + cert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return fmt.Errorf("error while loading certificate: %v", err) + } + scanner.certificate = cert.Certificate[0] + return nil +} + +// InitPerSender implements zgrab2.Scanner. +func (*Scanner) InitPerSender(senderID int) error { + return nil +} + +// Protocol implements zgrab2.Scanner. +func (*Scanner) Protocol() string { + return "opcua" +} + +func RegisterModule() { + var module Module + _, err := zgrab2.AddCommand("opcuac", "OPC UA module", module.Description(), 4840, &module) + if err != nil { + log.Fatal(err) + } +} + +func (f *Flags) Validate(args []string) error { + return nil +} + +func (module *Module) NewFlags() interface{} { + return new(Flags) +} + +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 OPC-UA" +} + +// Help returns the module's help string. +func (flags *Flags) Help() string { + return "" +} + +type scan struct { + scanner *Scanner + ctx context.Context + + results Results + endpoint string + authModes []string + browser *browser +} + +func (s *scan) setEndpoints(eps []*ua.EndpointDescription) { + for _, ep := range eps { + r := newEndpoint(ep) + s.results.Endpoints = append(s.results.Endpoints, r) + } +} + +func (s *scan) Grab() *zgrab2.ScanError { + // Get endpoints + eps, err := opcua.GetEndpoints(s.ctx, s.endpoint) + if err != nil { + return zgrab2.DetectScanError(err) + } + s.setEndpoints(eps) + + // Authenticate to each endpoints + for _, r := range s.results.Endpoints { + s.authAndBrowse(r) + } + return nil +} + +func (s *scan) getAuth(auth string) (ua.UserTokenType, opcua.Option) { + // We only care about anonymous or self-signed certificate authentications + if auth == "anonymous" { + return ua.UserTokenTypeAnonymous, opcua.AuthAnonymous() + } + + if auth == "certificate" { + return ua.UserTokenTypeCertificate, opcua.AuthCertificate(s.scanner.certificate) + } + + return 0, nil +} + +func (s *scan) clientOptions(ep *ua.EndpointDescription, auth string) []opcua.Option { + authMode, authOption := s.getAuth(auth) + return []opcua.Option{ + authOption, + opcua.SecurityFromEndpoint(ep, authMode), + } +} + +func (s *scan) authAndBrowse(r *EndpointResult) { + ep := r.EndpointDescription + + var authedClient *opcua.Client + + // TODO FIXME: The endpointdescription already tell us which modes does it + // accept. Use those instead? + for _, a := range s.authModes { + ops := s.clientOptions(ep, a) + client, err := opcua.NewClient(ep.EndpointURL, ops...) + if err != nil { + continue + } + defer client.Close(s.ctx) + + if err := client.Connect(s.ctx); err != nil { + continue + } + authedClient = client + r.Authenticated = append(r.Authenticated, a) + } + + if authedClient != nil { + r.Namespaces = authedClient.Namespaces() + id, _ := ua.ParseNodeID("i=84") + nodes, err := s.browser.browse(authedClient.Node(id), "", 0) + if err != nil { + return + } + r.Nodes = nodes + } +} + +func (s *Scanner) newOPCUAscan(ep string) *scan { + ctx := context.Background() + return &scan{ + scanner: s, + ctx: ctx, + endpoint: ep, + authModes: []string{"anonymous", "certificate"}, + browser: newBrowser(s.config.BrowseDepth, ctx), + } +} + +func (s *Scanner) Scan(t zgrab2.ScanTarget) (zgrab2.ScanStatus, interface{}, error) { + ep := fmt.Sprintf("opc.tcp://%s:%d", t.String(), *t.Port) + ep = strings.Join([]string{ep, s.endpoint}, "/") + + scan := s.newOPCUAscan(ep) + if err := scan.Grab(); err != nil { + return err.Unpack(&scan.results) + } + return zgrab2.SCAN_SUCCESS, &scan.results, nil +} + +func generateSelfSignedCert(host string) ([]byte, []byte, error) { + // Create a new ECDSA private key + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, err + } + + // Generate a random serial number + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return nil, nil, err + } + + // Create a certificate template + template := x509.Certificate{ + SerialNumber: serialNumber, + NotBefore: time.Now(), + NotAfter: time.Now().Add(365 * 24 * time.Hour), // Valid for 1 year + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + BasicConstraintsValid: true, + } + + // Add IP and DNS names + ip := net.ParseIP(host) + if ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, host) + } + + // Self-sign the certificate + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return nil, nil, err + } + + // Encode the certificate and private key in PEM format (in-memory) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + keyPEM, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return nil, nil, err + } + + keyPEMBlock := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyPEM}) + return certPEM, keyPEMBlock, nil +} diff --git a/modules/rtsp/rtsp.go b/modules/rtsp/scanner.go similarity index 100% rename from modules/rtsp/rtsp.go rename to modules/rtsp/scanner.go diff --git a/modules/webproxy/scanner.go b/modules/webproxy/scanner.go index dada8988..aade2c3a 100644 --- a/modules/webproxy/scanner.go +++ b/modules/webproxy/scanner.go @@ -47,7 +47,6 @@ type Flags struct { UseTLS bool `long:"use-tls" description:"Perform an HTTPS connection on the initial host"` RetryTLS bool `long:"retry-tls" description:"If the initial request fails, reconnect and try with HTTPS."` - // TODO: not implemented yet! UseSOCKS bool `long:"use-socks" description:"Perform a SOCKS connection on the initial host"` RetrySOCKS bool `long:"retry-socks" description:"If the initial request fails, reconnect and try with SOCKS."` diff --git a/modules/xmpp.go b/modules/xmpp.go new file mode 100644 index 00000000..390cb99f --- /dev/null +++ b/modules/xmpp.go @@ -0,0 +1,7 @@ +package modules + +import "github.com/zmap/zgrab2/modules/xmpp" + +func init() { + xmpp.RegisterModule() +} diff --git a/modules/xmpp/scanner.go b/modules/xmpp/scanner.go new file mode 100644 index 00000000..76457795 --- /dev/null +++ b/modules/xmpp/scanner.go @@ -0,0 +1,145 @@ +package xmpp + +import ( + "fmt" + "time" + + log "github.com/sirupsen/logrus" + "github.com/zmap/zgrab2" +) + +type Flags struct { + zgrab2.BaseFlags + zgrab2.UDPFlags +} + +type Result struct { + Response string `json:"banner,omitempty"` +} + +type Module struct{} + +type Scanner struct { + config *Flags +} + +// Validate performs any needed validation on the arguments +func (flags *Flags) Validate(args []string) error { + return nil +} + +// Help returns module-specific help +func (flags *Flags) Help() string { + return "" +} + +func (module *Module) NewFlags() interface{} { + return new(Flags) +} + +// NewScanner returns a new Scanner instance. +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 run XMPP." +} +func (module *Module) Help() string { + return "Scan for XMPP (Extensible Messaging and Presence Protocol) protocol" +} + +func RegisterModule() { + var module Module + _, err := zgrab2.AddCommand("xmpp", "Extensible Messaging and Presence Protocol", module.Description(), 5222, &module) + if err != nil { + log.Fatal(err) + } +} + +// Protocol returns the protocol identifier of the scan. +func (scanner *Scanner) Protocol() string { + return "xmpp" +} + +// GetName returns the Scanner name defined in the Flags. +func (scanner *Scanner) GetName() string { + return scanner.config.Name +} + +// GetTrigger returns the Trigger defined in the Flags. +func (scanner *Scanner) GetTrigger() string { + return scanner.config.Trigger +} + +// InitPerSender initializes the scanner for a given sender. +func (scanner *Scanner) InitPerSender(senderID int) error { + return nil +} + +func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error { + f, _ := flags.(*Flags) + scanner.config = f + + if f.Timeout <= 0 { + f.Timeout = 10 * time.Second + } + + return nil +} + +type scan struct { + scanner *Scanner + target *zgrab2.ScanTarget + result *Result +} + +func (scan *scan) Grab() *zgrab2.ScanError { + conn, err := scan.target.OpenUDP(&scan.scanner.config.BaseFlags, &scan.scanner.config.UDPFlags) + if err != nil { + return zgrab2.NewScanError(zgrab2.TryGetScanStatus(err), err) + } + defer conn.Close() + + stanza := fmt.Sprintf(``+ + ``, + scan.target.Host(), + ) + + if err := conn.SetReadDeadline(time.Now().Add(scan.scanner.config.Timeout)); err != nil { + return zgrab2.NewScanError(zgrab2.SCAN_UNKNOWN_ERROR, err) + } + + if _, err := conn.Write([]byte(stanza)); err != nil { + return zgrab2.DetectScanError(err) + } + + response, err := zgrab2.ReadAvailable(conn) + if err != nil { + return zgrab2.DetectScanError(err) + } + + scan.result = &Result{ + Response: string(response), + } + + return nil +} + +func (scanner *Scanner) newXMPPscan(t *zgrab2.ScanTarget) *scan { + return &scan{ + target: t, + scanner: scanner, + } +} + +// perform the XMPP scan +func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, interface{}, error) { + scan := scanner.newXMPPscan(&target) + err := scan.Grab() + if err != nil { + return err.Unpack(&scan.result) + } + return zgrab2.SCAN_SUCCESS, &scan.result, nil +}