package services import ( "context" "fmt" "net" "strings" "time" "github.com/shadow1ng/fscan/common" ) type TelnetPlugin struct { name string ports []int } func NewTelnetPlugin() *TelnetPlugin { return &TelnetPlugin{ name: "telnet", ports: []int{23, 2323}, } } func (p *TelnetPlugin) Name() string { return p.name } func (p *TelnetPlugin) GetPorts() []int { return p.ports } func (p *TelnetPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) if common.DisableBrute { return p.identifyService(ctx, info) } if result := p.testUnauthAccess(ctx, info); result != nil && result.Success { common.LogSuccess(fmt.Sprintf("Telnet %s 未授权访问", target)) return result } credentials := GenerateCredentials("telnet") if len(credentials) == 0 { return &ScanResult{ Success: false, Service: "telnet", Error: fmt.Errorf("没有可用的测试凭据"), } } for _, cred := range credentials { if p.testCredential(ctx, info, cred) { common.LogSuccess(fmt.Sprintf("Telnet %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, Service: "telnet", Username: cred.Username, Password: cred.Password, } } } return &ScanResult{ Success: false, Service: "telnet", Error: fmt.Errorf("未发现弱密码"), } } func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return nil } defer conn.Close() conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { return nil } welcome := string(buffer[:n]) if strings.Contains(welcome, "$") || strings.Contains(welcome, "#") || strings.Contains(welcome, ">") { return &ScanResult{ Success: true, Service: "telnet", Banner: "未授权访问", } } return nil } func (p *TelnetPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return false } defer conn.Close() conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { return false } data := string(buffer[:n]) data = p.cleanTelnetData(data) if strings.Contains(strings.ToLower(data), "login") || strings.Contains(strings.ToLower(data), "username") { conn.Write([]byte(cred.Username + "\r\n")) time.Sleep(500 * time.Millisecond) n, err = conn.Read(buffer) if err != nil { return false } data = string(buffer[:n]) if strings.Contains(strings.ToLower(data), "password") { conn.Write([]byte(cred.Password + "\r\n")) time.Sleep(1 * time.Second) n, err = conn.Read(buffer) if err != nil { return false } result := string(buffer[:n]) result = p.cleanTelnetData(result) return p.isLoginSuccessful(result) } } return false } func (p *TelnetPlugin) cleanTelnetData(data string) string { cleaned := "" for i := 0; i < len(data); i++ { b := data[i] if b == 255 && i+2 < len(data) { i += 2 continue } if b >= 32 && b <= 126 || b == '\r' || b == '\n' { cleaned += string(b) } } return cleaned } func (p *TelnetPlugin) isLoginSuccessful(data string) bool { data = strings.ToLower(data) successIndicators := []string{"$", "#", ">", "welcome", "last login"} for _, indicator := range successIndicators { if strings.Contains(data, indicator) { return true } } failIndicators := []string{"incorrect", "failed", "denied", "invalid", "login:"} for _, indicator := range failIndicators { if strings.Contains(data, indicator) { return false } } return false } func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return &ScanResult{ Success: false, Service: "telnet", Error: err, } } defer conn.Close() conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { return &ScanResult{ Success: false, Service: "telnet", Error: err, } } data := string(buffer[:n]) data = p.cleanTelnetData(data) var banner string if strings.Contains(strings.ToLower(data), "login") || strings.Contains(strings.ToLower(data), "username") { banner = "Telnet远程终端服务" } else if strings.Contains(data, "$") || strings.Contains(data, "#") || strings.Contains(data, ">") { banner = "Telnet远程终端服务 (无认证)" } else { banner = "Telnet远程终端服务" } common.LogSuccess(fmt.Sprintf("Telnet %s %s", target, banner)) return &ScanResult{ Success: true, Service: "telnet", Banner: banner, } } func init() { // 使用高效注册方式:直接传递端口信息,避免实例创建 RegisterPluginWithPorts("telnet", func() Plugin { return NewTelnetPlugin() }, []int{23, 2323}) }