Files
dpi/protocol/detect_ssh.go
maze 81a3829382 Refactoring
Refactored Protocol.Name -> Protocol.Type; added Encapsulation field
Refactored TLS parsing; added support for ALPN
2025-10-10 12:41:44 +02:00

94 lines
1.9 KiB
Go

package protocol
import (
"bytes"
"strings"
)
func init() {
// We can't match on SSH-?.? here, because the client or server may send a banner prior
// to sending the SSH handshake.
Register(Both, "", detectSSH)
}
// The required prefix for the SSH protocol identification line.
const (
ssh199Prefix = "SSH-1.99-"
ssh20Prefix = "SSH-2.0-"
)
var (
commonPort = map[int]bool{
22: true,
2200: true,
2222: true,
}
commonImplementations = []string{
"OpenSSH_",
"PuTTY",
"libssh",
"dropbear",
"Go",
"paramiko",
"Cyberduck",
}
)
func detectSSH(dir Direction, data []byte, srcPort, dstPort int) (proto *Protocol, confidence float64) {
// The data must be at least as long as the prefix itself.
if len(data) < len(ssh20Prefix) {
return nil, 0
}
if commonPort[srcPort] || commonPort[dstPort] {
confidence += .1
}
// The protocol allows for pre-banner text, so we have to check all lines.
for _, line := range bytes.Split(data, []byte{'\n'}) {
line = bytes.TrimSuffix(line, []byte{'\r'})
// Match the most common SSH 2.0 protocol.
if bytes.HasPrefix(line, []byte(ssh20Prefix)) {
implementation := string(line[len(ssh20Prefix):])
for _, prefix := range commonImplementations {
if strings.HasPrefix(implementation, prefix) {
confidence += .2
break
}
}
return &Protocol{
Type: TypeSSH,
Version: Version{
Major: 2,
Minor: 0,
Patch: -1,
Extra: implementation,
},
}, confidence + 0.65
}
// Match the (far) less common SSH 1.99 protocol.
if bytes.HasPrefix(line, []byte(ssh199Prefix)) {
implementation := string(line[len(ssh20Prefix):])
for _, prefix := range commonImplementations {
if strings.HasPrefix(implementation, prefix) {
confidence += .2
break
}
}
return &Protocol{
Type: TypeSSH,
Version: Version{
Major: 1,
Minor: 99,
Patch: -1,
Extra: implementation,
},
}, confidence + 0.65
}
}
return nil, 0
}