diff --git a/pkg/plugins/services/echo/echo.go b/pkg/plugins/services/echo/echo.go new file mode 100644 index 0000000..8484f51 --- /dev/null +++ b/pkg/plugins/services/echo/echo.go @@ -0,0 +1,76 @@ +// Copyright 2022 Praetorian Security, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package echo + +import ( + "bytes" + "crypto/rand" + "net" + "time" + + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/praetorian-inc/fingerprintx/pkg/plugins/pluginutils" +) + +type EchoPlugin struct{} + +const ECHO = "echo" + +func isEcho(conn net.Conn, timeout time.Duration) (bool, error) { + // Generate a random 64 byte payload + payload := make([]byte, 64) + if _, err := rand.Read(payload); err != nil { + return false, err + } + + response, err := pluginutils.SendRecv(conn, payload, timeout) + if err != nil { + return false, err + } + + // Check if the response matches the payload + isEchoService := bytes.Equal(payload, response) + + return isEchoService, nil +} + +func init() { + plugins.RegisterPlugin(&EchoPlugin{}) +} + +func (p *EchoPlugin) PortPriority(port uint16) bool { + return port == 7 +} + +func (p *EchoPlugin) Run(conn net.Conn, timeout time.Duration, target plugins.Target) (*plugins.Service, error) { + if isEcho, err := isEcho(conn, timeout); !isEcho || err != nil { + return nil, nil + } + payload := plugins.ServiceEcho{} + + return plugins.CreateServiceFrom(target, payload, false, "", plugins.TCP), nil +} + +func (p *EchoPlugin) Name() string { + return ECHO +} + +func (p *EchoPlugin) Type() plugins.Protocol { + return plugins.TCP +} + +func (p *EchoPlugin) Priority() int { + return 1 +} diff --git a/pkg/plugins/services/echo/echo_test.go b/pkg/plugins/services/echo/echo_test.go new file mode 100644 index 0000000..a89e190 --- /dev/null +++ b/pkg/plugins/services/echo/echo_test.go @@ -0,0 +1,55 @@ +// Copyright 2022 Praetorian Security, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package echo + +import ( + "testing" + + "github.com/ory/dockertest/v3" + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/praetorian-inc/fingerprintx/pkg/test" +) + +func TestEcho(t *testing.T) { + testcases := []test.Testcase{ + { + Description: "echo", + Port: 7, + Protocol: plugins.TCP, + Expected: func(res *plugins.Service) bool { + return res != nil + }, + RunConfig: dockertest.RunOptions{ + Repository: "itsthenetwork/alpine-ncat", + Cmd: []string{"-e", "/bin/cat", "-k", "-l", "-p", "7"}, + Entrypoint: []string{"/usr/bin/ncat"}, + ExposedPorts: []string{"7"}, + }, + }, + } + + p := &EchoPlugin{} + + for _, tc := range testcases { + tc := tc + t.Run(tc.Description, func(t *testing.T) { + t.Parallel() + err := test.RunTest(t, tc, p) + if err != nil { + t.Errorf(err.Error()) + } + }) + } +} diff --git a/pkg/plugins/types.go b/pkg/plugins/types.go index f22a1e9..9a6c633 100644 --- a/pkg/plugins/types.go +++ b/pkg/plugins/types.go @@ -38,6 +38,7 @@ const TypeService string = "service" const ( ProtoDNS = "dns" ProtoDHCP = "dhcp" + ProtoEcho = "echo" ProtoFTP = "ftp" ProtoHTTP = "http" ProtoHTTPS = "https" @@ -483,6 +484,10 @@ type ServiceDHCP struct { func (e ServiceDHCP) Type() string { return ProtoDHCP } +type ServiceEcho struct{} + +func (e ServiceEcho) Type() string { return ProtoEcho } + type ServiceRsync struct{} func (e ServiceRsync) Type() string { return ProtoRsync } diff --git a/pkg/scan/plugin_list.go b/pkg/scan/plugin_list.go index bff44b9..3e3070e 100644 --- a/pkg/scan/plugin_list.go +++ b/pkg/scan/plugin_list.go @@ -20,6 +20,7 @@ package scan import ( _ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/dhcp" _ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/dns" + _ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/echo" _ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/ftp" _ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/http" _ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/imap"