uncover logic refactor to v0.0.4 (#3663)

* uncover logic refactor to v0.0.4

* remove deprecated import: stringsutil
dev
Tarun Koyalwar 2023-05-09 03:57:56 +05:30 committed by GitHub
parent abc1d595ac
commit c62dc01f9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 235 deletions

View File

@ -230,7 +230,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.StringSliceVarP(&options.UncoverEngine, "uncover-engine", "ue", nil, fmt.Sprintf("uncover search engine (%s) (default shodan)", uncover.GetUncoverSupportedAgents()), goflags.FileStringSliceOptions),
flagSet.StringVarP(&options.UncoverField, "uncover-field", "uf", "ip:port", "uncover fields to return (ip,port,host)"),
flagSet.IntVarP(&options.UncoverLimit, "uncover-limit", "ul", 100, "uncover results to return"),
flagSet.IntVarP(&options.UncoverDelay, "uncover-delay", "ucd", 1, "delay between uncover query requests in seconds (0 to disable)"),
flagSet.IntVarP(&options.UncoverRateLimit, "uncover-ratelimit", "ur", 60, "override ratelimit of engines with unknown ratelimit (default 60 req/min)"),
)
flagSet.CreateGroup("rate-limit", "Rate-Limit",

View File

@ -26,8 +26,7 @@ require (
github.com/projectdiscovery/interactsh v1.1.3
github.com/projectdiscovery/rawhttp v0.1.11
github.com/projectdiscovery/retryabledns v1.0.24
github.com/projectdiscovery/retryablehttp-go v1.0.15
github.com/projectdiscovery/stringsutil v0.0.2 // indirect
github.com/projectdiscovery/retryablehttp-go v1.0.16
github.com/projectdiscovery/yamldoc-go v1.0.4
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/xid v1.5.0
@ -78,8 +77,8 @@ require (
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917
github.com/projectdiscovery/sarif v0.0.1
github.com/projectdiscovery/tlsx v1.0.9
github.com/projectdiscovery/uncover v1.0.2
github.com/projectdiscovery/utils v0.0.27
github.com/projectdiscovery/uncover v1.0.4
github.com/projectdiscovery/utils v0.0.28
github.com/projectdiscovery/wappalyzergo v0.0.92
github.com/stretchr/testify v1.8.2
gopkg.in/src-d/go-git.v4 v4.13.1
@ -241,7 +240,6 @@ require (
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.4.1 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect

View File

@ -255,8 +255,6 @@ github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUD
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk=
@ -440,19 +438,18 @@ github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917 h1:m03X4gB
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917/go.mod h1:JxXtZC9e195awe7EynrcnBJmFoad/BNDzW9mzFkK8Sg=
github.com/projectdiscovery/retryabledns v1.0.24 h1:CbC0a1EcyRDBcGFHZDGfW5orkWkOCfa0mAMF060XJpI=
github.com/projectdiscovery/retryabledns v1.0.24/go.mod h1:bCmv0neiqgemgmFChevfX2BgCxIp8sn5OnbwL1Gov9M=
github.com/projectdiscovery/retryablehttp-go v1.0.15 h1:kP9x9f++QimRwb8ABqnI1dhEymvnZXS2Wp2Zs4rWk/c=
github.com/projectdiscovery/retryablehttp-go v1.0.15/go.mod h1:+OzSFUv3sQcPt+MgbNx6X/Q3ESxqPUQSphqG5kxoIgI=
github.com/projectdiscovery/retryablehttp-go v1.0.16 h1:xrEK9VVkLf0eN4bYOTc2Pg0qRqz47g8T823vqpCIsl8=
github.com/projectdiscovery/retryablehttp-go v1.0.16/go.mod h1:9m76To4lNgBtVfqADzLxZg1wWajv6y/uYMWCOs1Olo8=
github.com/projectdiscovery/sarif v0.0.1 h1:C2Tyj0SGOKbCLgHrx83vaE6YkzXEVrMXYRGLkKCr/us=
github.com/projectdiscovery/sarif v0.0.1/go.mod h1:cEYlDu8amcPf6b9dSakcz2nNnJsoz4aR6peERwV+wuQ=
github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=
github.com/projectdiscovery/stringsutil v0.0.2/go.mod h1:EJ3w6bC5fBYjVou6ryzodQq37D5c6qbAYQpGmAy+DC0=
github.com/projectdiscovery/tlsx v1.0.9 h1:StUwVpPG0lrXq6FvxbFarPM+fu9Vke1sOPoyJcBVOFA=
github.com/projectdiscovery/tlsx v1.0.9/go.mod h1:4vGhl8lxUWjXuyoVvnL2ObMY/FSrb9Mpx5U2KaTV05A=
github.com/projectdiscovery/uncover v1.0.2 h1:mRFzflYyvwKkHd3XKufMlDRrb6p1mjFZTSHoNAUpFwo=
github.com/projectdiscovery/uncover v1.0.2/go.mod h1:lz4QYfArSA6jJkXyB71kN2/Pc7IW7nJB8c95n7xtwqY=
github.com/projectdiscovery/uncover v1.0.4 h1:d0lUNcnqDMfG4gR/WP2+UoQKLVmLE7opBiwnmI7ir/0=
github.com/projectdiscovery/uncover v1.0.4/go.mod h1:fvSHBhj8zZFEXGOhQIpPe5j5LtDXI7adOns1iFJ5TTY=
github.com/projectdiscovery/utils v0.0.3/go.mod h1:ne3eSlZlUKuhjHr8FfsfGcGteCzxcbJvFBx4VDBCxK0=
github.com/projectdiscovery/utils v0.0.27 h1:HfLjy1ZuWWtGfLW5ixHcgWPqybuJhPltpHnHeBID2QQ=
github.com/projectdiscovery/utils v0.0.27/go.mod h1:RpMyTA/KACEBK+Bo9mk9MgLH0Hfc+BZtSlZ4T9bjLBE=
github.com/projectdiscovery/utils v0.0.28 h1:lFTXbW/SZX3vjmCjReCdvbi5Fj46gCU3egdp098tHzo=
github.com/projectdiscovery/utils v0.0.28/go.mod h1:RpMyTA/KACEBK+Bo9mk9MgLH0Hfc+BZtSlZ4T9bjLBE=
github.com/projectdiscovery/wappalyzergo v0.0.92 h1:IQgYS3xN6J9KKq1FjMVjso+gVchhPBqi/wzChYrr1JA=
github.com/projectdiscovery/wappalyzergo v0.0.92/go.mod h1:HvYuW0Be4JCjVds/+XAEaMSqRG9yrI97UmZq0TPk6A0=
github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE=

View File

@ -14,6 +14,7 @@ import (
"github.com/projectdiscovery/nuclei/v2/internal/installer"
"github.com/projectdiscovery/nuclei/v2/internal/runner/nucleicloud"
uncoverlib "github.com/projectdiscovery/uncover"
updateutils "github.com/projectdiscovery/utils/update"
"github.com/logrusorgru/aurora"
@ -489,7 +490,14 @@ func (r *Runner) RunEnumeration() error {
// add the hosts from the metadata queries of loaded templates into input provider
if r.options.Uncover && len(r.options.UncoverQuery) == 0 {
ret := uncover.GetUncoverTargetsFromMetadata(store.Templates(), r.options.UncoverDelay, r.options.UncoverLimit, r.options.UncoverField)
uncoverOpts := &uncoverlib.Options{
Limit: r.options.UncoverLimit,
MaxRetry: r.options.Retries,
Timeout: r.options.Timeout,
RateLimit: uint(r.options.UncoverRateLimit),
RateLimitUnit: time.Minute, // default unit is minute
}
ret := uncover.GetUncoverTargetsFromMetadata(context.TODO(), store.Templates(), r.options.UncoverField, uncoverOpts)
for host := range ret {
r.hmapInputProvider.Set(host)
}

View File

@ -4,6 +4,7 @@ package hybrid
import (
"bufio"
"context"
"fmt"
"io"
"os"
@ -22,6 +23,7 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/uncover"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
uncoverlib "github.com/projectdiscovery/uncover"
fileutil "github.com/projectdiscovery/utils/file"
iputil "github.com/projectdiscovery/utils/ip"
readerutil "github.com/projectdiscovery/utils/reader"
@ -137,7 +139,16 @@ func (i *Input) initializeInputSources(opts *Options) error {
}
if options.Uncover && options.UncoverQuery != nil {
gologger.Info().Msgf("Running uncover query against: %s", strings.Join(options.UncoverEngine, ","))
ch, err := uncover.GetTargetsFromUncover(options.UncoverDelay, options.UncoverLimit, options.UncoverField, options.UncoverEngine, options.UncoverQuery)
uncoverOpts := &uncoverlib.Options{
Agents: options.UncoverEngine,
Queries: options.UncoverQuery,
Limit: options.UncoverLimit,
MaxRetry: options.Retries,
Timeout: options.Timeout,
RateLimit: uint(options.UncoverRateLimit),
RateLimitUnit: time.Minute, // default unit is minute
}
ch, err := uncover.GetTargetsFromUncover(context.TODO(), options.UncoverField, uncoverOpts)
if err != nil {
return err
}

View File

@ -3,247 +3,133 @@ package uncover
import (
"context"
"fmt"
"os"
"runtime"
"strings"
"sync"
"time"
"github.com/pkg/errors"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
"github.com/projectdiscovery/ratelimit"
ucRunner "github.com/projectdiscovery/uncover/runner"
"github.com/projectdiscovery/uncover/uncover"
"github.com/projectdiscovery/uncover/uncover/agent/censys"
"github.com/projectdiscovery/uncover/uncover/agent/criminalip"
"github.com/projectdiscovery/uncover/uncover/agent/fofa"
"github.com/projectdiscovery/uncover/uncover/agent/hunter"
"github.com/projectdiscovery/uncover/uncover/agent/netlas"
"github.com/projectdiscovery/uncover/uncover/agent/quake"
"github.com/projectdiscovery/uncover/uncover/agent/shodan"
"github.com/projectdiscovery/uncover/uncover/agent/shodanidb"
"github.com/projectdiscovery/uncover/uncover/agent/zoomeye"
"github.com/projectdiscovery/uncover"
"github.com/projectdiscovery/uncover/sources"
mapsutil "github.com/projectdiscovery/utils/maps"
"github.com/remeh/sizedwaitgroup"
stringsutil "github.com/projectdiscovery/utils/strings"
)
const maxConcurrentAgents = 50
// returns csv string of uncover supported agents
func GetUncoverSupportedAgents() string {
uncoverSupportedAgents := []string{"shodan", "shodan-idb", "fofa", "censys", "quake", "hunter", "zoomeye", "netlas", "criminalip"}
return strings.Join(uncoverSupportedAgents, ",")
u, _ := uncover.New(&uncover.Options{})
return strings.Join(u.AllAgents(), ",")
}
func GetTargetsFromUncover(delay, limit int, field string, engine, query []string) (chan string, error) {
uncoverOptions := &ucRunner.Options{
Provider: &ucRunner.Provider{},
Delay: delay,
Limit: limit,
Query: query,
Engine: engine,
// GetTargetsFromUncover returns targets from uncover
func GetTargetsFromUncover(ctx context.Context, outputFormat string, opts *uncover.Options) (chan string, error) {
u, err := uncover.New(opts)
if err != nil {
return nil, err
}
for _, eng := range engine {
err := loadKeys(eng, uncoverOptions)
if err != nil {
gologger.Error().Label("WRN").Msgf(err.Error())
continue
}
resChan, err := u.Execute(ctx)
if err != nil {
return nil, err
}
return getTargets(uncoverOptions, field)
}
func GetUncoverTargetsFromMetadata(templates []*templates.Template, delay, limit int, field string) chan string {
ret := make(chan string)
var uqMap = make(map[string][]string)
var eng, query string
for _, template := range templates {
for k, v := range template.Info.Metadata {
switch k {
case "shodan-query":
eng = "shodan"
case "fofa-query":
eng = "fofa"
case "censys-query":
eng = "censys"
case "quake-query":
eng = "quake"
case "hunter-query":
eng = "hunter"
case "zoomeye-query":
eng = "zoomeye"
case "netlas-query":
eng = "netlas"
case "criminalip-query":
eng = "criminalip"
default:
continue
}
query = fmt.Sprintf("%v", v)
uqMap[eng] = append(uqMap[eng], query)
}
}
keys := mapsutil.GetKeys(uqMap)
gologger.Info().Msgf("Running uncover query against: %s", strings.Join(keys, ","))
var wg sync.WaitGroup
outputChan := make(chan string) // buffered channel
go func() {
for k, v := range uqMap {
wg.Add(1)
go func(engine, query []string) {
ch, _ := GetTargetsFromUncover(delay, limit, field, engine, query)
for c := range ch {
ret <- c
defer close(outputChan)
for {
select {
case <-ctx.Done():
return
case res, ok := <-resChan:
if !ok {
return
}
wg.Done()
}([]string{k}, v)
if res.Error != nil {
// only log in verbose mode
gologger.Verbose().Msgf("uncover: %v", res.Error)
continue
}
outputChan <- processUncoverOutput(res, outputFormat)
}
}
wg.Wait()
close(ret)
}()
return ret
return outputChan, nil
}
func getTargets(uncoverOptions *ucRunner.Options, field string) (chan string, error) {
var rateLimiter *ratelimit.Limiter
// create rateLimiter for uncover delay
if uncoverOptions.Delay > 0 {
rateLimiter = ratelimit.New(context.Background(), 1, time.Duration(uncoverOptions.Delay))
} else {
rateLimiter = ratelimit.NewUnlimited(context.Background())
// processUncoverOutput returns output strign depending on uncover field
func processUncoverOutput(result sources.Result, outputFormat string) string {
if (result.IP == "" || result.Port == 0) && stringsutil.ContainsAny(outputFormat, "ip", "port") {
// if ip or port is not present, fallback to using host
outputFormat = "host"
}
var agents []uncover.Agent
// declare clients
for _, engine := range uncoverOptions.Engine {
var (
agent uncover.Agent
err error
)
switch engine {
case "shodan":
agent, err = shodan.NewWithOptions(&uncover.AgentOptions{RateLimiter: rateLimiter})
case "censys":
agent, err = censys.NewWithOptions(&uncover.AgentOptions{RateLimiter: rateLimiter})
case "fofa":
agent, err = fofa.NewWithOptions(&uncover.AgentOptions{RateLimiter: rateLimiter})
case "shodan-idb":
agent, err = shodanidb.NewWithOptions(&uncover.AgentOptions{RateLimiter: rateLimiter})
case "quake":
agent, err = quake.NewWithOptions(&uncover.AgentOptions{RateLimiter: rateLimiter})
case "hunter":
agent, err = hunter.NewWithOptions(&uncover.AgentOptions{RateLimiter: rateLimiter})
case "zoomeye":
agent, err = zoomeye.NewWithOptions(&uncover.AgentOptions{RateLimiter: rateLimiter})
case "netlas":
agent, err = netlas.NewWithOptions(&uncover.AgentOptions{RateLimiter: rateLimiter})
case "criminalip":
agent, err = criminalip.NewWithOptions(&uncover.AgentOptions{RateLimiter: rateLimiter})
default:
err = errors.Errorf("%s unknown uncover agent type", engine)
}
if err != nil {
return nil, err
}
agents = append(agents, agent)
}
// enumerate
swg := sizedwaitgroup.New(maxConcurrentAgents)
ret := make(chan string)
go func() {
for _, q := range uncoverOptions.Query {
uncoverQuery := &uncover.Query{
Query: q,
Limit: uncoverOptions.Limit,
replacer := strings.NewReplacer(
"ip", result.IP,
"host", result.Host,
"port", fmt.Sprint(result.Port),
"url", result.Url,
)
return replacer.Replace(outputFormat)
}
// GetUncoverTargetsFromMetadata returns targets from uncover metadata
func GetUncoverTargetsFromMetadata(ctx context.Context, templates []*templates.Template, outputFormat string, opts *uncover.Options) chan string {
// contains map[engine]queries
queriesMap := make(map[string][]string)
for _, template := range templates {
innerLoop:
for k, v := range template.Info.Metadata {
if !strings.HasSuffix(k, "-query") {
// this is not a query
// query keys are like shodan-query, fofa-query, etc
continue innerLoop
}
for _, agent := range agents {
swg.Add()
go func(agent uncover.Agent, uncoverQuery *uncover.Query) {
defer swg.Done()
keys := uncoverOptions.Provider.GetKeys()
session, err := uncover.NewSession(&keys, uncoverOptions.Retries, uncoverOptions.Timeout)
if err != nil {
gologger.Error().Label(agent.Name()).Msgf("couldn't create uncover new session: %s", err)
}
ch, err := agent.Query(session, uncoverQuery)
if err != nil {
gologger.Warning().Msgf("%s", err)
engine := strings.TrimSuffix(k, "-query")
if queriesMap[engine] == nil {
queriesMap[engine] = []string{}
}
queriesMap[engine] = append(queriesMap[engine], fmt.Sprint(v))
}
}
keys := mapsutil.GetKeys(queriesMap)
gologger.Info().Msgf("Running uncover queries from template against: %s", strings.Join(keys, ","))
result := make(chan string, runtime.NumCPU())
go func() {
defer close(result)
// unfortunately uncover doesn't support execution of map[engine]queries
// if queries are given they are executed against all engines which is not what we want
// TODO: add support for map[engine]queries in uncover
// Note below implementation is intentionally sequential to avoid burning all the API keys
counter := 0
for eng, queries := range queriesMap {
// create new uncover options for each engine
uncoverOpts := &uncover.Options{
Agents: []string{eng},
Queries: queries,
Limit: opts.Limit,
MaxRetry: opts.MaxRetry,
Timeout: opts.Timeout,
RateLimit: opts.RateLimit,
RateLimitUnit: opts.RateLimitUnit,
}
ch, err := GetTargetsFromUncover(ctx, outputFormat, uncoverOpts)
if err != nil {
gologger.Error().Msgf("Could not get targets using %v engine from uncover: %s", eng, err)
return
}
for {
select {
case <-ctx.Done():
return
case res, ok := <-ch:
if !ok {
return
}
for result := range ch {
replacer := strings.NewReplacer(
"ip", result.IP,
"host", result.Host,
"port", fmt.Sprint(result.Port),
)
ret <- replacer.Replace(field)
result <- res
counter++
if opts.Limit > 0 && counter >= opts.Limit {
return
}
}(agent, uncoverQuery)
}
}
}
swg.Wait()
close(ret)
}()
return ret, nil
}
func loadKeys(engine string, options *ucRunner.Options) error {
switch engine {
case "fofa":
if email, exists := os.LookupEnv("FOFA_EMAIL"); exists {
if key, exists := os.LookupEnv("FOFA_KEY"); exists {
options.Provider.Fofa = append(options.Provider.Fofa, fmt.Sprintf("%s:%s", email, key))
} else {
return errors.New("missing FOFA_KEY env variable")
}
} else {
return errors.Errorf("FOFA_EMAIL & FOFA_KEY env variables are not configured")
}
case "shodan":
if key, exists := os.LookupEnv("SHODAN_API_KEY"); exists {
options.Provider.Shodan = append(options.Provider.Shodan, key)
} else {
return errors.Errorf("SHODAN_API_KEY env variable is not configured")
}
case "censys":
if id, exists := os.LookupEnv("CENSYS_API_ID"); exists {
if secret, exists := os.LookupEnv("CENSYS_API_SECRET"); exists {
options.Provider.Censys = append(options.Provider.Censys, fmt.Sprintf("%s:%s", id, secret))
} else {
return errors.New("missing CENSYS_API_SECRET env variable")
}
} else {
return errors.Errorf("CENSYS_API_ID & CENSYS_API_SECRET env variable is not configured")
}
case "hunter":
if key, exists := os.LookupEnv("HUNTER_API_KEY"); exists {
options.Provider.Hunter = append(options.Provider.Hunter, key)
} else {
return errors.Errorf("HUNTER_API_KEY env variable is not configured")
}
case "zoomeye":
if key, exists := os.LookupEnv("ZOOMEYE_API_KEY"); exists {
options.Provider.ZoomEye = append(options.Provider.ZoomEye, key)
} else {
return errors.Errorf("ZOOMEYE_API_KEY env variable is not configured")
}
case "quake":
if key, exists := os.LookupEnv("QUAKE_TOKEN"); exists {
options.Provider.Quake = append(options.Provider.Quake, key)
} else {
return errors.Errorf("QUAKE_TOKEN env variable is not configured")
}
case "netlas":
if key, exists := os.LookupEnv("NETLAS_API_KEY"); exists {
options.Provider.Netlas = append(options.Provider.Netlas, key)
} else {
return errors.Errorf("NETLAS_API_KEY env variable is not configured")
}
case "criminalip":
if key, exists := os.LookupEnv("CRIMINALIP_API_KEY"); exists {
options.Provider.CriminalIP = append(options.Provider.CriminalIP, key)
} else {
return errors.Errorf("CRIMINALIP_API_KEY env variable is not configured")
}
default:
return errors.Errorf("unknown uncover agent")
}
return nil
return result
}

View File

@ -321,7 +321,7 @@ type Options struct {
// Uncover search limit
UncoverLimit int
// Uncover search delay
UncoverDelay int
UncoverRateLimit int
// ScanAllIPs associated to a dns record
ScanAllIPs bool
// IPVersion to scan (4,6)