package services import ( "context" "fmt" "strings" "time" ftplib "github.com/jlaffaye/ftp" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" ) // FTPPlugin FTP服务扫描和利用插件 - 包含文件操作利用功能 type FTPPlugin struct { name string ports []int } // NewFTPPlugin 创建FTP插件 func NewFTPPlugin() *FTPPlugin { return &FTPPlugin{ name: "ftp", ports: []int{21, 2121, 990}, // FTP端口 + FTPS端口 } } // GetName 实现Plugin接口 func (p *FTPPlugin) GetName() string { return p.name } // GetPorts 实现Plugin接口 func (p *FTPPlugin) GetPorts() []int { return p.ports } // Scan 执行FTP扫描 - 弱密码检测和匿名访问检测 func (p *FTPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) // 如果禁用暴力破解,只做服务识别 if common.DisableBrute { return p.identifyService(info) } // 首先检查匿名访问 if result := p.testAnonymousAccess(ctx, info); result != nil && result.Success { common.LogSuccess(i18n.GetText("ftp_anonymous_success", target)) return result } // 生成测试凭据 credentials := GenerateCredentials("ftp") if len(credentials) == 0 { // FTP默认凭据 credentials = []Credential{ {Username: "ftp", Password: "ftp"}, {Username: "admin", Password: "admin"}, {Username: "admin", Password: ""}, {Username: "user", Password: "user"}, {Username: "test", Password: "test"}, } } // 逐个测试凭据 for _, cred := range credentials { // 检查Context是否被取消 select { case <-ctx.Done(): return &ScanResult{ Success: false, Service: "ftp", Error: ctx.Err(), } default: } // 测试凭据 if conn := p.testCredential(ctx, info, cred); conn != nil { conn.Quit() // 关闭测试连接 // FTP认证成功 common.LogSuccess(i18n.GetText("ftp_scan_success", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, Service: "ftp", Username: cred.Username, Password: cred.Password, } } } // 所有凭据都失败 return &ScanResult{ Success: false, Service: "ftp", Error: fmt.Errorf("未发现弱密码或匿名访问"), } } // testAnonymousAccess 测试匿名访问 func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostInfo) *ScanResult { // 尝试匿名登录 anonCreds := []Credential{ {Username: "anonymous", Password: ""}, {Username: "anonymous", Password: "anonymous"}, {Username: "anonymous", Password: "guest@example.com"}, {Username: "ftp", Password: ""}, } for _, cred := range anonCreds { if conn := p.testCredential(ctx, info, cred); conn != nil { conn.Quit() return &ScanResult{ Success: true, Service: "ftp", Username: cred.Username, Password: cred.Password, Banner: "匿名访问", } } } return nil } // testCredential 测试单个凭据 - 返回FTP连接或nil func (p *FTPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *ftplib.ServerConn { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second // 使用Context控制超时的连接 type ftpResult struct { conn *ftplib.ServerConn err error } connChan := make(chan ftpResult, 1) go func() { // 建立FTP连接 conn, err := ftplib.DialTimeout(target, timeout) if err != nil { connChan <- ftpResult{nil, err} return } // 尝试登录 err = conn.Login(cred.Username, cred.Password) if err != nil { conn.Quit() connChan <- ftpResult{nil, err} return } connChan <- ftpResult{conn, nil} }() // 等待连接结果或超时 select { case result := <-connChan: if result.err != nil { return nil } return result.conn case <-ctx.Done(): return nil } } // testWritePermission 测试写权限 func (p *FTPPlugin) testWritePermission(conn *ftplib.ServerConn, filename, content string) error { // 尝试创建文件并写入内容 return conn.Stor(filename, strings.NewReader(content)) } // getSystemInfo 获取系统信息 func (p *FTPPlugin) getSystemInfo(conn *ftplib.ServerConn) string { var info strings.Builder // 尝试获取当前目录作为系统信息 if pwd, err := conn.CurrentDir(); err == nil { info.WriteString(fmt.Sprintf("当前目录: %s\n", pwd)) } // 尝试列出当前目录获取更多信息 if entries, err := conn.List("."); err == nil && len(entries) > 0 { info.WriteString(fmt.Sprintf("目录项数量: %d\n", len(entries))) } return info.String() } // identifyService 服务识别 - 检测FTP服务 func (p *FTPPlugin) identifyService(info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second // 尝试连接FTP服务 conn, err := ftplib.DialTimeout(target, timeout) if err != nil { return &ScanResult{ Success: false, Service: "ftp", Error: err, } } defer conn.Quit() // 获取FTP服务器信息 var banner string if pwd, err := conn.CurrentDir(); err == nil { banner = fmt.Sprintf("FTP服务 (根目录: %s)", pwd) } else { banner = "FTP服务" } common.LogSuccess(i18n.GetText("ftp_service_identified", target, banner)) return &ScanResult{ Success: true, Service: "ftp", Banner: banner, } } // init 自动注册插件 func init() { RegisterPlugin("ftp", func() Plugin { return NewFTPPlugin() }) }