fix: 修复进度条显示错位问题,实现真正的固定底部进度条

- 简化进度条定位逻辑,移除复杂的光标定位操作
- 优化LogWithProgress协调机制,确保日志与进度条正确交互
- 修复ANSI转义序列被直接输出的问题
- 进度条现在能够在底部原地更新,不再与日志输出争抢显示空间
This commit is contained in:
ZacharyZcR 2025-08-06 05:00:21 +08:00
parent 9b6c389ea8
commit c8038bdc62
47 changed files with 582 additions and 718 deletions

View File

@ -1,430 +0,0 @@
package common
/*
Bridge.go - 统一桥接模块
将Config.goLog.goOutput.goProxy.go的桥接功能合并到一个文件中
减少文件数量提高代码组织性保持所有原有API的完全兼容性
*/
import (
"context"
"crypto/tls"
"fmt"
"io"
"log"
"net"
"sync"
"time"
"github.com/fatih/color"
"github.com/schollz/progressbar/v3"
"github.com/shadow1ng/fscan/common/config"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/common/proxy"
)
// =============================================================================
// 配置桥接 (Config.go 功能)
// =============================================================================
var version = "2.0.2"
// 向后兼容的输出配置变量
var (
Outputfile string
OutputFormat string
ProgressBar *progressbar.ProgressBar
OutputMutex sync.Mutex
)
// PocInfo POC详细信息结构
type PocInfo = config.PocInfo
func syncOutputConfig() {
cfg := config.GetGlobalConfig()
if cfg != nil && cfg.Output != nil {
Outputfile = cfg.Output.Outputfile
OutputFormat = cfg.Output.OutputFormat
}
ProgressBar = config.GetGlobalProgressBar()
}
// =============================================================================
// 日志桥接 (Log.go 功能)
// =============================================================================
// 全局日志状态和变量
var (
status = logging.NewScanStatus()
Num, End int64
StartTime = time.Now()
globalLogger *logging.Logger
loggerMutex sync.Mutex
)
// 日志级别常量
const (
LogLevelAll = string(logging.LevelAll)
LogLevelError = string(logging.LevelError)
LogLevelBase = string(logging.LevelBase)
LogLevelInfo = string(logging.LevelInfo)
LogLevelSuccess = string(logging.LevelSuccess)
LogLevelDebug = string(logging.LevelDebug)
LogLevelInfoSuccess = string(logging.LevelInfoSuccess)
LogLevelBaseInfoSuccess = string(logging.LevelBaseInfoSuccess)
)
// 类型别名
type LogEntry = logging.LogEntry
type ScanStatus = logging.ScanStatus
type ProgressDisplay = logging.ProgressDisplay
func getGlobalLogger() *logging.Logger {
loggerMutex.Lock()
defer loggerMutex.Unlock()
if globalLogger == nil {
// 获取正确的日志级别 - 动态获取确保使用最新值
level := getLogLevelFromString(LogLevel)
config := &logging.LoggerConfig{
Level: level, EnableColor: !NoColor, SlowOutput: SlowLogOutput,
ShowProgress: true, StartTime: StartTime,
LevelColors: map[logging.LogLevel]color.Attribute{
logging.LevelError: color.FgBlue, logging.LevelBase: color.FgYellow,
logging.LevelInfo: color.FgGreen, logging.LevelSuccess: color.FgRed, logging.LevelDebug: color.FgWhite,
},
}
globalLogger = logging.NewLogger(config)
if ProgressBar != nil {
globalLogger.SetProgressBar(ProgressBar)
}
globalLogger.SetOutputMutex(&OutputMutex)
status = globalLogger.GetScanStatus()
}
return globalLogger
}
// shouldLogLevel 检查是否应该记录该级别的日志
func shouldLogLevel(logger *logging.Logger, level logging.LogLevel) bool {
// 直接基于当前的LogLevel配置判断
switch LogLevel {
case "all", "ALL":
return true
case "error", "ERROR":
return level == logging.LevelError
case "base", "BASE":
return level == logging.LevelBase
case "info", "INFO":
return level == logging.LevelInfo
case "success", "SUCCESS":
return level == logging.LevelSuccess
case "debug", "DEBUG":
return level == logging.LevelDebug || level == logging.LevelError
case "info,success", "INFO_SUCCESS":
return level == logging.LevelInfo || level == logging.LevelSuccess
case "base,info,success", "BASE_INFO_SUCCESS":
return level == logging.LevelBase || level == logging.LevelInfo || level == logging.LevelSuccess
default:
// 默认使用BaseInfoSuccess级别不包含DEBUG和ERROR级别
return level == logging.LevelBase || level == logging.LevelInfo || level == logging.LevelSuccess
}
}
// getLogLevelFromString 根据字符串获取日志级别
func getLogLevelFromString(levelStr string) logging.LogLevel {
switch levelStr {
case "all", "ALL":
return logging.LevelAll
case "error", "ERROR":
return logging.LevelError
case "base", "BASE":
return logging.LevelBase
case "info", "INFO":
return logging.LevelInfo
case "success", "SUCCESS":
return logging.LevelSuccess
case "debug", "DEBUG":
return logging.LevelDebug
case "info,success":
return logging.LevelInfoSuccess
case "base,info,success", "BASE_INFO_SUCCESS":
return logging.LevelBaseInfoSuccess
default:
// 默认使用InfoSuccess级别只显示Info和Success消息
return logging.LevelInfoSuccess
}
}
// 日志相关函数
func InitLogger() {
// 重置全局logger以确保使用最新的LogLevel配置
loggerMutex.Lock()
globalLogger = nil
loggerMutex.Unlock()
log.SetOutput(io.Discard)
getGlobalLogger().Initialize()
}
func LogDebug(msg string) { logWithProgressCoordination(msg, "debug") }
func LogBase(msg string) { logWithProgressCoordination(msg, "base") }
func LogInfo(msg string) { logWithProgressCoordination(msg, "info") }
func LogSuccess(result string) { logWithProgressCoordination(result, "success") }
func LogError(errMsg string) { logWithProgressCoordination(errMsg, "error") }
func CheckErrs(err error) error { return logging.CheckErrs(err) }
// logWithProgressCoordination 协调日志输出与进度条的冲突
func logWithProgressCoordination(msg, level string) {
logger := getGlobalLogger()
// 首先检查是否应该记录这个级别的日志
var logLevel logging.LogLevel
switch level {
case "debug":
logLevel = logging.LevelDebug
case "base":
logLevel = logging.LevelBase
case "info":
logLevel = logging.LevelInfo
case "success":
logLevel = logging.LevelSuccess
case "error":
logLevel = logging.LevelError
default:
logLevel = logging.LevelDebug
}
// 如果当前日志级别不应该显示这条消息,直接返回
if !shouldLogLevel(logger, logLevel) {
return
}
// 如果进度条活跃,使用协调输出
if IsProgressActive() {
// 简单格式化消息,保持时间戳格式一致
elapsed := time.Since(StartTime)
var prefix, colorCode, resetCode string
if !NoColor {
resetCode = "\033[0m"
switch level {
case "debug":
colorCode = "\033[37m" // 白色
prefix = " "
case "base":
colorCode = "\033[33m" // 黄色
prefix = " "
case "info":
colorCode = "\033[32m" // 绿色
prefix = " [*] "
case "success":
colorCode = "\033[31m" // 红色
prefix = " [+] "
case "error":
colorCode = "\033[34m" // 蓝色
prefix = " [-] "
}
} else {
switch level {
case "info":
prefix = " [*] "
case "success":
prefix = " [+] "
case "error":
prefix = " [-] "
default:
prefix = " "
}
}
formattedMsg := fmt.Sprintf("[%.1fs]%s%s%s%s",
elapsed.Seconds(), prefix, colorCode, msg, resetCode)
LogWithProgress(formattedMsg)
} else {
// 如果进度条不活跃,使用原始日志方法
switch level {
case "debug":
logger.Debug(msg)
case "base":
logger.Base(msg)
case "info":
logger.Info(msg)
case "success":
logger.Success(msg)
case "error":
logger.Error(msg)
}
}
}
// =============================================================================
// 输出桥接 (Output.go 功能)
// =============================================================================
// 全局输出管理器
var ResultOutput *OutputManager
type OutputManager struct{ manager *output.Manager }
type ResultType = output.ResultType
const (
HOST ResultType = output.TypeHost
PORT ResultType = output.TypePort
SERVICE ResultType = output.TypeService
VULN ResultType = output.TypeVuln
)
type ScanResult = output.ScanResult
func createOutputManager(outputPath, outputFormat string) (*OutputManager, error) {
var format output.OutputFormat
switch outputFormat {
case "txt":
format = output.FormatTXT
case "json":
format = output.FormatJSON
case "csv":
format = output.FormatCSV
default:
return nil, fmt.Errorf(i18n.GetText("output_format_invalid", outputFormat))
}
config := output.DefaultManagerConfig(outputPath, format)
manager, err := output.NewManager(config)
if err != nil {
return nil, err
}
return &OutputManager{manager: manager}, nil
}
// 输出相关函数
func InitOutput() error {
LogDebug(i18n.GetText("output_init_start"))
switch OutputFormat {
case "txt", "json", "csv":
default:
return fmt.Errorf(i18n.GetText("output_format_invalid", OutputFormat))
}
if Outputfile == "" {
return fmt.Errorf(i18n.GetText("output_path_empty"))
}
manager, err := createOutputManager(Outputfile, OutputFormat)
if err != nil {
LogDebug(i18n.GetText("output_init_failed", err))
return fmt.Errorf(i18n.GetText("output_init_failed", err))
}
ResultOutput = manager
output.SetGlobalManager(manager.manager)
LogDebug(i18n.GetText("output_init_success"))
return nil
}
func (om *OutputManager) saveResult(result *ScanResult) error {
if om.manager == nil {
return fmt.Errorf(i18n.GetText("output_not_init"))
}
LogDebug(i18n.GetText("output_saving_result", result.Type, result.Target))
return om.manager.SaveResult(result)
}
func SaveResult(result *ScanResult) error {
if ResultOutput == nil {
LogDebug(i18n.GetText("output_not_init"))
return fmt.Errorf(i18n.GetText("output_not_init"))
}
return ResultOutput.saveResult(result)
}
func CloseOutput() error {
if ResultOutput == nil {
return nil
}
LogDebug(i18n.GetText("output_closing"))
err := ResultOutput.manager.Close()
if err != nil {
return fmt.Errorf(i18n.GetText("output_close_failed", err))
}
LogDebug(i18n.GetText("output_closed"))
return nil
}
// =============================================================================
// 代理桥接 (Proxy.go 功能)
// =============================================================================
// 代理相关函数
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return proxy.DialContextWithProxy(ctx, network, address)
}
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
return proxy.DialContextWithProxy(ctx, network, address)
}
func Socks5Dialer(forward *net.Dialer) (interface{}, error) {
if err := syncProxyConfig(); err != nil {
return nil, fmt.Errorf(i18n.GetText("socks5_create_failed", err))
}
manager := proxy.GetGlobalProxy()
dialer, err := manager.GetDialer()
if err != nil {
return nil, fmt.Errorf(i18n.GetText("socks5_create_failed", err))
}
return dialer, nil
}
func WrapperTlsWithContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) {
if err := syncProxyConfig(); err != nil {
LogError(i18n.GetText("proxy_config_sync_failed", err.Error()))
}
conn, err := proxy.DialTLSContextWithProxy(ctx, network, address, tlsConfig)
if err != nil {
LogError(i18n.GetText("tls_conn_failed", err.Error()))
return nil, fmt.Errorf(i18n.GetText("tls_conn_failed", err))
}
return conn, nil
}
func syncProxyConfig() error {
var proxyURL string
if Socks5Proxy != "" {
proxyURL = Socks5Proxy
} else if HttpProxy != "" {
proxyURL = HttpProxy
}
if proxy.IsProxyEnabledGlobally() {
currentAddr := proxy.GetGlobalProxyAddress()
if (proxyURL == "" && currentAddr == "") ||
(proxyURL != "" && currentAddr != "" && (Socks5Proxy == currentAddr || HttpProxy == currentAddr)) {
return nil
}
}
if err := proxy.InitGlobalProxy(proxyURL); err != nil {
return fmt.Errorf(i18n.GetText("proxy_init_failed", err))
}
if proxyURL != "" {
LogBase(i18n.GetText("proxy_enabled", proxy.GetGlobalProxyType(), proxy.GetGlobalProxyAddress()))
} else {
LogBase(i18n.GetText("proxy_disabled"))
}
return nil
}
// =============================================================================
// 初始化函数
// =============================================================================
func init() {
config.SetGlobalVersion(version)
syncOutputConfig()
}

View File

@ -42,10 +42,10 @@ var (
LocalMode bool // 本地模式 LocalMode bool // 本地模式
// 基础认证配置 // 基础认证配置
Username string // 用户名 Username string // 用户名
Password string // 密码 Password string // 密码
Userdict map[string][]string // 用户字典 Userdict map[string][]string // 用户字典
Passwords []string // 密码列表 Passwords []string // 密码列表
// 网络配置 // 网络配置
HttpProxy string // HTTP代理 HttpProxy string // HTTP代理
@ -130,4 +130,4 @@ func SetThreadNum(num int) {
// init 自动初始化 // init 自动初始化
func init() { func init() {
InitGlobalConfig() InitGlobalConfig()
} }

View File

@ -131,6 +131,9 @@ func Parse(Info *HostInfo) error {
// 显示解析结果摘要 // 显示解析结果摘要
showParseSummary(result.Config) showParseSummary(result.Config)
// 同步变量到core包
syncToCore()
return nil return nil
} }
@ -498,6 +501,9 @@ func applyLogLevel() {
newLogger.SetProgressBar(ProgressBar) newLogger.SetProgressBar(ProgressBar)
} }
newLogger.SetOutputMutex(&OutputMutex) newLogger.SetOutputMutex(&OutputMutex)
// 设置协调输出函数使用LogWithProgress
newLogger.SetCoordinatedOutput(LogWithProgress)
// 更新全局日志管理器 // 更新全局日志管理器
globalLogger = newLogger globalLogger = newLogger

View File

@ -72,6 +72,9 @@ func (pm *ProgressManager) InitProgress(total int64, description string) {
pm.isActive = true pm.isActive = true
pm.enabled = true pm.enabled = true
// 为进度条保留空间
pm.setupProgressSpace()
// 初始显示进度条 // 初始显示进度条
pm.renderProgress() pm.renderProgress()
} }
@ -130,24 +133,23 @@ func (pm *ProgressManager) FinishProgress() {
pm.isActive = false pm.isActive = false
} }
// reserveProgressSpace 为进度条保留底部空间 // setupProgressSpace 设置进度条空间
func (pm *ProgressManager) reserveProgressSpace() { func (pm *ProgressManager) setupProgressSpace() {
if pm.terminalHeight <= 0 { // 简化设计:进度条在原地更新,不需要预留额外空间
return // 只是标记进度条开始的位置
} pm.lastContentLine = 0
}
// 移动到底部保留区域上方
targetLine := pm.terminalHeight - pm.reservedLines // moveToContentArea 移动到内容输出区域
fmt.Printf("\033[%d;1H", targetLine) func (pm *ProgressManager) moveToContentArea() {
// 对于简化的实现,直接在当前位置输出内容
// 在底部创建空行 // 不需要复杂的光标移动
for i := 0; i < pm.reservedLines; i++ { }
fmt.Println()
} // moveToProgressLine 移动到进度条位置
func (pm *ProgressManager) moveToProgressLine() {
// 回到内容输出位置 // 移动到行首准备重绘进度条
fmt.Printf("\033[%d;1H", targetLine) fmt.Print("\r")
pm.lastContentLine = targetLine - 1
} }
// renderProgress 渲染进度条(使用锁避免输出冲突) // renderProgress 渲染进度条(使用锁避免输出冲突)
@ -155,9 +157,6 @@ func (pm *ProgressManager) renderProgress() {
pm.outputMutex.Lock() pm.outputMutex.Lock()
defer pm.outputMutex.Unlock() defer pm.outputMutex.Unlock()
// 清除当前行并回到行首
fmt.Print("\033[2K\r")
pm.renderProgressUnsafe() pm.renderProgressUnsafe()
} }
@ -308,13 +307,13 @@ func LogWithProgress(message string) {
pm.outputMutex.Lock() pm.outputMutex.Lock()
defer pm.outputMutex.Unlock() defer pm.outputMutex.Unlock()
// 清除当前进度条 // 清除当前行(清除进度条
fmt.Print("\033[2K\r") fmt.Print("\033[2K\r")
// 输出消息 // 输出日志消息
fmt.Println(message) fmt.Println(message)
// 重进度条 // 重绘进度条
pm.renderProgressUnsafe() pm.renderProgressUnsafe()
} }
@ -324,6 +323,9 @@ func (pm *ProgressManager) renderProgressUnsafe() {
return return
} }
// 移动到行首并清除当前行
fmt.Print("\033[2K\r")
// 生成进度条内容 // 生成进度条内容
progressBar := pm.generateProgressBar() progressBar := pm.generateProgressBar()

View File

@ -1,52 +0,0 @@
package common
import "github.com/shadow1ng/fscan/common/core"
/*
Types.go - 类型定义向后兼容层
此文件保持向后兼容实际类型定义和插件系统已迁移到Core/Plugin.go
*/
// =============================================================================
// 向后兼容的类型别名
// =============================================================================
// HostInfo 主机信息结构 - 引用Core包中的定义
type HostInfo = core.HostInfo
// ScanPlugin 扫描插件结构 - 引用Core包中的定义
type ScanPlugin = core.ScanPlugin
// =============================================================================
// 向后兼容的插件类型常量
// =============================================================================
const (
PluginTypeService = core.PluginTypeService // 服务类型插件
PluginTypeWeb = core.PluginTypeWeb // Web类型插件
PluginTypeLocal = core.PluginTypeLocal // 本地类型插件
PluginTypeBrute = core.PluginTypeBrute // 暴力破解插件
PluginTypePoc = core.PluginTypePoc // POC验证插件
PluginTypeScan = core.PluginTypeScan // 扫描探测插件
)
// =============================================================================
// 向后兼容的插件管理函数
// =============================================================================
// RegisterPlugin 注册插件到全局管理器 - 保持向后兼容
func RegisterPlugin(name string, plugin ScanPlugin) {
if err := core.RegisterPlugin(name, plugin); err != nil {
// 注册失败时记录错误,但不中断程序
LogError("Failed to register plugin " + name + ": " + err.Error())
}
}
// GetGlobalPluginManager 获取全局插件管理器
func GetGlobalPluginManager() *core.PluginManager {
return core.GetGlobalPluginManager()
}
// 向后兼容的全局变量 - 引用Core包中的定义
var PluginManager = core.LegacyPluginManager

View File

@ -1,97 +0,0 @@
package common
import "github.com/shadow1ng/fscan/common/core"
/*
Variables.go - 全局变量向后兼容层
此文件保持向后兼容实际变量管理已迁移到Core/Manager.go
建议新代码使用Core.GetGlobalConfigManager()获取配置管理器
*/
// =============================================================================
// 向后兼容的全局变量 - 直接引用Core包中的定义
// =============================================================================
// 核心扫描配置
var (
ScanMode = core.ScanMode // 扫描模式
ThreadNum = core.ThreadNum // 线程数
Timeout = core.Timeout // 超时时间
DisablePing = core.DisablePing // 禁用ping
LocalMode = core.LocalMode // 本地模式
)
// 基础认证配置
var (
Username = core.Username // 用户名
Password = core.Password // 密码
Userdict = core.Userdict // 用户字典
Passwords = core.Passwords // 密码列表
)
// 网络配置
var (
HttpProxy = core.HttpProxy // HTTP代理
Socks5Proxy = core.Socks5Proxy // SOCKS5代理
)
// 显示控制
var (
NoColor = core.NoColor // 禁用颜色
Language = core.Language // 语言
LogLevel = core.LogLevel // 日志级别
)
// 端口映射
var (
PortMap = core.PortMap // 端口映射
DefaultMap = core.DefaultMap // 默认映射
)
// 输出配置 (已在Bridge.go中定义此处不重复声明)
// var Outputfile, OutputFormat
// 其他全局状态 (已在Flag.go中定义此处不重复声明)
// var SlowLogOutput
// =============================================================================
// 向后兼容的访问函数
// =============================================================================
// GetGlobalConfigManager 获取全局配置管理器(已废弃)
// 注意ConfigManager 已被移除,此函数不再可用
// func GetGlobalConfigManager() *core.ConfigManager {
// return core.GetGlobalConfigManager()
// }
// InitGlobalConfig 初始化全局配置
func InitGlobalConfig() {
core.InitGlobalConfig()
}
// GetScanMode 获取扫描模式
func GetScanMode() string {
return core.GetScanMode()
}
// SetScanMode 设置扫描模式
func SetScanMode(mode string) {
core.SetScanMode(mode)
}
// GetThreadNum 获取线程数
func GetThreadNum() int {
return core.GetThreadNum()
}
// SetThreadNum 设置线程数
func SetThreadNum(num int) {
core.SetThreadNum(num)
}
// =============================================================================
// 向后兼容的日志级别常量 (已在Bridge.go中定义此处不重复声明)
// =============================================================================
// const LogLevelAll, LogLevelError, etc. - 已在Bridge.go中定义

202
Common/common.go Normal file
View File

@ -0,0 +1,202 @@
package common
/*
common.go - 简化的统一入口
移除所有向后兼容层提供清晰的模块化接口
直接导出各子模块的核心功能避免代码债务
*/
import (
"context"
"crypto/tls"
"fmt"
"net"
"sync"
"time"
"github.com/shadow1ng/fscan/common/core"
"github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/output"
)
// =============================================================================
// 核心类型导出 - 直接从core模块导出
// =============================================================================
type HostInfo = core.HostInfo
type ScanPlugin = core.ScanPlugin
// 插件类型常量
const (
PluginTypeService = core.PluginTypeService
PluginTypeWeb = core.PluginTypeWeb
PluginTypeLocal = core.PluginTypeLocal
PluginTypeBrute = core.PluginTypeBrute
PluginTypePoc = core.PluginTypePoc
PluginTypeScan = core.PluginTypeScan
)
// 全局插件管理器
var PluginManager = core.LegacyPluginManager
// =============================================================================
// 核心功能导出 - 直接调用对应模块
// =============================================================================
// 插件系统
func RegisterPlugin(name string, plugin ScanPlugin) {
if err := core.RegisterPlugin(name, plugin); err != nil {
LogError("Failed to register plugin " + name + ": " + err.Error())
}
}
func GetGlobalPluginManager() *core.PluginManager {
return core.GetGlobalPluginManager()
}
// =============================================================================
// 日志系统简化接口
// =============================================================================
var globalLogger *logging.Logger
var loggerMutex sync.Mutex
func getGlobalLogger() *logging.Logger {
loggerMutex.Lock()
defer loggerMutex.Unlock()
if globalLogger == nil {
level := getLogLevelFromString(LogLevel)
config := &logging.LoggerConfig{
Level: level,
EnableColor: !NoColor,
SlowOutput: SlowLogOutput,
ShowProgress: true,
StartTime: StartTime,
}
globalLogger = logging.NewLogger(config)
if ProgressBar != nil {
globalLogger.SetProgressBar(ProgressBar)
}
globalLogger.SetOutputMutex(&OutputMutex)
// 设置协调输出函数使用LogWithProgress
globalLogger.SetCoordinatedOutput(LogWithProgress)
}
return globalLogger
}
func getLogLevelFromString(levelStr string) logging.LogLevel {
switch levelStr {
case "all", "ALL":
return logging.LevelAll
case "error", "ERROR":
return logging.LevelError
case "base", "BASE":
return logging.LevelBase
case "info", "INFO":
return logging.LevelInfo
case "success", "SUCCESS":
return logging.LevelSuccess
case "debug", "DEBUG":
return logging.LevelDebug
case "info,success":
return logging.LevelInfoSuccess
case "base,info,success", "BASE_INFO_SUCCESS":
return logging.LevelBaseInfoSuccess
default:
return logging.LevelInfoSuccess
}
}
// 日志函数
func InitLogger() {
loggerMutex.Lock()
globalLogger = nil
loggerMutex.Unlock()
getGlobalLogger().Initialize()
}
func LogDebug(msg string) { getGlobalLogger().Debug(msg) }
func LogBase(msg string) { getGlobalLogger().Base(msg) }
func LogInfo(msg string) { getGlobalLogger().Info(msg) }
func LogSuccess(result string) { getGlobalLogger().Success(result) }
func LogError(errMsg string) { getGlobalLogger().Error(errMsg) }
// =============================================================================
// 输出系统简化接口
// =============================================================================
var ResultOutput *output.Manager
func InitOutput() error {
if Outputfile == "" {
return fmt.Errorf("output file not specified")
}
var format output.OutputFormat
switch OutputFormat {
case "txt":
format = output.FormatTXT
case "json":
format = output.FormatJSON
case "csv":
format = output.FormatCSV
default:
return fmt.Errorf("invalid output format: %s", OutputFormat)
}
config := output.DefaultManagerConfig(Outputfile, format)
manager, err := output.NewManager(config)
if err != nil {
return err
}
ResultOutput = manager
output.SetGlobalManager(manager)
return nil
}
func CloseOutput() error {
if ResultOutput == nil {
return nil
}
return ResultOutput.Close()
}
func SaveResult(result *output.ScanResult) error {
if ResultOutput == nil {
return fmt.Errorf("output not initialized")
}
return ResultOutput.SaveResult(result)
}
// =============================================================================
// 网络连接辅助函数
// =============================================================================
// WrapperTcpWithTimeout TCP连接包装器带超时
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
return net.DialTimeout(network, address, timeout)
}
// WrapperTcpWithContext TCP连接包装器带上下文
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, network, address)
}
// WrapperTlsWithContext TLS连接包装器带上下文
func WrapperTlsWithContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error) {
d := &tls.Dialer{Config: config}
return d.DialContext(ctx, network, address)
}
// =============================================================================
// 错误处理辅助函数
// =============================================================================
// CheckErrs 检查单个错误 - 简化版本
func CheckErrs(err error) error {
return err
}

169
Common/globals.go Normal file
View File

@ -0,0 +1,169 @@
package common
import (
"sync"
"time"
"github.com/schollz/progressbar/v3"
"github.com/shadow1ng/fscan/common/core"
"github.com/shadow1ng/fscan/common/logging"
)
/*
globals.go - 全局变量定义
直接导出core模块的变量避免兼容层重定向
这些变量被Flag.go和其他模块直接使用
*/
// =============================================================================
// 版本信息
// =============================================================================
var version = "2.0.2"
// =============================================================================
// 核心扫描配置 - 直接使用core包变量
// =============================================================================
var (
ScanMode string // 扫描模式
ThreadNum int // 线程数
Timeout int64 // 超时时间
DisablePing bool // 禁用ping
LocalMode bool // 本地模式
)
// =============================================================================
// 基础认证配置 - 直接定义
// =============================================================================
var (
Username string // 用户名
Password string // 密码
Userdict map[string][]string // 用户字典
Passwords []string // 密码列表
)
// =============================================================================
// 网络配置 - 直接定义
// =============================================================================
var (
HttpProxy string // HTTP代理
Socks5Proxy string // SOCKS5代理
)
// =============================================================================
// 显示控制 - 直接定义
// =============================================================================
var (
NoColor bool // 禁用颜色
Language string // 语言
LogLevel string // 日志级别
)
// =============================================================================
// 端口映射 - 直接定义
// =============================================================================
var (
PortMap map[int][]string // 端口映射
DefaultMap []string // 默认映射
)
// =============================================================================
// 其他全局变量 - 直接定义,避免多层引用
// =============================================================================
var (
// 输出配置
Outputfile string
OutputFormat string
ProgressBar *progressbar.ProgressBar
OutputMutex sync.Mutex
// 日志状态
status = logging.NewScanStatus()
Num, End int64
StartTime = time.Now()
// 其他变量按需添加
)
// =============================================================================
// 日志级别常量
// =============================================================================
const (
LogLevelAll = string(logging.LevelAll)
LogLevelError = string(logging.LevelError)
LogLevelBase = string(logging.LevelBase)
LogLevelInfo = string(logging.LevelInfo)
LogLevelSuccess = string(logging.LevelSuccess)
LogLevelDebug = string(logging.LevelDebug)
LogLevelInfoSuccess = string(logging.LevelInfoSuccess)
LogLevelBaseInfoSuccess = string(logging.LevelBaseInfoSuccess)
)
// =============================================================================
// 初始化和同步函数
// =============================================================================
func init() {
// 初始化核心配置
core.InitGlobalConfig()
// 同步变量
syncWithCore()
}
// syncWithCore 同步common包变量与core包变量
func syncWithCore() {
// 读取core包的默认值
ScanMode = core.ScanMode
ThreadNum = core.ThreadNum
Timeout = core.Timeout
DisablePing = core.DisablePing
LocalMode = core.LocalMode
Username = core.Username
Password = core.Password
Userdict = core.Userdict
Passwords = core.Passwords
HttpProxy = core.HttpProxy
Socks5Proxy = core.Socks5Proxy
NoColor = core.NoColor
Language = core.Language
LogLevel = core.LogLevel
PortMap = core.PortMap
DefaultMap = core.DefaultMap
}
// syncToCore 将common包变量同步回core包
func syncToCore() {
core.ScanMode = ScanMode
core.ThreadNum = ThreadNum
core.Timeout = Timeout
core.DisablePing = DisablePing
core.LocalMode = LocalMode
core.Username = Username
core.Password = Password
core.Userdict = Userdict
core.Passwords = Passwords
core.HttpProxy = HttpProxy
core.Socks5Proxy = Socks5Proxy
core.NoColor = NoColor
core.Language = Language
core.LogLevel = LogLevel
core.PortMap = PortMap
core.DefaultMap = DefaultMap
}

View File

@ -79,6 +79,18 @@ func (l *Logger) SetOutputMutex(mutex *sync.Mutex) {
l.outputMutex = mutex l.outputMutex = mutex
} }
// SetCoordinatedOutput 设置协调输出函数(用于进度条协调)
func (l *Logger) SetCoordinatedOutput(fn func(string)) {
l.mu.RLock()
defer l.mu.RUnlock()
for _, handler := range l.handlers {
if consoleHandler, ok := handler.(*ConsoleHandler); ok {
consoleHandler.SetCoordinatedOutput(fn)
}
}
}
// Log 记录日志 // Log 记录日志
func (l *Logger) Log(level LogLevel, content string, metadata ...map[string]interface{}) { func (l *Logger) Log(level LogLevel, content string, metadata ...map[string]interface{}) {
if !l.shouldLog(level) { if !l.shouldLog(level) {
@ -213,10 +225,11 @@ func (l *Logger) Initialize() {
// ConsoleHandler 控制台日志处理器 // ConsoleHandler 控制台日志处理器
type ConsoleHandler struct { type ConsoleHandler struct {
config *LoggerConfig config *LoggerConfig
formatter LogFormatter formatter LogFormatter
enabled bool enabled bool
mu sync.RWMutex coordinatedOutput func(string) // 协调输出函数
mu sync.RWMutex
} }
// NewConsoleHandler 创建控制台处理器 // NewConsoleHandler 创建控制台处理器
@ -243,15 +256,29 @@ func (h *ConsoleHandler) Handle(entry *LogEntry) {
// 使用自己的格式化器格式化消息 // 使用自己的格式化器格式化消息
logMsg := h.formatter.Format(entry) logMsg := h.formatter.Format(entry)
// 根据颜色设置输出 // 使用协调输出函数,如果设置了的话
if h.config.EnableColor { if h.coordinatedOutput != nil {
if colorAttr, ok := h.config.LevelColors[entry.Level]; ok { if h.config.EnableColor {
color.New(colorAttr).Println(logMsg) if colorAttr, ok := h.config.LevelColors[entry.Level]; ok {
coloredMsg := color.New(colorAttr).Sprint(logMsg)
h.coordinatedOutput(coloredMsg)
} else {
h.coordinatedOutput(logMsg)
}
} else {
h.coordinatedOutput(logMsg)
}
} else {
// 回到原来的直接输出方式
if h.config.EnableColor {
if colorAttr, ok := h.config.LevelColors[entry.Level]; ok {
color.New(colorAttr).Println(logMsg)
} else {
fmt.Println(logMsg)
}
} else { } else {
fmt.Println(logMsg) fmt.Println(logMsg)
} }
} else {
fmt.Println(logMsg)
} }
// 根据慢速输出设置决定是否添加延迟 // 根据慢速输出设置决定是否添加延迟
@ -267,6 +294,13 @@ func (h *ConsoleHandler) SetEnabled(enabled bool) {
h.enabled = enabled h.enabled = enabled
} }
// SetCoordinatedOutput 设置协调输出函数
func (h *ConsoleHandler) SetCoordinatedOutput(fn func(string)) {
h.mu.Lock()
defer h.mu.Unlock()
h.coordinatedOutput = fn
}
// IsEnabled 检查处理器是否启用 // IsEnabled 检查处理器是否启用
func (h *ConsoleHandler) IsEnabled() bool { func (h *ConsoleHandler) IsEnabled() bool {
h.mu.RLock() h.mu.RLock()

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/output"
"golang.org/x/net/icmp" "golang.org/x/net/icmp"
"net" "net"
"os/exec" "os/exec"
@ -68,9 +69,9 @@ func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool) {
protocol = "PING" protocol = "PING"
} }
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.HOST, Type: output.TypeHost,
Target: ip, Target: ip,
Status: "alive", Status: "alive",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/common/parsers" "github.com/shadow1ng/fscan/common/parsers"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
@ -85,8 +86,8 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
atomic.AddInt64(&count, 1) atomic.AddInt64(&count, 1)
aliveMap.Store(addr, struct{}{}) aliveMap.Store(addr, struct{}{})
common.LogInfo("端口开放 " + addr) common.LogInfo("端口开放 " + addr)
common.SaveResult(&common.ScanResult{ common.SaveResult(&output.ScanResult{
Time: time.Now(), Type: common.PORT, Target: host, Time: time.Now(), Type: output.TypePort, Target: host,
Status: "open", Details: map[string]interface{}{"port": port}, Status: "open", Details: map[string]interface{}{"port": port},
}) })
@ -116,8 +117,8 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
} }
// 保存服务结果 // 保存服务结果
common.SaveResult(&common.ScanResult{ common.SaveResult(&output.ScanResult{
Time: time.Now(), Type: common.SERVICE, Target: host, Time: time.Now(), Type: output.TypeService, Target: host,
Status: "identified", Details: details, Status: "identified", Details: details,
}) })

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// ActiveMQCredential 表示一个ActiveMQ凭据 // ActiveMQCredential 表示一个ActiveMQ凭据
@ -300,9 +301,9 @@ func saveActiveMQSuccess(info *common.HostInfo, target string, credential Active
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -11,6 +11,7 @@ import (
"github.com/gocql/gocql" "github.com/gocql/gocql"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// CassandraProxyDialer 实现gocql.Dialer接口支持代理连接 // CassandraProxyDialer 实现gocql.Dialer接口支持代理连接
@ -353,9 +354,9 @@ func saveCassandraSuccess(info *common.HostInfo, target string, credential Cassa
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -3,6 +3,7 @@
package Plugins package Plugins
import "github.com/shadow1ng/fscan/common" import "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
func DCInfoScan(info *common.HostInfo) (err error) { func DCInfoScan(info *common.HostInfo) (err error) {
return nil return nil

View File

@ -6,6 +6,7 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
@ -295,9 +296,9 @@ func saveElasticResult(info *common.HostInfo, target string, credential ElasticC
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/jlaffaye/ftp" "github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -326,9 +327,9 @@ func saveFtpResult(info *common.HostInfo, target string, result *FtpScanResult)
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -5,6 +5,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"net" "net"
"regexp" "regexp"
"strconv" "strconv"
@ -192,9 +193,9 @@ func read(text []byte, host string) error {
} }
// 保存扫描结果 // 保存扫描结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.SERVICE, Type: output.TypeService,
Target: host, Target: host,
Status: "identified", Status: "identified",
Details: details, Details: details,

View File

@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// IMAPCredential 表示一个IMAP凭据 // IMAPCredential 表示一个IMAP凭据
@ -309,9 +310,9 @@ func saveIMAPResult(info *common.HostInfo, target string, credential IMAPCredent
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/IBM/sarama" "github.com/IBM/sarama"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -45,9 +46,9 @@ func KafkaScan(info *common.HostInfo) error {
common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target)) common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target))
// 保存无认证访问结果 // 保存无认证访问结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -79,9 +80,9 @@ func KafkaScan(info *common.HostInfo) error {
result := concurrentKafkaScan(ctx, info, credentials, common.Timeout, common.MaxRetries) result := concurrentKafkaScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 保存爆破成功结果 // 保存爆破成功结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -9,6 +9,7 @@ import (
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// LDAPCredential 表示一个LDAP凭据 // LDAPCredential 表示一个LDAP凭据
@ -296,9 +297,9 @@ func saveLDAPResult(info *common.HostInfo, target string, result *LDAPScanResult
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -5,6 +5,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"os" "os"
"strings" "strings"
"time" "time"
@ -218,9 +219,9 @@ func MS17010Scan(info *common.HostInfo) error {
} }
// 保存 MS17-010 漏洞结果 // 保存 MS17-010 漏洞结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: ip, Target: ip,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
@ -248,9 +249,9 @@ func MS17010Scan(info *common.HostInfo) error {
common.LogSuccess(fmt.Sprintf("发现后门 %s DOUBLEPULSAR", ip)) common.LogSuccess(fmt.Sprintf("发现后门 %s DOUBLEPULSAR", ip))
// 保存 DOUBLEPULSAR 后门结果 // 保存 DOUBLEPULSAR 后门结果
backdoorResult := &common.ScanResult{ backdoorResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: ip, Target: ip,
Status: "backdoor", Status: "backdoor",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -270,9 +271,9 @@ func MS17010Scan(info *common.HostInfo) error {
common.LogBase(fmt.Sprintf("系统信息 %s [%s]", ip, os)) common.LogBase(fmt.Sprintf("系统信息 %s [%s]", ip, os))
// 保存系统信息 // 保存系统信息
sysResult := &common.ScanResult{ sysResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.SERVICE, Type: output.TypeService,
Target: ip, Target: ip,
Status: "identified", Status: "identified",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -11,6 +11,7 @@ import (
mssql "github.com/denisenkom/go-mssqldb" mssql "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// MSSQLProxyDialer 自定义dialer结构体 // MSSQLProxyDialer 自定义dialer结构体
@ -315,9 +316,9 @@ func saveMssqlResult(info *common.HostInfo, target string, credential MssqlCrede
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"strings" "strings"
"time" "time"
) )
@ -29,9 +30,9 @@ func MemcachedScan(info *common.HostInfo) error {
if result.Success { if result.Success {
// 保存成功结果 // 保存成功结果
scanResult := &common.ScanResult{ scanResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -3,6 +3,7 @@
package Plugins package Plugins
import "github.com/shadow1ng/fscan/common" import "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
func MiniDump(info *common.HostInfo) (err error) { func MiniDump(info *common.HostInfo) (err error) {
return nil return nil

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// ModbusScanResult 表示 Modbus 扫描结果 // ModbusScanResult 表示 Modbus 扫描结果
@ -251,9 +252,9 @@ func parseModbusResponse(response []byte) string {
// saveModbusResult 保存Modbus扫描结果 // saveModbusResult 保存Modbus扫描结果
func saveModbusResult(info *common.HostInfo, target string, result *ModbusScanResult) { func saveModbusResult(info *common.HostInfo, target string, result *ModbusScanResult) {
// 保存扫描结果 // 保存扫描结果
scanResult := &common.ScanResult{ scanResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// MongodbScan 执行MongoDB未授权扫描 // MongodbScan 执行MongoDB未授权扫描
@ -53,9 +54,9 @@ func MongodbScan(info *common.HostInfo) error {
common.LogSuccess(fmt.Sprintf("MongoDB %v 未授权访问", target)) common.LogSuccess(fmt.Sprintf("MongoDB %v 未授权访问", target))
// 保存未授权访问结果 // 保存未授权访问结果
scanResult := &common.ScanResult{ scanResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -11,6 +11,7 @@ import (
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// MySQLProxyDialer 自定义dialer结构体 // MySQLProxyDialer 自定义dialer结构体
@ -327,9 +328,9 @@ func saveMySQLResult(info *common.HostInfo, target string, credential MySQLCrede
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -9,6 +9,7 @@ import (
"github.com/neo4j/neo4j-go-driver/v4/neo4j" "github.com/neo4j/neo4j-go-driver/v4/neo4j"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// Neo4jCredential 表示一个Neo4j凭据 // Neo4jCredential 表示一个Neo4j凭据
@ -349,9 +350,9 @@ func saveNeo4jResult(info *common.HostInfo, target string, result *Neo4jScanResu
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -52,15 +52,7 @@ func NetBIOS(info *common.HostInfo) error {
details["os_version"] = netbios.OsVersion details["os_version"] = netbios.OsVersion
} }
scanResult := &common.ScanResult{ // NetBIOS信息已通过上面的LogSuccess记录不需要额外保存结果
Time: time.Now(),
Type: common.SERVICE,
Target: info.Host,
Status: "identified",
Details: details,
}
common.SaveResult(scanResult)
return nil return nil
} }
return errNetBIOS return errNetBIOS

View File

@ -5,6 +5,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
_ "github.com/sijms/go-ora/v2" _ "github.com/sijms/go-ora/v2"
"strings" "strings"
"sync" "sync"
@ -417,9 +418,9 @@ func saveOracleResult(info *common.HostInfo, target string, credential OracleCre
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// POP3Credential 表示一个POP3凭据 // POP3Credential 表示一个POP3凭据
@ -395,9 +396,9 @@ func savePOP3Result(info *common.HostInfo, target string, result *POP3ScanResult
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -12,6 +12,7 @@ import (
"github.com/lib/pq" "github.com/lib/pq"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// PostgresProxyDialer 自定义dialer结构体 // PostgresProxyDialer 自定义dialer结构体
@ -302,9 +303,9 @@ func savePostgresResult(info *common.HostInfo, target string, credential Postgre
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/tomatome/grdp/core" "github.com/tomatome/grdp/core"
"github.com/tomatome/grdp/glog" "github.com/tomatome/grdp/glog"
"github.com/tomatome/grdp/protocol/nla" "github.com/tomatome/grdp/protocol/nla"
@ -255,9 +256,9 @@ func saveRdpResult(info *common.HostInfo, target string, port int, credential RD
details["domain"] = credential.Domain details["domain"] = credential.Domain
} }
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
amqp "github.com/rabbitmq/amqp091-go" amqp "github.com/rabbitmq/amqp091-go"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"net" "net"
"strings" "strings"
"sync" "sync"
@ -291,9 +292,9 @@ func saveRabbitMQResult(info *common.HostInfo, target string, credential RabbitM
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -5,6 +5,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"io" "io"
"net" "net"
"os" "os"
@ -62,9 +63,9 @@ func RedisScan(info *common.HostInfo) error {
common.LogSuccess(fmt.Sprintf("Redis无密码连接成功: %s", target)) common.LogSuccess(fmt.Sprintf("Redis无密码连接成功: %s", target))
// 保存未授权访问结果 // 保存未授权访问结果
scanResult := &common.ScanResult{ scanResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -107,9 +108,9 @@ func RedisScan(info *common.HostInfo) error {
common.LogSuccess(fmt.Sprintf("Redis认证成功 %s [%s]", target, result.Credential.Password)) common.LogSuccess(fmt.Sprintf("Redis认证成功 %s [%s]", target, result.Credential.Password))
// 保存弱密码结果 // 保存弱密码结果
scanResult := &common.ScanResult{ scanResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// RsyncCredential 表示一个Rsync凭据 // RsyncCredential 表示一个Rsync凭据
@ -465,9 +466,9 @@ func saveRsyncResult(info *common.HostInfo, target string, result *RsyncScanResu
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/stacktitan/smb/smb" "github.com/stacktitan/smb/smb"
"strings" "strings"
"sync" "sync"
@ -242,9 +243,9 @@ func saveSmbResult(info *common.HostInfo, target string, credential SmbCredentia
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/hirochachacha/go-smb2" "github.com/hirochachacha/go-smb2"
) )
@ -401,9 +402,9 @@ func logSuccessfulAuth(info *common.HostInfo, user, pass string, hash []byte) {
} }
// 保存认证成功结果 // 保存认证成功结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "success", Status: "success",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -456,9 +457,9 @@ func logShareInfo(info *common.HostInfo, user string, pass string, hash []byte,
} }
// 保存共享信息结果 // 保存共享信息结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "shares-found", Status: "shares-found",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// SmtpCredential 表示一个SMTP凭据 // SmtpCredential 表示一个SMTP凭据
@ -314,9 +315,9 @@ func saveSmtpResult(info *common.HostInfo, target string, result *SmtpScanResult
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/gosnmp/gosnmp" "github.com/gosnmp/gosnmp"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -66,9 +67,9 @@ func SNMPScan(info *common.HostInfo) (tmperr error) {
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"io/ioutil" "io/ioutil"
"net" "net"
@ -348,9 +349,9 @@ func logAndSaveSuccess(info *common.HostInfo, target string, result *SshScanResu
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// TelnetCredential 表示一个Telnet凭据 // TelnetCredential 表示一个Telnet凭据
@ -313,9 +314,9 @@ func saveTelnetResult(info *common.HostInfo, target string, result *TelnetScanRe
common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -8,6 +8,7 @@ import (
"github.com/mitchellh/go-vnc" "github.com/mitchellh/go-vnc"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// VncCredential 表示VNC凭据 // VncCredential 表示VNC凭据
@ -257,9 +258,9 @@ func saveVncResult(info *common.HostInfo, target string, credential VncCredentia
common.LogSuccess(successLog) common.LogSuccess(successLog)
// 保存结果 // 保存结果
vulnResult := &common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -15,6 +15,7 @@ import (
"unicode/utf8" "unicode/utf8"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/webscan" "github.com/shadow1ng/fscan/webscan"
"github.com/shadow1ng/fscan/webscan/lib" "github.com/shadow1ng/fscan/webscan/lib"
"golang.org/x/text/encoding/simplifiedchinese" "golang.org/x/text/encoding/simplifiedchinese"
@ -379,9 +380,9 @@ func saveWebResult(info *common.HostInfo, resp *WebResponse) {
} }
// 保存扫描结果 // 保存扫描结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.SERVICE, Type: output.TypeService,
Target: info.Host, Target: info.Host,
Status: "identified", Status: "identified",
Details: map[string]interface{}{ Details: map[string]interface{}{

View File

@ -14,6 +14,7 @@ import (
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/config"
"github.com/shadow1ng/fscan/webscan/lib" "github.com/shadow1ng/fscan/webscan/lib"
) )
@ -72,13 +73,13 @@ func WebScan(info *common.HostInfo) {
// 根据扫描策略执行POC // 根据扫描策略执行POC
if common.Pocinfo.PocName == "" && len(info.Infostr) == 0 { if common.Pocinfo.PocName == "" && len(info.Infostr) == 0 {
// 执行所有POC // 执行所有POC
executePOCs(ctx, common.PocInfo{Target: target}) executePOCs(ctx, config.PocInfo{Target: target})
} else if len(info.Infostr) > 0 { } else if len(info.Infostr) > 0 {
// 基于指纹信息执行POC // 基于指纹信息执行POC
scanByFingerprints(ctx, target, info.Infostr) scanByFingerprints(ctx, target, info.Infostr)
} else if common.Pocinfo.PocName != "" { } else if common.Pocinfo.PocName != "" {
// 基于指定POC名称执行 // 基于指定POC名称执行
executePOCs(ctx, common.PocInfo{Target: target, PocName: common.Pocinfo.PocName}) executePOCs(ctx, config.PocInfo{Target: target, PocName: common.Pocinfo.PocName})
} }
} }
@ -117,12 +118,12 @@ func scanByFingerprints(ctx context.Context, target string, fingerprints []strin
continue continue
} }
executePOCs(ctx, common.PocInfo{Target: target, PocName: pocName}) executePOCs(ctx, config.PocInfo{Target: target, PocName: pocName})
} }
} }
// executePOCs 执行POC检测 // executePOCs 执行POC检测
func executePOCs(ctx context.Context, pocInfo common.PocInfo) { func executePOCs(ctx context.Context, pocInfo config.PocInfo) {
// 验证目标 // 验证目标
if pocInfo.Target == "" { if pocInfo.Target == "" {
common.LogError(ErrEmptyTarget.Error()) common.LogError(ErrEmptyTarget.Error())

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/webscan/info" "github.com/shadow1ng/fscan/webscan/info"
"math/rand" "math/rand"
"net/http" "net/http"
@ -90,9 +91,9 @@ func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int) {
} }
// 创建并保存扫描结果 // 创建并保存扫描结果
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: task.Req.URL.String(), Target: task.Req.URL.String(),
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
@ -495,9 +496,9 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{},
// 保存漏洞结果(除非明确指示跳过) // 保存漏洞结果(除非明确指示跳过)
if !skipSave { if !skipSave {
result := &common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: common.VULN, Type: output.TypeVuln,
Target: targetURL, Target: targetURL,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,

View File

@ -7,7 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"golang.org/x/net/proxy" "github.com/shadow1ng/fscan/common/proxy"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"net" "net"
"net/http" "net/http"
@ -67,15 +67,17 @@ func InitHttpClient(ThreadsNum int, DownProxy string, Timeout time.Duration) err
// 配置Socks5代理 // 配置Socks5代理
if common.Socks5Proxy != "" { if common.Socks5Proxy != "" {
dialSocksProxy, err := common.Socks5Dialer(dialer) proxyConfig := &proxy.ProxyConfig{
Type: proxy.ProxyTypeSOCKS5,
Address: common.Socks5Proxy,
Timeout: time.Second * 10,
}
proxyManager := proxy.NewProxyManager(proxyConfig)
proxyDialer, err := proxyManager.GetDialer()
if err != nil { if err != nil {
return err return err
} }
if contextDialer, ok := dialSocksProxy.(proxy.ContextDialer); ok { tr.DialContext = proxyDialer.DialContext
tr.DialContext = contextDialer.DialContext
} else {
return errors.New("无法转换为DialContext类型")
}
} else if DownProxy != "" { } else if DownProxy != "" {
// 处理其他代理配置 // 处理其他代理配置
if DownProxy == "1" { if DownProxy == "1" {