package ftp import ( "context" "fmt" "net" "strings" "time" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins/base" ) // FTPPlugin FTP插件实现 type FTPPlugin struct { *base.ServicePlugin exploiter *FTPExploiter } // NewFTPPlugin 创建FTP插件 func NewFTPPlugin() *FTPPlugin { // 插件元数据 metadata := &base.PluginMetadata{ Name: "ftp", Version: "2.0.0", Author: "fscan-team", Description: "FTP文件传输协议扫描和利用插件", Category: "service", Ports: []int{21, 2121}, // 21: 标准FTP端口, 2121: 常见替代端口 Protocols: []string{"tcp"}, Tags: []string{"ftp", "file_transfer", "bruteforce", "anonymous"}, } // 创建连接器和服务插件 connector := NewFTPConnector() servicePlugin := base.NewServicePlugin(metadata, connector) // 创建FTP插件 plugin := &FTPPlugin{ ServicePlugin: servicePlugin, exploiter: NewFTPExploiter(), } // 设置能力 plugin.SetCapabilities([]base.Capability{ base.CapWeakPassword, base.CapUnauthorized, base.CapDataExtraction, base.CapFileUpload, base.CapFileWrite, }) return plugin } // Scan 重写扫描方法以支持匿名登录检测和自动利用 func (p *FTPPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { // 如果禁用暴力破解,只进行服务识别 if common.DisableBrute { return p.performServiceIdentification(ctx, info) } // 首先尝试匿名登录 anonymousCred := &base.Credential{ Username: "anonymous", Password: "", } result, err := p.ScanCredential(ctx, info, anonymousCred) if err == nil && result.Success { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) common.LogSuccess(i18n.GetText("ftp_anonymous_success", target)) // 自动利用匿名访问 if !common.DisableExploit { p.autoExploit(context.Background(), info, anonymousCred) } return result, nil } // 执行基础的密码扫描 result, err = p.ServicePlugin.Scan(ctx, info) if err != nil || !result.Success { return result, err } // 记录成功的弱密码发现 target := fmt.Sprintf("%s:%s", info.Host, info.Ports) cred := result.Credentials[0] common.LogSuccess(i18n.GetText("ftp_weak_pwd_success", target, cred.Username, cred.Password)) // 自动利用功能(可通过-ne参数禁用) if result.Success && len(result.Credentials) > 0 && !common.DisableExploit { p.autoExploit(context.Background(), info, result.Credentials[0]) } return result, nil } // generateCredentials 生成FTP凭据 func (p *FTPPlugin) generateCredentials() []*base.Credential { // 获取FTP专用的用户名字典 usernames := common.Userdict["ftp"] if len(usernames) == 0 { // 默认FTP用户名 usernames = []string{"ftp", "ftpuser", "admin", "test", "user", "guest"} } return base.GenerateCredentials(usernames, common.Passwords) } // autoExploit 自动利用功能 func (p *FTPPlugin) autoExploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) common.LogDebug(i18n.GetText("plugin_exploit_start", "FTP", target)) // 执行利用操作 result, err := p.exploiter.Exploit(ctx, info, creds) if err != nil { common.LogError(i18n.GetText("plugin_exploit_failed", "FTP", err)) return } // 处理利用结果 if result != nil && result.Success { // SaveExploitResult会自动使用LogSuccess显示红色利用成功消息 base.SaveExploitResult(info, result, "FTP") } } // Exploit 使用exploiter执行利用 func (p *FTPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { return p.exploiter.Exploit(ctx, info, creds) } // GetExploitMethods 获取利用方法 func (p *FTPPlugin) GetExploitMethods() []base.ExploitMethod { return p.exploiter.GetExploitMethods() } // IsExploitSupported 检查利用支持 func (p *FTPPlugin) IsExploitSupported(method base.ExploitType) bool { return p.exploiter.IsExploitSupported(method) } // performServiceIdentification 执行FTP服务识别(-nobr模式) func (p *FTPPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) // 尝试连接到FTP服务获取Banner ftpInfo, isFTP := p.identifyFTPService(ctx, info) if isFTP { // 记录服务识别成功 common.LogSuccess(i18n.GetText("ftp_service_identified", target, ftpInfo)) return &base.ScanResult{ Success: true, Service: "FTP", Banner: ftpInfo, Extra: map[string]interface{}{ "service": "FTP", "port": info.Ports, "info": ftpInfo, }, }, nil } // 如果无法识别为FTP,返回失败 return &base.ScanResult{ Success: false, Error: fmt.Errorf("无法识别为FTP服务"), }, nil } // identifyFTPService 通过Banner识别FTP服务 func (p *FTPPlugin) identifyFTPService(ctx context.Context, info *common.HostInfo) (string, bool) { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) // 尝试建立TCP连接 conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return "", false } defer conn.Close() // 设置读取超时 conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) // FTP服务器在连接后会发送Welcome Banner banner := make([]byte, 1024) n, err := conn.Read(banner) if err != nil || n < 3 { return "", false } bannerStr := strings.TrimSpace(string(banner[:n])) // 检查FTP协议标识 if strings.HasPrefix(bannerStr, "220") { // FTP服务器通常以220开头发送welcome消息 // 提取FTP服务器信息 lines := strings.Split(bannerStr, "\n") if len(lines) > 0 { firstLine := strings.TrimSpace(lines[0]) // 移除状态码 if len(firstLine) > 4 && firstLine[:3] == "220" { serverInfo := strings.TrimSpace(firstLine[3:]) // 移除可能的连字符 if len(serverInfo) > 0 && serverInfo[0] == '-' { serverInfo = strings.TrimSpace(serverInfo[1:]) } if serverInfo != "" { return fmt.Sprintf("FTP服务: %s", serverInfo), true } } } return "FTP服务", true } // 检查其他可能的FTP响应 lowerBanner := strings.ToLower(bannerStr) if strings.Contains(lowerBanner, "ftp") || strings.Contains(lowerBanner, "file transfer") || strings.Contains(lowerBanner, "vsftpd") || strings.Contains(lowerBanner, "proftpd") || strings.Contains(lowerBanner, "pure-ftpd") { return fmt.Sprintf("FTP服务: %s", bannerStr), true } return "", false } // ============================================================================= // 插件注册 // ============================================================================= // RegisterFTPPlugin 注册FTP插件 func RegisterFTPPlugin() { factory := base.NewSimplePluginFactory( &base.PluginMetadata{ Name: "ftp", Version: "2.0.0", Author: "fscan-team", Description: "FTP文件传输协议扫描和利用插件", Category: "service", Ports: []int{21, 2121}, // 21: 标准FTP端口, 2121: 常见替代端口 Protocols: []string{"tcp"}, Tags: []string{"ftp", "file_transfer", "bruteforce", "anonymous"}, }, func() base.Plugin { return NewFTPPlugin() }, ) base.GlobalPluginRegistry.Register("ftp", factory) } // 自动注册 func init() { RegisterFTPPlugin() }