fscan/common/flag.go
ZacharyZcR d19abcac36 feat: 新增发包频率控制功能
- 添加-rate参数:控制每分钟最大发包次数
- 添加-maxpkts参数:控制整个程序最大发包总数
- 在所有网络操作点集成发包限制检查
- 支持端口扫描、Web检测、服务插件、POC扫描等场景
- 默认不限制,保持向后兼容性
2025-09-02 11:24:09 +00:00

400 lines
15 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 common
import (
"flag"
"fmt"
"os"
"strings"
"github.com/fatih/color"
"github.com/shadow1ng/fscan/common/i18n"
)
// Flag专用变量 (只在flag.go中使用的变量globals.go中已定义的不重复声明)
var (
ExcludeHosts string
Ports string
ExcludePorts string
AddPorts string
HostsFile string
PortsFile string
ModuleThreadNum int
GlobalTimeout int64
// EnableFingerprint 已删除:服务指纹识别默认启用
AddUsers string
AddPasswords string
UsersFile string
PasswordsFile string
HashFile string
HashValue string
TargetURL string
URLsFile string
Cookie string
UserAgent string
Accept string
PocPath string
PocFull bool
PocNum int
DisablePocScan bool
RedisFile string
RedisShell string
RedisWritePath string
RedisWriteContent string
RedisWriteFile string
// 反弹Shell相关变量
ReverseShellTarget string
// SOCKS5代理相关变量
Socks5ProxyPort int // SOCKS5代理监听端口
// 正向Shell相关变量
ForwardShellPort int // 正向Shell监听端口
// 键盘记录相关变量
KeyloggerOutputFile string // 键盘记录输出文件
// 文件下载相关变量
DownloadURL string // 下载文件的URL
DownloadSavePath string // 下载文件保存路径
// 发包频率控制相关变量
PacketRateLimit int64 // 每分钟最大发包次数 (0表示不限制)
MaxPacketCount int64 // 整个程序最大发包总数 (0表示不限制)
// Parse.go 使用的变量
HostPort []string
URLs []string
HashValues []string
HashBytes [][]byte
)
// POC信息变量已在globals.go中定义此处不重复声明
func Banner() {
// 定义暗绿色系
colors := []color.Attribute{
color.FgGreen, // 基础绿
color.FgHiGreen, // 亮绿
}
lines := []string{
" ___ _ ",
" / _ \\ ___ ___ _ __ __ _ ___| | __ ",
" / /_\\/____/ __|/ __| '__/ _` |/ __| |/ /",
"/ /_\\\\_____\\__ \\ (__| | | (_| | (__| < ",
"\\____/ |___/\\___|_| \\__,_|\\___|_|\\_\\ ",
}
// 获取最长行的长度
maxLength := 0
for _, line := range lines {
if len(line) > maxLength {
maxLength = len(line)
}
}
// 创建边框
topBorder := "┌" + strings.Repeat("─", maxLength+2) + "┐"
bottomBorder := "└" + strings.Repeat("─", maxLength+2) + "┘"
// 打印banner
fmt.Println(topBorder)
for lineNum, line := range lines {
fmt.Print("│ ")
// 使用对应的颜色打印每个字符
c := color.New(colors[lineNum%2])
c.Print(line)
// 补齐空格
padding := maxLength - len(line)
fmt.Printf("%s │\n", strings.Repeat(" ", padding))
}
fmt.Println(bottomBorder)
// 打印版本信息
c := color.New(colors[1])
c.Printf(" Fscan Version: %s\n\n", version)
}
// Flag 解析命令行参数并配置扫描选项
func Flag(Info *HostInfo) {
Banner()
// 预处理语言设置 - 在定义flag之前检查lang参数
preProcessLanguage()
// ═════════════════════════════════════════════════
// 目标配置参数
// ═════════════════════════════════════════════════
flag.StringVar(&Info.Host, "h", "", i18n.GetText("flag_host"))
flag.StringVar(&ExcludeHosts, "eh", "", i18n.GetText("flag_exclude_hosts"))
flag.StringVar(&Ports, "p", MainPorts, i18n.GetText("flag_ports"))
flag.StringVar(&ExcludePorts, "ep", "", i18n.GetText("flag_exclude_ports"))
flag.StringVar(&HostsFile, "hf", "", i18n.GetText("flag_hosts_file"))
flag.StringVar(&PortsFile, "pf", "", i18n.GetText("flag_ports_file"))
// ═════════════════════════════════════════════════
// 扫描控制参数
// ═════════════════════════════════════════════════
flag.StringVar(&ScanMode, "m", "all", i18n.GetText("flag_scan_mode"))
flag.IntVar(&ThreadNum, "t", 600, i18n.GetText("flag_thread_num"))
flag.Int64Var(&Timeout, "time", 3, i18n.GetText("flag_timeout"))
flag.IntVar(&ModuleThreadNum, "mt", 50, i18n.GetText("flag_module_thread_num"))
flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout"))
// LiveTop 参数已移除,改为智能控制
flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
// 移除-fp参数服务指纹识别已默认启用无需额外参数控制
flag.StringVar(&LocalPlugin, "local", "", "指定本地插件名称 (如: cleaner, avdetect, keylogger 等)")
flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only"))
// ═════════════════════════════════════════════════
// 认证与凭据参数
// ═════════════════════════════════════════════════
flag.StringVar(&Username, "user", "", i18n.GetText("flag_username"))
flag.StringVar(&Password, "pwd", "", i18n.GetText("flag_password"))
flag.StringVar(&AddUsers, "usera", "", i18n.GetText("flag_add_users"))
flag.StringVar(&AddPasswords, "pwda", "", i18n.GetText("flag_add_passwords"))
flag.StringVar(&UsersFile, "userf", "", i18n.GetText("flag_users_file"))
flag.StringVar(&PasswordsFile, "pwdf", "", i18n.GetText("flag_passwords_file"))
flag.StringVar(&HashFile, "hashf", "", i18n.GetText("flag_hash_file"))
flag.StringVar(&HashValue, "hash", "", i18n.GetText("flag_hash_value"))
flag.StringVar(&Domain, "domain", "", i18n.GetText("flag_domain")) // SMB扫描用
flag.StringVar(&SshKeyPath, "sshkey", "", i18n.GetText("flag_ssh_key")) // SSH扫描用
// ═════════════════════════════════════════════════
// Web扫描参数
// ═════════════════════════════════════════════════
flag.StringVar(&TargetURL, "u", "", i18n.GetText("flag_target_url"))
flag.StringVar(&URLsFile, "uf", "", i18n.GetText("flag_urls_file"))
flag.StringVar(&Cookie, "cookie", "", i18n.GetText("flag_cookie"))
flag.Int64Var(&WebTimeout, "wt", 5, i18n.GetText("flag_web_timeout"))
flag.StringVar(&HttpProxy, "proxy", "", i18n.GetText("flag_http_proxy"))
flag.StringVar(&Socks5Proxy, "socks5", "", i18n.GetText("flag_socks5_proxy"))
// ═════════════════════════════════════════════════
// POC测试参数
// ═════════════════════════════════════════════════
flag.StringVar(&PocPath, "pocpath", "", i18n.GetText("flag_poc_path"))
flag.StringVar(&Pocinfo.PocName, "pocname", "", i18n.GetText("flag_poc_name"))
flag.BoolVar(&PocFull, "full", false, i18n.GetText("flag_poc_full"))
flag.BoolVar(&DnsLog, "dns", false, i18n.GetText("flag_dns_log"))
flag.IntVar(&PocNum, "num", 20, i18n.GetText("flag_poc_num"))
flag.BoolVar(&DisablePocScan, "nopoc", false, i18n.GetText("flag_no_poc"))
// ═════════════════════════════════════════════════
// Redis利用参数
// ═════════════════════════════════════════════════
flag.StringVar(&RedisFile, "rf", "", i18n.GetText("flag_redis_file"))
flag.StringVar(&RedisShell, "rs", "", i18n.GetText("flag_redis_shell"))
flag.StringVar(&RedisWritePath, "rwp", "", i18n.GetText("flag_redis_write_path"))
flag.StringVar(&RedisWriteContent, "rwc", "", i18n.GetText("flag_redis_write_content"))
flag.StringVar(&RedisWriteFile, "rwf", "", i18n.GetText("flag_redis_write_file"))
// ═════════════════════════════════════════════════
// 暴力破解控制参数
// ═════════════════════════════════════════════════
flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries"))
// ═════════════════════════════════════════════════
// 发包频率控制参数
// ═════════════════════════════════════════════════
flag.Int64Var(&PacketRateLimit, "rate", 0, i18n.GetText("flag_packet_rate_limit"))
flag.Int64Var(&MaxPacketCount, "maxpkts", 0, i18n.GetText("flag_max_packet_count"))
// ═════════════════════════════════════════════════
// 输出与显示控制参数
// ═════════════════════════════════════════════════
flag.StringVar(&Outputfile, "o", "result.txt", i18n.GetText("flag_output_file"))
flag.StringVar(&OutputFormat, "f", "txt", i18n.GetText("flag_output_format"))
flag.BoolVar(&DisableSave, "no", false, i18n.GetText("flag_disable_save"))
flag.BoolVar(&Silent, "silent", false, i18n.GetText("flag_silent_mode"))
flag.BoolVar(&NoColor, "nocolor", false, i18n.GetText("flag_no_color"))
flag.StringVar(&LogLevel, "log", LogLevelBaseInfoSuccess, i18n.GetText("flag_log_level"))
flag.BoolVar(&DisableProgress, "nopg", false, i18n.GetText("flag_disable_progress"))
// ═════════════════════════════════════════════════
// 其他参数
// ═════════════════════════════════════════════════
flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target"))
flag.IntVar(&Socks5ProxyPort, "start-socks5", 0, i18n.GetText("flag_start_socks5_server"))
flag.IntVar(&ForwardShellPort, "fsh-port", 4444, i18n.GetText("flag_forward_shell_port"))
flag.StringVar(&PersistenceTargetFile, "persistence-file", "", i18n.GetText("flag_persistence_file"))
flag.StringVar(&WinPEFile, "win-pe", "", i18n.GetText("flag_win_pe_file"))
flag.StringVar(&KeyloggerOutputFile, "keylog-output", "keylog.txt", i18n.GetText("flag_keylogger_output"))
// 文件下载插件参数
flag.StringVar(&DownloadURL, "download-url", "", i18n.GetText("flag_download_url"))
flag.StringVar(&DownloadSavePath, "download-path", "", i18n.GetText("flag_download_path"))
flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language"))
// 帮助参数
var showHelp bool
flag.BoolVar(&showHelp, "help", false, i18n.GetText("flag_help"))
// 解析命令行参数
parseCommandLineArgs()
// 设置语言
i18n.SetLanguage(Language)
// 更新进度条显示状态
ShowProgress = !DisableProgress
// 如果显示帮助或者没有提供目标,显示帮助信息并退出
if showHelp || shouldShowHelp(Info) {
flag.Usage()
os.Exit(0)
}
}
// parseCommandLineArgs 处理来自环境变量和命令行的参数
func parseCommandLineArgs() {
// 首先检查环境变量中的参数
envArgsString := os.Getenv("FS_ARGS")
if envArgsString != "" {
// 解析环境变量参数 (跨平台支持)
envArgs, err := parseEnvironmentArgs(envArgsString)
if err == nil && len(envArgs) > 0 {
flag.CommandLine.Parse(envArgs)
os.Unsetenv("FS_ARGS") // 使用后清除环境变量
return
}
// 如果环境变量解析失败,继续使用命令行参数
}
// 解析命令行参数
flag.Parse()
// 检查参数冲突
checkParameterConflicts()
// 额外的本地插件互斥检查
// 需要在解析后检查因为Host是通过Info.Host设置的
// 这个检查在app/initializer.go中进行
}
// parseEnvironmentArgs 安全地解析环境变量中的参数
func parseEnvironmentArgs(argsString string) ([]string, error) {
if strings.TrimSpace(argsString) == "" {
return nil, fmt.Errorf("empty arguments string")
}
// 使用更安全的参数分割方法
var args []string
var currentArg strings.Builder
inQuote := false
quoteChar := ' '
for _, char := range argsString {
switch {
case char == '"' || char == '\'':
if inQuote && char == quoteChar {
inQuote = false
} else if !inQuote {
inQuote = true
quoteChar = char
} else {
currentArg.WriteRune(char)
}
case char == ' ' && !inQuote:
if currentArg.Len() > 0 {
args = append(args, currentArg.String())
currentArg.Reset()
}
default:
currentArg.WriteRune(char)
}
}
if currentArg.Len() > 0 {
args = append(args, currentArg.String())
}
return args, nil
}
// preProcessLanguage 预处理语言参数在定义flag之前设置语言
func preProcessLanguage() {
// 遍历命令行参数查找-lang参数
for i, arg := range os.Args {
if arg == "-lang" && i+1 < len(os.Args) {
lang := os.Args[i+1]
if lang == "en" || lang == "zh" {
Language = lang
i18n.SetLanguage(lang)
return
}
} else if strings.HasPrefix(arg, "-lang=") {
lang := strings.TrimPrefix(arg, "-lang=")
if lang == "en" || lang == "zh" {
Language = lang
i18n.SetLanguage(lang)
return
}
}
}
// 检查环境变量
envLang := os.Getenv("FS_LANG")
if envLang == "en" || envLang == "zh" {
Language = envLang
i18n.SetLanguage(envLang)
}
}
// shouldShowHelp 检查是否应该显示帮助信息
func shouldShowHelp(Info *HostInfo) bool {
// 检查是否提供了扫描目标
hasTarget := Info.Host != "" || TargetURL != ""
// 本地模式需要指定插件才算有效目标
if LocalMode && LocalPlugin != "" {
hasTarget = true
}
// 如果没有提供任何扫描目标,则显示帮助
if !hasTarget {
return true
}
return false
}
// checkParameterConflicts 检查参数冲突和兼容性
func checkParameterConflicts() {
// 检查 -ao 和 -m icmp 同时指定的情况(向后兼容提示)
if AliveOnly && ScanMode == "icmp" {
LogBase(i18n.GetText("param_conflict_ao_icmp_both"))
}
// 检查本地插件参数
if LocalPlugin != "" {
// 检查是否包含分隔符(确保只能指定单个插件)
invalidChars := []string{",", ";", " ", "|", "&"}
for _, char := range invalidChars {
if strings.Contains(LocalPlugin, char) {
fmt.Printf("错误: 本地插件只能指定单个插件,不支持使用 '%s' 分隔的多个插件\n", char)
LogError(fmt.Sprintf("本地插件只能指定单个插件,不支持使用 '%s' 分隔的多个插件", char))
os.Exit(1)
}
}
// 自动启用本地模式
LocalMode = true
// 验证本地插件名称 - 使用统一插件系统验证
// 这里不进行验证,让运行时的插件系统来处理不存在的插件
}
}