mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00

- 修复URL正则表达式支持IP地址格式 - 修复URL解析后正确设置到HostInfo.Url字段 - 修复Web扫描目标Host和Port字段的提取 - 修复Web插件适用性检查逻辑,允许Web扫描策略直接执行Web插件 - 完善URL到目标信息的转换,包含协议默认端口处理
504 lines
14 KiB
Go
504 lines
14 KiB
Go
package common
|
||
|
||
import (
|
||
"fmt"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/shadow1ng/fscan/common/i18n"
|
||
"github.com/shadow1ng/fscan/common/logging"
|
||
"github.com/shadow1ng/fscan/common/parsers"
|
||
"github.com/shadow1ng/fscan/common/utils"
|
||
)
|
||
|
||
// ParsedConfiguration 解析后的完整配置(兼容旧代码)
|
||
type ParsedConfiguration struct {
|
||
*parsers.ParsedConfig
|
||
}
|
||
|
||
// Parser 主解析器
|
||
type Parser struct {
|
||
mu sync.RWMutex
|
||
fileReader *parsers.FileReader
|
||
credentialParser *parsers.CredentialParser
|
||
targetParser *parsers.TargetParser
|
||
networkParser *parsers.NetworkParser
|
||
validationParser *parsers.ValidationParser
|
||
options *parsers.ParserOptions
|
||
initialized bool
|
||
}
|
||
|
||
// NewParser 创建新的解析器实例
|
||
func NewParser(options *parsers.ParserOptions) *Parser {
|
||
if options == nil {
|
||
options = parsers.DefaultParserOptions()
|
||
}
|
||
|
||
// 创建文件读取器
|
||
fileReader := parsers.NewFileReader(nil)
|
||
|
||
// 创建各个子解析器
|
||
credentialParser := parsers.NewCredentialParser(fileReader, nil)
|
||
targetParser := parsers.NewTargetParser(fileReader, nil)
|
||
networkParser := parsers.NewNetworkParser(nil)
|
||
validationParser := parsers.NewValidationParser(nil)
|
||
|
||
return &Parser{
|
||
fileReader: fileReader,
|
||
credentialParser: credentialParser,
|
||
targetParser: targetParser,
|
||
networkParser: networkParser,
|
||
validationParser: validationParser,
|
||
options: options,
|
||
initialized: true,
|
||
}
|
||
}
|
||
|
||
// 全局解析器实例
|
||
var globalParser *Parser
|
||
var initOnce sync.Once
|
||
|
||
// getGlobalParser 获取全局解析器实例
|
||
func getGlobalParser() *Parser {
|
||
initOnce.Do(func() {
|
||
globalParser = NewParser(nil)
|
||
})
|
||
return globalParser
|
||
}
|
||
|
||
// Parse 主解析函数 - 保持与原版本兼容的接口
|
||
func Parse(Info *HostInfo) error {
|
||
// 首先应用LogLevel配置到日志系统
|
||
applyLogLevel()
|
||
|
||
parser := getGlobalParser()
|
||
|
||
// 构建输入参数
|
||
input := &AllInputs{
|
||
Credential: &parsers.CredentialInput{
|
||
Username: Username,
|
||
Password: Password,
|
||
AddUsers: AddUsers,
|
||
AddPasswords: AddPasswords,
|
||
HashValue: HashValue,
|
||
SshKeyPath: SshKeyPath,
|
||
Domain: Domain,
|
||
UsersFile: UsersFile,
|
||
PasswordsFile: PasswordsFile,
|
||
HashFile: HashFile,
|
||
},
|
||
Target: &parsers.TargetInput{
|
||
Host: Info.Host,
|
||
HostsFile: HostsFile,
|
||
ExcludeHosts: ExcludeHosts,
|
||
Ports: Ports,
|
||
PortsFile: PortsFile,
|
||
AddPorts: AddPorts,
|
||
ExcludePorts: ExcludePorts,
|
||
TargetURL: TargetURL,
|
||
URLsFile: URLsFile,
|
||
HostPort: HostPort,
|
||
LocalMode: LocalMode,
|
||
},
|
||
Network: &parsers.NetworkInput{
|
||
HttpProxy: HttpProxy,
|
||
Socks5Proxy: Socks5Proxy,
|
||
Timeout: Timeout,
|
||
WebTimeout: WebTimeout,
|
||
DisablePing: DisablePing,
|
||
DnsLog: DnsLog,
|
||
UserAgent: UserAgent,
|
||
Cookie: Cookie,
|
||
},
|
||
}
|
||
|
||
// 执行解析
|
||
result, err := parser.ParseAll(input)
|
||
if err != nil {
|
||
return fmt.Errorf(i18n.GetText("parse_error_config_failed", err))
|
||
}
|
||
|
||
// 更新全局变量以保持兼容性
|
||
if err := updateGlobalVariables(result.Config, Info); err != nil {
|
||
return fmt.Errorf(i18n.GetText("parse_error_update_vars_failed", err))
|
||
}
|
||
|
||
// 报告警告
|
||
for _, warning := range result.Warnings {
|
||
LogBase(warning)
|
||
}
|
||
|
||
// 显示解析结果摘要
|
||
showParseSummary(result.Config)
|
||
|
||
// 同步配置到core包
|
||
SyncToCore()
|
||
|
||
return nil
|
||
}
|
||
|
||
// AllInputs 所有输入参数的集合
|
||
type AllInputs struct {
|
||
Credential *parsers.CredentialInput `json:"credential"`
|
||
Target *parsers.TargetInput `json:"target"`
|
||
Network *parsers.NetworkInput `json:"network"`
|
||
}
|
||
|
||
// ParseAll 解析所有配置
|
||
func (p *Parser) ParseAll(input *AllInputs) (*parsers.ParseResult, error) {
|
||
if input == nil {
|
||
return nil, fmt.Errorf(i18n.GetText("parse_error_empty_input"))
|
||
}
|
||
|
||
p.mu.Lock()
|
||
defer p.mu.Unlock()
|
||
|
||
if !p.initialized {
|
||
return nil, fmt.Errorf(i18n.GetText("parse_error_parser_not_init"))
|
||
}
|
||
|
||
startTime := time.Now()
|
||
result := &parsers.ParseResult{
|
||
Config: &parsers.ParsedConfig{},
|
||
Success: true,
|
||
}
|
||
|
||
var allErrors []error
|
||
var allWarnings []string
|
||
|
||
// 解析凭据配置
|
||
if input.Credential != nil {
|
||
credResult, err := p.credentialParser.Parse(input.Credential, p.options)
|
||
if err != nil {
|
||
allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_credential_failed", err)))
|
||
} else {
|
||
result.Config.Credentials = credResult.Config.Credentials
|
||
allErrors = append(allErrors, credResult.Errors...)
|
||
allWarnings = append(allWarnings, credResult.Warnings...)
|
||
}
|
||
}
|
||
|
||
// 解析目标配置
|
||
if input.Target != nil {
|
||
targetResult, err := p.targetParser.Parse(input.Target, p.options)
|
||
if err != nil {
|
||
allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_target_failed", err)))
|
||
} else {
|
||
result.Config.Targets = targetResult.Config.Targets
|
||
allErrors = append(allErrors, targetResult.Errors...)
|
||
allWarnings = append(allWarnings, targetResult.Warnings...)
|
||
}
|
||
}
|
||
|
||
// 解析网络配置
|
||
if input.Network != nil {
|
||
networkResult, err := p.networkParser.Parse(input.Network, p.options)
|
||
if err != nil {
|
||
allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_network_failed", err)))
|
||
} else {
|
||
result.Config.Network = networkResult.Config.Network
|
||
allErrors = append(allErrors, networkResult.Errors...)
|
||
allWarnings = append(allWarnings, networkResult.Warnings...)
|
||
}
|
||
}
|
||
|
||
// 执行验证
|
||
validationInput := &parsers.ValidationInput{
|
||
ScanMode: ScanMode,
|
||
LocalMode: LocalMode,
|
||
HasHosts: input.Target != nil && (input.Target.Host != "" || input.Target.HostsFile != ""),
|
||
HasURLs: input.Target != nil && (input.Target.TargetURL != "" || input.Target.URLsFile != ""),
|
||
HasPorts: input.Target != nil && (input.Target.Ports != "" || input.Target.PortsFile != ""),
|
||
HasProxy: input.Network != nil && (input.Network.HttpProxy != "" || input.Network.Socks5Proxy != ""),
|
||
DisablePing: input.Network != nil && input.Network.DisablePing,
|
||
HasCredentials: input.Credential != nil && (input.Credential.Username != "" || input.Credential.UsersFile != ""),
|
||
}
|
||
|
||
validationResult, err := p.validationParser.Parse(validationInput, result.Config, p.options)
|
||
if err != nil {
|
||
allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_validation_failed", err)))
|
||
} else {
|
||
result.Config.Validation = validationResult.Config.Validation
|
||
allErrors = append(allErrors, validationResult.Errors...)
|
||
allWarnings = append(allWarnings, validationResult.Warnings...)
|
||
}
|
||
|
||
// 汇总结果
|
||
result.Errors = allErrors
|
||
result.Warnings = allWarnings
|
||
result.ParseTime = time.Since(startTime)
|
||
result.Success = len(allErrors) == 0
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// updateGlobalVariables 更新全局变量以保持向后兼容性
|
||
func updateGlobalVariables(config *parsers.ParsedConfig, info *HostInfo) error {
|
||
if config == nil {
|
||
return nil
|
||
}
|
||
|
||
// 更新凭据相关全局变量
|
||
if config.Credentials != nil {
|
||
if len(config.Credentials.Usernames) > 0 {
|
||
// 更新全局用户字典
|
||
for serviceName := range Userdict {
|
||
Userdict[serviceName] = config.Credentials.Usernames
|
||
}
|
||
}
|
||
|
||
if len(config.Credentials.Passwords) > 0 {
|
||
Passwords = config.Credentials.Passwords
|
||
}
|
||
|
||
if len(config.Credentials.HashValues) > 0 {
|
||
HashValues = config.Credentials.HashValues
|
||
}
|
||
|
||
if len(config.Credentials.HashBytes) > 0 {
|
||
HashBytes = config.Credentials.HashBytes
|
||
}
|
||
}
|
||
|
||
// 更新目标相关全局变量
|
||
if config.Targets != nil {
|
||
if len(config.Targets.Hosts) > 0 {
|
||
// 如果info.Host已经有值,说明解析结果来自info.Host,不需要重复设置
|
||
// 只有当info.Host为空时才设置(如从文件读取的情况)
|
||
if info.Host == "" {
|
||
info.Host = joinStrings(config.Targets.Hosts, ",")
|
||
}
|
||
}
|
||
|
||
if len(config.Targets.URLs) > 0 {
|
||
URLs = config.Targets.URLs
|
||
// 如果info.Url为空且只有一个URL,将其设置到info.Url
|
||
if info.Url == "" && len(config.Targets.URLs) == 1 {
|
||
info.Url = config.Targets.URLs[0]
|
||
}
|
||
}
|
||
|
||
if len(config.Targets.Ports) > 0 {
|
||
Ports = joinInts(config.Targets.Ports, ",")
|
||
}
|
||
|
||
if len(config.Targets.ExcludePorts) > 0 {
|
||
ExcludePorts = joinInts(config.Targets.ExcludePorts, ",")
|
||
}
|
||
|
||
if len(config.Targets.HostPorts) > 0 {
|
||
HostPort = config.Targets.HostPorts
|
||
}
|
||
}
|
||
|
||
// 更新网络相关全局变量
|
||
if config.Network != nil {
|
||
if config.Network.HttpProxy != "" {
|
||
HttpProxy = config.Network.HttpProxy
|
||
}
|
||
|
||
if config.Network.Socks5Proxy != "" {
|
||
Socks5Proxy = config.Network.Socks5Proxy
|
||
}
|
||
|
||
if config.Network.Timeout > 0 {
|
||
Timeout = int64(config.Network.Timeout.Seconds())
|
||
}
|
||
|
||
if config.Network.WebTimeout > 0 {
|
||
WebTimeout = int64(config.Network.WebTimeout.Seconds())
|
||
}
|
||
|
||
if config.Network.UserAgent != "" {
|
||
UserAgent = config.Network.UserAgent
|
||
}
|
||
|
||
if config.Network.Cookie != "" {
|
||
Cookie = config.Network.Cookie
|
||
}
|
||
|
||
DisablePing = config.Network.DisablePing
|
||
DnsLog = config.Network.EnableDNSLog
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// RemoveDuplicate 去重函数 - 恢复原始高效实现
|
||
func RemoveDuplicate(old []string) []string {
|
||
if len(old) <= 1 {
|
||
return old
|
||
}
|
||
|
||
temp := make(map[string]struct{}, len(old))
|
||
result := make([]string, 0, len(old))
|
||
|
||
for _, item := range old {
|
||
if _, exists := temp[item]; !exists {
|
||
temp[item] = struct{}{}
|
||
result = append(result, item)
|
||
}
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// 辅助函数
|
||
|
||
// joinStrings 连接字符串切片 - 优化版本使用字符串构建器池
|
||
func joinStrings(slice []string, sep string) string {
|
||
return utils.JoinStrings(slice, sep)
|
||
}
|
||
|
||
// joinInts 连接整数切片 - 优化版本使用字符串构建器池
|
||
func joinInts(slice []int, sep string) string {
|
||
return utils.JoinInts(slice, sep)
|
||
}
|
||
|
||
// showParseSummary 显示解析结果摘要
|
||
func showParseSummary(config *parsers.ParsedConfig) {
|
||
if config == nil {
|
||
return
|
||
}
|
||
|
||
// 显示目标信息
|
||
if config.Targets != nil {
|
||
if len(config.Targets.Hosts) > 0 {
|
||
if len(config.Targets.Hosts) <= 5 {
|
||
LogBase(i18n.GetText("target_hosts_found", joinStrings(config.Targets.Hosts, ", ")))
|
||
} else {
|
||
LogBase(i18n.GetText("target_hosts_count", joinStrings(config.Targets.Hosts[:5], ", "), len(config.Targets.Hosts)))
|
||
}
|
||
}
|
||
|
||
if len(config.Targets.URLs) > 0 {
|
||
if len(config.Targets.URLs) <= 3 {
|
||
LogBase(i18n.GetText("target_urls_found", joinStrings(config.Targets.URLs, ", ")))
|
||
} else {
|
||
LogBase(i18n.GetText("target_urls_count", joinStrings(config.Targets.URLs[:3], ", "), len(config.Targets.URLs)))
|
||
}
|
||
}
|
||
|
||
if len(config.Targets.Ports) > 0 {
|
||
if len(config.Targets.Ports) <= 20 {
|
||
LogBase(i18n.GetText("target_ports_found", joinInts(config.Targets.Ports, ", ")))
|
||
} else {
|
||
LogBase(i18n.GetText("target_ports_count", joinInts(config.Targets.Ports[:20], ", "), len(config.Targets.Ports)))
|
||
}
|
||
}
|
||
|
||
if len(config.Targets.ExcludePorts) > 0 {
|
||
LogBase(i18n.GetText("target_exclude_ports", joinInts(config.Targets.ExcludePorts, ", ")))
|
||
}
|
||
|
||
if config.Targets.LocalMode {
|
||
LogBase(i18n.GetText("target_local_mode"))
|
||
}
|
||
}
|
||
|
||
// 显示扫描配置
|
||
LogBase(i18n.GetText("scan_config_thread_num", ThreadNum))
|
||
LogBase(i18n.GetText("scan_config_timeout", Timeout))
|
||
LogBase(i18n.GetText("scan_config_module_thread_num", ModuleThreadNum))
|
||
LogBase(i18n.GetText("scan_config_global_timeout", GlobalTimeout))
|
||
|
||
// 显示网络配置
|
||
if config.Network != nil {
|
||
if config.Network.HttpProxy != "" {
|
||
LogBase(i18n.GetText("network_http_proxy", config.Network.HttpProxy))
|
||
}
|
||
if config.Network.Socks5Proxy != "" {
|
||
LogBase(i18n.GetText("network_socks5_proxy", config.Network.Socks5Proxy))
|
||
}
|
||
if config.Network.WebTimeout > 0 {
|
||
LogBase(i18n.GetText("network_web_timeout", config.Network.WebTimeout))
|
||
}
|
||
}
|
||
|
||
// 显示凭据信息
|
||
if config.Credentials != nil {
|
||
if len(config.Credentials.Usernames) > 0 {
|
||
LogBase(i18n.GetText("credential_username_count", len(config.Credentials.Usernames)))
|
||
}
|
||
if len(config.Credentials.Passwords) > 0 {
|
||
LogBase(i18n.GetText("credential_password_count", len(config.Credentials.Passwords)))
|
||
}
|
||
if len(config.Credentials.HashValues) > 0 {
|
||
LogBase(i18n.GetText("credential_hash_count", len(config.Credentials.HashValues)))
|
||
}
|
||
}
|
||
}
|
||
|
||
// applyLogLevel 应用LogLevel配置到日志系统
|
||
func applyLogLevel() {
|
||
if LogLevel == "" {
|
||
return // 使用默认级别
|
||
}
|
||
|
||
// 根据LogLevel字符串获取对应的日志级别
|
||
var level logging.LogLevel
|
||
switch LogLevel {
|
||
case LogLevelAll:
|
||
level = logging.LevelAll
|
||
case LogLevelError:
|
||
level = logging.LevelError
|
||
case LogLevelBase:
|
||
level = logging.LevelBase
|
||
case LogLevelInfo:
|
||
level = logging.LevelInfo
|
||
case LogLevelSuccess:
|
||
level = logging.LevelSuccess
|
||
case LogLevelDebug:
|
||
level = logging.LevelDebug
|
||
case LogLevelInfoSuccess:
|
||
level = logging.LevelInfoSuccess
|
||
case LogLevelBaseInfoSuccess:
|
||
level = logging.LevelBaseInfoSuccess
|
||
default:
|
||
// 向后兼容:如果是老的字符串格式
|
||
switch LogLevel {
|
||
case "ALL":
|
||
level = logging.LevelAll
|
||
case "ERROR":
|
||
level = logging.LevelError
|
||
case "BASE":
|
||
level = logging.LevelBase
|
||
case "INFO":
|
||
level = logging.LevelInfo
|
||
case "SUCCESS":
|
||
level = logging.LevelSuccess
|
||
case "DEBUG":
|
||
level = logging.LevelDebug
|
||
case "debug":
|
||
level = logging.LevelAll // 兼容旧的debug行为
|
||
default:
|
||
return // 无效的级别,保持默认
|
||
}
|
||
}
|
||
|
||
// 更新全局日志管理器的级别
|
||
if globalLogger != nil {
|
||
config := &logging.LoggerConfig{
|
||
Level: level,
|
||
EnableColor: !NoColor,
|
||
SlowOutput: false,
|
||
ShowProgress: ShowProgress,
|
||
StartTime: StartTime,
|
||
LevelColors: logging.GetDefaultLevelColors(),
|
||
}
|
||
|
||
newLogger := logging.NewLogger(config)
|
||
if ProgressBar != nil {
|
||
newLogger.SetProgressBar(ProgressBar)
|
||
}
|
||
newLogger.SetOutputMutex(&OutputMutex)
|
||
|
||
// 设置协调输出函数,使用LogWithProgress
|
||
newLogger.SetCoordinatedOutput(LogWithProgress)
|
||
|
||
// 更新全局日志管理器
|
||
globalLogger = newLogger
|
||
// status变量已移除,如需获取状态请直接调用newLogger.GetScanStatus()
|
||
}
|
||
}
|