nuclei/pkg/catalog/loader/remote_loader.go
HD Moore 0c7bade615 Remove singletons from Nuclei engine (continuation of #6210) (#6296)
* introducing execution id

* wip

* .

* adding separate execution context id

* lint

* vet

* fixing pg dialers

* test ignore

* fixing loader FD limit

* test

* fd fix

* wip: remove CloseProcesses() from dev merge

* wip: fix merge issue

* protocolstate: stop memguarding on last dialer delete

* avoid data race in dialers.RawHTTPClient

* use shared logger and avoid race conditions

* use shared logger and avoid race conditions

* go mod

* patch executionId into compiled template cache

* clean up comment in Parse

* go mod update

* bump echarts

* address merge issues

* fix use of gologger

* switch cmd/nuclei to options.Logger

* address merge issues with go.mod

* go vet: address copy of lock with new Copy function

* fixing tests

* disable speed control

* fix nil ExecuterOptions

* removing deprecated code

* fixing result print

* default logger

* cli default logger

* filter warning from results

* fix performance test

* hardcoding path

* disable upload

* refactor(runner): uses `Warning` instead of `Print` for `pdcpUploadErrMsg`

Signed-off-by: Dwi Siswanto <git@dw1.io>

* Revert "disable upload"

This reverts commit 114fbe6663361bf41cf8b2645fd2d57083d53682.

* Revert "hardcoding path"

This reverts commit cf12ca800e0a0e974bd9fd4826a24e51547f7c00.

---------

Signed-off-by: Dwi Siswanto <git@dw1.io>
Co-authored-by: Mzack9999 <mzack9999@protonmail.com>
Co-authored-by: Dwi Siswanto <git@dw1.io>
Co-authored-by: Dwi Siswanto <25837540+dwisiswant0@users.noreply.github.com>
2025-08-02 15:56:04 +05:30

138 lines
3.4 KiB
Go

package loader
import (
"bufio"
"fmt"
"net/url"
"strings"
"sync"
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/retryablehttp-go"
sliceutil "github.com/projectdiscovery/utils/slice"
stringsutil "github.com/projectdiscovery/utils/strings"
syncutil "github.com/projectdiscovery/utils/sync"
)
type ContentType string
const (
Template ContentType = "Template"
Workflow ContentType = "Workflow"
)
type RemoteContent struct {
Content []string
Type ContentType
Error error
}
func getRemoteTemplatesAndWorkflows(templateURLs, workflowURLs, remoteTemplateDomainList []string) ([]string, []string, error) {
var (
err error
muErr sync.Mutex
)
remoteTemplateList := sliceutil.NewSyncSlice[string]()
remoteWorkFlowList := sliceutil.NewSyncSlice[string]()
awg, errAwg := syncutil.New(syncutil.WithSize(50))
if errAwg != nil {
return nil, nil, errAwg
}
loadItem := func(URL string, contentType ContentType) {
defer awg.Done()
remoteContent := getRemoteContent(URL, remoteTemplateDomainList, contentType)
if remoteContent.Error != nil {
muErr.Lock()
if err != nil {
err = errors.New(remoteContent.Error.Error() + ": " + err.Error())
} else {
err = remoteContent.Error
}
muErr.Unlock()
} else {
switch remoteContent.Type {
case Template:
remoteTemplateList.Append(remoteContent.Content...)
case Workflow:
remoteWorkFlowList.Append(remoteContent.Content...)
}
}
}
for _, templateURL := range templateURLs {
awg.Add()
go loadItem(templateURL, Template)
}
for _, workflowURL := range workflowURLs {
awg.Add()
go loadItem(workflowURL, Workflow)
}
awg.Wait()
return remoteTemplateList.Slice, remoteWorkFlowList.Slice, err
}
func getRemoteContent(URL string, remoteTemplateDomainList []string, contentType ContentType) RemoteContent {
if err := validateRemoteTemplateURL(URL, remoteTemplateDomainList); err != nil {
return RemoteContent{Error: err}
}
if strings.HasPrefix(URL, "http") && stringsutil.HasSuffixAny(URL, extensions.YAML) {
return RemoteContent{
Content: []string{URL},
Type: contentType,
}
}
response, err := retryablehttp.DefaultClient().Get(URL)
if err != nil {
return RemoteContent{Error: err}
}
defer func() {
_ = response.Body.Close()
}()
if response.StatusCode < 200 || response.StatusCode > 299 {
return RemoteContent{Error: fmt.Errorf("get \"%s\": unexpect status %d", URL, response.StatusCode)}
}
scanner := bufio.NewScanner(response.Body)
var templateList []string
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if text == "" {
continue
}
if utils.IsURL(text) {
if err := validateRemoteTemplateURL(text, remoteTemplateDomainList); err != nil {
return RemoteContent{Error: err}
}
}
templateList = append(templateList, text)
}
if err := scanner.Err(); err != nil {
return RemoteContent{Error: errors.Wrap(err, "get \"%s\"")}
}
return RemoteContent{
Content: templateList,
Type: contentType,
}
}
func validateRemoteTemplateURL(inputURL string, remoteTemplateDomainList []string) error {
parsedURL, err := url.Parse(inputURL)
if err != nil {
return err
}
if !utils.StringSliceContains(remoteTemplateDomainList, parsedURL.Host) {
return errors.Errorf("Remote template URL host (%s) is not present in the `remote-template-domain` list in nuclei config", parsedURL.Host)
}
return nil
}