2022-03-14 12:32:05 +05:30
|
|
|
package automaticscan
|
2022-01-18 20:59:37 +05:30
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"io"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/corpix/uarand"
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"github.com/projectdiscovery/gologger"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/core"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool"
|
2022-03-14 12:32:05 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
|
2022-03-08 12:43:24 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
2022-01-18 20:59:37 +05:30
|
|
|
"github.com/projectdiscovery/retryablehttp-go"
|
|
|
|
|
wappalyzer "github.com/projectdiscovery/wappalyzergo"
|
|
|
|
|
)
|
|
|
|
|
|
2022-03-14 12:32:05 +05:30
|
|
|
// Service is a service for automatic automatic scan execution
|
2022-01-18 20:59:37 +05:30
|
|
|
type Service struct {
|
2022-03-08 12:43:24 +05:30
|
|
|
opts protocols.ExecuterOptions
|
|
|
|
|
store *loader.Store
|
|
|
|
|
engine *core.Engine
|
|
|
|
|
target core.InputProvider
|
|
|
|
|
wappalyzer *wappalyzer.Wappalyze
|
|
|
|
|
childExecuter *core.ChildExecuter
|
|
|
|
|
httpclient *retryablehttp.Client
|
|
|
|
|
|
|
|
|
|
results bool
|
|
|
|
|
allTemplates []string
|
2022-01-18 20:59:37 +05:30
|
|
|
}
|
|
|
|
|
|
2022-03-14 12:32:05 +05:30
|
|
|
// Options contains configuration options for automatic scan service
|
2022-01-18 20:59:37 +05:30
|
|
|
type Options struct {
|
|
|
|
|
ExecuterOpts protocols.ExecuterOptions
|
|
|
|
|
Store *loader.Store
|
|
|
|
|
Engine *core.Engine
|
|
|
|
|
Target core.InputProvider
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New takes options and returns a new smart workflow service
|
|
|
|
|
func New(opts Options) (*Service, error) {
|
|
|
|
|
wappalyzer, err := wappalyzer.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-03-08 12:43:24 +05:30
|
|
|
|
|
|
|
|
// Collect path for default directories we want to look for templates in
|
|
|
|
|
var allTemplates []string
|
|
|
|
|
for _, directory := range defaultTemplatesDirectories {
|
|
|
|
|
templates, err := opts.ExecuterOpts.Catalog.GetTemplatePath(directory)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "could not get templates in directory")
|
|
|
|
|
}
|
|
|
|
|
allTemplates = append(allTemplates, templates...)
|
|
|
|
|
}
|
|
|
|
|
childExecuter := opts.Engine.ChildExecuter()
|
|
|
|
|
|
|
|
|
|
httpclient, err := httpclientpool.Get(opts.ExecuterOpts.Options, &httpclientpool.Configuration{
|
|
|
|
|
Connection: &httpclientpool.ConnectionConfiguration{DisableKeepAlive: true},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "could not get http client")
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-18 20:59:37 +05:30
|
|
|
return &Service{
|
2022-03-08 12:43:24 +05:30
|
|
|
opts: opts.ExecuterOpts,
|
|
|
|
|
store: opts.Store,
|
|
|
|
|
engine: opts.Engine,
|
|
|
|
|
target: opts.Target,
|
|
|
|
|
wappalyzer: wappalyzer,
|
|
|
|
|
allTemplates: allTemplates,
|
|
|
|
|
childExecuter: childExecuter,
|
|
|
|
|
httpclient: httpclient,
|
2022-01-18 20:59:37 +05:30
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close closes the service
|
|
|
|
|
func (s *Service) Close() bool {
|
2022-03-08 12:43:24 +05:30
|
|
|
results := s.childExecuter.Close()
|
|
|
|
|
if results.Load() {
|
|
|
|
|
s.results = true
|
|
|
|
|
}
|
2022-01-18 20:59:37 +05:30
|
|
|
return s.results
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute performs the execution of smart workflows on provided input
|
2022-03-14 12:32:05 +05:30
|
|
|
func (s *Service) Execute() {
|
|
|
|
|
if err := s.executeWappalyzerTechDetection(); err != nil {
|
|
|
|
|
gologger.Error().Msgf("Could not execute wappalyzer based detection: %s", err)
|
2022-01-18 20:59:37 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
defaultTemplatesDirectories = []string{"cves/", "default-logins/", "dns/", "exposures/", "miscellaneous/", "misconfiguration/", "network/", "takeovers/", "vulnerabilities/"}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const maxDefaultBody = 2 * 1024 * 1024
|
|
|
|
|
|
|
|
|
|
// executeWappalyzerTechDetection implements the logic to run the wappalyzer
|
|
|
|
|
// technologies detection on inputs which returns tech.
|
|
|
|
|
//
|
|
|
|
|
// The returned tags are then used for further execution.
|
2022-03-08 12:43:24 +05:30
|
|
|
func (s *Service) executeWappalyzerTechDetection() error {
|
2022-01-18 20:59:37 +05:30
|
|
|
gologger.Info().Msgf("[workflow] Executing wappalyzer based tech detection on inputs")
|
|
|
|
|
|
|
|
|
|
// Iterate through each target making http request and identifying fingerprints
|
2022-03-08 12:43:24 +05:30
|
|
|
inputPool := s.engine.WorkPool().InputPool(types.HTTPProtocol)
|
2022-01-18 20:59:37 +05:30
|
|
|
|
2022-03-08 12:43:24 +05:30
|
|
|
s.target.Scan(func(value string) {
|
|
|
|
|
inputPool.WaitGroup.Add()
|
2022-01-18 20:59:37 +05:30
|
|
|
|
2022-03-08 12:43:24 +05:30
|
|
|
go func(input string) {
|
|
|
|
|
defer inputPool.WaitGroup.Done()
|
|
|
|
|
s.processWappalyzerInputPair(input)
|
|
|
|
|
}(value)
|
2022-01-18 20:59:37 +05:30
|
|
|
})
|
2022-03-08 12:43:24 +05:30
|
|
|
inputPool.WaitGroup.Wait()
|
|
|
|
|
return nil
|
2022-01-18 20:59:37 +05:30
|
|
|
}
|
|
|
|
|
|
2022-03-08 12:43:24 +05:30
|
|
|
func (s *Service) processWappalyzerInputPair(input string) {
|
|
|
|
|
req, err := retryablehttp.NewRequest(http.MethodGet, input, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
req.Header.Set("User-Agent", uarand.GetRandom())
|
2022-01-18 20:59:37 +05:30
|
|
|
|
2022-03-08 12:43:24 +05:30
|
|
|
resp, err := s.httpclient.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if resp != nil {
|
|
|
|
|
resp.Body.Close()
|
2022-01-18 20:59:37 +05:30
|
|
|
}
|
2022-03-08 12:43:24 +05:30
|
|
|
return
|
2022-01-18 20:59:37 +05:30
|
|
|
}
|
2022-03-08 12:43:24 +05:30
|
|
|
reader := io.LimitReader(resp.Body, maxDefaultBody)
|
|
|
|
|
data, err := ioutil.ReadAll(reader)
|
|
|
|
|
if err != nil {
|
|
|
|
|
resp.Body.Close()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
resp.Body.Close()
|
2022-01-18 20:59:37 +05:30
|
|
|
|
2022-03-08 12:43:24 +05:30
|
|
|
fingerprints := s.wappalyzer.Fingerprint(resp.Header, data)
|
|
|
|
|
items := make([]string, 0, len(fingerprints))
|
|
|
|
|
for k := range fingerprints {
|
|
|
|
|
items = append(items, strings.ToLower(k))
|
2022-01-18 20:59:37 +05:30
|
|
|
}
|
2022-03-08 12:43:24 +05:30
|
|
|
if len(items) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
2022-03-14 12:32:05 +05:30
|
|
|
templatesList := s.store.LoadTemplatesWithTags(s.allTemplates, items)
|
|
|
|
|
gologger.Info().Msgf("Executing tags (%v) for host %s (%d templates)", strings.Join(items, ","), input, len(templatesList))
|
|
|
|
|
for _, t := range templatesList {
|
|
|
|
|
if s.opts.Options.VerboseVerbose {
|
|
|
|
|
gologger.Print().Msgf("%s\n", templates.TemplateLogMessage(t.ID,
|
|
|
|
|
t.Info.Name,
|
|
|
|
|
t.Info.Authors.ToSlice(),
|
|
|
|
|
t.Info.SeverityHolder.Severity))
|
|
|
|
|
}
|
|
|
|
|
s.childExecuter.Execute(t, input)
|
2022-01-18 20:59:37 +05:30
|
|
|
}
|
|
|
|
|
}
|