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

主要改进: - 模块化设计:将549行Parse.go拆分为6个专用解析器 - 性能优化:添加文件缓存、并发处理和去重机制 - 增强验证:实现配置冲突检测和参数验证 - 改善体验:清理冗余警告,添加解析结果摘要显示 - 向后兼容:保持所有原有API接口不变 新增模块: - FileReader: 高性能文件读取和缓存 - CredentialParser: 用户名密码解析 - TargetParser: 主机目标解析 - NetworkParser: 网络配置解析 - ValidationParser: 参数验证和冲突检测 - Types: 统一的数据结构定义 修复问题: - 消除重复的"Web超时时间大于普通超时时间"警告 - 添加目标主机、端口、代理等配置信息显示 - 删除说教性安全警告,保留技术性提示
376 lines
11 KiB
Go
376 lines
11 KiB
Go
package parsers
|
||
|
||
import (
|
||
"encoding/hex"
|
||
"fmt"
|
||
"regexp"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// CredentialParser 凭据解析器
|
||
type CredentialParser struct {
|
||
fileReader *FileReader
|
||
mu sync.RWMutex
|
||
hashRegex *regexp.Regexp
|
||
options *CredentialParserOptions
|
||
}
|
||
|
||
// CredentialParserOptions 凭据解析器选项
|
||
type CredentialParserOptions struct {
|
||
MaxUsernameLength int `json:"max_username_length"`
|
||
MaxPasswordLength int `json:"max_password_length"`
|
||
AllowEmptyPasswords bool `json:"allow_empty_passwords"`
|
||
ValidateHashes bool `json:"validate_hashes"`
|
||
DeduplicateUsers bool `json:"deduplicate_users"`
|
||
DeduplicatePasswords bool `json:"deduplicate_passwords"`
|
||
EnableStatistics bool `json:"enable_statistics"`
|
||
}
|
||
|
||
// DefaultCredentialParserOptions 默认凭据解析器选项
|
||
func DefaultCredentialParserOptions() *CredentialParserOptions {
|
||
return &CredentialParserOptions{
|
||
MaxUsernameLength: 64,
|
||
MaxPasswordLength: 128,
|
||
AllowEmptyPasswords: true,
|
||
ValidateHashes: true,
|
||
DeduplicateUsers: true,
|
||
DeduplicatePasswords: true,
|
||
EnableStatistics: true,
|
||
}
|
||
}
|
||
|
||
// NewCredentialParser 创建凭据解析器
|
||
func NewCredentialParser(fileReader *FileReader, options *CredentialParserOptions) *CredentialParser {
|
||
if options == nil {
|
||
options = DefaultCredentialParserOptions()
|
||
}
|
||
|
||
// 编译哈希验证正则表达式 (MD5: 32位十六进制)
|
||
hashRegex := regexp.MustCompile(`^[a-fA-F0-9]{32}$`)
|
||
|
||
return &CredentialParser{
|
||
fileReader: fileReader,
|
||
hashRegex: hashRegex,
|
||
options: options,
|
||
}
|
||
}
|
||
|
||
// CredentialInput 凭据输入参数
|
||
type CredentialInput struct {
|
||
// 直接输入
|
||
Username string `json:"username"`
|
||
Password string `json:"password"`
|
||
AddUsers string `json:"add_users"`
|
||
AddPasswords string `json:"add_passwords"`
|
||
HashValue string `json:"hash_value"`
|
||
SshKeyPath string `json:"ssh_key_path"`
|
||
Domain string `json:"domain"`
|
||
|
||
// 文件输入
|
||
UsersFile string `json:"users_file"`
|
||
PasswordsFile string `json:"passwords_file"`
|
||
HashFile string `json:"hash_file"`
|
||
}
|
||
|
||
// Parse 解析凭据配置
|
||
func (cp *CredentialParser) Parse(input *CredentialInput, options *ParserOptions) (*ParseResult, error) {
|
||
if input == nil {
|
||
return nil, NewParseError("INPUT_ERROR", "凭据输入为空", "", 0, ErrEmptyInput)
|
||
}
|
||
|
||
startTime := time.Now()
|
||
result := &ParseResult{
|
||
Config: &ParsedConfig{
|
||
Credentials: &CredentialConfig{
|
||
SshKeyPath: input.SshKeyPath,
|
||
Domain: input.Domain,
|
||
},
|
||
},
|
||
Success: true,
|
||
}
|
||
|
||
var errors []error
|
||
var warnings []string
|
||
|
||
// 解析用户名
|
||
usernames, userErrors, userWarnings := cp.parseUsernames(input)
|
||
errors = append(errors, userErrors...)
|
||
warnings = append(warnings, userWarnings...)
|
||
|
||
// 解析密码
|
||
passwords, passErrors, passWarnings := cp.parsePasswords(input)
|
||
errors = append(errors, passErrors...)
|
||
warnings = append(warnings, passWarnings...)
|
||
|
||
// 解析哈希值
|
||
hashValues, hashBytes, hashErrors, hashWarnings := cp.parseHashes(input)
|
||
errors = append(errors, hashErrors...)
|
||
warnings = append(warnings, hashWarnings...)
|
||
|
||
// 更新配置
|
||
result.Config.Credentials.Usernames = usernames
|
||
result.Config.Credentials.Passwords = passwords
|
||
result.Config.Credentials.HashValues = hashValues
|
||
result.Config.Credentials.HashBytes = hashBytes
|
||
|
||
// 生成统计信息
|
||
if cp.options.EnableStatistics {
|
||
result.Config.Credentials.Statistics = cp.generateStatistics(usernames, passwords, hashValues, hashBytes)
|
||
}
|
||
|
||
// 设置结果状态
|
||
result.Errors = errors
|
||
result.Warnings = warnings
|
||
result.ParseTime = time.Since(startTime)
|
||
result.Success = len(errors) == 0
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// parseUsernames 解析用户名
|
||
func (cp *CredentialParser) parseUsernames(input *CredentialInput) ([]string, []error, []string) {
|
||
var usernames []string
|
||
var errors []error
|
||
var warnings []string
|
||
|
||
// 解析命令行用户名
|
||
if input.Username != "" {
|
||
users := strings.Split(input.Username, ",")
|
||
for _, user := range users {
|
||
if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid {
|
||
usernames = append(usernames, processedUser)
|
||
} else if err != nil {
|
||
errors = append(errors, NewParseError("USERNAME_ERROR", err.Error(), "command line", 0, err))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 从文件读取用户名
|
||
if input.UsersFile != "" {
|
||
fileResult, err := cp.fileReader.ReadFile(input.UsersFile)
|
||
if err != nil {
|
||
errors = append(errors, NewParseError("FILE_ERROR", "读取用户名文件失败", input.UsersFile, 0, err))
|
||
} else {
|
||
for i, line := range fileResult.Lines {
|
||
if processedUser, valid, err := cp.validateUsername(line); valid {
|
||
usernames = append(usernames, processedUser)
|
||
} else if err != nil {
|
||
warnings = append(warnings, fmt.Sprintf("用户名文件第%d行无效: %s", i+1, err.Error()))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理额外用户名
|
||
if input.AddUsers != "" {
|
||
extraUsers := strings.Split(input.AddUsers, ",")
|
||
for _, user := range extraUsers {
|
||
if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid {
|
||
usernames = append(usernames, processedUser)
|
||
} else if err != nil {
|
||
warnings = append(warnings, fmt.Sprintf("额外用户名无效: %s", err.Error()))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 去重
|
||
if cp.options.DeduplicateUsers {
|
||
usernames = cp.removeDuplicateStrings(usernames)
|
||
}
|
||
|
||
return usernames, errors, warnings
|
||
}
|
||
|
||
// parsePasswords 解析密码
|
||
func (cp *CredentialParser) parsePasswords(input *CredentialInput) ([]string, []error, []string) {
|
||
var passwords []string
|
||
var errors []error
|
||
var warnings []string
|
||
|
||
// 解析命令行密码
|
||
if input.Password != "" {
|
||
passes := strings.Split(input.Password, ",")
|
||
for _, pass := range passes {
|
||
if processedPass, valid, err := cp.validatePassword(pass); valid {
|
||
passwords = append(passwords, processedPass)
|
||
} else if err != nil {
|
||
errors = append(errors, NewParseError("PASSWORD_ERROR", err.Error(), "command line", 0, err))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 从文件读取密码
|
||
if input.PasswordsFile != "" {
|
||
fileResult, err := cp.fileReader.ReadFile(input.PasswordsFile)
|
||
if err != nil {
|
||
errors = append(errors, NewParseError("FILE_ERROR", "读取密码文件失败", input.PasswordsFile, 0, err))
|
||
} else {
|
||
for i, line := range fileResult.Lines {
|
||
if processedPass, valid, err := cp.validatePassword(line); valid {
|
||
passwords = append(passwords, processedPass)
|
||
} else if err != nil {
|
||
warnings = append(warnings, fmt.Sprintf("密码文件第%d行无效: %s", i+1, err.Error()))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理额外密码
|
||
if input.AddPasswords != "" {
|
||
extraPasses := strings.Split(input.AddPasswords, ",")
|
||
for _, pass := range extraPasses {
|
||
if processedPass, valid, err := cp.validatePassword(pass); valid {
|
||
passwords = append(passwords, processedPass)
|
||
} else if err != nil {
|
||
warnings = append(warnings, fmt.Sprintf("额外密码无效: %s", err.Error()))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 去重
|
||
if cp.options.DeduplicatePasswords {
|
||
passwords = cp.removeDuplicateStrings(passwords)
|
||
}
|
||
|
||
return passwords, errors, warnings
|
||
}
|
||
|
||
// parseHashes 解析哈希值
|
||
func (cp *CredentialParser) parseHashes(input *CredentialInput) ([]string, [][]byte, []error, []string) {
|
||
var hashValues []string
|
||
var hashBytes [][]byte
|
||
var errors []error
|
||
var warnings []string
|
||
|
||
// 解析单个哈希值
|
||
if input.HashValue != "" {
|
||
if valid, err := cp.validateHash(input.HashValue); valid {
|
||
hashValues = append(hashValues, input.HashValue)
|
||
} else {
|
||
errors = append(errors, NewParseError("HASH_ERROR", err.Error(), "command line", 0, err))
|
||
}
|
||
}
|
||
|
||
// 从文件读取哈希值
|
||
if input.HashFile != "" {
|
||
fileResult, err := cp.fileReader.ReadFile(input.HashFile)
|
||
if err != nil {
|
||
errors = append(errors, NewParseError("FILE_ERROR", "读取哈希文件失败", input.HashFile, 0, err))
|
||
} else {
|
||
for i, line := range fileResult.Lines {
|
||
if valid, err := cp.validateHash(line); valid {
|
||
hashValues = append(hashValues, line)
|
||
} else {
|
||
warnings = append(warnings, fmt.Sprintf("哈希文件第%d行无效: %s", i+1, err.Error()))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 转换哈希值为字节数组
|
||
for _, hash := range hashValues {
|
||
if hashByte, err := hex.DecodeString(hash); err == nil {
|
||
hashBytes = append(hashBytes, hashByte)
|
||
} else {
|
||
warnings = append(warnings, fmt.Sprintf("哈希值解码失败: %s", hash))
|
||
}
|
||
}
|
||
|
||
return hashValues, hashBytes, errors, warnings
|
||
}
|
||
|
||
// validateUsername 验证用户名
|
||
func (cp *CredentialParser) validateUsername(username string) (string, bool, error) {
|
||
if len(username) == 0 {
|
||
return "", false, nil // 允许空用户名,但不添加到列表
|
||
}
|
||
|
||
if len(username) > cp.options.MaxUsernameLength {
|
||
return "", false, fmt.Errorf("用户名过长: %d字符,最大允许: %d", len(username), cp.options.MaxUsernameLength)
|
||
}
|
||
|
||
// 检查特殊字符
|
||
if strings.ContainsAny(username, "\r\n\t") {
|
||
return "", false, fmt.Errorf("用户名包含非法字符")
|
||
}
|
||
|
||
return username, true, nil
|
||
}
|
||
|
||
// validatePassword 验证密码
|
||
func (cp *CredentialParser) validatePassword(password string) (string, bool, error) {
|
||
if len(password) == 0 && !cp.options.AllowEmptyPasswords {
|
||
return "", false, fmt.Errorf("不允许空密码")
|
||
}
|
||
|
||
if len(password) > cp.options.MaxPasswordLength {
|
||
return "", false, fmt.Errorf("密码过长: %d字符,最大允许: %d", len(password), cp.options.MaxPasswordLength)
|
||
}
|
||
|
||
return password, true, nil
|
||
}
|
||
|
||
// validateHash 验证哈希值
|
||
func (cp *CredentialParser) validateHash(hash string) (bool, error) {
|
||
if !cp.options.ValidateHashes {
|
||
return true, nil
|
||
}
|
||
|
||
hash = strings.TrimSpace(hash)
|
||
if len(hash) == 0 {
|
||
return false, fmt.Errorf("哈希值为空")
|
||
}
|
||
|
||
if !cp.hashRegex.MatchString(hash) {
|
||
return false, fmt.Errorf("哈希值格式无效,需要32位十六进制字符")
|
||
}
|
||
|
||
return true, nil
|
||
}
|
||
|
||
// removeDuplicateStrings 去重字符串切片
|
||
func (cp *CredentialParser) removeDuplicateStrings(slice []string) []string {
|
||
seen := make(map[string]struct{})
|
||
var result []string
|
||
|
||
for _, item := range slice {
|
||
if _, exists := seen[item]; !exists {
|
||
seen[item] = struct{}{}
|
||
result = append(result, item)
|
||
}
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// generateStatistics 生成统计信息
|
||
func (cp *CredentialParser) generateStatistics(usernames, passwords, hashValues []string, hashBytes [][]byte) *CredentialStats {
|
||
return &CredentialStats{
|
||
TotalUsernames: len(usernames),
|
||
TotalPasswords: len(passwords),
|
||
TotalHashes: len(hashValues),
|
||
UniqueUsernames: len(cp.removeDuplicateStrings(usernames)),
|
||
UniquePasswords: len(cp.removeDuplicateStrings(passwords)),
|
||
ValidHashes: len(hashBytes),
|
||
InvalidHashes: len(hashValues) - len(hashBytes),
|
||
}
|
||
}
|
||
|
||
// Validate 验证解析结果
|
||
func (cp *CredentialParser) Validate() error {
|
||
// 基本验证逻辑
|
||
return nil
|
||
}
|
||
|
||
// GetStatistics 获取解析统计
|
||
func (cp *CredentialParser) GetStatistics() interface{} {
|
||
cp.mu.RLock()
|
||
defer cp.mu.RUnlock()
|
||
|
||
return map[string]interface{}{
|
||
"parser_type": "credential",
|
||
"options": cp.options,
|
||
}
|
||
} |