diff --git a/Common/Config.go b/Common/Config.go index 1111b60..00d54f9 100644 --- a/Common/Config.go +++ b/Common/Config.go @@ -886,9 +886,6 @@ var ( Command string SkipFingerprint bool - // 本地扫描配置 - LocalScan bool - // 文件配置 HostsFile string // 原HostFile UsersFile string // 原Userfile @@ -897,6 +894,8 @@ var ( PortsFile string // 原PortFile // Web配置 + TargetURL string // 原URL + URLsFile string // 原UrlFile URLs []string // 原Urls WebTimeout int64 = 5 HttpProxy string // 原Proxy diff --git a/Common/Flag.go b/Common/Flag.go index 30231d9..a3ef824 100644 --- a/Common/Flag.go +++ b/Common/Flag.go @@ -200,9 +200,6 @@ func Flag(Info *HostInfo) { flag.StringVar(&Command, "c", "", "指定要执行的系统命令(支持ssh和wmiexec)") flag.BoolVar(&SkipFingerprint, "skip", false, "跳过端口指纹识别") - // 本地扫描配置 - flag.BoolVar(&LocalScan, "local", false, "启用本地扫描模式") - // 文件配置 flag.StringVar(&HostsFile, "hf", "", "从文件中读取目标主机列表") flag.StringVar(&UsersFile, "userf", "", "从文件中读取用户名字典") @@ -211,6 +208,8 @@ func Flag(Info *HostInfo) { flag.StringVar(&PortsFile, "portf", "", "从文件中读取端口列表") // Web配置 + flag.StringVar(&TargetURL, "u", "", "指定目标URL") + flag.StringVar(&URLsFile, "uf", "", "从文件中读取URL列表") flag.StringVar(&Cookie, "cookie", "", "设置HTTP请求Cookie") flag.Int64Var(&WebTimeout, "wt", 5, "设置Web请求超时时间(单位:秒)") flag.StringVar(&HttpProxy, "proxy", "", "设置HTTP代理服务器") diff --git a/Common/Parse.go b/Common/Parse.go index d8029cf..c26def7 100644 --- a/Common/Parse.go +++ b/Common/Parse.go @@ -3,7 +3,6 @@ package Common import ( "bufio" "encoding/hex" - "flag" "fmt" "net/url" "os" @@ -114,6 +113,40 @@ func ParsePass(Info *HostInfo) error { LogInfo(fmt.Sprintf("加载有效哈希值: %d 个", validCount)) } + // 处理直接指定的URL列表 + if TargetURL != "" { + urls := strings.Split(TargetURL, ",") + tmpUrls := make(map[string]struct{}) + for _, url := range urls { + if url != "" { + if _, ok := tmpUrls[url]; !ok { + tmpUrls[url] = struct{}{} + URLs = append(URLs, url) + } + } + } + LogInfo(fmt.Sprintf("加载URL: %d 个", len(URLs))) + } + + // 从文件加载URL列表 + if URLsFile != "" { + urls, err := Readfile(URLsFile) + if err != nil { + return fmt.Errorf("读取URL文件失败: %v", err) + } + + tmpUrls := make(map[string]struct{}) + for _, url := range urls { + if url != "" { + if _, ok := tmpUrls[url]; !ok { + tmpUrls[url] = struct{}{} + URLs = append(URLs, url) + } + } + } + LogInfo(fmt.Sprintf("从文件加载URL: %d 个", len(urls))) + } + // 从文件加载端口列表 if PortsFile != "" { ports, err := Readfile(PortsFile) @@ -171,16 +204,9 @@ func Readfile(filename string) ([]string, error) { // ParseInput 解析和验证输入参数配置 func ParseInput(Info *HostInfo) error { - // 检查必要的目标参数 - if Info.Host == "" && HostsFile == "" { - LogError("未指定扫描目标") - flag.Usage() - return fmt.Errorf("必须指定扫描目标") - } - - // 如果是本地扫描模式,输出提示 - if LocalScan { - LogInfo("已启用本地扫描模式") + // 所有目标参数为空时表示本地扫描模式 + if Info.Host == "" && HostsFile == "" && TargetURL == "" && URLsFile == "" { + LogInfo("未指定扫描目标,将以本地模式运行") } // 配置基本参数 diff --git a/Common/ParseScanMode.go b/Common/ParseScanMode.go index f6a92a1..f3c478c 100644 --- a/Common/ParseScanMode.go +++ b/Common/ParseScanMode.go @@ -18,7 +18,7 @@ const ( // 插件分类映射表 - 所有插件名使用小写 var pluginGroups = map[string][]string{ ModeAll: { - "webtitle", "webpoc", "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", // 服务类 @@ -33,7 +33,7 @@ var pluginGroups = map[string][]string{ "postgres", "oracle", "memcached", "elasticsearch", "rabbitmq", "kafka", "activemq", "cassandra", "neo4j", }, ModeWeb: { - "webtitle", "webpoc", "fcgi", + "webtitle", "webpoc", }, ModeService: { "ftp", "ssh", "telnet", "smb", "rdp", "vnc", "netbios", "ldap", "smtp", "imap", "pop3", "modbus", "rsync", diff --git a/Core/Scanner.go b/Core/Scanner.go index 5607838..ce6f382 100644 --- a/Core/Scanner.go +++ b/Core/Scanner.go @@ -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) @@ -115,12 +161,10 @@ func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGro pluginsToRun = []string{mode} isSinglePlugin = true } - + 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) } diff --git a/Plugins/WebTitle.go b/Plugins/WebTitle.go index 653f920..b828425 100644 --- a/Plugins/WebTitle.go +++ b/Plugins/WebTitle.go @@ -20,13 +20,19 @@ import ( // 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 } } @@ -42,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) @@ -51,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 } @@ -80,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 { @@ -93,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 } @@ -136,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) } @@ -175,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)) } // 输出结果 @@ -193,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() @@ -218,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 { @@ -225,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)