mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-07-13 21:02:44 +08:00
Compare commits
2 Commits
a42ee523b0
...
af06345aa5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
af06345aa5 | ||
![]() |
75aeee5215 |
@ -876,17 +876,15 @@ var (
|
||||
AddPasswords string // 原PassAdd
|
||||
|
||||
// 扫描配置
|
||||
ScanMode string // 原Scantype
|
||||
ThreadNum int // 原Threads
|
||||
UseSynScan bool
|
||||
Timeout int64 = 3
|
||||
LiveTop int
|
||||
DisablePing bool // 原NoPing
|
||||
UsePing bool // 原Ping
|
||||
Command string
|
||||
|
||||
// 本地扫描配置
|
||||
LocalScan bool
|
||||
ScanMode string // 原Scantype
|
||||
ThreadNum int // 原Threads
|
||||
UseSynScan bool
|
||||
Timeout int64 = 3
|
||||
LiveTop int
|
||||
DisablePing bool // 原NoPing
|
||||
UsePing bool // 原Ping
|
||||
Command string
|
||||
SkipFingerprint bool
|
||||
|
||||
// 文件配置
|
||||
HostsFile string // 原HostFile
|
||||
@ -904,9 +902,8 @@ var (
|
||||
Socks5Proxy string
|
||||
|
||||
// POC配置
|
||||
PocPath string
|
||||
Pocinfo PocInfo
|
||||
DisablePoc bool // 原NoPoc
|
||||
PocPath string
|
||||
Pocinfo PocInfo
|
||||
|
||||
// Redis配置
|
||||
RedisFile string
|
||||
|
@ -185,7 +185,6 @@ func Flag(Info *HostInfo) {
|
||||
" - Port: 端口扫描模式\n"+
|
||||
" - ICMP: ICMP存活探测\n"+
|
||||
" - Local: 本地信息收集\n\n"+
|
||||
" - UDP: UDP扫描模式\n\n"+
|
||||
"单个插件模式(小写):\n"+
|
||||
" Web类: web, fcgi\n"+
|
||||
" 数据库类: mysql, mssql, redis, mongodb, postgres, oracle, memcached\n"+
|
||||
@ -199,9 +198,7 @@ func Flag(Info *HostInfo) {
|
||||
flag.BoolVar(&DisablePing, "np", false, "禁用主机存活探测")
|
||||
flag.BoolVar(&UsePing, "ping", false, "使用系统ping命令替代ICMP探测")
|
||||
flag.StringVar(&Command, "c", "", "指定要执行的系统命令(支持ssh和wmiexec)")
|
||||
|
||||
// 本地扫描配置
|
||||
flag.BoolVar(&LocalScan, "local", false, "启用本地网段扫描模式")
|
||||
flag.BoolVar(&SkipFingerprint, "skip", false, "跳过端口指纹识别")
|
||||
|
||||
// 文件配置
|
||||
flag.StringVar(&HostsFile, "hf", "", "从文件中读取目标主机列表")
|
||||
@ -221,7 +218,6 @@ func Flag(Info *HostInfo) {
|
||||
// POC配置
|
||||
flag.StringVar(&PocPath, "pocpath", "", "指定自定义POC文件路径")
|
||||
flag.StringVar(&Pocinfo.PocName, "pocname", "", "指定要使用的POC名称,如: -pocname weblogic")
|
||||
flag.BoolVar(&DisablePoc, "nopoc", false, "禁用Web漏洞POC扫描")
|
||||
flag.BoolVar(&PocFull, "full", false, "启用完整POC扫描(如测试shiro全部100个key)")
|
||||
flag.BoolVar(&DnsLog, "dns", false, "启用dnslog进行漏洞验证")
|
||||
flag.IntVar(&PocNum, "num", 20, "设置POC扫描并发数")
|
||||
@ -248,7 +244,7 @@ func Flag(Info *HostInfo) {
|
||||
flag.BoolVar(&NoColor, "nocolor", false, "禁用彩色输出显示")
|
||||
flag.BoolVar(&JsonFormat, "json", false, "以JSON格式输出结果")
|
||||
flag.StringVar(&LogLevel, "log", LogLevelInfo, "日志输出级别(ALL/SUCCESS/ERROR/INFO/DEBUG)")
|
||||
flag.BoolVar(&NoProgress, "noprogress", false, "禁用进度条显示")
|
||||
flag.BoolVar(&NoProgress, "nopg", false, "禁用进度条显示")
|
||||
|
||||
flag.Parse()
|
||||
}
|
||||
|
@ -83,9 +83,17 @@ func formatLogMessage(entry *LogEntry) string {
|
||||
|
||||
// 修改 printLog 函数
|
||||
func printLog(entry *LogEntry) {
|
||||
if LogLevel != LogLevelAll &&
|
||||
entry.Level != LogLevel &&
|
||||
!(LogLevel == LogLevelInfo && (entry.Level == LogLevelInfo || entry.Level == LogLevelSuccess)) {
|
||||
// 默认情况(LogLevelInfo)下打印 INFO、SUCCESS、ERROR
|
||||
if LogLevel == LogLevelInfo {
|
||||
if entry.Level != LogLevelInfo &&
|
||||
entry.Level != LogLevelSuccess &&
|
||||
entry.Level != LogLevelError {
|
||||
return
|
||||
}
|
||||
} else if LogLevel == LogLevelDebug || LogLevel == LogLevelAll {
|
||||
// Debug或ALL模式打印所有日志
|
||||
} else if entry.Level != LogLevel {
|
||||
// 其他情况只打印指定等级的日志
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ package Common
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -205,16 +204,9 @@ func Readfile(filename string) ([]string, error) {
|
||||
|
||||
// ParseInput 解析和验证输入参数配置
|
||||
func ParseInput(Info *HostInfo) error {
|
||||
// 检查必要的目标参数
|
||||
// 所有目标参数为空时表示本地扫描模式
|
||||
if Info.Host == "" && HostsFile == "" && TargetURL == "" && URLsFile == "" {
|
||||
LogError("未指定扫描目标")
|
||||
flag.Usage()
|
||||
return fmt.Errorf("必须指定扫描目标")
|
||||
}
|
||||
|
||||
// 如果是本地扫描模式,输出提示
|
||||
if LocalScan {
|
||||
LogInfo("已启用本地扫描模式")
|
||||
LogInfo("未指定扫描目标,将以本地模式运行")
|
||||
}
|
||||
|
||||
// 配置基本参数
|
||||
|
@ -18,7 +18,7 @@ const (
|
||||
// 插件分类映射表 - 所有插件名使用小写
|
||||
var pluginGroups = map[string][]string{
|
||||
ModeAll: {
|
||||
"web", "fcgi", // web类
|
||||
"webtitle", "webpoc", // web类
|
||||
"mysql", "mssql", "redis", "mongodb", "postgres", // 数据库类
|
||||
"oracle", "memcached", "elasticsearch", "rabbitmq", "kafka", "activemq", "cassandra", "neo4j", // 数据库类
|
||||
"ftp", "ssh", "telnet", "smb", "rdp", "vnc", "netbios", "ldap", "smtp", "imap", "pop3", "snmp", "modbus", "rsync", // 服务类
|
||||
@ -26,14 +26,14 @@ var pluginGroups = map[string][]string{
|
||||
"findnet", // 其他
|
||||
},
|
||||
ModeBasic: {
|
||||
"web", "ftp", "ssh", "smb", "findnet",
|
||||
"webtitle", "ftp", "ssh", "smb", "findnet",
|
||||
},
|
||||
ModeDatabase: {
|
||||
"mysql", "mssql", "redis", "mongodb",
|
||||
"postgres", "oracle", "memcached", "elasticsearch", "rabbitmq", "kafka", "activemq", "cassandra", "neo4j",
|
||||
},
|
||||
ModeWeb: {
|
||||
"web", "fcgi",
|
||||
"webtitle", "webpoc",
|
||||
},
|
||||
ModeService: {
|
||||
"ftp", "ssh", "telnet", "smb", "rdp", "vnc", "netbios", "ldap", "smtp", "imap", "pop3", "modbus", "rsync",
|
||||
|
@ -126,27 +126,23 @@ func PortConnect(addr Addr, results chan<- ScanResult, timeout int64, wg *sync.W
|
||||
Port: addr.port,
|
||||
}
|
||||
|
||||
// 进行服务识别
|
||||
if conn != nil {
|
||||
// 只在未跳过指纹识别时进行服务识别
|
||||
if !Common.SkipFingerprint && conn != nil {
|
||||
scanner := NewPortInfoScanner(addr.ip, addr.port, conn, time.Duration(timeout)*time.Second)
|
||||
if serviceInfo, err := scanner.Identify(); err == nil {
|
||||
result.Service = serviceInfo
|
||||
|
||||
// 打印服务识别信息
|
||||
var logMsg strings.Builder
|
||||
logMsg.WriteString(fmt.Sprintf("服务识别 %s => ", address))
|
||||
|
||||
// 添加服务名称
|
||||
if serviceInfo.Name != "unknown" {
|
||||
logMsg.WriteString(fmt.Sprintf("[%s]", serviceInfo.Name))
|
||||
}
|
||||
|
||||
// 添加版本信息
|
||||
if serviceInfo.Version != "" {
|
||||
logMsg.WriteString(fmt.Sprintf(" 版本:%s", serviceInfo.Version))
|
||||
}
|
||||
|
||||
// 添加其他有用的信息
|
||||
if v, ok := serviceInfo.Extras["vendor_product"]; ok && v != "" {
|
||||
logMsg.WriteString(fmt.Sprintf(" 产品:%s", v))
|
||||
}
|
||||
@ -157,7 +153,6 @@ func PortConnect(addr Addr, results chan<- ScanResult, timeout int64, wg *sync.W
|
||||
logMsg.WriteString(fmt.Sprintf(" 信息:%s", v))
|
||||
}
|
||||
|
||||
// 如果有Banner且长度合适,也输出
|
||||
if len(serviceInfo.Banner) > 0 && len(serviceInfo.Banner) < 100 {
|
||||
logMsg.WriteString(fmt.Sprintf(" Banner:[%s]", strings.TrimSpace(serviceInfo.Banner)))
|
||||
}
|
||||
@ -166,7 +161,6 @@ func PortConnect(addr Addr, results chan<- ScanResult, timeout int64, wg *sync.W
|
||||
}
|
||||
}
|
||||
|
||||
// 发送结果
|
||||
results <- result
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ func init() {
|
||||
})
|
||||
|
||||
// web 相关插件添加 WebPorts 配置
|
||||
Common.RegisterPlugin("web", Common.ScanPlugin{
|
||||
Common.RegisterPlugin("webtitle", Common.ScanPlugin{
|
||||
Name: "WebTitle",
|
||||
Ports: Common.ParsePortsFromString(Common.WebPorts), // 将 WebPorts 字符串解析为端口数组
|
||||
ScanFunc: Plugins.WebTitle,
|
||||
|
@ -13,28 +13,74 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 定义在文件开头
|
||||
var (
|
||||
LocalScan bool // 本地扫描模式标识
|
||||
WebScan bool // Web扫描模式标识
|
||||
)
|
||||
|
||||
// Scan 执行扫描主流程
|
||||
func Scan(info Common.HostInfo) {
|
||||
Common.LogInfo("开始信息扫描")
|
||||
|
||||
// 初始化HTTP客户端
|
||||
lib.Inithttp()
|
||||
|
||||
// 处理特殊情况
|
||||
if info.Host == "" && len(Common.URLs) == 0 {
|
||||
// Host为空且没有URLs,设置Local模式
|
||||
LocalScan = true
|
||||
Common.ScanMode = Common.ModeLocal
|
||||
Common.LogInfo("未检测到目标,自动切换为本地扫描模式")
|
||||
} else if len(Common.URLs) > 0 {
|
||||
// 存在URLs时设置为Web模式
|
||||
WebScan = true
|
||||
Common.ScanMode = Common.ModeWeb
|
||||
Common.LogInfo("检测到URL列表,自动切换为Web扫描模式")
|
||||
}
|
||||
|
||||
Common.ParseScanMode(Common.ScanMode)
|
||||
|
||||
ch := make(chan struct{}, Common.ThreadNum)
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
// 本地信息收集模式
|
||||
if Common.LocalScan {
|
||||
if LocalScan {
|
||||
executeScans([]Common.HostInfo{info}, &ch, &wg)
|
||||
finishScan(&wg)
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化并解析目标
|
||||
hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts)
|
||||
if err != nil {
|
||||
Common.LogError(fmt.Sprintf("解析主机错误: %v", err))
|
||||
// Web模式直接处理URLs
|
||||
if WebScan {
|
||||
var targetInfos []Common.HostInfo
|
||||
for _, url := range Common.URLs {
|
||||
urlInfo := info
|
||||
// 确保URL包含协议前缀
|
||||
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
|
||||
url = "http://" + url
|
||||
}
|
||||
urlInfo.Url = url
|
||||
targetInfos = append(targetInfos, urlInfo)
|
||||
}
|
||||
if len(targetInfos) > 0 {
|
||||
Common.LogInfo("开始Web扫描")
|
||||
executeScans(targetInfos, &ch, &wg)
|
||||
finishScan(&wg)
|
||||
}
|
||||
return
|
||||
}
|
||||
lib.Inithttp()
|
||||
|
||||
// 常规模式:初始化并解析目标
|
||||
var hosts []string
|
||||
var err error
|
||||
if info.Host != "" {
|
||||
hosts, err = Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts)
|
||||
if err != nil {
|
||||
Common.LogError(fmt.Sprintf("解析主机错误: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 执行目标扫描
|
||||
executeScan(hosts, info, &ch, &wg)
|
||||
@ -117,10 +163,8 @@ func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGro
|
||||
}
|
||||
|
||||
loadedPlugins := make([]string, 0)
|
||||
// 先遍历一遍计算实际要执行的任务数
|
||||
actualTasks := 0
|
||||
|
||||
// 定义任务结构
|
||||
type ScanTask struct {
|
||||
pluginName string
|
||||
target Common.HostInfo
|
||||
@ -137,7 +181,19 @@ func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGro
|
||||
continue
|
||||
}
|
||||
|
||||
if Common.LocalScan {
|
||||
// Web模式特殊处理
|
||||
if WebScan {
|
||||
actualTasks++
|
||||
loadedPlugins = append(loadedPlugins, pluginName)
|
||||
tasks = append(tasks, ScanTask{
|
||||
pluginName: pluginName,
|
||||
target: target,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// 本地扫描模式
|
||||
if LocalScan {
|
||||
if len(plugin.Ports) == 0 {
|
||||
actualTasks++
|
||||
loadedPlugins = append(loadedPlugins, pluginName)
|
||||
@ -149,6 +205,7 @@ func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGro
|
||||
continue
|
||||
}
|
||||
|
||||
// 单插件模式
|
||||
if isSinglePlugin {
|
||||
actualTasks++
|
||||
loadedPlugins = append(loadedPlugins, pluginName)
|
||||
@ -159,6 +216,7 @@ func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGro
|
||||
continue
|
||||
}
|
||||
|
||||
// 常规模式
|
||||
if len(plugin.Ports) > 0 {
|
||||
if plugin.HasPort(targetPort) {
|
||||
actualTasks++
|
||||
@ -193,7 +251,7 @@ func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGro
|
||||
|
||||
Common.LogInfo(fmt.Sprintf("加载的插件: %s", strings.Join(finalPlugins, ", ")))
|
||||
|
||||
// 在初始化进度条的地方添加判断
|
||||
// 初始化进度条
|
||||
if !Common.NoProgress {
|
||||
Common.ProgressBar = progressbar.NewOptions(actualTasks,
|
||||
progressbar.OptionEnableColorCodes(true),
|
||||
@ -213,7 +271,7 @@ func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGro
|
||||
)
|
||||
}
|
||||
|
||||
// 开始执行收集到的所有任务
|
||||
// 执行收集的任务
|
||||
for _, task := range tasks {
|
||||
AddScan(task.pluginName, task.target, ch, wg)
|
||||
}
|
||||
@ -222,16 +280,18 @@ func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGro
|
||||
// finishScan 完成扫描任务
|
||||
func finishScan(wg *sync.WaitGroup) {
|
||||
wg.Wait()
|
||||
// 确保进度条完成
|
||||
Common.ProgressBar.Finish()
|
||||
fmt.Println() // 添加一个换行
|
||||
// 确保进度条完成,只在存在进度条时调用
|
||||
if Common.ProgressBar != nil {
|
||||
Common.ProgressBar.Finish()
|
||||
fmt.Println() // 添加一个换行
|
||||
}
|
||||
Common.LogSuccess(fmt.Sprintf("扫描已完成: %v/%v", Common.End, Common.Num))
|
||||
}
|
||||
|
||||
// Mutex用于保护共享资源的并发访问
|
||||
var Mutex = &sync.Mutex{}
|
||||
|
||||
// AddScan 也需要修改
|
||||
// AddScan
|
||||
func AddScan(plugin string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
||||
*ch <- struct{}{}
|
||||
wg.Add(1)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -279,26 +279,30 @@ func IsAdmin() bool {
|
||||
func MiniDump(info *Common.HostInfo) (err error) {
|
||||
// 先检查管理员权限
|
||||
if !IsAdmin() {
|
||||
Common.LogError("需要管理员权限才能执行此操作")
|
||||
return fmt.Errorf("需要管理员权限才能执行此操作")
|
||||
}
|
||||
|
||||
pm, err := NewProcessManager()
|
||||
if err != nil {
|
||||
Common.LogError(fmt.Sprintf("初始化进程管理器失败: %v", err))
|
||||
return fmt.Errorf("初始化进程管理器失败: %v", err)
|
||||
}
|
||||
|
||||
// 查找 lsass.exe
|
||||
pid, err := pm.FindProcess("lsass.exe")
|
||||
if err != nil {
|
||||
Common.LogError(fmt.Sprintf("查找进程失败: %v", err))
|
||||
return fmt.Errorf("查找进程失败: %v", err)
|
||||
}
|
||||
fmt.Printf("找到进程 lsass.exe, PID: %d\n", pid)
|
||||
Common.LogSuccess(fmt.Sprintf("找到进程 lsass.exe, PID: %d", pid))
|
||||
|
||||
// 提升权限
|
||||
if err := pm.ElevatePrivileges(); err != nil {
|
||||
Common.LogError(fmt.Sprintf("提升权限失败: %v", err))
|
||||
return fmt.Errorf("提升权限失败: %v", err)
|
||||
}
|
||||
fmt.Println("成功提升进程权限")
|
||||
Common.LogSuccess("成功提升进程权限")
|
||||
|
||||
// 创建输出路径
|
||||
outputPath := filepath.Join(".", fmt.Sprintf("fscan-%d.dmp", pid))
|
||||
@ -306,9 +310,10 @@ func MiniDump(info *Common.HostInfo) (err error) {
|
||||
// 执行转储
|
||||
if err := pm.DumpProcess(pid, outputPath); err != nil {
|
||||
os.Remove(outputPath)
|
||||
Common.LogError(fmt.Sprintf("进程转储失败: %v", err))
|
||||
return fmt.Errorf("进程转储失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("成功将进程内存转储到文件: %s\n", outputPath)
|
||||
Common.LogSuccess(fmt.Sprintf("成功将进程内存转储到文件: %s", outputPath))
|
||||
return nil
|
||||
}
|
||||
|
@ -18,23 +18,27 @@ import (
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
)
|
||||
|
||||
// WebTitle 获取Web标题并执行扫描
|
||||
// WebTitle 获取Web标题和指纹信息
|
||||
func WebTitle(info *Common.HostInfo) error {
|
||||
Common.LogDebug(fmt.Sprintf("开始获取Web标题,初始信息: %+v", info))
|
||||
|
||||
// 获取网站标题信息
|
||||
err, CheckData := GOWebTitle(info)
|
||||
Common.LogDebug(fmt.Sprintf("GOWebTitle执行完成 - 错误: %v, 检查数据长度: %d", err, len(CheckData)))
|
||||
|
||||
info.Infostr = WebScan.InfoCheck(info.Url, &CheckData)
|
||||
Common.LogDebug(fmt.Sprintf("信息检查完成,获得信息: %v", info.Infostr))
|
||||
|
||||
// 检查是否为打印机,避免意外打印
|
||||
for _, v := range info.Infostr {
|
||||
if v == "打印机" {
|
||||
Common.LogDebug("检测到打印机,停止扫描")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 根据配置决定是否执行漏洞扫描
|
||||
if !Common.DisablePoc && err == nil {
|
||||
WebScan.WebScan(info)
|
||||
} else {
|
||||
// 输出错误信息(如果有)
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("网站标题 %v %v", info.Url, err)
|
||||
Common.LogError(errlog)
|
||||
}
|
||||
@ -44,8 +48,11 @@ func WebTitle(info *Common.HostInfo) error {
|
||||
|
||||
// GOWebTitle 获取网站标题并处理URL
|
||||
func GOWebTitle(info *Common.HostInfo) (err error, CheckData []WebScan.CheckDatas) {
|
||||
Common.LogDebug(fmt.Sprintf("开始处理URL: %s", info.Url))
|
||||
|
||||
// 如果URL未指定,根据端口生成URL
|
||||
if info.Url == "" {
|
||||
Common.LogDebug("URL为空,根据端口生成URL")
|
||||
switch info.Ports {
|
||||
case "80":
|
||||
info.Url = fmt.Sprintf("http://%s", info.Host)
|
||||
@ -53,28 +60,37 @@ func GOWebTitle(info *Common.HostInfo) (err error, CheckData []WebScan.CheckData
|
||||
info.Url = fmt.Sprintf("https://%s", info.Host)
|
||||
default:
|
||||
host := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||
Common.LogDebug(fmt.Sprintf("正在检测主机协议: %s", host))
|
||||
protocol := GetProtocol(host, Common.Timeout)
|
||||
Common.LogDebug(fmt.Sprintf("检测到协议: %s", protocol))
|
||||
info.Url = fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports)
|
||||
}
|
||||
} else {
|
||||
// 处理未指定协议的URL
|
||||
if !strings.Contains(info.Url, "://") {
|
||||
Common.LogDebug("URL未包含协议,开始检测")
|
||||
host := strings.Split(info.Url, "/")[0]
|
||||
protocol := GetProtocol(host, Common.Timeout)
|
||||
Common.LogDebug(fmt.Sprintf("检测到协议: %s", protocol))
|
||||
info.Url = fmt.Sprintf("%s://%s", protocol, info.Url)
|
||||
}
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("协议检测完成后的URL: %s", info.Url))
|
||||
|
||||
// 第一次获取URL
|
||||
Common.LogDebug("第一次尝试访问URL")
|
||||
err, result, CheckData := geturl(info, 1, CheckData)
|
||||
Common.LogDebug(fmt.Sprintf("第一次访问结果 - 错误: %v, 返回信息: %s", err, result))
|
||||
if err != nil && !strings.Contains(err.Error(), "EOF") {
|
||||
return
|
||||
}
|
||||
|
||||
// 处理URL跳转
|
||||
if strings.Contains(result, "://") {
|
||||
Common.LogDebug(fmt.Sprintf("检测到重定向到: %s", result))
|
||||
info.Url = result
|
||||
err, result, CheckData = geturl(info, 3, CheckData)
|
||||
Common.LogDebug(fmt.Sprintf("重定向请求结果 - 错误: %v, 返回信息: %s", err, result))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -82,11 +98,14 @@ func GOWebTitle(info *Common.HostInfo) (err error, CheckData []WebScan.CheckData
|
||||
|
||||
// 处理HTTP到HTTPS的升级
|
||||
if result == "https" && !strings.HasPrefix(info.Url, "https://") {
|
||||
Common.LogDebug("正在升级到HTTPS")
|
||||
info.Url = strings.Replace(info.Url, "http://", "https://", 1)
|
||||
Common.LogDebug(fmt.Sprintf("升级后的URL: %s", info.Url))
|
||||
err, result, CheckData = geturl(info, 1, CheckData)
|
||||
|
||||
// 处理升级后的跳转
|
||||
if strings.Contains(result, "://") {
|
||||
Common.LogDebug(fmt.Sprintf("HTTPS升级后发现重定向到: %s", result))
|
||||
info.Url = result
|
||||
err, _, CheckData = geturl(info, 3, CheckData)
|
||||
if err != nil {
|
||||
@ -95,38 +114,34 @@ func GOWebTitle(info *Common.HostInfo) (err error, CheckData []WebScan.CheckData
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("GOWebTitle执行完成 - 错误: %v", err))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// geturl 获取URL响应内容和信息
|
||||
// 参数:
|
||||
// - info: 主机配置信息
|
||||
// - flag: 请求类型标志(1:首次尝试 2:获取favicon 3:处理302跳转 4:处理400转https)
|
||||
// - CheckData: 检查数据数组
|
||||
//
|
||||
// 返回:
|
||||
// - error: 错误信息
|
||||
// - string: 重定向URL或协议
|
||||
// - []WebScan.CheckDatas: 更新后的检查数据
|
||||
func geturl(info *Common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) {
|
||||
Common.LogDebug(fmt.Sprintf("geturl开始执行 - URL: %s, 标志位: %d", info.Url, flag))
|
||||
|
||||
// 处理目标URL
|
||||
Url := info.Url
|
||||
if flag == 2 {
|
||||
// 获取favicon.ico的URL
|
||||
Common.LogDebug("处理favicon.ico URL")
|
||||
URL, err := url.Parse(Url)
|
||||
if err == nil {
|
||||
Url = fmt.Sprintf("%s://%s/favicon.ico", URL.Scheme, URL.Host)
|
||||
} else {
|
||||
Url += "/favicon.ico"
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("favicon URL: %s", Url))
|
||||
}
|
||||
|
||||
// 创建HTTP请求
|
||||
Common.LogDebug("开始创建HTTP请求")
|
||||
req, err := http.NewRequest("GET", Url, nil)
|
||||
if err != nil {
|
||||
Common.LogDebug(fmt.Sprintf("创建HTTP请求失败: %v", err))
|
||||
return err, "", CheckData
|
||||
}
|
||||
|
||||
@ -138,36 +153,52 @@ func geturl(info *Common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (er
|
||||
req.Header.Set("Cookie", Common.Cookie)
|
||||
}
|
||||
req.Header.Set("Connection", "close")
|
||||
Common.LogDebug("已设置请求头")
|
||||
|
||||
// 选择HTTP客户端
|
||||
var client *http.Client
|
||||
if flag == 1 {
|
||||
client = lib.ClientNoRedirect // 不跟随重定向
|
||||
client = lib.ClientNoRedirect
|
||||
Common.LogDebug("使用不跟随重定向的客户端")
|
||||
} else {
|
||||
client = lib.Client // 跟随重定向
|
||||
client = lib.Client
|
||||
Common.LogDebug("使用普通客户端")
|
||||
}
|
||||
|
||||
// 检查客户端是否为空
|
||||
if client == nil {
|
||||
Common.LogDebug("错误: HTTP客户端为空")
|
||||
return fmt.Errorf("HTTP客户端未初始化"), "", CheckData
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
Common.LogDebug("开始发送HTTP请求")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
Common.LogDebug(fmt.Sprintf("HTTP请求失败: %v", err))
|
||||
return err, "https", CheckData
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
Common.LogDebug(fmt.Sprintf("收到HTTP响应,状态码: %d", resp.StatusCode))
|
||||
|
||||
// 读取响应内容
|
||||
body, err := getRespBody(resp)
|
||||
if err != nil {
|
||||
Common.LogDebug(fmt.Sprintf("读取响应内容失败: %v", err))
|
||||
return err, "https", CheckData
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("成功读取响应内容,长度: %d", len(body)))
|
||||
|
||||
// 保存检查数据
|
||||
CheckData = append(CheckData, WebScan.CheckDatas{body, fmt.Sprintf("%s", resp.Header)})
|
||||
Common.LogDebug("已保存检查数据")
|
||||
|
||||
// 处理非favicon请求
|
||||
var reurl string
|
||||
if flag != 2 {
|
||||
// 处理编码
|
||||
if !utf8.Valid(body) {
|
||||
Common.LogDebug("检测到非UTF8编码,尝试GBK解码")
|
||||
body, _ = simplifiedchinese.GBK.NewDecoder().Bytes(body)
|
||||
}
|
||||
|
||||
@ -177,11 +208,13 @@ func geturl(info *Common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (er
|
||||
if length == "" {
|
||||
length = fmt.Sprintf("%v", len(body))
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("提取的标题: %s, 内容长度: %s", title, length))
|
||||
|
||||
// 处理重定向
|
||||
redirURL, err1 := resp.Location()
|
||||
if err1 == nil {
|
||||
reurl = redirURL.String()
|
||||
Common.LogDebug(fmt.Sprintf("检测到重定向URL: %s", reurl))
|
||||
}
|
||||
|
||||
// 输出结果
|
||||
@ -195,22 +228,28 @@ func geturl(info *Common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (er
|
||||
|
||||
// 返回结果
|
||||
if reurl != "" {
|
||||
Common.LogDebug(fmt.Sprintf("返回重定向URL: %s", reurl))
|
||||
return nil, reurl, CheckData
|
||||
}
|
||||
if resp.StatusCode == 400 && !strings.HasPrefix(info.Url, "https") {
|
||||
Common.LogDebug("返回HTTPS升级标志")
|
||||
return nil, "https", CheckData
|
||||
}
|
||||
Common.LogDebug("geturl执行完成,无特殊返回")
|
||||
return nil, "", CheckData
|
||||
}
|
||||
|
||||
// getRespBody 读取HTTP响应体内容
|
||||
func getRespBody(oResp *http.Response) ([]byte, error) {
|
||||
Common.LogDebug("开始读取响应体内容")
|
||||
var body []byte
|
||||
|
||||
// 处理gzip压缩的响应
|
||||
if oResp.Header.Get("Content-Encoding") == "gzip" {
|
||||
Common.LogDebug("检测到gzip压缩,开始解压")
|
||||
gr, err := gzip.NewReader(oResp.Body)
|
||||
if err != nil {
|
||||
Common.LogDebug(fmt.Sprintf("创建gzip解压器失败: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
defer gr.Close()
|
||||
@ -220,6 +259,7 @@ func getRespBody(oResp *http.Response) ([]byte, error) {
|
||||
buf := make([]byte, 1024)
|
||||
n, err := gr.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
Common.LogDebug(fmt.Sprintf("读取压缩内容失败: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
@ -227,25 +267,32 @@ func getRespBody(oResp *http.Response) ([]byte, error) {
|
||||
}
|
||||
body = append(body, buf...)
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("gzip解压完成,内容长度: %d", len(body)))
|
||||
} else {
|
||||
// 直接读取未压缩的响应
|
||||
Common.LogDebug("读取未压缩的响应内容")
|
||||
raw, err := io.ReadAll(oResp.Body)
|
||||
if err != nil {
|
||||
Common.LogDebug(fmt.Sprintf("读取响应内容失败: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
body = raw
|
||||
Common.LogDebug(fmt.Sprintf("读取完成,内容长度: %d", len(body)))
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// gettitle 从HTML内容中提取网页标题
|
||||
func gettitle(body []byte) (title string) {
|
||||
Common.LogDebug("开始提取网页标题")
|
||||
|
||||
// 使用正则表达式匹配title标签内容
|
||||
re := regexp.MustCompile("(?ims)<title.*?>(.*?)</title>")
|
||||
find := re.FindSubmatch(body)
|
||||
|
||||
if len(find) > 1 {
|
||||
title = string(find[1])
|
||||
Common.LogDebug(fmt.Sprintf("找到原始标题: %s", title))
|
||||
|
||||
// 清理标题内容
|
||||
title = strings.TrimSpace(title) // 去除首尾空格
|
||||
@ -255,38 +302,48 @@ func gettitle(body []byte) (title string) {
|
||||
|
||||
// 截断过长的标题
|
||||
if len(title) > 100 {
|
||||
Common.LogDebug("标题超过100字符,进行截断")
|
||||
title = title[:100]
|
||||
}
|
||||
|
||||
// 处理空标题
|
||||
if title == "" {
|
||||
title = "\"\"" // 空标题显示为双引号
|
||||
Common.LogDebug("标题为空,使用双引号代替")
|
||||
title = "\"\""
|
||||
}
|
||||
} else {
|
||||
title = "无标题" // 没有找到title标签
|
||||
Common.LogDebug("未找到标题标签")
|
||||
title = "无标题"
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("最终标题: %s", title))
|
||||
return
|
||||
}
|
||||
|
||||
// GetProtocol 检测目标主机的协议类型(HTTP/HTTPS)
|
||||
func GetProtocol(host string, Timeout int64) (protocol string) {
|
||||
Common.LogDebug(fmt.Sprintf("开始检测主机协议 - 主机: %s, 超时: %d秒", host, Timeout))
|
||||
protocol = "http"
|
||||
|
||||
// 根据标准端口快速判断协议
|
||||
if strings.HasSuffix(host, ":80") || !strings.Contains(host, ":") {
|
||||
Common.LogDebug("检测到HTTP标准端口或无端口,使用HTTP协议")
|
||||
return
|
||||
} else if strings.HasSuffix(host, ":443") {
|
||||
Common.LogDebug("检测到HTTPS标准端口,使用HTTPS协议")
|
||||
protocol = "https"
|
||||
return
|
||||
}
|
||||
|
||||
// 尝试建立TCP连接
|
||||
Common.LogDebug("尝试建立TCP连接")
|
||||
socksconn, err := Common.WrapperTcpWithTimeout("tcp", host, time.Duration(Timeout)*time.Second)
|
||||
if err != nil {
|
||||
Common.LogDebug(fmt.Sprintf("TCP连接失败: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 尝试TLS握手
|
||||
Common.LogDebug("开始TLS握手")
|
||||
conn := tls.Client(socksconn, &tls.Config{
|
||||
MinVersion: tls.VersionTLS10,
|
||||
InsecureSkipVerify: true,
|
||||
@ -300,6 +357,7 @@ func GetProtocol(host string, Timeout int64) (protocol string) {
|
||||
Common.LogError(fmt.Sprintf("连接关闭时发生错误: %v", err))
|
||||
}
|
||||
}()
|
||||
Common.LogDebug("关闭连接")
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
@ -310,8 +368,12 @@ func GetProtocol(host string, Timeout int64) (protocol string) {
|
||||
// 执行TLS握手
|
||||
err = conn.Handshake()
|
||||
if err == nil || strings.Contains(err.Error(), "handshake failure") {
|
||||
Common.LogDebug("TLS握手成功或握手失败但确认是HTTPS协议")
|
||||
protocol = "https"
|
||||
} else {
|
||||
Common.LogDebug(fmt.Sprintf("TLS握手失败: %v,使用HTTP协议", err))
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("协议检测完成,使用: %s", protocol))
|
||||
return protocol
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"github.com/shadow1ng/fscan/WebScan/lib"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -19,22 +20,43 @@ var AllPocs []*lib.Poc
|
||||
|
||||
// WebScan 执行Web漏洞扫描
|
||||
func WebScan(info *Common.HostInfo) {
|
||||
// 确保POC只初始化一次
|
||||
once.Do(initpoc)
|
||||
|
||||
// 构建扫描信息
|
||||
var pocinfo = Common.Pocinfo
|
||||
urlParts := strings.Split(info.Url, "/")
|
||||
pocinfo.Target = strings.Join(urlParts[:3], "/")
|
||||
|
||||
// 执行扫描
|
||||
if pocinfo.PocName != "" {
|
||||
// 指定POC扫描
|
||||
// 自动构建URL
|
||||
if info.Url == "" {
|
||||
info.Url = fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
|
||||
}
|
||||
|
||||
urlParts := strings.Split(info.Url, "/")
|
||||
|
||||
// 检查切片长度并构建目标URL
|
||||
if len(urlParts) >= 3 {
|
||||
pocinfo.Target = strings.Join(urlParts[:3], "/")
|
||||
} else {
|
||||
pocinfo.Target = info.Url
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描目标: %s", pocinfo.Target))
|
||||
|
||||
// 如果是直接调用WebPoc(没有指定pocName),执行所有POC
|
||||
if pocinfo.PocName == "" && len(info.Infostr) == 0 {
|
||||
Common.LogDebug("直接调用WebPoc,执行所有POC")
|
||||
Execute(pocinfo)
|
||||
} else {
|
||||
// 根据指纹信息选择POC扫描
|
||||
for _, infostr := range info.Infostr {
|
||||
pocinfo.PocName = lib.CheckInfoPoc(infostr)
|
||||
// 根据指纹信息选择性执行POC
|
||||
if len(info.Infostr) > 0 {
|
||||
for _, infostr := range info.Infostr {
|
||||
pocinfo.PocName = lib.CheckInfoPoc(infostr)
|
||||
if pocinfo.PocName != "" {
|
||||
Common.LogDebug(fmt.Sprintf("根据指纹 %s 执行对应POC", infostr))
|
||||
Execute(pocinfo)
|
||||
}
|
||||
}
|
||||
} else if pocinfo.PocName != "" {
|
||||
// 指定了特定的POC
|
||||
Common.LogDebug(fmt.Sprintf("执行指定POC: %s", pocinfo.PocName))
|
||||
Execute(pocinfo)
|
||||
}
|
||||
}
|
||||
@ -42,6 +64,20 @@ func WebScan(info *Common.HostInfo) {
|
||||
|
||||
// Execute 执行具体的POC检测
|
||||
func Execute(PocInfo Common.PocInfo) {
|
||||
Common.LogDebug(fmt.Sprintf("开始执行POC检测,目标: %s", PocInfo.Target))
|
||||
|
||||
// 确保URL格式正确
|
||||
if !strings.HasPrefix(PocInfo.Target, "http://") && !strings.HasPrefix(PocInfo.Target, "https://") {
|
||||
PocInfo.Target = "http://" + PocInfo.Target
|
||||
}
|
||||
|
||||
// 验证URL格式
|
||||
_, err := url.Parse(PocInfo.Target)
|
||||
if err != nil {
|
||||
Common.LogError(fmt.Sprintf("无效的URL格式 %v: %v", PocInfo.Target, err))
|
||||
return
|
||||
}
|
||||
|
||||
// 创建基础HTTP请求
|
||||
req, err := http.NewRequest("GET", PocInfo.Target, nil)
|
||||
if err != nil {
|
||||
@ -59,12 +95,16 @@ func Execute(PocInfo Common.PocInfo) {
|
||||
|
||||
// 根据名称筛选POC并执行
|
||||
pocs := filterPoc(PocInfo.PocName)
|
||||
Common.LogDebug(fmt.Sprintf("筛选到的POC数量: %d", len(pocs)))
|
||||
lib.CheckMultiPoc(req, pocs, Common.PocNum)
|
||||
}
|
||||
|
||||
// initpoc 初始化POC加载
|
||||
func initpoc() {
|
||||
Common.LogDebug("开始初始化POC")
|
||||
|
||||
if Common.PocPath == "" {
|
||||
Common.LogDebug("从内置目录加载POC")
|
||||
// 从嵌入的POC目录加载
|
||||
entries, err := Pocs.ReadDir("pocs")
|
||||
if err != nil {
|
||||
@ -78,9 +118,11 @@ func initpoc() {
|
||||
if strings.HasSuffix(filename, ".yaml") || strings.HasSuffix(filename, ".yml") {
|
||||
if poc, err := lib.LoadPoc(filename, Pocs); err == nil && poc != nil {
|
||||
AllPocs = append(AllPocs, poc)
|
||||
} else if err != nil {
|
||||
}
|
||||
}
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("内置POC加载完成,共加载 %d 个", len(AllPocs)))
|
||||
} else {
|
||||
// 从指定目录加载POC
|
||||
Common.LogSuccess(fmt.Sprintf("从目录加载POC: %s", Common.PocPath))
|
||||
@ -92,6 +134,7 @@ func initpoc() {
|
||||
if !info.IsDir() && (strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")) {
|
||||
if poc, err := lib.LoadPocbyPath(path); err == nil && poc != nil {
|
||||
AllPocs = append(AllPocs, poc)
|
||||
} else if err != nil {
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -100,12 +143,16 @@ func initpoc() {
|
||||
if err != nil {
|
||||
Common.LogError(fmt.Sprintf("加载外部POC失败: %v", err))
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("外部POC加载完成,共加载 %d 个", len(AllPocs)))
|
||||
}
|
||||
}
|
||||
|
||||
// filterPoc 根据POC名称筛选
|
||||
func filterPoc(pocname string) []*lib.Poc {
|
||||
Common.LogDebug(fmt.Sprintf("开始筛选POC,筛选条件: %s", pocname))
|
||||
|
||||
if pocname == "" {
|
||||
Common.LogDebug(fmt.Sprintf("未指定POC名称,返回所有POC: %d 个", len(AllPocs)))
|
||||
return AllPocs
|
||||
}
|
||||
|
||||
@ -115,5 +162,6 @@ func filterPoc(pocname string) []*lib.Poc {
|
||||
matchedPocs = append(matchedPocs, poc)
|
||||
}
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("POC筛选完成,匹配到 %d 个", len(matchedPocs)))
|
||||
return matchedPocs
|
||||
}
|
||||
|
@ -637,7 +637,7 @@ func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Re
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
||||
// 检查表达式执行结果
|
||||
if fmt.Sprintf("%v", out) == "false" {
|
||||
return false, nil
|
||||
|
Loading…
Reference in New Issue
Block a user