Skip to content

Commit

Permalink
Merge pull request #12 from praetorian-inc/service-detection-updates
Browse files Browse the repository at this point in the history
Service detection updates
  • Loading branch information
praetorian-thendrickson authored Jan 18, 2023
2 parents de68181 + 588313c commit 1b17e14
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 94 deletions.
2 changes: 1 addition & 1 deletion pkg/plugins/services/smtp/smtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func init() {
}

func (p *SMTPPlugin) PortPriority(port uint16) bool {
return port == 25 || port == 587
return port == 25 || port == 587 || port == 465 || port == 2525
}

func handleSMTPConn(response []byte) (bool, bool) {
Expand Down
57 changes: 40 additions & 17 deletions pkg/plugins/services/ssh/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ func checkAlgo(data []byte) (map[string]string, error) {

func (p *SSHPlugin) Run(conn net.Conn, timeout time.Duration, target plugins.Target) (*plugins.Service, error) {
response, err := utils.Recv(conn, timeout)
passwordAuth := false

if err != nil {
return nil, err
}
Expand Down Expand Up @@ -223,6 +225,20 @@ func (p *SSHPlugin) Run(conn net.Conn, timeout time.Duration, target plugins.Tar
return plugins.CreateServiceFrom(target, payload, false, "", plugins.TCP), nil
}

// check auth methods
conf := ssh.ClientConfig{}
conf.Auth = nil
conf.Auth = append(conf.Auth, ssh.Password("admin"))
conf.User = "admin"
conf.HostKeyCallback = ssh.InsecureIgnoreHostKey()

authClient, err := ssh.Dial("tcp", target.Address.String(), &conf)

passwordAuth = strings.Contains(err.Error(), "password")
if authClient != nil {
authClient.Close()
}

sshConfig := &ssh.ClientConfig{}
fullConf := *sshConfig
fullConf.SetDefaults()
Expand All @@ -242,8 +258,9 @@ func (p *SSHPlugin) Run(conn net.Conn, timeout time.Duration, target plugins.Tar
_, err = io.ReadFull(rand.Reader, sendMsg.Cookie[:])
if err != nil {
payload := plugins.ServiceSSH{
Banner: banner,
Algo: fmt.Sprintf("%s", algo),
Banner: banner,
PasswordAuthEnabled: passwordAuth,
Algo: fmt.Sprintf("%s", algo),
}
return plugins.CreateServiceFrom(target, payload, false, "", plugins.TCP), nil
}
Expand All @@ -259,8 +276,9 @@ func (p *SSHPlugin) Run(conn net.Conn, timeout time.Duration, target plugins.Tar
err = ssh.PushPacket(t.HandshakeTransport, packetCopy)
if err != nil {
payload := plugins.ServiceSSH{
Banner: banner,
Algo: fmt.Sprintf("%s", algo),
Banner: banner,
PasswordAuthEnabled: passwordAuth,
Algo: fmt.Sprintf("%s", algo),
}
return plugins.CreateServiceFrom(target, payload, false, "", plugins.TCP), nil
}
Expand All @@ -271,8 +289,9 @@ func (p *SSHPlugin) Run(conn net.Conn, timeout time.Duration, target plugins.Tar

if err != nil {
payload := plugins.ServiceSSH{
Banner: banner,
Algo: fmt.Sprintf("%s", algo),
Banner: banner,
PasswordAuthEnabled: passwordAuth,
Algo: fmt.Sprintf("%s", algo),
}
return plugins.CreateServiceFrom(target, payload, false, "", plugins.TCP), nil
}
Expand All @@ -293,8 +312,9 @@ func (p *SSHPlugin) Run(conn net.Conn, timeout time.Duration, target plugins.Tar
t.Algorithms, err = ssh.FindAgreedAlgorithms(false, &sendMsg, otherInit)
if err != nil {
payload := plugins.ServiceSSH{
Banner: banner,
Algo: fmt.Sprintf("%s", algo),
Banner: banner,
PasswordAuthEnabled: passwordAuth,
Algo: fmt.Sprintf("%s", algo),
}
return plugins.CreateServiceFrom(target, payload, false, "", plugins.TCP), nil
}
Expand All @@ -310,28 +330,31 @@ func (p *SSHPlugin) Run(conn net.Conn, timeout time.Duration, target plugins.Tar
result, err := ssh.Clients(t, kex, &magics)
if err != nil {
payload := plugins.ServiceSSH{
Banner: banner,
Algo: fmt.Sprintf("%s", algo),
Banner: banner,
PasswordAuthEnabled: passwordAuth,
Algo: fmt.Sprintf("%s", algo),
}
return plugins.CreateServiceFrom(target, payload, false, "", plugins.TCP), nil
}
hostKey, err := ssh.ParsePublicKey(result.HostKey)
if err != nil {
payload := plugins.ServiceSSH{
Banner: banner,
Algo: fmt.Sprintf("%s", algo),
Banner: banner,
PasswordAuthEnabled: passwordAuth,
Algo: fmt.Sprintf("%s", algo),
}
return plugins.CreateServiceFrom(target, payload, false, "", plugins.TCP), nil
}
fingerprint := ssh.FingerprintSHA256(hostKey)
base64HostKey := base64.StdEncoding.EncodeToString(result.HostKey)

payload := plugins.ServiceSSH{
Banner: banner,
Algo: fmt.Sprintf("%s", algo),
HostKey: base64HostKey,
HostKeyType: hostKey.Type(),
HostKeyFingerprint: fingerprint,
Banner: banner,
PasswordAuthEnabled: passwordAuth,
Algo: fmt.Sprintf("%s", algo),
HostKey: base64HostKey,
HostKeyType: hostKey.Type(),
HostKeyFingerprint: fingerprint,
}
return plugins.CreateServiceFrom(target, payload, false, "", plugins.TCP), nil
}
Expand Down
17 changes: 9 additions & 8 deletions pkg/plugins/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ type ServiceHTTP struct {
Status string `json:"status"` // e.g. "200 OK"
StatusCode int `json:"statusCode"` // e.g. 200
ResponseHeaders http.Header `json:"responseHeaders"`
Technologies []string `json:"technologies"`
Technologies []string `json:"technologies,omitempty"`
}

func (e ServiceHTTP) Type() string { return ProtoHTTP }
Expand All @@ -274,13 +274,13 @@ type ServiceHTTPS struct {
Status string `json:"status"` // e.g. "200 OK"
StatusCode int `json:"statusCode"` // e.g. 200
ResponseHeaders http.Header `json:"responseHeaders"`
Technologies []string `json:"technologies"`
Technologies []string `json:"technologies,omitempty"`
}

func (e ServiceHTTPS) Type() string { return ProtoHTTPS }

type ServiceRDP struct {
OSFingerprint string `json:"fingerprint"` // e.g. Windows Server 2016 or 2019
OSFingerprint string `json:"fingerprint,omitempty"` // e.g. Windows Server 2016 or 2019
OSVersion string `json:"osVersion,omitempty"`
TargetName string `json:"targetName,omitempty"`
NetBIOSComputerName string `json:"netBIOSComputerName,omitempty"`
Expand Down Expand Up @@ -427,11 +427,12 @@ type ServiceStun struct {
func (e ServiceStun) Type() string { return ProtoStun }

type ServiceSSH struct {
Banner string `json:"banner"`
Algo string `json:"algo"`
HostKey string `json:"hostKey,omitempty"`
HostKeyType string `json:"hostKeyType,omitempty"`
HostKeyFingerprint string `json:"hostKeyFingerprint,omitempty"`
Banner string `json:"banner"`
PasswordAuthEnabled bool `json:"passwordAuthEnabled"`
Algo string `json:"algo"`
HostKey string `json:"hostKey,omitempty"`
HostKeyType string `json:"hostKeyType,omitempty"`
HostKeyFingerprint string `json:"hostKeyFingerprint,omitempty"`
}

func (e ServiceSSH) Type() string { return ProtoSSH }
Expand Down
125 changes: 57 additions & 68 deletions pkg/scan/simple_scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,22 @@ var dialer = &net.Dialer{
var sortedTCPPlugins = make([]plugins.Plugin, 0)
var sortedTCPTLSPlugins = make([]plugins.Plugin, 0)
var sortedUDPPlugins = make([]plugins.Plugin, 0)
var tlsConfig = tls.Config{} //nolint:gosec

func init() {
setupPlugins()
cipherSuites := make([]uint16, 0)

for _, suite := range tls.CipherSuites() {
cipherSuites = append(cipherSuites, suite.ID)
}

for _, suite := range tls.InsecureCipherSuites() {
cipherSuites = append(cipherSuites, suite.ID)
}
tlsConfig.InsecureSkipVerify = true //nolint:gosec
tlsConfig.CipherSuites = cipherSuites
tlsConfig.MinVersion = tls.VersionTLS10
}

func setupPlugins() {
Expand Down Expand Up @@ -83,99 +96,75 @@ func (c *Config) SimpleScanTarget(target plugins.Target) (*plugins.Service, erro
ip := target.Address.Addr().String()
port := target.Address.Port()

// Some services leverage TCP and TLS services on the
// same port. This causes a weird bug with certain services like RDP
// where the handshake seems to be different when TLS is leveraged. We
// will eventually properly fingerprint the service in the slow lane,
// but only after calling back to TCP plugins and running all the
// corresponding TLS plugins. This is a hack to get around this edge
// case as an optimization.
//
// If the port has multiple default mappings we only run the first one
// so in some cases we still bail out to the slow path.
if c.FastMode {
for _, plugin := range sortedTCPPlugins {
if plugin.PortPriority(port) {
conn, err := DialTCP(ip, port)
if err != nil {
return nil, fmt.Errorf("unable to connect, err = %w", err)
}
result, err := simplePluginRunner(conn, target, c, plugin)
if err != nil && c.Verbose {
log.Printf(
"error: %v scanning %v\n",
err,
target.Address.String(),
)
}
if result != nil && err == nil {
return result, nil
}
// first check the default port mappings for TCP / TLS
for _, plugin := range sortedTCPPlugins {
if plugin.PortPriority(port) {
conn, err := DialTCP(ip, port)
if err != nil {
return nil, fmt.Errorf("unable to connect, err = %w", err)
}
result, err := simplePluginRunner(conn, target, c, plugin)
if err != nil && c.Verbose {
log.Printf("error: %v scanning %v\n", err, target.Address.String())
}
if result != nil && err == nil {
return result, nil
}
}
}

// We attempt an initial TLS connection to the target port to
// determine if the service running on that port leverages TLS as a
// transport. If this is true we can exclude all plugins that don't
// support TLS as an optimization.

tlsConn, err := DialTLS(ip, port)
isTLS := err == nil
if isTLS {
for _, plugin := range sortedTCPTLSPlugins {
// If we are running in fast mode we only invoke a plugin if it registers
// itself as being the one of the default services
// associated with this port. In slow, mode we need to
// run every registered plugin.
if plugin.PortPriority(port) || !c.FastMode {
// Invoke the plugin and return the discovered service event if
// we are successful
if plugin.PortPriority(port) {
result, err := simplePluginRunner(tlsConn, target, c, plugin)
if err != nil && c.Verbose {
log.Printf(
"error: %v scanning %v\n",
err,
target.Address.String(),
)
log.Printf("error: %v scanning %v\n", err, target.Address.String())
}
if result != nil && err == nil {
// identified plugin match
return result, nil
}

// Unfortunately, if we run a plugin and it fails we have to create an entirely
// new TLS connection in order to invoke the next plugin.
tlsConn, err = DialTLS(ip, port)
if err != nil {
return nil, fmt.Errorf("error connecting via TLS, err = %w", err)
}
}
}
// If we fail to fingerprint the service in fast lane we just bail out to the
// slow lane. However, in the slow lane we want to also try the TCP plugins
// just to be safe.
if c.FastMode {
return nil, nil
}
}

for _, plugin := range sortedTCPPlugins {
// In fast mode we only run the corresponding TCP port plugin if it is
// associated with the default port for the service. Within the slow path
// we run every plugin regardless of the default port mapping.
if plugin.PortPriority(port) || !c.FastMode {
// if we're fast mode, return (because fast mode only checks the default port service mapping)
if c.FastMode {
return nil, nil
}

// go through each service mapping and check it

if isTLS {
for _, plugin := range sortedTCPTLSPlugins {
result, err := simplePluginRunner(tlsConn, target, c, plugin)
if err != nil && c.Verbose {
log.Printf("error: %v scanning %v\n", err, target.Address.String())
}
if result != nil && err == nil {
// identified plugin match
return result, nil
}
tlsConn, err = DialTLS(ip, port)
if err != nil {
return nil, fmt.Errorf("error connecting via TLS, err = %w", err)
}
}
} else {
for _, plugin := range sortedTCPPlugins {
conn, err := DialTCP(ip, port)
if err != nil {
return nil, fmt.Errorf("unable to connect, err = %w", err)
}
result, err := simplePluginRunner(conn, target, c, plugin)
if err != nil && c.Verbose {
log.Printf(
"error: %v scanning %v\n",
err,
target.Address.String(),
)
log.Printf("error: %v scanning %v\n", err, target.Address.String())
}
if result != nil && err == nil {
// identified plugin match
Expand All @@ -196,8 +185,7 @@ func simplePluginRunner(
) (*plugins.Service, error) {
// Log probe start.
if config.Verbose {
log.Printf(
"%v -> scanning %v (%v)\n",
log.Printf("%v %v-> scanning %v\n",
target.Address.String(),
target.Host,
plugins.CreatePluginID(plugin),
Expand All @@ -209,7 +197,7 @@ func simplePluginRunner(
// Log probe completion.
if config.Verbose {
log.Printf(
"%v (%v)-> completed %v\n",
"%v %v-> completed %v\n",
target.Address.String(),
target.Host,
plugins.CreatePluginID(plugin),
Expand All @@ -220,7 +208,8 @@ func simplePluginRunner(

func DialTLS(ip string, port uint16) (net.Conn, error) {
addr := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{InsecureSkipVerify: true}) //nolint:gosec
conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tlsConfig)

return conn, err
}

Expand Down

0 comments on commit 1b17e14

Please sign in to comment.