2020-12-29 17:17:10 +08:00
|
|
|
|
package Plugins
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2021-05-12 10:57:12 +08:00
|
|
|
|
"compress/gzip"
|
2025-04-05 21:55:57 +08:00
|
|
|
|
"context"
|
2021-09-10 22:43:50 +08:00
|
|
|
|
"crypto/tls"
|
2020-12-29 17:17:10 +08:00
|
|
|
|
"fmt"
|
2021-05-12 10:57:12 +08:00
|
|
|
|
"io"
|
2025-04-05 21:55:57 +08:00
|
|
|
|
"net"
|
2020-12-29 17:17:10 +08:00
|
|
|
|
"net/http"
|
2021-04-22 23:41:56 +08:00
|
|
|
|
"net/url"
|
2020-12-29 17:17:10 +08:00
|
|
|
|
"regexp"
|
|
|
|
|
|
"strings"
|
2021-09-10 22:43:50 +08:00
|
|
|
|
"time"
|
2022-02-17 14:37:06 +08:00
|
|
|
|
"unicode/utf8"
|
2023-06-05 19:02:55 +03:00
|
|
|
|
|
2024-12-18 22:00:18 +08:00
|
|
|
|
"github.com/shadow1ng/fscan/Common"
|
2023-06-05 19:02:55 +03:00
|
|
|
|
"github.com/shadow1ng/fscan/WebScan"
|
|
|
|
|
|
"github.com/shadow1ng/fscan/WebScan/lib"
|
|
|
|
|
|
"golang.org/x/text/encoding/simplifiedchinese"
|
2021-04-22 23:41:56 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-01-04 11:49:59 +08:00
|
|
|
|
// WebTitle 获取Web标题和指纹信息
|
2024-12-19 16:15:53 +08:00
|
|
|
|
func WebTitle(info *Common.HostInfo) error {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("开始获取Web标题,初始信息: %+v", info))
|
|
|
|
|
|
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// 获取网站标题信息
|
2023-11-13 11:27:34 +08:00
|
|
|
|
err, CheckData := GOWebTitle(info)
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("GOWebTitle执行完成 - 错误: %v, 检查数据长度: %d", err, len(CheckData)))
|
|
|
|
|
|
|
2022-02-17 14:37:06 +08:00
|
|
|
|
info.Infostr = WebScan.InfoCheck(info.Url, &CheckData)
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("信息检查完成,获得信息: %v", info.Infostr))
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查是否为打印机,避免意外打印
|
2024-10-25 16:41:19 +08:00
|
|
|
|
for _, v := range info.Infostr {
|
|
|
|
|
|
if v == "打印机" {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("检测到打印机,停止扫描")
|
2024-10-25 16:41:19 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2025-01-04 11:49:59 +08:00
|
|
|
|
// 输出错误信息(如果有)
|
|
|
|
|
|
if err != nil {
|
2025-01-01 05:24:49 +08:00
|
|
|
|
errlog := fmt.Sprintf("网站标题 %v %v", info.Url, err)
|
2024-12-18 22:00:18 +08:00
|
|
|
|
Common.LogError(errlog)
|
2021-03-30 18:12:54 +08:00
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2021-03-30 18:12:54 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2025-04-13 15:46:37 +08:00
|
|
|
|
// GOWebTitle 获取网站标题并处理URL,增强错误处理和协议切换
|
2024-12-19 16:15:53 +08:00
|
|
|
|
func GOWebTitle(info *Common.HostInfo) (err error, CheckData []WebScan.CheckDatas) {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("开始处理URL: %s", info.Url))
|
|
|
|
|
|
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// 如果URL未指定,根据端口生成URL
|
2021-03-04 14:42:10 +08:00
|
|
|
|
if info.Url == "" {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("URL为空,根据端口生成URL")
|
2022-02-17 14:37:06 +08:00
|
|
|
|
switch info.Ports {
|
|
|
|
|
|
case "80":
|
2021-03-04 14:42:10 +08:00
|
|
|
|
info.Url = fmt.Sprintf("http://%s", info.Host)
|
2022-02-17 14:37:06 +08:00
|
|
|
|
case "443":
|
2021-03-04 14:42:10 +08:00
|
|
|
|
info.Url = fmt.Sprintf("https://%s", info.Host)
|
2022-02-17 14:37:06 +08:00
|
|
|
|
default:
|
2021-09-10 22:43:50 +08:00
|
|
|
|
host := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("正在检测主机协议: %s", host))
|
2024-12-18 22:00:18 +08:00
|
|
|
|
protocol := GetProtocol(host, Common.Timeout)
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("检测到协议: %s", protocol))
|
2021-09-10 22:43:50 +08:00
|
|
|
|
info.Url = fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports)
|
2021-03-04 14:42:10 +08:00
|
|
|
|
}
|
2020-12-29 17:17:10 +08:00
|
|
|
|
} else {
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// 处理未指定协议的URL
|
2021-03-04 14:42:10 +08:00
|
|
|
|
if !strings.Contains(info.Url, "://") {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("URL未包含协议,开始检测")
|
2022-02-17 14:37:06 +08:00
|
|
|
|
host := strings.Split(info.Url, "/")[0]
|
2024-12-18 22:00:18 +08:00
|
|
|
|
protocol := GetProtocol(host, Common.Timeout)
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("检测到协议: %s", protocol))
|
2021-09-10 22:43:50 +08:00
|
|
|
|
info.Url = fmt.Sprintf("%s://%s", protocol, info.Url)
|
2021-03-04 14:42:10 +08:00
|
|
|
|
}
|
2020-12-29 17:17:10 +08:00
|
|
|
|
}
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("协议检测完成后的URL: %s", info.Url))
|
2021-09-10 22:43:50 +08:00
|
|
|
|
|
2025-04-13 15:46:37 +08:00
|
|
|
|
// 记录原始URL协议
|
|
|
|
|
|
originalProtocol := "http"
|
|
|
|
|
|
if strings.HasPrefix(info.Url, "https://") {
|
|
|
|
|
|
originalProtocol = "https"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// 第一次获取URL
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("第一次尝试访问URL")
|
2023-11-13 11:27:34 +08:00
|
|
|
|
err, result, CheckData := geturl(info, 1, CheckData)
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("第一次访问结果 - 错误: %v, 返回信息: %s", err, result))
|
2025-04-13 15:46:37 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果访问失败并且使用的是HTTPS,尝试降级到HTTP
|
2021-05-14 16:02:22 +08:00
|
|
|
|
if err != nil && !strings.Contains(err.Error(), "EOF") {
|
2025-04-13 15:46:37 +08:00
|
|
|
|
if originalProtocol == "https" {
|
|
|
|
|
|
Common.LogDebug("HTTPS访问失败,尝试降级到HTTP")
|
|
|
|
|
|
// 替换协议部分
|
|
|
|
|
|
info.Url = strings.Replace(info.Url, "https://", "http://", 1)
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("降级后的URL: %s", info.Url))
|
|
|
|
|
|
err, result, CheckData = geturl(info, 1, CheckData)
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("HTTP降级访问结果 - 错误: %v, 返回信息: %s", err, result))
|
|
|
|
|
|
|
|
|
|
|
|
// 如果仍然失败,返回错误
|
|
|
|
|
|
if err != nil && !strings.Contains(err.Error(), "EOF") {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果本来就是HTTP并且失败了,直接返回错误
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2020-12-29 17:17:10 +08:00
|
|
|
|
}
|
2021-09-10 20:32:51 +08:00
|
|
|
|
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// 处理URL跳转
|
2021-04-22 23:41:56 +08:00
|
|
|
|
if strings.Contains(result, "://") {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("检测到重定向到: %s", result))
|
2022-02-17 14:37:06 +08:00
|
|
|
|
info.Url = result
|
2023-11-13 11:27:34 +08:00
|
|
|
|
err, result, CheckData = geturl(info, 3, CheckData)
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("重定向请求结果 - 错误: %v, 返回信息: %s", err, result))
|
2022-02-17 14:37:06 +08:00
|
|
|
|
if err != nil {
|
2025-04-13 15:46:37 +08:00
|
|
|
|
// 如果重定向跟踪失败,尝试降级协议
|
|
|
|
|
|
if strings.HasPrefix(info.Url, "https://") {
|
|
|
|
|
|
Common.LogDebug("重定向HTTPS访问失败,尝试降级到HTTP")
|
|
|
|
|
|
info.Url = strings.Replace(info.Url, "https://", "http://", 1)
|
|
|
|
|
|
err, result, CheckData = geturl(info, 3, CheckData)
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("重定向降级访问结果 - 错误: %v, 返回信息: %s", err, result))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2021-04-22 23:41:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-03-04 14:42:10 +08:00
|
|
|
|
|
2025-04-13 15:46:37 +08:00
|
|
|
|
// 处理HTTP到HTTPS的升级提示
|
2021-09-10 22:43:50 +08:00
|
|
|
|
if result == "https" && !strings.HasPrefix(info.Url, "https://") {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("正在升级到HTTPS")
|
2021-05-14 16:02:22 +08:00
|
|
|
|
info.Url = strings.Replace(info.Url, "http://", "https://", 1)
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("升级后的URL: %s", info.Url))
|
2023-11-13 11:27:34 +08:00
|
|
|
|
err, result, CheckData = geturl(info, 1, CheckData)
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("HTTPS升级访问结果 - 错误: %v, 返回信息: %s", err, result))
|
|
|
|
|
|
|
|
|
|
|
|
// 如果HTTPS升级后访问失败,回退到HTTP
|
|
|
|
|
|
if err != nil && !strings.Contains(err.Error(), "EOF") {
|
|
|
|
|
|
Common.LogDebug("HTTPS升级访问失败,回退到HTTP")
|
|
|
|
|
|
info.Url = strings.Replace(info.Url, "https://", "http://", 1)
|
|
|
|
|
|
err, result, CheckData = geturl(info, 1, CheckData)
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("回退到HTTP访问结果 - 错误: %v, 返回信息: %s", err, result))
|
|
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理升级后的跳转
|
2021-04-22 23:41:56 +08:00
|
|
|
|
if strings.Contains(result, "://") {
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("协议升级后发现重定向到: %s", result))
|
2022-02-17 14:37:06 +08:00
|
|
|
|
info.Url = result
|
2023-11-13 11:27:34 +08:00
|
|
|
|
err, _, CheckData = geturl(info, 3, CheckData)
|
2021-04-22 23:41:56 +08:00
|
|
|
|
if err != nil {
|
2025-04-13 15:46:37 +08:00
|
|
|
|
// 如果重定向跟踪失败,再次尝试降级
|
|
|
|
|
|
if strings.HasPrefix(info.Url, "https://") {
|
|
|
|
|
|
Common.LogDebug("升级后重定向HTTPS访问失败,尝试降级到HTTP")
|
|
|
|
|
|
info.Url = strings.Replace(info.Url, "https://", "http://", 1)
|
|
|
|
|
|
err, _, CheckData = geturl(info, 3, CheckData)
|
|
|
|
|
|
}
|
2021-04-22 23:41:56 +08:00
|
|
|
|
}
|
2020-12-29 17:17:10 +08:00
|
|
|
|
}
|
2021-02-08 15:11:43 +08:00
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("GOWebTitle执行完成 - 错误: %v", err))
|
2022-02-17 14:37:06 +08:00
|
|
|
|
return
|
2020-12-29 17:17:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-19 16:15:53 +08:00
|
|
|
|
func geturl(info *Common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("geturl开始执行 - URL: %s, 标志位: %d", info.Url, flag))
|
|
|
|
|
|
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// 处理目标URL
|
2021-02-08 15:11:43 +08:00
|
|
|
|
Url := info.Url
|
2021-04-22 23:41:56 +08:00
|
|
|
|
if flag == 2 {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("处理favicon.ico URL")
|
2021-04-22 23:41:56 +08:00
|
|
|
|
URL, err := url.Parse(Url)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
Url = fmt.Sprintf("%s://%s/favicon.ico", URL.Scheme, URL.Host)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Url += "/favicon.ico"
|
|
|
|
|
|
}
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("favicon URL: %s", Url))
|
2021-02-08 15:11:43 +08:00
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 创建HTTP请求
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("开始创建HTTP请求")
|
2021-11-16 11:53:46 +08:00
|
|
|
|
req, err := http.NewRequest("GET", Url, nil)
|
2022-02-17 14:37:06 +08:00
|
|
|
|
if err != nil {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("创建HTTP请求失败: %v", err))
|
2022-02-17 14:37:06 +08:00
|
|
|
|
return err, "", CheckData
|
|
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 设置请求头
|
2024-12-18 22:00:18 +08:00
|
|
|
|
req.Header.Set("User-agent", Common.UserAgent)
|
|
|
|
|
|
req.Header.Set("Accept", Common.Accept)
|
2022-02-17 14:37:06 +08:00
|
|
|
|
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
|
2024-12-18 22:00:18 +08:00
|
|
|
|
if Common.Cookie != "" {
|
|
|
|
|
|
req.Header.Set("Cookie", Common.Cookie)
|
2022-11-19 17:04:13 +08:00
|
|
|
|
}
|
2022-02-17 14:37:06 +08:00
|
|
|
|
req.Header.Set("Connection", "close")
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("已设置请求头")
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 选择HTTP客户端
|
2022-02-17 14:37:06 +08:00
|
|
|
|
var client *http.Client
|
|
|
|
|
|
if flag == 1 {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
client = lib.ClientNoRedirect
|
|
|
|
|
|
Common.LogDebug("使用不跟随重定向的客户端")
|
2022-02-17 14:37:06 +08:00
|
|
|
|
} else {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
client = lib.Client
|
|
|
|
|
|
Common.LogDebug("使用普通客户端")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查客户端是否为空
|
|
|
|
|
|
if client == nil {
|
|
|
|
|
|
Common.LogDebug("错误: HTTP客户端为空")
|
|
|
|
|
|
return fmt.Errorf("HTTP客户端未初始化"), "", CheckData
|
2022-02-17 14:37:06 +08:00
|
|
|
|
}
|
2021-04-22 23:41:56 +08:00
|
|
|
|
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// 发送请求
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("开始发送HTTP请求")
|
2022-02-17 14:37:06 +08:00
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
|
|
if err != nil {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("HTTP请求失败: %v", err))
|
2021-05-14 16:02:22 +08:00
|
|
|
|
return err, "https", CheckData
|
2020-12-29 17:17:10 +08:00
|
|
|
|
}
|
2022-02-17 14:37:06 +08:00
|
|
|
|
defer resp.Body.Close()
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("收到HTTP响应,状态码: %d", resp.StatusCode))
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 读取响应内容
|
2022-02-17 14:37:06 +08:00
|
|
|
|
body, err := getRespBody(resp)
|
|
|
|
|
|
if err != nil {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("读取响应内容失败: %v", err))
|
2022-02-17 14:37:06 +08:00
|
|
|
|
return err, "https", CheckData
|
2021-04-22 23:41:56 +08:00
|
|
|
|
}
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("成功读取响应内容,长度: %d", len(body)))
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 保存检查数据
|
2023-11-13 17:41:54 +08:00
|
|
|
|
CheckData = append(CheckData, WebScan.CheckDatas{body, fmt.Sprintf("%s", resp.Header)})
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("已保存检查数据")
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理非favicon请求
|
2022-02-17 14:37:06 +08:00
|
|
|
|
var reurl string
|
|
|
|
|
|
if flag != 2 {
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// 处理编码
|
2023-11-13 17:41:54 +08:00
|
|
|
|
if !utf8.Valid(body) {
|
|
|
|
|
|
body, _ = simplifiedchinese.GBK.NewDecoder().Bytes(body)
|
|
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取页面信息
|
|
|
|
|
|
title := gettitle(body)
|
2022-02-17 14:37:06 +08:00
|
|
|
|
length := resp.Header.Get("Content-Length")
|
|
|
|
|
|
if length == "" {
|
|
|
|
|
|
length = fmt.Sprintf("%v", len(body))
|
|
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2025-01-14 23:38:58 +08:00
|
|
|
|
// 收集服务器信息
|
|
|
|
|
|
serverInfo := make(map[string]interface{})
|
|
|
|
|
|
serverInfo["title"] = title
|
|
|
|
|
|
serverInfo["length"] = length
|
|
|
|
|
|
serverInfo["status_code"] = resp.StatusCode
|
|
|
|
|
|
|
|
|
|
|
|
// 收集响应头信息
|
|
|
|
|
|
for k, v := range resp.Header {
|
|
|
|
|
|
if len(v) > 0 {
|
|
|
|
|
|
serverInfo[strings.ToLower(k)] = v[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查重定向
|
2022-02-17 14:37:06 +08:00
|
|
|
|
redirURL, err1 := resp.Location()
|
|
|
|
|
|
if err1 == nil {
|
|
|
|
|
|
reurl = redirURL.String()
|
2025-01-14 23:38:58 +08:00
|
|
|
|
serverInfo["redirect_url"] = reurl
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-05 17:42:13 +08:00
|
|
|
|
// 处理指纹信息 - 添加调试日志
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("保存结果前的指纹信息: %v", info.Infostr))
|
|
|
|
|
|
|
|
|
|
|
|
// 处理空指纹情况
|
|
|
|
|
|
fingerprints := info.Infostr
|
|
|
|
|
|
if len(fingerprints) == 1 && fingerprints[0] == "" {
|
|
|
|
|
|
// 如果是只包含空字符串的数组,替换为空数组
|
|
|
|
|
|
fingerprints = []string{}
|
|
|
|
|
|
Common.LogDebug("检测到空指纹,已转换为空数组")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-14 23:38:58 +08:00
|
|
|
|
// 保存扫描结果
|
|
|
|
|
|
result := &Common.ScanResult{
|
|
|
|
|
|
Time: time.Now(),
|
|
|
|
|
|
Type: Common.SERVICE,
|
|
|
|
|
|
Target: info.Host,
|
|
|
|
|
|
Status: "identified",
|
|
|
|
|
|
Details: map[string]interface{}{
|
|
|
|
|
|
"port": info.Ports,
|
|
|
|
|
|
"service": "http",
|
|
|
|
|
|
"title": title,
|
|
|
|
|
|
"url": resp.Request.URL.String(),
|
|
|
|
|
|
"status_code": resp.StatusCode,
|
|
|
|
|
|
"length": length,
|
|
|
|
|
|
"server_info": serverInfo,
|
2025-04-05 17:42:13 +08:00
|
|
|
|
"fingerprints": fingerprints, // 使用处理过的指纹信息
|
2025-01-14 23:38:58 +08:00
|
|
|
|
},
|
2022-02-17 14:37:06 +08:00
|
|
|
|
}
|
2025-01-14 23:38:58 +08:00
|
|
|
|
Common.SaveResult(result)
|
2025-04-05 17:42:13 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("已保存结果,指纹信息: %v", fingerprints))
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2025-01-14 23:38:58 +08:00
|
|
|
|
// 输出控制台日志
|
|
|
|
|
|
logMsg := fmt.Sprintf("网站标题 %-25v 状态码:%-3v 长度:%-6v 标题:%v",
|
2024-12-19 14:15:58 +08:00
|
|
|
|
resp.Request.URL, resp.StatusCode, length, title)
|
2022-02-17 14:37:06 +08:00
|
|
|
|
if reurl != "" {
|
2025-01-14 23:38:58 +08:00
|
|
|
|
logMsg += fmt.Sprintf(" 重定向地址: %s", reurl)
|
2022-02-17 14:37:06 +08:00
|
|
|
|
}
|
2025-04-05 17:42:13 +08:00
|
|
|
|
// 添加指纹信息到控制台日志
|
|
|
|
|
|
if len(fingerprints) > 0 {
|
|
|
|
|
|
logMsg += fmt.Sprintf(" 指纹:%v", fingerprints)
|
|
|
|
|
|
}
|
2025-01-14 23:38:58 +08:00
|
|
|
|
Common.LogSuccess(logMsg)
|
2022-02-17 14:37:06 +08:00
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 返回结果
|
2022-02-17 14:37:06 +08:00
|
|
|
|
if reurl != "" {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("返回重定向URL: %s", reurl))
|
2022-02-17 14:37:06 +08:00
|
|
|
|
return nil, reurl, CheckData
|
|
|
|
|
|
}
|
|
|
|
|
|
if resp.StatusCode == 400 && !strings.HasPrefix(info.Url, "https") {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("返回HTTPS升级标志")
|
2022-02-17 14:37:06 +08:00
|
|
|
|
return nil, "https", CheckData
|
|
|
|
|
|
}
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("geturl执行完成,无特殊返回")
|
2022-02-17 14:37:06 +08:00
|
|
|
|
return nil, "", CheckData
|
2021-04-22 23:41:56 +08:00
|
|
|
|
}
|
2021-05-12 10:57:12 +08:00
|
|
|
|
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// getRespBody 读取HTTP响应体内容
|
2021-05-12 10:57:12 +08:00
|
|
|
|
func getRespBody(oResp *http.Response) ([]byte, error) {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("开始读取响应体内容")
|
2021-05-12 10:57:12 +08:00
|
|
|
|
var body []byte
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理gzip压缩的响应
|
2021-05-12 10:57:12 +08:00
|
|
|
|
if oResp.Header.Get("Content-Encoding") == "gzip" {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("检测到gzip压缩,开始解压")
|
2021-05-12 10:57:12 +08:00
|
|
|
|
gr, err := gzip.NewReader(oResp.Body)
|
|
|
|
|
|
if err != nil {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("创建gzip解压器失败: %v", err))
|
2021-05-12 10:57:12 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer gr.Close()
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 循环读取解压内容
|
2021-05-12 10:57:12 +08:00
|
|
|
|
for {
|
|
|
|
|
|
buf := make([]byte, 1024)
|
|
|
|
|
|
n, err := gr.Read(buf)
|
|
|
|
|
|
if err != nil && err != io.EOF {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("读取压缩内容失败: %v", err))
|
2021-05-12 10:57:12 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
if n == 0 {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
body = append(body, buf...)
|
|
|
|
|
|
}
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("gzip解压完成,内容长度: %d", len(body)))
|
2021-05-12 10:57:12 +08:00
|
|
|
|
} else {
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// 直接读取未压缩的响应
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("读取未压缩的响应内容")
|
2023-06-05 19:02:55 +03:00
|
|
|
|
raw, err := io.ReadAll(oResp.Body)
|
2021-05-12 10:57:12 +08:00
|
|
|
|
if err != nil {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("读取响应内容失败: %v", err))
|
2021-05-12 10:57:12 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
body = raw
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("读取完成,内容长度: %d", len(body)))
|
2021-05-12 10:57:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
return body, nil
|
|
|
|
|
|
}
|
2021-09-10 22:43:50 +08:00
|
|
|
|
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// gettitle 从HTML内容中提取网页标题
|
2022-02-17 14:37:06 +08:00
|
|
|
|
func gettitle(body []byte) (title string) {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("开始提取网页标题")
|
|
|
|
|
|
|
2024-12-19 14:15:58 +08:00
|
|
|
|
// 使用正则表达式匹配title标签内容
|
2024-01-15 16:22:40 +08:00
|
|
|
|
re := regexp.MustCompile("(?ims)<title.*?>(.*?)</title>")
|
2022-02-17 14:37:06 +08:00
|
|
|
|
find := re.FindSubmatch(body)
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2022-02-17 14:37:06 +08:00
|
|
|
|
if len(find) > 1 {
|
|
|
|
|
|
title = string(find[1])
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("找到原始标题: %s", title))
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 清理标题内容
|
|
|
|
|
|
title = strings.TrimSpace(title) // 去除首尾空格
|
|
|
|
|
|
title = strings.Replace(title, "\n", "", -1) // 去除换行
|
|
|
|
|
|
title = strings.Replace(title, "\r", "", -1) // 去除回车
|
|
|
|
|
|
title = strings.Replace(title, " ", " ", -1) // 替换HTML空格
|
|
|
|
|
|
|
|
|
|
|
|
// 截断过长的标题
|
2022-02-17 14:37:06 +08:00
|
|
|
|
if len(title) > 100 {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("标题超过100字符,进行截断")
|
2022-02-17 14:37:06 +08:00
|
|
|
|
title = title[:100]
|
|
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理空标题
|
2023-11-13 17:41:54 +08:00
|
|
|
|
if title == "" {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("标题为空,使用双引号代替")
|
|
|
|
|
|
title = "\"\""
|
2023-11-13 17:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug("未找到标题标签")
|
|
|
|
|
|
title = "无标题"
|
2022-02-17 14:37:06 +08:00
|
|
|
|
}
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("最终标题: %s", title))
|
2022-02-17 14:37:06 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-13 15:46:37 +08:00
|
|
|
|
// GetProtocol 检测目标主机的协议类型(HTTP/HTTPS),优先返回可用的协议
|
2023-11-13 11:27:34 +08:00
|
|
|
|
func GetProtocol(host string, Timeout int64) (protocol string) {
|
2025-01-04 14:04:41 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("开始检测主机协议 - 主机: %s, 超时: %d秒", host, Timeout))
|
2025-04-13 15:46:37 +08:00
|
|
|
|
|
|
|
|
|
|
// 默认使用http协议
|
|
|
|
|
|
protocol = "http"
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2025-04-05 21:55:57 +08:00
|
|
|
|
timeoutDuration := time.Duration(Timeout) * time.Second
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 根据标准端口快速判断协议
|
|
|
|
|
|
if strings.HasSuffix(host, ":80") {
|
|
|
|
|
|
Common.LogDebug("检测到标准HTTP端口,使用HTTP协议")
|
2025-04-13 15:46:37 +08:00
|
|
|
|
return "http"
|
2023-11-13 11:27:34 +08:00
|
|
|
|
} else if strings.HasSuffix(host, ":443") {
|
2025-04-05 21:55:57 +08:00
|
|
|
|
Common.LogDebug("检测到标准HTTPS端口,使用HTTPS协议")
|
|
|
|
|
|
return "https"
|
2022-02-17 14:37:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-05 21:55:57 +08:00
|
|
|
|
// 2. 并发检测HTTP和HTTPS
|
2025-04-13 15:46:37 +08:00
|
|
|
|
type protocolResult struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
success bool
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
resultChan := make(chan protocolResult, 2)
|
|
|
|
|
|
singleTimeout := timeoutDuration / 2 // 每个协议检测的超时时间减半
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2025-04-05 21:55:57 +08:00
|
|
|
|
// 检测HTTPS
|
|
|
|
|
|
go func() {
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Common.LogDebug("开始检测HTTPS协议")
|
2025-04-05 21:55:57 +08:00
|
|
|
|
tlsConfig := &tls.Config{
|
|
|
|
|
|
InsecureSkipVerify: true,
|
|
|
|
|
|
MinVersion: tls.VersionTLS10,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dialer := &net.Dialer{
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Timeout: singleTimeout,
|
2025-04-05 21:55:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
conn, err := tls.DialWithDialer(dialer, "tcp", host, tlsConfig)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
Common.LogDebug("HTTPS连接成功")
|
2021-09-10 22:43:50 +08:00
|
|
|
|
conn.Close()
|
2025-04-13 15:46:37 +08:00
|
|
|
|
resultChan <- protocolResult{"https", true}
|
2025-04-05 21:55:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 分析TLS错误
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
errMsg := strings.ToLower(err.Error())
|
|
|
|
|
|
// 这些错误可能表明服务器确实支持TLS,但有其他问题
|
|
|
|
|
|
if strings.Contains(errMsg, "handshake failure") ||
|
|
|
|
|
|
strings.Contains(errMsg, "certificate") ||
|
|
|
|
|
|
strings.Contains(errMsg, "tls") ||
|
|
|
|
|
|
strings.Contains(errMsg, "x509") ||
|
|
|
|
|
|
strings.Contains(errMsg, "secure") {
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("TLS握手有错误但可能是HTTPS协议: %v", err))
|
2025-04-13 15:46:37 +08:00
|
|
|
|
resultChan <- protocolResult{"https", true}
|
2025-04-05 21:55:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("HTTPS连接失败: %v", err))
|
2021-09-10 22:43:50 +08:00
|
|
|
|
}
|
2025-04-13 15:46:37 +08:00
|
|
|
|
resultChan <- protocolResult{"https", false}
|
2021-09-10 22:43:50 +08:00
|
|
|
|
}()
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2025-04-05 21:55:57 +08:00
|
|
|
|
// 检测HTTP
|
|
|
|
|
|
go func() {
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Common.LogDebug("开始检测HTTP协议")
|
2025-04-05 21:55:57 +08:00
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "HEAD", fmt.Sprintf("http://%s", host), nil)
|
|
|
|
|
|
if err != nil {
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("创建HTTP请求失败: %v", err))
|
|
|
|
|
|
resultChan <- protocolResult{"http", false}
|
2025-04-05 21:55:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2025-04-05 21:55:57 +08:00
|
|
|
|
client := &http.Client{
|
|
|
|
|
|
Transport: &http.Transport{
|
|
|
|
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
|
|
|
|
DialContext: (&net.Dialer{
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Timeout: singleTimeout,
|
2025-04-05 21:55:57 +08:00
|
|
|
|
}).DialContext,
|
|
|
|
|
|
},
|
|
|
|
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
|
|
|
|
return http.ErrUseLastResponse // 不跟随重定向
|
|
|
|
|
|
},
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Timeout: singleTimeout,
|
2025-04-05 21:55:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
resp.Body.Close()
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("HTTP连接成功,状态码: %d", resp.StatusCode))
|
2025-04-13 15:46:37 +08:00
|
|
|
|
resultChan <- protocolResult{"http", true}
|
2025-04-05 21:55:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Common.LogDebug(fmt.Sprintf("标准HTTP请求失败: %v,尝试原始TCP连接", err))
|
|
|
|
|
|
|
2025-04-05 21:55:57 +08:00
|
|
|
|
// 尝试原始TCP连接和简单HTTP请求
|
2025-04-13 15:46:37 +08:00
|
|
|
|
netConn, err := net.DialTimeout("tcp", host, singleTimeout)
|
2025-04-05 21:55:57 +08:00
|
|
|
|
if err == nil {
|
|
|
|
|
|
defer netConn.Close()
|
2025-04-13 15:46:37 +08:00
|
|
|
|
netConn.SetDeadline(time.Now().Add(singleTimeout))
|
2025-04-05 21:55:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 发送简单HTTP请求
|
|
|
|
|
|
_, err = netConn.Write([]byte("HEAD / HTTP/1.0\r\nHost: " + host + "\r\n\r\n"))
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
// 读取响应
|
|
|
|
|
|
buf := make([]byte, 1024)
|
2025-04-13 15:46:37 +08:00
|
|
|
|
netConn.SetDeadline(time.Now().Add(singleTimeout))
|
2025-04-05 21:55:57 +08:00
|
|
|
|
n, err := netConn.Read(buf)
|
|
|
|
|
|
if err == nil && n > 0 {
|
|
|
|
|
|
response := string(buf[:n])
|
|
|
|
|
|
if strings.Contains(response, "HTTP/") {
|
|
|
|
|
|
Common.LogDebug("通过原始TCP连接确认HTTP协议")
|
2025-04-13 15:46:37 +08:00
|
|
|
|
resultChan <- protocolResult{"http", true}
|
2025-04-05 21:55:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-04-13 15:46:37 +08:00
|
|
|
|
Common.LogDebug("原始TCP连接成功但HTTP响应无效")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("原始TCP连接失败: %v", err))
|
2025-04-05 21:55:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-13 15:46:37 +08:00
|
|
|
|
resultChan <- protocolResult{"http", false}
|
2025-04-05 21:55:57 +08:00
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 收集结果并决定使用哪种协议
|
2025-04-13 15:46:37 +08:00
|
|
|
|
var httpsSuccess, httpSuccess bool
|
2025-04-05 21:55:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 等待两个goroutine返回结果或超时
|
|
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case result := <-resultChan:
|
2025-04-13 15:46:37 +08:00
|
|
|
|
if result.name == "https" {
|
|
|
|
|
|
httpsSuccess = result.success
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("HTTPS检测结果: %v", httpsSuccess))
|
|
|
|
|
|
} else if result.name == "http" {
|
|
|
|
|
|
httpSuccess = result.success
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("HTTP检测结果: %v", httpSuccess))
|
2025-04-05 21:55:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
Common.LogDebug("协议检测超时")
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
2021-09-10 22:43:50 +08:00
|
|
|
|
}
|
2024-12-19 14:15:58 +08:00
|
|
|
|
|
2025-04-13 15:46:37 +08:00
|
|
|
|
// 4. 决定使用哪种协议 - 优先使用HTTPS,如果HTTPS不可用则使用HTTP
|
|
|
|
|
|
if httpsSuccess {
|
|
|
|
|
|
Common.LogDebug("选择使用HTTPS协议")
|
2025-04-05 21:55:57 +08:00
|
|
|
|
return "https"
|
2025-04-13 15:46:37 +08:00
|
|
|
|
} else if httpSuccess {
|
|
|
|
|
|
Common.LogDebug("选择使用HTTP协议")
|
2025-04-05 21:55:57 +08:00
|
|
|
|
return "http"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 如果两种协议都无法确认,保持默认值
|
|
|
|
|
|
Common.LogDebug(fmt.Sprintf("无法确定协议,使用默认协议: %s", protocol))
|
|
|
|
|
|
return
|
2021-09-10 22:43:50 +08:00
|
|
|
|
}
|