fscan/plugins/services/ftp.go
ZacharyZcR 6cf5719e8a refactor: 彻底清理插件系统,消除虚假利用功能
- 删除整个legacy插件系统(7794行代码)
- 完成所有插件向单文件架构迁移
- 移除19个插件的虚假Exploit功能,只保留真实利用:
  * Redis: 文件写入、SSH密钥注入、计划任务
  * SSH: 命令执行
  * MS17010: EternalBlue漏洞利用
- 统一插件接口,简化架构复杂度
- 清理临时文件和备份文件

重构效果:
- 代码行数: -7794行
- 插件文件数: 从3文件架构→单文件架构
- 真实利用插件: 从22个→3个
- 架构复杂度: 大幅简化
2025-08-26 11:43:48 +08:00

235 lines
5.4 KiB
Go

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