diff --git a/plugins/services/telnet.go b/plugins/services/telnet.go index 66259db..eeefd88 100644 --- a/plugins/services/telnet.go +++ b/plugins/services/telnet.go @@ -297,10 +297,6 @@ func (p *TelnetPlugin) performTelnetAuth(conn net.Conn, username, password strin cleaned := p.cleanResponse(response) cleanedLower := strings.ToLower(cleaned) - if p.isShellPrompt(cleaned) { - return true - } - if strings.Contains(cleanedLower, "login") || strings.Contains(cleanedLower, "username") || strings.Contains(cleaned, ":") { @@ -429,14 +425,47 @@ func (p *TelnetPlugin) isShellPrompt(data string) bool { return false } - data = strings.ToLower(strings.TrimSpace(data)) + data = strings.TrimSpace(data) - shellPrompts := []string{"$", "#", ">", "~$", "]$", ")#", "bash", "shell", "cmd"} - - for _, prompt := range shellPrompts { - if strings.Contains(data, prompt) { - return true + for _, line := range strings.Split(data, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue } + + lineLower := strings.ToLower(line) + + // 关键字匹配(整行包含即可) + for _, kw := range []string{"bash", "shell", "cmd"} { + if strings.Contains(lineLower, kw) { + return true + } + } + + // 行尾 prompt 符号匹配:取最后一个非空格字符 + trimmed := strings.TrimRight(line, " ") + if len(trimmed) == 0 { + continue + } + tail := trimmed[len(trimmed)-1] + + if tail != '#' && tail != '$' && tail != '>' { + continue + } + + // 排除装饰线:整行都是同一个字符(如 #### 或 >>>>) + allSame := true + for _, c := range trimmed { + if byte(c) != tail { + allSame = false + break + } + } + if allSame { + continue + } + + return true } return false diff --git a/plugins/services/telnet_test.go b/plugins/services/telnet_test.go index 8ccaeeb..3885c1f 100644 --- a/plugins/services/telnet_test.go +++ b/plugins/services/telnet_test.go @@ -37,3 +37,48 @@ func TestClassifyTelnetErrorType(t *testing.T) { }) } } + +func TestIsShellPrompt(t *testing.T) { + p := NewTelnetPlugin() + + positive := []struct { + name, data string + }{ + {"linux root", "root@host:~#"}, + {"linux user", "user@host:~$"}, + {"cisco", "Router>"}, + {"cisco enable", "Router#"}, + {"bracket prompt", "[admin@host ~]$"}, + {"paren prompt", "host(config)#"}, + {"bash keyword", "bash-4.2$"}, + {"trailing space", "root@host:~# "}, + {"multiline last", "Welcome\nroot@host:~#"}, + } + + negative := []struct { + name, data string + }{ + {"empty", ""}, + {"decoration hashes", "################"}, + {"decoration arrows", ">>>>>>>>"}, + {"decoration dollars", "$$$$$$$$"}, + {"cisco motd border", "###################################################"}, + {"motd with hash mid", "# Welcome to Cisco IOS"}, + {"plain text", "Cisco IOS Software, Version 12.2"}, + {"login prompt", "Login:"}, + {"password prompt", "Password:"}, + {"motd multiline", "##########\nWelcome to Router\n##########"}, + } + + for _, tt := range positive { + if !p.isShellPrompt(tt.data) { + t.Errorf("isShellPrompt(%q) = false, want true [%s]", tt.data, tt.name) + } + } + + for _, tt := range negative { + if p.isShellPrompt(tt.data) { + t.Errorf("isShellPrompt(%q) = true, want false [%s]", tt.data, tt.name) + } + } +}