nuclei/pkg/catalog/loader/remote_loader.go

138 lines
3.4 KiB
Go
Raw Normal View History

package loader
import (
"bufio"
"fmt"
"net/url"
"strings"
2025-05-09 21:22:37 +02:00
"sync"
2021-11-05 17:24:23 +05:30
"github.com/pkg/errors"
2022-02-07 16:41:55 +02:00
"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
2023-03-02 14:54:01 +01:00
"github.com/projectdiscovery/retryablehttp-go"
2025-05-09 21:22:37 +02:00
sliceutil "github.com/projectdiscovery/utils/slice"
stringsutil "github.com/projectdiscovery/utils/strings"
2025-05-09 21:22:37 +02:00
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) {
2025-05-09 21:22:37 +02:00
var (
err error
muErr sync.Mutex
)
remoteTemplateList := sliceutil.NewSyncSlice[string]()
remoteWorkFlowList := sliceutil.NewSyncSlice[string]()
2025-05-09 21:22:37 +02:00
awg, errAwg := syncutil.New(syncutil.WithSize(50))
if errAwg != nil {
return nil, nil, errAwg
}
2025-05-09 21:22:37 +02:00
loadItem := func(URL string, contentType ContentType) {
defer awg.Done()
remoteContent := getRemoteContent(URL, remoteTemplateDomainList, contentType)
if remoteContent.Error != nil {
2025-05-09 21:22:37 +02:00
muErr.Lock()
if err != nil {
err = errors.New(remoteContent.Error.Error() + ": " + err.Error())
} else {
err = remoteContent.Error
}
2025-05-09 21:22:37 +02:00
muErr.Unlock()
} else {
2025-05-07 23:18:33 +02:00
switch remoteContent.Type {
case Template:
2025-05-09 21:22:37 +02:00
remoteTemplateList.Append(remoteContent.Content...)
2025-05-07 23:18:33 +02:00
case Workflow:
2025-05-09 21:22:37 +02:00
remoteWorkFlowList.Append(remoteContent.Content...)
}
}
}
2025-05-09 21:22:37 +02:00
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
}
2025-05-09 21:22:37 +02:00
func getRemoteContent(URL string, remoteTemplateDomainList []string, contentType ContentType) RemoteContent {
2022-02-07 16:41:55 +02:00
if err := validateRemoteTemplateURL(URL, remoteTemplateDomainList); err != nil {
2025-05-09 21:22:37 +02:00
return RemoteContent{Error: err}
}
if strings.HasPrefix(URL, "http") && stringsutil.HasSuffixAny(URL, extensions.YAML) {
2025-05-09 21:22:37 +02:00
return RemoteContent{
Content: []string{URL},
Type: contentType,
}
}
2023-03-02 14:54:01 +01:00
response, err := retryablehttp.DefaultClient().Get(URL)
if err != nil {
2025-05-09 21:22:37 +02:00
return RemoteContent{Error: err}
}
2025-05-07 23:18:33 +02:00
defer func() {
_ = response.Body.Close()
}()
if response.StatusCode < 200 || response.StatusCode > 299 {
2025-05-09 21:22:37 +02:00
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) {
2022-02-07 16:41:55 +02:00
if err := validateRemoteTemplateURL(text, remoteTemplateDomainList); err != nil {
2025-05-09 21:22:37 +02:00
return RemoteContent{Error: err}
}
}
templateList = append(templateList, text)
}
if err := scanner.Err(); err != nil {
2025-05-09 21:22:37 +02:00
return RemoteContent{Error: errors.Wrap(err, "get \"%s\"")}
}
2025-05-09 21:22:37 +02:00
return RemoteContent{
Content: templateList,
Type: contentType,
}
}
2022-02-07 16:41:55 +02:00
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
}