fscan/plugins/services/ftp/plugin.go
ZacharyZcR 074aebfeec refactor: 简化插件利用架构,移除不必要的exploit控制参数
- 移除-ne (DisableExploit)参数及其相关控制逻辑
- 统一服务插件exploit实现为空函数,保持接口一致性
- MySQL/FTP/Cassandra/ActiveMQ插件exploit方法改为空实现
- Redis插件移除-ne控制,利用功能由专用参数控制(-rwp/-rf/-rs)
- VNC插件exploit功能改为空实现,专注于服务检测和弱密码扫描
- 更新插件描述,准确反映实际功能范围

所有exploit功能现在由明确的用户参数控制,无需通用禁用开关
2025-08-12 17:56:06 +08:00

233 lines
6.3 KiB
Go
Raw 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))
// FTP插件不提供利用功能仅记录匿名访问
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))
// FTP插件不提供利用功能仅进行弱密码扫描
return result, nil
}
// 已移除未使用的 generateCredentials 方法
// autoExploit方法已移除 - 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()
}