fscan/Plugins/base/scanner.go
ZacharyZcR 8a2c9737f3 enhance: 增强进度条显示和扫描配置功能
- 在扫描开始时显示详细配置信息(-t, -time, -mt, -gt参数)
- 端口扫描进度条显示线程数配置
- 进度条实时显示插件和连接并发状态
- 添加ConcurrencyMonitor并发监控系统
- 提升mt参数默认值从10到50以提高扫描性能
- 移除端口范围限制,支持全端口扫描(1-65535)
- 完善中英文国际化支持
2025-08-07 14:48:32 +08:00

309 lines
7.4 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 base
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"strings"
"sync"
"time"
)
// =============================================================================
// 通用扫描器基础实现
// =============================================================================
// BaseScanner 基础扫描器,提供通用的扫描逻辑
type BaseScanner struct {
Name string
metadata *PluginMetadata
capabilities []Capability
}
// NewBaseScanner 创建基础扫描器
func NewBaseScanner(name string, metadata *PluginMetadata) *BaseScanner {
return &BaseScanner{
Name: name,
metadata: metadata,
}
}
// GetName 获取扫描器名称
func (s *BaseScanner) GetName() string {
return s.Name
}
// GetCapabilities 获取扫描器支持的能力
func (s *BaseScanner) GetCapabilities() []Capability {
return s.capabilities
}
// SetCapabilities 设置扫描器能力
func (s *BaseScanner) SetCapabilities(caps []Capability) {
s.capabilities = caps
}
// GetMetadata 获取插件元数据
func (s *BaseScanner) GetMetadata() *PluginMetadata {
return s.metadata
}
// =============================================================================
// 通用并发扫描框架
// =============================================================================
// ConcurrentScanConfig 并发扫描配置
type ConcurrentScanConfig struct {
MaxConcurrent int // 最大并发数
Timeout time.Duration // 单次扫描超时
MaxRetries int // 最大重试次数
RetryDelay time.Duration // 重试延迟
}
// CredentialScanner 凭据扫描器接口
type CredentialScanner interface {
// ScanCredential 扫描单个凭据
ScanCredential(ctx context.Context, info *common.HostInfo, cred *Credential) (*ScanResult, error)
}
// ConcurrentCredentialScan 并发凭据扫描通用实现
func ConcurrentCredentialScan(
ctx context.Context,
scanner CredentialScanner,
info *common.HostInfo,
credentials []*Credential,
config *ConcurrentScanConfig,
) (*ScanResult, error) {
if len(credentials) == 0 {
return nil, fmt.Errorf("没有提供凭据")
}
// 设置默认配置
if config == nil {
config = &ConcurrentScanConfig{
MaxConcurrent: 10,
Timeout: time.Duration(common.Timeout) * time.Second,
MaxRetries: common.MaxRetries,
RetryDelay: 500 * time.Millisecond,
}
}
// 限制并发数
maxConcurrent := config.MaxConcurrent
if maxConcurrent <= 0 {
maxConcurrent = 10
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *ScanResult, 1)
workChan := make(chan *Credential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
// 开始监控连接
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
monitor := common.GetConcurrencyMonitor()
monitor.StartConnection("credential", target)
result := scanCredentialWithRetry(scanCtx, scanner, info, credential, config)
// 完成连接监控
monitor.FinishConnection("credential", target)
if result != nil && result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for _, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result, nil
}
return nil, fmt.Errorf(i18n.GetText("plugin_all_creds_failed"))
case <-ctx.Done():
scanCancel()
return nil, ctx.Err()
}
}
// scanCredentialWithRetry 带重试的单凭据扫描
func scanCredentialWithRetry(
ctx context.Context,
scanner CredentialScanner,
info *common.HostInfo,
cred *Credential,
config *ConcurrentScanConfig,
) *ScanResult {
for retry := 0; retry < config.MaxRetries; retry++ {
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Error: ctx.Err(),
}
default:
if retry > 0 {
time.Sleep(config.RetryDelay)
}
// 创建独立的超时上下文
connCtx, cancel := context.WithTimeout(ctx, config.Timeout)
result, err := scanner.ScanCredential(connCtx, info, cred)
cancel()
if result != nil && result.Success {
return result
}
// 检查是否需要重试
if err != nil && !shouldRetry(err) {
break
}
}
}
return &ScanResult{
Success: false,
Error: fmt.Errorf("重试次数耗尽"),
}
}
// shouldRetry 判断是否应该重试
func shouldRetry(err error) bool {
if err == nil {
return false
}
errStr := strings.ToLower(err.Error())
// 不需要重试的错误
noRetryErrors := []string{
"access denied",
"authentication failed",
"invalid credentials",
"permission denied",
"unauthorized",
}
for _, noRetry := range noRetryErrors {
if strings.Contains(errStr, noRetry) {
return false
}
}
return true
}
// =============================================================================
// 凭据生成工具
// =============================================================================
// GenerateCredentials 生成用户名密码组合的凭据列表
func GenerateCredentials(usernames []string, passwords []string) []*Credential {
var credentials []*Credential
for _, username := range usernames {
for _, password := range passwords {
// 支持 {user} 占位符替换
actualPassword := strings.ReplaceAll(password, "{user}", username)
credentials = append(credentials, &Credential{
Username: username,
Password: actualPassword,
Extra: make(map[string]string),
})
}
}
return credentials
}
// GeneratePasswordOnlyCredentials 生成仅密码的凭据列表如Redis
func GeneratePasswordOnlyCredentials(passwords []string) []*Credential {
var credentials []*Credential
for _, password := range passwords {
credentials = append(credentials, &Credential{
Password: password,
Extra: make(map[string]string),
})
}
return credentials
}
// =============================================================================
// 结果处理工具
// =============================================================================
// SaveScanResult 保存扫描结果到通用输出系统
func SaveScanResult(info *common.HostInfo, result *ScanResult, pluginName string) {
if result == nil || !result.Success {
return
}
target := fmt.Sprintf("%s:%d", info.Host, info.Ports)
// 保存成功的凭据
for _, cred := range result.Credentials {
var message string
if cred.Username != "" && cred.Password != "" {
message = fmt.Sprintf("%s %s %s %s", pluginName, target, cred.Username, cred.Password)
} else if cred.Password != "" {
message = fmt.Sprintf("%s %s [密码] %s", pluginName, target, cred.Password)
} else {
message = fmt.Sprintf("%s %s 未授权访问", pluginName, target)
}
common.LogSuccess(message)
// 保存到输出系统的详细实现...
// 这里可以调用common.SaveResult等函数
}
}