6 Commits
main ... v2.0.2

Author SHA1 Message Date
DullJZ
a66de1bff0 feat: tcp端口扫描支持socks5 (#527)
* feat: tcp端口扫描支持socks5

* feat: PG插件支持socks5

* feat: 完成大部分插件的socks5支持
2025-08-05 00:37:24 +08:00
ZacharyZcR
9b38dc0006 feat: 修复密码解析逻辑保留空密码
- 移除密码解析时对空密码的过滤逻辑
- 保留用户在命令行或文件中指定的空密码
- 确保空口令爆破功能正常工作
- 更新.gitignore排除开发工具目录
2025-08-05 00:36:38 +08:00
ZacharyZcR
0f491bc9d0 perf: 清理无用函数 2025-07-21 02:36:48 +08:00
ZacharyZcR
a518e80185 perf: 清理无用函数 2025-07-21 02:25:56 +08:00
ZacharyZcR
1d6f411677 perf: 清理无用函数 2025-07-21 02:25:03 +08:00
ZacharyZcR
33f2b36186 perf: 清理无用函数 2025-07-21 02:24:28 +08:00
29 changed files with 436 additions and 560 deletions

View File

@@ -1,90 +0,0 @@
name: 🐛 Bug 报告
description: 报告扫描异常、崩溃或错误行为
title: "[Bug] "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
感谢您提交 Bug 报告!请尽可能详细地填写以下信息,这将帮助我们更快定位和修复问题。
- type: dropdown
id: module
attributes:
label: 问题模块
description: 问题出现在哪个功能模块?
options:
- 端口扫描 (Port Scan)
- 主机存活检测 (Host Discovery)
- 服务识别 (Service Detection)
- 弱口令爆破 (Brute Force)
- POC/漏洞扫描 (POC Scan)
- Web指纹识别 (Web Fingerprint)
- 输出/日志 (Output/Logging)
- 命令行参数 (CLI Arguments)
- 其他 (Other)
validations:
required: true
- type: dropdown
id: severity
attributes:
label: 严重程度
options:
- 崩溃/无法使用 (Crash)
- 功能异常 (Malfunction)
- 结果不准确 (Inaccurate)
- 性能问题 (Performance)
- 其他 (Other)
validations:
required: true
- type: textarea
id: description
attributes:
label: 问题描述
description: 清晰描述遇到的问题
placeholder: |
发生了什么?
预期的行为是什么?
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: 复现步骤
description: 提供可以复现问题的命令或步骤
placeholder: |
1. 执行命令: fscan -h xxx -p xxx
2. 观察到...
render: shell
validations:
required: true
- type: textarea
id: output
attributes:
label: 错误输出
description: 粘贴相关的错误信息或日志(请脱敏敏感信息)
render: shell
- type: textarea
id: environment
attributes:
label: 环境信息
description: 请提供运行环境信息
value: |
- fscan 版本: [如 1.8.4]
- 操作系统: [如 Windows 11 / Ubuntu 22.04 / macOS 14]
- 架构: [如 amd64 / arm64]
- Go 版本 (如自编译): [如 go1.20.10]
validations:
required: true
- type: textarea
id: additional
attributes:
label: 补充信息
description: 其他可能有助于排查问题的信息

View File

@@ -1,11 +0,0 @@
# Issue 模板配置
# 禁止空白 issue强制用户选择模板
blank_issues_enabled: false
contact_links:
- name: 📖 使用文档
url: https://github.com/shadow1ng/fscan/blob/main/README.md
about: 提交 Issue 前请先查阅文档
- name: 💬 讨论区
url: https://github.com/shadow1ng/fscan/discussions
about: 一般性问题和讨论请使用 Discussions

View File

@@ -1,94 +0,0 @@
name: 🎯 误报/漏报
description: 报告扫描结果不准确的问题
title: "[Accuracy] "
labels: ["accuracy"]
body:
- type: markdown
attributes:
value: |
感谢您帮助提高 fscan 的准确性!误报和漏报都是需要优化的问题。
- type: dropdown
id: type
attributes:
label: 问题类型
options:
- 误报 (False Positive) - 报告了不存在的问题
- 漏报 (False Negative) - 未能检测到存在的问题
validations:
required: true
- type: dropdown
id: category
attributes:
label: 涉及功能
options:
- 主机存活检测
- 端口状态判断
- 服务识别
- 弱口令检测
- POC/漏洞检测
- Web指纹识别
- 其他
validations:
required: true
- type: textarea
id: fscan-output
attributes:
label: fscan 输出结果
description: 粘贴相关的扫描输出请脱敏敏感信息如真实IP、密码等
render: shell
validations:
required: true
- type: textarea
id: actual
attributes:
label: 实际情况
description: 描述目标的真实状态
placeholder: |
实际上这个端口是关闭的 / 服务版本是 xxx / 密码不是 xxx...
验证方式: 通过 nmap/手动连接/其他工具 确认...
validations:
required: true
- type: textarea
id: environment
attributes:
label: 目标环境
description: 描述目标的环境信息(请脱敏)
placeholder: |
- 目标系统: Windows Server 2019 / Ubuntu 22.04
- 服务版本: MySQL 8.0 / Redis 7.0
- 网络环境: 直连 / 通过代理 / VPN
validations:
required: true
- type: textarea
id: command
attributes:
label: 使用的命令
description: 执行的 fscan 命令
placeholder: "fscan -h x.x.x.x -p 1-65535 -pwdf pass.txt"
render: shell
validations:
required: true
- type: input
id: version
attributes:
label: fscan 版本
placeholder: "如: 1.8.4"
validations:
required: true
- type: textarea
id: suggestion
attributes:
label: 改进建议
description: 如果您有改进的想法,请分享
placeholder: |
建议增加 xxx 判断条件...
或者调整 xxx 检测逻辑...

View File

@@ -1,74 +0,0 @@
name: ✨ 功能请求
description: 提议新功能或改进现有功能
title: "[Feature] "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
感谢您的功能建议!请详细描述您的需求,这将帮助我们评估和实现。
- type: dropdown
id: category
attributes:
label: 功能类别
options:
- 新扫描能力 (New Scan Capability)
- 性能优化 (Performance)
- 用户体验 (UX/CLI)
- 输出格式 (Output Format)
- 配置选项 (Configuration)
- 集成/API (Integration/API)
- 其他 (Other)
validations:
required: true
- type: textarea
id: problem
attributes:
label: 解决什么问题?
description: 描述您遇到的痛点或使用场景
placeholder: |
在进行 xxx 操作时,我希望能够...
目前的问题是...
validations:
required: true
- type: textarea
id: solution
attributes:
label: 期望的解决方案
description: 描述您希望的功能或行为
placeholder: |
希望能够通过 -xxx 参数来...
或者增加一个新的模块来...
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 替代方案
description: 您考虑过的其他解决方案或变通方法
placeholder: |
目前我通过 xxx 方式来解决,但是...
- type: dropdown
id: priority
attributes:
label: 优先级建议
description: 您认为这个功能的重要程度
options:
- 高 - 核心功能缺失
- 中 - 明显改善体验
- 低 - 锦上添花
validations:
required: true
- type: checkboxes
id: contribution
attributes:
label: 贡献意愿
options:
- label: 我愿意尝试实现这个功能并提交 PR

View File

@@ -1,86 +0,0 @@
name: 🔌 新插件/协议支持
description: 请求支持新的服务、协议或漏洞检测
title: "[Plugin] "
labels: ["plugin", "enhancement"]
body:
- type: markdown
attributes:
value: |
感谢您的插件请求fscan 持续扩展对各种服务和协议的支持。
- type: dropdown
id: type
attributes:
label: 请求类型
options:
- 新服务/协议支持 (New Service)
- 新弱口令检测 (New Brute Force)
- 新漏洞 POC (New POC)
- 新指纹识别 (New Fingerprint)
validations:
required: true
- type: input
id: service
attributes:
label: 服务/协议名称
placeholder: "如: Kafka, ClickHouse, etcd, Consul"
validations:
required: true
- type: input
id: port
attributes:
label: 默认端口
placeholder: "如: 9092, 8123, 2379"
- type: textarea
id: description
attributes:
label: 服务描述
description: 简要介绍这个服务/协议
placeholder: |
这是一个用于 xxx 的服务...
在内网环境中常见于...
validations:
required: true
- type: textarea
id: detection
attributes:
label: 识别方法
description: 如何识别/检测这个服务(如有了解)
placeholder: |
Banner 特征: xxx
默认响应: xxx
认证方式: xxx
- type: textarea
id: reference
attributes:
label: 参考资料
description: 相关文档、其他工具实现、漏洞详情等
placeholder: |
- 官方文档: https://...
- 其他工具实现: https://...
- CVE编号: CVE-xxxx-xxxx
- type: dropdown
id: prevalence
attributes:
label: 使用普遍程度
description: 这个服务在目标环境中的常见程度
options:
- 非常常见 (企业环境标配)
- 较为常见 (经常遇到)
- 偶尔遇到
- 较少见但重要
- type: checkboxes
id: contribution
attributes:
label: 贡献意愿
options:
- label: 我愿意尝试实现这个插件并提交 PR
- label: 我可以提供测试环境

57
.gitignore vendored
View File

@@ -5,3 +5,60 @@ fscan.exe
fscan
makefile
fscanapi.csv
# IDE files / IDE 文件
.vscode/
.cursor/
.cursorrules
.claude/
# Local development files / 本地开发文件
*.local
*.tmp
*.temp
.env
.env.local
.env.development
.env.test
.env.production
# OS files / 操作系统文件
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
desktop.ini
# Logs / 日志文件
*.log
logs/
log/
# Test coverage / 测试覆盖率
coverage.txt
coverage.html
*.cover
# Build artifacts / 构建产物
dist/
build/
bin/
*.exe
*.dll
*.so
*.dylib
# Go specific / Go 相关
vendor/
*.test
*.prof
*.mem
*.cpu
__debug_bin*
# Local development tools / 本地开发工具
.air.toml
air_tmp/

View File

@@ -957,8 +957,6 @@ var (
ShowScanPlan bool // 是否显示扫描计划详情
SlowLogOutput bool // 是否启用慢速日志输出
Language string // 界面语言设置
ApiAddr string // API地址
SecretKey string // 加密密钥
)
var (

View File

@@ -152,8 +152,7 @@ func Flag(Info *HostInfo) {
// ═════════════════════════════════════════════════
flag.StringVar(&Shellcode, "sc", "", GetText("flag_shellcode"))
flag.StringVar(&Language, "lang", "zh", GetText("flag_language"))
flag.StringVar(&ApiAddr, "api", "", GetText("flag_api"))
flag.StringVar(&SecretKey, "secret", "", GetText("flag_api_key"))
// 解析命令行参数
parseCommandLineArgs()
@@ -161,95 +160,6 @@ func Flag(Info *HostInfo) {
SetLanguage()
}
// FlagFormRemote 解析远程扫描的命令行参数
func FlagFromRemote(info *HostInfo, argString string) error {
if strings.TrimSpace(argString) == "" {
return fmt.Errorf("参数为空")
}
args, err := parseEnvironmentArgs(argString)
if err != nil {
return fmt.Errorf("远程参数解析失败: %v", err)
}
// 创建一个新的 FlagSet 用于远程参数解析,避免污染主命令行
fs := flag.NewFlagSet("remote", flag.ContinueOnError)
// 注册需要的远程 flag注意使用 fs 而非 flag 包的全局变量
fs.StringVar(&info.Host, "h", "", GetText("flag_host"))
fs.StringVar(&ExcludeHosts, "eh", "", GetText("flag_exclude_hosts"))
fs.StringVar(&Ports, "p", MainPorts, GetText("flag_ports"))
fs.StringVar(&ExcludePorts, "ep", "", GetText("flag_exclude_ports"))
fs.StringVar(&HostsFile, "hf", "", GetText("flag_hosts_file"))
fs.StringVar(&PortsFile, "pf", "", GetText("flag_ports_file"))
fs.StringVar(&ScanMode, "m", "all", GetText("flag_scan_mode"))
fs.IntVar(&ThreadNum, "t", 10, GetText("flag_thread_num"))
fs.Int64Var(&Timeout, "time", 3, GetText("flag_timeout"))
fs.IntVar(&ModuleThreadNum, "mt", 10, GetText("flag_module_thread_num"))
fs.Int64Var(&GlobalTimeout, "gt", 180, GetText("flag_global_timeout"))
fs.IntVar(&LiveTop, "top", 10, GetText("flag_live_top"))
fs.BoolVar(&DisablePing, "np", false, GetText("flag_disable_ping"))
fs.BoolVar(&UsePing, "ping", false, GetText("flag_use_ping"))
fs.BoolVar(&EnableFingerprint, "fingerprint", false, GetText("flag_enable_fingerprint"))
fs.BoolVar(&LocalMode, "local", false, GetText("flag_local_mode"))
fs.StringVar(&Username, "user", "", GetText("flag_username"))
fs.StringVar(&Password, "pwd", "", GetText("flag_password"))
fs.StringVar(&AddUsers, "usera", "", GetText("flag_add_users"))
fs.StringVar(&AddPasswords, "pwda", "", GetText("flag_add_passwords"))
fs.StringVar(&UsersFile, "userf", "", GetText("flag_users_file"))
fs.StringVar(&PasswordsFile, "pwdf", "", GetText("flag_passwords_file"))
fs.StringVar(&HashFile, "hashf", "", GetText("flag_hash_file"))
fs.StringVar(&HashValue, "hash", "", GetText("flag_hash_value"))
fs.StringVar(&Domain, "domain", "", GetText("flag_domain"))
fs.StringVar(&SshKeyPath, "sshkey", "", GetText("flag_ssh_key"))
fs.StringVar(&TargetURL, "u", "", GetText("flag_target_url"))
fs.StringVar(&URLsFile, "uf", "", GetText("flag_urls_file"))
fs.StringVar(&Cookie, "cookie", "", GetText("flag_cookie"))
fs.Int64Var(&WebTimeout, "wt", 5, GetText("flag_web_timeout"))
fs.StringVar(&HttpProxy, "proxy", "", GetText("flag_http_proxy"))
fs.StringVar(&Socks5Proxy, "socks5", "", GetText("flag_socks5_proxy"))
fs.StringVar(&PocPath, "pocpath", "", GetText("flag_poc_path"))
fs.StringVar(&Pocinfo.PocName, "pocname", "", GetText("flag_poc_name"))
fs.BoolVar(&PocFull, "full", false, GetText("flag_poc_full"))
fs.BoolVar(&DnsLog, "dns", false, GetText("flag_dns_log"))
fs.IntVar(&PocNum, "num", 20, GetText("flag_poc_num"))
fs.BoolVar(&DisablePocScan, "nopoc", false, GetText("flag_no_poc"))
fs.StringVar(&RedisFile, "rf", "", GetText("flag_redis_file"))
fs.StringVar(&RedisShell, "rs", "", GetText("flag_redis_shell"))
fs.BoolVar(&DisableRedis, "noredis", false, GetText("flag_disable_redis"))
fs.StringVar(&RedisWritePath, "rwp", "", GetText("flag_redis_write_path"))
fs.StringVar(&RedisWriteContent, "rwc", "", GetText("flag_redis_write_content"))
fs.StringVar(&RedisWriteFile, "rwf", "", GetText("flag_redis_write_file"))
fs.BoolVar(&DisableBrute, "nobr", false, GetText("flag_disable_brute"))
fs.IntVar(&MaxRetries, "retry", 3, GetText("flag_max_retries"))
fs.StringVar(&Outputfile, "o", "result.txt", GetText("flag_output_file"))
fs.StringVar(&OutputFormat, "f", "txt", GetText("flag_output_format"))
fs.BoolVar(&DisableSave, "no", false, GetText("flag_disable_save"))
fs.BoolVar(&Silent, "silent", false, GetText("flag_silent_mode"))
fs.BoolVar(&NoColor, "nocolor", false, GetText("flag_no_color"))
fs.StringVar(&LogLevel, "log", LogLevelSuccess, GetText("flag_log_level"))
fs.BoolVar(&ShowProgress, "pg", false, GetText("flag_show_progress"))
fs.BoolVar(&ShowScanPlan, "sp", false, GetText("flag_show_scan_plan"))
fs.BoolVar(&SlowLogOutput, "slow", false, GetText("flag_slow_log_output"))
fs.StringVar(&Shellcode, "sc", "", GetText("flag_shellcode"))
fs.StringVar(&Language, "lang", "zh", GetText("flag_language"))
// 开始解析远程传入的参数
if err := fs.Parse(args); err != nil {
return fmt.Errorf("远程参数解析失败: %v", err)
}
return nil
}
// parseCommandLineArgs 处理来自环境变量和命令行的参数
func parseCommandLineArgs() {
// 首先检查环境变量中的参数

View File

@@ -67,18 +67,6 @@ func InitOutput() error {
return fmt.Errorf(GetText("output_create_dir_failed", err))
}
if ApiAddr != "" {
OutputFormat = "csv"
Outputfile = filepath.Join(dir, "fscanapi.csv")
Num = 0
End = 0
if _, err := os.Stat(Outputfile); err == nil {
if err := os.Remove(Outputfile); err != nil {
return fmt.Errorf(GetText("output_file_remove_failed", err))
}
}
}
manager := &OutputManager{
outputPath: Outputfile,
outputFormat: OutputFormat,

View File

@@ -108,9 +108,8 @@ func parsePasswords() {
if Password != "" {
passes := strings.Split(Password, ",")
for _, pass := range passes {
if pass != "" {
pwdList = append(pwdList, pass)
}
// 保留空密码,因为空口令是重要的安全测试场景
pwdList = append(pwdList, pass)
}
Passwords = pwdList
LogBase(GetText("load_passwords", len(pwdList)))
@@ -125,9 +124,8 @@ func parsePasswords() {
}
for _, pass := range passes {
if pass != "" {
pwdList = append(pwdList, pass)
}
// 保留空密码,用户可能在文件中故意添加空行来测试空口令
pwdList = append(pwdList, pass)
}
Passwords = pwdList
LogBase(GetText("load_passwords_from_file", len(passes)))

View File

@@ -1,13 +1,16 @@
package Common
import (
"context"
"crypto/tls"
"errors"
"fmt"
"golang.org/x/net/proxy"
"net"
"net/url"
"strings"
"time"
"golang.org/x/net/proxy"
)
// WrapperTcpWithTimeout 创建一个带超时的TCP连接
@@ -16,6 +19,12 @@ func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.
return WrapperTCP(network, address, d)
}
// WrapperTcpWithContext 创建一个带上下文的TCP连接
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
d := &net.Dialer{}
return WrapperTCPWithContext(ctx, network, address, d)
}
// WrapperTCP 根据配置创建TCP连接
func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) {
// 直连模式
@@ -41,6 +50,54 @@ func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error)
return conn, nil
}
// WrapperTCPWithContext 根据配置创建支持上下文的TCP连接
func WrapperTCPWithContext(ctx context.Context, network, address string, forward *net.Dialer) (net.Conn, error) {
// 直连模式
if Socks5Proxy == "" {
conn, err := forward.DialContext(ctx, network, address)
if err != nil {
return nil, fmt.Errorf(GetText("tcp_conn_failed"), err)
}
return conn, nil
}
// Socks5代理模式
dialer, err := Socks5Dialer(forward)
if err != nil {
return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
}
// 创建一个结果通道来处理连接和取消
connChan := make(chan struct {
conn net.Conn
err error
}, 1)
go func() {
conn, err := dialer.Dial(network, address)
select {
case <-ctx.Done():
if conn != nil {
conn.Close()
}
case connChan <- struct {
conn net.Conn
err error
}{conn, err}:
}
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case result := <-connChan:
if result.err != nil {
return nil, fmt.Errorf(GetText("socks5_conn_failed"), result.err)
}
return result.conn, nil
}
}
// Socks5Dialer 创建Socks5代理拨号器
func Socks5Dialer(forward *net.Dialer) (proxy.Dialer, error) {
// 解析代理URL
@@ -76,3 +133,59 @@ func Socks5Dialer(forward *net.Dialer) (proxy.Dialer, error) {
return dialer, nil
}
// WrapperTlsWithContext 创建一个通过代理的TLS连接
func WrapperTlsWithContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) {
// 直连模式
if Socks5Proxy == "" {
dialer := &net.Dialer{}
tcpConn, err := dialer.DialContext(ctx, network, address)
if err != nil {
return nil, fmt.Errorf("直连TCP连接失败: %v", err)
}
// 在TCP连接上进行TLS握手
tlsConn := tls.Client(tcpConn, tlsConfig)
// 使用ctx的deadline设置TLS握手超时
if deadline, ok := ctx.Deadline(); ok {
tlsConn.SetDeadline(deadline)
}
if err := tlsConn.Handshake(); err != nil {
tcpConn.Close()
return nil, fmt.Errorf("TLS握手失败: %v", err)
}
// 清除deadline让上层代码自己管理超时
tlsConn.SetDeadline(time.Time{})
return tlsConn, nil
}
// Socks5代理模式
// 首先通过代理建立到目标的TCP连接
tcpConn, err := WrapperTcpWithContext(ctx, network, address)
if err != nil {
return nil, fmt.Errorf("通过代理建立TCP连接失败: %v", err)
}
// 在TCP连接上进行TLS握手
tlsConn := tls.Client(tcpConn, tlsConfig)
// 使用ctx的deadline设置TLS握手超时
if deadline, ok := ctx.Deadline(); ok {
tlsConn.SetDeadline(deadline)
}
if err := tlsConn.Handshake(); err != nil {
tcpConn.Close()
return nil, fmt.Errorf("TLS握手失败: %v", err)
}
// 清除deadline让上层代码自己管理超时
tlsConn.SetDeadline(time.Time{})
return tlsConn, nil
}

View File

@@ -407,8 +407,8 @@ func (i *Info) Write(msg []byte) error {
_, err := i.Conn.Write(msg)
if err != nil && strings.Contains(err.Error(), "close") {
i.Conn.Close()
// 连接关闭时重试
i.Conn, err = net.DialTimeout("tcp4", fmt.Sprintf("%s:%d", i.Address, i.Port), time.Duration(6)*time.Second)
// 连接关闭时重试 - 支持SOCKS5代理
i.Conn, err = Common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%d", i.Address, i.Port), time.Duration(6)*time.Second)
if err == nil {
i.Conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
_, err = i.Conn.Write(msg)

View File

@@ -6,7 +6,6 @@ import (
"github.com/shadow1ng/fscan/Common"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
"net"
"strings"
"sync"
"sync/atomic"
@@ -53,8 +52,8 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
g.Go(func() error {
defer sem.Release(1)
// 连接测试
conn, err := net.DialTimeout("tcp", addr, to)
// 连接测试 - 支持SOCKS5代理
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, to)
if err != nil {
return nil
}

View File

@@ -3,11 +3,11 @@ package Plugins
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/Common"
)
// ActiveMQCredential 表示一个ActiveMQ凭据
@@ -213,8 +213,7 @@ func ActiveMQConn(ctx context.Context, info *Common.HostInfo, user string, pass
addr := fmt.Sprintf("%s:%v", info.Host, info.Ports)
// 使用上下文创建带超时的连接
dialer := &net.Dialer{Timeout: time.Duration(Common.Timeout) * time.Second}
conn, err := dialer.DialContext(ctx, "tcp", addr)
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, time.Duration(Common.Timeout)*time.Second)
if err != nil {
return false, err
}

View File

@@ -3,14 +3,29 @@ package Plugins
import (
"context"
"fmt"
"github.com/gocql/gocql"
"github.com/shadow1ng/fscan/Common"
"net"
"strconv"
"strings"
"sync"
"time"
"github.com/gocql/gocql"
"github.com/shadow1ng/fscan/Common"
)
// CassandraProxyDialer 实现gocql.Dialer接口支持代理连接
type CassandraProxyDialer struct {
timeout time.Duration
}
func (d *CassandraProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return Common.WrapperTcpWithContext(ctx, network, fmt.Sprintf("%s:%s", host, port))
}
// CassandraCredential 表示一个Cassandra凭据
type CassandraCredential struct {
Username string
@@ -223,6 +238,13 @@ func CassandraConn(ctx context.Context, info *Common.HostInfo, user string, pass
cluster.ProtoVersion = 4
cluster.Consistency = gocql.One
// 如果配置了代理设置自定义Dialer
if Common.Socks5Proxy != "" {
cluster.Dialer = &CassandraProxyDialer{
timeout: timeout,
}
}
if user != "" || pass != "" {
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: user,

View File

@@ -5,12 +5,13 @@ import (
"context"
"crypto/tls"
"fmt"
"github.com/shadow1ng/fscan/Common"
"io"
"net"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/Common"
)
// IMAPCredential 表示一个IMAP凭据
@@ -211,8 +212,7 @@ func IMAPConn(ctx context.Context, info *Common.HostInfo, user string, pass stri
// 在协程中尝试连接
go func() {
// 先尝试普通连接
dialer := &net.Dialer{Timeout: timeout}
conn, err := dialer.DialContext(ctx, "tcp", addr)
conn, err := Common.WrapperTcpWithContext(ctx, "tcp", addr)
if err == nil {
flag, authErr := tryIMAPAuth(conn, user, pass, timeout)
conn.Close()
@@ -232,14 +232,16 @@ func IMAPConn(ctx context.Context, info *Common.HostInfo, user string, pass stri
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
// 使用支持代理的TLS连接
tlsConn, tlsErr := Common.WrapperTlsWithContext(ctx, "tcp", addr, tlsConfig)
if tlsErr != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{false, fmt.Errorf("连接失败: %v", tlsErr)}:
}{false, fmt.Errorf("TLS连接失败: %v", tlsErr)}:
}
return
}

View File

@@ -3,12 +3,12 @@ package Plugins
import (
"context"
"fmt"
"github.com/go-ldap/ldap/v3"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"sync"
"time"
"github.com/go-ldap/ldap/v3"
"github.com/shadow1ng/fscan/Common"
)
// LDAPCredential 表示一个LDAP凭据
@@ -210,13 +210,8 @@ func tryLDAPCredential(ctx context.Context, info *Common.HostInfo, credential LD
func LDAPConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 创建拨号器并设置超时
dialer := &net.Dialer{
Timeout: time.Duration(Common.Timeout) * time.Second,
}
// 使用上下文控制的拨号过程
conn, err := dialer.DialContext(ctx, "tcp", address)
conn, err := Common.WrapperTcpWithContext(ctx, "tcp", address)
if err != nil {
return false, err
}

View File

@@ -4,13 +4,25 @@ import (
"context"
"database/sql"
"fmt"
_ "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"sync"
"time"
mssql "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/Common"
)
// MSSQLProxyDialer 自定义dialer结构体
type MSSQLProxyDialer struct {
timeout time.Duration
}
// DialContext 实现mssql.Dialer接口支持socks代理
func (d *MSSQLProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
return Common.WrapperTcpWithContext(ctx, network, addr)
}
// MssqlCredential 表示一个MSSQL凭据
type MssqlCredential struct {
Username string
@@ -205,7 +217,58 @@ func MssqlConn(ctx context.Context, info *Common.HostInfo, user string, pass str
host, username, password, port,
)
// 建立数据库连接
// 检查是否需要使用socks代理
if Common.Socks5Proxy != "" {
// 使用自定义dialer创建连接器
connector, err := mssql.NewConnector(connStr)
if err != nil {
return false, err
}
// 设置自定义dialer
connector.Dialer = &MSSQLProxyDialer{
timeout: time.Duration(Common.Timeout) * time.Millisecond,
}
// 使用连接器创建数据库连接
db := sql.OpenDB(connector)
defer db.Close()
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(1)
// 通过上下文执行ping操作以支持超时控制
pingCtx, pingCancel := context.WithTimeout(ctx, timeout)
defer pingCancel()
errChan := make(chan error, 1)
go func() {
errChan <- db.PingContext(pingCtx)
}()
// 等待ping结果或者超时
select {
case err := <-errChan:
if err != nil {
return false, err
}
return true, nil
case <-ctx.Done():
// 全局超时或取消
return false, ctx.Err()
case <-pingCtx.Done():
if pingCtx.Err() == context.DeadlineExceeded {
// 单个连接超时
return false, fmt.Errorf("连接超时")
}
return false, pingCtx.Err()
}
}
// 使用标准连接方式
db, err := sql.Open("mssql", connStr)
if err != nil {
return false, err

View File

@@ -4,9 +4,9 @@ import (
"context"
"encoding/binary"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"time"
"github.com/shadow1ng/fscan/Common"
)
// ModbusScanResult 表示 Modbus 扫描结果
@@ -77,8 +77,7 @@ func tryModbusScan(ctx context.Context, info *Common.HostInfo, timeoutSeconds in
// 在协程中执行扫描
go func() {
// 尝试建立连接
var d net.Dialer
conn, err := d.DialContext(connCtx, "tcp", target)
conn, err := Common.WrapperTcpWithContext(connCtx, "tcp", target)
if err != nil {
select {
case <-connCtx.Done():

View File

@@ -3,11 +3,11 @@ package Plugins
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/Common"
"io"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/Common"
)
// MongodbScan 执行MongoDB未授权扫描
@@ -112,13 +112,8 @@ func MongodbUnauth(ctx context.Context, info *Common.HostInfo) (bool, error) {
func checkMongoAuth(ctx context.Context, address string, packet []byte) (string, error) {
Common.LogDebug(fmt.Sprintf("建立MongoDB连接: %s", address))
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second)
defer cancel()
// 使用带超时的连接
var d net.Dialer
conn, err := d.DialContext(connCtx, "tcp", address)
conn, err := Common.WrapperTcpWithTimeout("tcp", address, time.Duration(Common.Timeout)*time.Second)
if err != nil {
return "", fmt.Errorf("连接失败: %v", err)
}

View File

@@ -4,13 +4,38 @@ import (
"context"
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"sync"
"time"
"github.com/go-sql-driver/mysql"
"github.com/shadow1ng/fscan/Common"
)
// MySQLProxyDialer 自定义dialer结构体
type MySQLProxyDialer struct {
timeout time.Duration
}
// Dial 实现mysql.Dialer接口支持socks代理
func (d *MySQLProxyDialer) Dial(ctx context.Context, addr string) (net.Conn, error) {
return Common.WrapperTcpWithContext(ctx, "tcp", addr)
}
// registerMySQLDialer 注册MySQL自定义dialer
func registerMySQLDialer() {
// 创建自定义dialer
dialer := &MySQLProxyDialer{
timeout: time.Duration(Common.Timeout) * time.Millisecond,
}
// 注册自定义dialer到go-sql-driver/mysql
mysql.RegisterDialContext("tcp-proxy", func(ctx context.Context, addr string) (net.Conn, error) {
return dialer.Dial(ctx, addr)
})
}
// MySQLCredential 表示一个MySQL凭据
type MySQLCredential struct {
Username string
@@ -204,11 +229,24 @@ func MysqlConn(ctx context.Context, info *Common.HostInfo, user string, pass str
host, port, username, password := info.Host, info.Ports, user, pass
timeout := time.Duration(Common.Timeout) * time.Second
// 构造连接字符串,包含超时设置
connStr := fmt.Sprintf(
"%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v",
username, password, host, port, timeout,
)
// 检查是否需要使用socks代理
var connStr string
if Common.Socks5Proxy != "" {
// 注册自定义dialer
registerMySQLDialer()
// 使用自定义网络类型的连接字符串
connStr = fmt.Sprintf(
"%v:%v@tcp-proxy(%v:%v)/mysql?charset=utf8&timeout=%v",
username, password, host, port, timeout,
)
} else {
// 标准连接字符串
connStr = fmt.Sprintf(
"%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v",
username, password, host, port, timeout,
)
}
// 创建结果通道
resultChan := make(chan struct {

View File

@@ -3,11 +3,12 @@ package Plugins
import (
"context"
"fmt"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
"github.com/shadow1ng/fscan/Common"
"strings"
"sync"
"time"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
"github.com/shadow1ng/fscan/Common"
)
// Neo4jCredential 表示一个Neo4j凭据
@@ -268,6 +269,9 @@ func Neo4jConn(info *Common.HostInfo, user string, pass string) (bool, error) {
config := func(c *neo4j.Config) {
c.SocketConnectTimeout = timeout
c.ConnectionAcquisitionTimeout = timeout
// 注意Neo4j驱动可能不支持代理配置
// 如果需要代理支持,可能需要使用更底层的连接方式
}
var driver neo4j.Driver

View File

@@ -5,11 +5,12 @@ import (
"context"
"crypto/tls"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/Common"
)
// POP3Credential 表示一个POP3凭据
@@ -255,12 +256,7 @@ func POP3Conn(ctx context.Context, info *Common.HostInfo, user string, pass stri
// 在协程中尝试连接,支持取消
go func() {
// 首先尝试普通连接
dialer := &net.Dialer{
Timeout: timeout,
// 增加KeepAlive设置可能有助于处理一些服务器的限制
KeepAlive: 30 * time.Second,
}
conn, err := dialer.DialContext(ctx, "tcp", addr)
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, timeout)
if err == nil {
flag, authErr := tryPOP3Auth(conn, user, pass, timeout)
conn.Close()
@@ -287,7 +283,10 @@ func POP3Conn(ctx context.Context, info *Common.HostInfo, user string, pass stri
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
// 对于TLS连接暂时使用标准dialer
// TODO: 实现通过socks代理的TLS连接
tempDialer := &net.Dialer{Timeout: timeout}
tlsConn, tlsErr := tls.DialWithDialer(tempDialer, "tcp", addr, tlsConfig)
if tlsErr != nil {
select {
case <-ctx.Done():

View File

@@ -3,14 +3,32 @@ package Plugins
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
_ "github.com/lib/pq"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"sync"
"time"
"github.com/lib/pq"
"github.com/shadow1ng/fscan/Common"
)
// PostgresProxyDialer 自定义dialer结构体
type PostgresProxyDialer struct {
timeout time.Duration
}
// Dial 实现pq.Dialer接口支持socks代理
func (d *PostgresProxyDialer) Dial(network, address string) (net.Conn, error) {
return Common.WrapperTcpWithTimeout(network, address, d.timeout)
}
// DialTimeout 实现具有超时的连接
func (d *PostgresProxyDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
return Common.WrapperTcpWithTimeout(network, address, timeout)
}
// PostgresCredential 表示一个PostgreSQL凭据
type PostgresCredential struct {
Username string
@@ -202,7 +220,41 @@ func PostgresConn(ctx context.Context, info *Common.HostInfo, user string, pass
user, pass, info.Host, info.Ports, Common.Timeout/1000, // 转换为秒
)
// 建立数据库连接
// 检查是否需要使用socks代理
if Common.Socks5Proxy != "" {
// 使用自定义dialer通过socks代理连接
dialer := &PostgresProxyDialer{
timeout: time.Duration(Common.Timeout) * time.Millisecond,
}
// 使用pq.DialOpen通过自定义dialer建立连接
conn, err := pq.DialOpen(dialer, connStr)
if err != nil {
return false, err
}
defer conn.Close()
// 转换为sql.DB进行测试
db := sql.OpenDB(&postgresConnector{conn: conn})
defer db.Close()
// 使用上下文测试连接
err = db.PingContext(ctx)
if err != nil {
return false, err
}
// 简单查询测试权限
var version string
err = db.QueryRowContext(ctx, "SELECT version()").Scan(&version)
if err != nil {
return false, err
}
return true, nil
}
// 使用标准连接方式
db, err := sql.Open("postgres", connStr)
if err != nil {
return false, err
@@ -230,6 +282,19 @@ func PostgresConn(ctx context.Context, info *Common.HostInfo, user string, pass
return true, nil
}
// postgresConnector 封装driver.Conn为sql.driver.Connector
type postgresConnector struct {
conn driver.Conn
}
func (c *postgresConnector) Connect(ctx context.Context) (driver.Conn, error) {
return c.conn, nil
}
func (c *postgresConnector) Driver() driver.Driver {
return &pq.Driver{}
}
// savePostgresResult 保存PostgreSQL扫描结果
func savePostgresResult(info *Common.HostInfo, target string, credential PostgresCredential) {
successMsg := fmt.Sprintf("PostgreSQL服务 %s 成功爆破 用户名: %v 密码: %v",

View File

@@ -3,11 +3,11 @@ package Plugins
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/Common"
)
// RsyncCredential 表示一个Rsync凭据
@@ -212,13 +212,8 @@ func RsyncConn(ctx context.Context, info *Common.HostInfo, user string, pass str
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
// 设置带有上下文的拨号器
dialer := &net.Dialer{
Timeout: timeout,
}
// 建立连接
conn, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", host, port))
conn, err := Common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout)
if err != nil {
return false, "", err
}
@@ -335,13 +330,11 @@ func RsyncConn(ctx context.Context, info *Common.HostInfo, user string, pass str
}
// 5. 为每个模块创建新连接尝试认证
authConn, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", host, port))
authConn, err := Common.WrapperTcpWithTimeout(host, port, timeout)
if err != nil {
continue
}
defer authConn.Close()
// 重复初始握手
defer authConn.Close() // 重复初始握手
authConn.SetReadDeadline(time.Now().Add(timeout))
_, err = authConn.Read(buffer)
if err != nil {

View File

@@ -3,13 +3,13 @@ package Plugins
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"os"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/Common"
"github.com/hirochachacha/go-smb2"
)
@@ -316,9 +316,8 @@ func trySmb2Credential(ctx context.Context, info *Common.HostInfo, credential Sm
// Smb2Con 尝试SMB2连接并进行认证检查共享访问权限
func Smb2Con(ctx context.Context, info *Common.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, shares []string) {
// 建立TCP连接使用上下文提供的超时控制
var d net.Dialer
conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("%s:445", info.Host))
// 建立TCP连接使用socks代理支持
conn, err := Common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:445", info.Host), time.Duration(Common.Timeout)*time.Second)
if err != nil {
return false, fmt.Errorf("连接失败: %v", err), nil
}

View File

@@ -3,12 +3,12 @@ package Plugins
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"net/smtp"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/Common"
)
// SmtpCredential 表示一个SMTP凭据
@@ -253,11 +253,7 @@ func SmtpConn(info *Common.HostInfo, user string, pass string, timeoutSeconds in
addr := fmt.Sprintf("%s:%s", host, port)
// 设置连接超时
dialer := &net.Dialer{
Timeout: timeout,
}
conn, err := dialer.Dial("tcp", addr)
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, timeout)
if err != nil {
return false, err
}

View File

@@ -5,12 +5,13 @@ import (
"context"
"errors"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"regexp"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/Common"
)
// TelnetCredential 表示一个Telnet凭据
@@ -248,9 +249,8 @@ func tryTelnetCredential(ctx context.Context, info *Common.HostInfo, credential
// telnetConnWithContext 带上下文的Telnet连接尝试
func telnetConnWithContext(ctx context.Context, info *Common.HostInfo, user, pass string) (bool, error) {
// 创建TCP连接(使用上下文控制)
var d net.Dialer
conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", info.Host, info.Ports))
// 创建TCP连接(使用支持context的socks代理)
conn, err := Common.WrapperTcpWithContext(ctx, "tcp", fmt.Sprintf("%s:%s", info.Host, info.Ports))
if err != nil {
return false, err
}

View File

@@ -3,11 +3,11 @@ package Plugins
import (
"context"
"fmt"
"github.com/mitchellh/go-vnc"
"github.com/shadow1ng/fscan/Common"
"net"
"sync"
"time"
"github.com/mitchellh/go-vnc"
"github.com/shadow1ng/fscan/Common"
)
// VncCredential 表示VNC凭据
@@ -190,8 +190,7 @@ func VncConn(ctx context.Context, info *Common.HostInfo, pass string) (bool, err
timeout := time.Duration(Common.Timeout) * time.Second
// 使用带上下文的TCP连接
var d net.Dialer
conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", Host, Port))
conn, err := Common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%s", Host, Port), timeout)
if err != nil {
return false, err
}