mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

将所有服务插件移动到plugins/services/目录下,使目录结构更加清晰直观: • 创建plugins/services/目录统一管理服务扫描插件 • 添加init.go提供类型别名和函数导出 • 更新main.go导入路径 • 所有20个服务插件功能验证正常 新的目录结构更便于插件管理和维护。
319 lines
8.0 KiB
Go
319 lines
8.0 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("未发现弱密码或匿名访问"),
|
|
}
|
|
}
|
|
|
|
// Exploit 执行FTP利用操作 - 实现文件操作功能
|
|
func (p *FTPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
|
|
// 建立FTP连接
|
|
conn := p.testCredential(ctx, info, creds)
|
|
if conn == nil {
|
|
return &ExploitResult{
|
|
Success: false,
|
|
Error: fmt.Errorf("FTP连接失败"),
|
|
}
|
|
}
|
|
defer conn.Quit()
|
|
|
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
common.LogSuccess(fmt.Sprintf("FTP利用开始: %s (用户: %s)", target, creds.Username))
|
|
|
|
var output strings.Builder
|
|
output.WriteString(fmt.Sprintf("=== FTP利用结果 - %s ===\n", target))
|
|
|
|
// 获取当前工作目录
|
|
if pwd, err := conn.CurrentDir(); err == nil {
|
|
output.WriteString(fmt.Sprintf("\n[当前目录] %s\n", pwd))
|
|
}
|
|
|
|
// 列出根目录文件
|
|
if entries, err := conn.List("/"); err == nil {
|
|
output.WriteString(fmt.Sprintf("\n[根目录文件列表] (共%d项)\n", len(entries)))
|
|
for i, entry := range entries {
|
|
if i >= 10 { // 限制显示前10项
|
|
output.WriteString("... (更多文件)\n")
|
|
break
|
|
}
|
|
output.WriteString(fmt.Sprintf(" %s %10d %s %s\n",
|
|
entry.Type, entry.Size, entry.Time.Format("2006-01-02 15:04"), entry.Name))
|
|
}
|
|
}
|
|
|
|
// 检查常见敏感目录
|
|
sensitiveDirectories := []string{"/etc", "/home", "/var", "/tmp", "/root", "/opt"}
|
|
for _, dir := range sensitiveDirectories {
|
|
select {
|
|
case <-ctx.Done():
|
|
return &ExploitResult{
|
|
Success: false,
|
|
Output: output.String(),
|
|
Error: ctx.Err(),
|
|
}
|
|
default:
|
|
}
|
|
|
|
if entries, err := conn.List(dir); err == nil {
|
|
output.WriteString(fmt.Sprintf("\n[敏感目录] %s (共%d项)\n", dir, len(entries)))
|
|
for i, entry := range entries {
|
|
if i >= 5 { // 每个目录只显示前5项
|
|
output.WriteString(" ... (更多文件)\n")
|
|
break
|
|
}
|
|
output.WriteString(fmt.Sprintf(" %s %s\n", entry.Type, entry.Name))
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查系统信息
|
|
if sysInfo := p.getSystemInfo(conn); sysInfo != "" {
|
|
output.WriteString(fmt.Sprintf("\n[系统信息]\n%s\n", sysInfo))
|
|
}
|
|
|
|
// 尝试创建测试文件(验证写权限)
|
|
testFileName := "fscan_test.txt"
|
|
testContent := "FScan Security Test File"
|
|
if err := p.testWritePermission(conn, testFileName, testContent); err == nil {
|
|
output.WriteString(fmt.Sprintf("\n[写权限测试] ✅ 成功创建文件: %s\n", testFileName))
|
|
// 清理测试文件
|
|
conn.Delete(testFileName)
|
|
} else {
|
|
output.WriteString(fmt.Sprintf("\n[写权限测试] ❌ 无写权限: %v\n", err))
|
|
}
|
|
|
|
common.LogSuccess(fmt.Sprintf("FTP利用完成: %s", target))
|
|
|
|
return &ExploitResult{
|
|
Success: true,
|
|
Output: output.String(),
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
})
|
|
} |