fscan/Plugins/services/ftp/plugin.go
ZacharyZcR 83afd0f994 feat: 实现FTP文件传输协议专业扫描插件
• 新增FTP插件支持标准21端口和常见替代端口
• 实现匿名访问检测和弱密码爆破功能
• 支持自动利用:目录枚举、文件下载测试、文件上传测试
• 集成-nobr模式服务识别和-ne自动利用功能
• 完整的Context超时机制和连接数管理
• 添加中英文国际化消息支持
• 基于新插件架构实现模块化设计

功能特性:
- 支持jlaffaye/ftp驱动的FTP协议通信
- 智能识别vsFTPd、ProFTPD等多种FTP服务器
- 三种利用方法:目录结构探测、文件操作测试
- 完整的错误处理和连接限制处理机制
2025-08-08 04:46:35 +08:00

266 lines
7.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()
}