dddd/common/uncover/hunter.go

308 lines
8.4 KiB
Go
Raw Normal View History

2023-08-18 08:55:46 +02:00
package uncover
import (
"dddd/structs"
"dddd/utils"
2024-01-15 08:19:45 +01:00
"dddd/utils/cdn"
2023-08-18 08:55:46 +02:00
"encoding/base64"
"encoding/json"
"fmt"
"github.com/projectdiscovery/gologger"
2023-11-27 10:10:41 +01:00
"github.com/projectdiscovery/httpx/common/hashes"
2023-08-18 08:55:46 +02:00
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/subfinder/v2/pkg/passive"
"gopkg.in/yaml.v3"
"io"
"math/rand"
"net/http"
"os"
2023-11-27 10:10:41 +01:00
"strconv"
2023-08-18 08:55:46 +02:00
"strings"
"time"
)
type HunterResp struct {
Code int `json:"code"`
Data hunterData `json:"data"`
Message string `json:"message"`
}
type infoArr struct {
URL string `json:"url"`
IP string `json:"ip"`
Port int `json:"port"`
Domain string `json:"domain"`
Protocol string `json:"protocol"`
IsWeb string `json:"is_web"`
City string `json:"city"`
Company string `json:"company"`
Code int `json:"status_code"`
Title string `json:"web_title"`
Country string `json:"country"`
2023-11-27 10:10:41 +01:00
Banner string `json:"banner"`
2023-08-18 08:55:46 +02:00
}
type hunterData struct {
InfoArr []infoArr `json:"arr"`
Total int `json:"total"`
RestQuota string `json:"rest_quota"`
}
func getHunterKeys() []string {
var apiKeys []string
f, err := os.Open("config/subfinder-config.yaml")
if err != nil {
gologger.Fatal().Msg("打开API Key配置文件config/subfinder-config.yaml失败")
return []string{}
}
defer f.Close()
sourceApiKeysMap := map[string][]string{}
err = yaml.NewDecoder(f).Decode(sourceApiKeysMap)
for _, source := range passive.AllSources {
sourceName := strings.ToLower(source.Name())
if sourceName == "hunter" {
apiKeys = sourceApiKeysMap[sourceName]
break
}
}
if len(apiKeys) == 0 {
gologger.Fatal().Msg("未获取到Hunter API Key")
return []string{}
}
return apiKeys
}
// SearchHunter 从Hunter中搜索目标
2023-11-24 09:00:49 +01:00
func SearchHunterCore(keyword string, pageSize int, maxQueryPage int) ([]string, []string) {
2023-08-18 08:55:46 +02:00
opts := retryablehttp.DefaultOptionsSpraying
client := retryablehttp.NewClient(opts)
url := "https://hunter.qianxin.com/openApi/search"
keys := getHunterKeys()
randKey := keys[rand.Intn(len(keys))]
page := 1
currentQueryCount := 0
var results []string
2023-11-24 09:00:49 +01:00
var ipResult []string
2023-08-18 08:55:46 +02:00
for page <= maxQueryPage {
req, err := retryablehttp.NewRequest(http.MethodGet, url, nil)
if err != nil {
gologger.Fatal().Msgf("Hunter API请求构建失败。")
}
unc := keyword
search := base64.URLEncoding.EncodeToString([]byte(unc))
q := req.URL.Query()
q.Add("search", search)
q.Add("api-key", randKey)
q.Add("page", fmt.Sprintf("%d", page))
q.Add("page_size", fmt.Sprintf("%d", pageSize))
q.Add("is_web", "3")
req.URL.RawQuery = q.Encode()
resp, errDo := client.Do(req)
if errDo != nil {
gologger.Error().Msgf("[Hunter] %s 资产查询失败请检查网络状态。Error:%s", keyword, errDo.Error())
time.Sleep(time.Second * 3)
continue
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
gologger.Error().Msgf("获取Hunter 响应Body失败: %v", err.Error())
time.Sleep(time.Second * 3)
continue
}
var responseJson HunterResp
if err = json.Unmarshal(data, &responseJson); err != nil {
gologger.Error().Msgf("[Hunter] 返回数据Json解析失败! Error:%s", err.Error())
time.Sleep(time.Second * 3)
continue
}
if responseJson.Code != 200 {
gologger.Error().Msgf("[Hunter] %s 搜索失败Error:%s", keyword, responseJson.Message)
2023-09-02 18:53:53 +02:00
2024-01-15 08:19:45 +01:00
if strings.Contains(responseJson.Message, "今日免费积分已用") ||
strings.Contains(responseJson.Message, "今日免费积分不足") {
2023-08-18 08:55:46 +02:00
time.Sleep(time.Second * 3)
continue
}
2023-09-02 18:53:53 +02:00
if responseJson.Message == "请求太多啦,稍后再试试" {
2023-08-18 08:55:46 +02:00
time.Sleep(time.Second * 3)
2023-09-02 18:53:53 +02:00
continue
2023-08-18 08:55:46 +02:00
}
2023-11-24 09:00:49 +01:00
return results, ipResult
2023-08-18 08:55:46 +02:00
}
if responseJson.Data.Total == 0 {
gologger.Error().Msgf("[Hunter] %s 无结果。", keyword)
2023-11-24 09:00:49 +01:00
return results, ipResult
2023-08-18 08:55:46 +02:00
}
2024-01-15 08:19:45 +01:00
// 做一个域名缓存避免重复dns请求
domainCDNMap := make(map[string]bool)
var domainList []string
2023-08-18 08:55:46 +02:00
for _, v := range responseJson.Data.InfoArr {
2024-01-15 08:19:45 +01:00
domainList = append(domainList, v.Domain)
}
2023-11-27 10:10:41 +01:00
2024-01-15 08:19:45 +01:00
domainList = utils.RemoveDuplicateElement(domainList)
if len(domainList) != 0 {
gologger.Info().Msgf("正在查询 [%v] 个域名是否为CDN资产", len(domainList))
}
cdnDomains, normalDomains, _ := cdn.CheckCDNs(domainList, structs.GlobalConfig.SubdomainBruteForceThreads)
for _, d := range cdnDomains {
_, ok := domainCDNMap[d]
if !ok {
domainCDNMap[d] = true
}
}
for _, d := range normalDomains {
_, ok := domainCDNMap[d]
if !ok {
domainCDNMap[d] = false
}
}
for _, v := range responseJson.Data.InfoArr {
isCDN := false
t, ok := domainCDNMap[v.Domain]
if ok {
isCDN = t
}
if !isCDN {
AddIPDomainMap(v.IP, v.Domain)
}
if v.IsWeb == "是" {
2023-11-27 10:10:41 +01:00
if structs.GlobalConfig.LowPerceptionMode {
rootURL := fmt.Sprintf("%s://%s:%d", v.Protocol, v.IP, v.Port)
structs.GlobalURLMapLock.Lock()
_, rootURLOK := structs.GlobalURLMap[rootURL]
structs.GlobalURLMapLock.Unlock()
if !rootURLOK {
responseCode, header, body, server, contentType, contentLen := utils.ExtractResponse(v.Banner)
md5 := hashes.Md5([]byte(body))
headerMd5 := hashes.Md5([]byte(header))
_ = structs.GlobalHttpBodyHMap.Set(md5, []byte(body))
_ = structs.GlobalHttpHeaderHMap.Set(headerMd5, []byte(header))
l, e := strconv.Atoi(contentLen)
if e != nil {
l = 0
}
rspc, re := strconv.Atoi(responseCode)
if re != nil {
rspc = 0
}
webPath := structs.UrlPathEntity{
Hash: md5,
Title: v.Title,
StatusCode: rspc,
ContentType: contentType,
Server: server,
ContentLength: l,
HeaderHashString: headerMd5,
IconHash: "", // hunter未提供hash
}
urlE := structs.URLEntity{
IP: v.IP,
Port: v.Port,
WebPaths: nil,
Cert: "", // hunter未提供证书信息
}
urlE.WebPaths = make(map[string]structs.UrlPathEntity)
urlE.WebPaths["/"] = webPath
structs.GlobalURLMapLock.Lock()
structs.GlobalURLMap[rootURL] = urlE
structs.GlobalURLMapLock.Unlock()
}
2024-01-15 08:19:45 +01:00
} else { // 正常模式
p := ""
if structs.GlobalConfig.OnlyIPPort && !isCDN {
p = fmt.Sprintf("%s://%s:%d", v.Protocol, v.IP, v.Port)
} else {
p = v.URL
}
if utils.GetItemInArray(results, p) == -1 {
if !isCDN || structs.GlobalConfig.AllowCDNAssets {
results = append(results, p)
gologger.Silent().Msgf("[Hunter] [%d] %s [%s] [%s] [%s]", v.Code, p, v.Title, v.City, v.Company)
}
}
2023-11-27 10:10:41 +01:00
}
2023-08-18 08:55:46 +02:00
} else {
2023-11-27 10:10:41 +01:00
if structs.GlobalConfig.LowPerceptionMode {
hostPort := fmt.Sprintf("%s:%d", v.IP, v.Port)
structs.GlobalIPPortMapLock.Lock()
_, ok := structs.GlobalIPPortMap[hostPort]
structs.GlobalIPPortMapLock.Unlock()
if !ok {
structs.GlobalBannerHMap.Set(hostPort, []byte(v.Banner))
structs.GlobalIPPortMapLock.Lock()
structs.GlobalIPPortMap[hostPort] = v.Protocol
structs.GlobalIPPortMapLock.Unlock()
}
} else {
results = append(results, fmt.Sprintf("%s:%v", v.IP, v.Port))
2024-01-15 08:19:45 +01:00
p := fmt.Sprintf("%s:%d", v.IP, v.Port)
if utils.GetItemInArray(results, p) == -1 {
if !isCDN || structs.GlobalConfig.AllowCDNAssets {
results = append(results, p)
gologger.Silent().Msgf("[Hunter] %s://%s:%d", v.Protocol, v.IP, v.Port)
}
}
2023-11-27 10:10:41 +01:00
}
2023-08-18 08:55:46 +02:00
}
2024-01-15 08:19:45 +01:00
if !isCDN {
ipResult = append(ipResult, v.IP)
}
2023-08-18 08:55:46 +02:00
}
currentQueryCount += len(responseJson.Data.InfoArr)
gologger.Info().Msgf("[Hunter] [%s] 当前第 [%d] 页 查询进度: %d/%d %v", keyword, page, currentQueryCount,
responseJson.Data.Total, responseJson.Data.RestQuota)
if currentQueryCount >= responseJson.Data.Total {
2023-11-24 09:00:49 +01:00
return results, ipResult
2023-08-18 08:55:46 +02:00
}
page += 1
// 避免请求过于频繁
time.Sleep(time.Second * 3)
}
2023-11-24 09:00:49 +01:00
return results, ipResult
2023-08-18 08:55:46 +02:00
}
2023-11-24 09:00:49 +01:00
func HunterSearch(keywords []string) ([]string, []string) {
gologger.Info().Msgf("准备从 Hunter 获取数据")
2024-01-02 14:50:55 +01:00
gologger.AuditTimeLogger("准备从 Hunter 获取数据")
2023-08-18 08:55:46 +02:00
var results []string
2023-11-24 09:00:49 +01:00
var ipResults []string
2023-08-18 08:55:46 +02:00
for _, keyword := range keywords {
2023-11-24 09:00:49 +01:00
result, ipResult := SearchHunterCore(keyword,
2023-08-18 08:55:46 +02:00
structs.GlobalConfig.HunterPageSize,
structs.GlobalConfig.HunterMaxPageCount)
results = append(results, result...)
2023-11-24 09:00:49 +01:00
ipResults = append(ipResults, ipResult...)
2023-08-18 08:55:46 +02:00
}
2023-11-24 09:00:49 +01:00
return utils.RemoveDuplicateElement(results), utils.RemoveDuplicateElement(ipResults)
2023-08-18 08:55:46 +02:00
}