package templates
import (
"io"
"path/filepath"
"strconv"
"strings"
"github.com/projectdiscovery/nuclei/v3/pkg/model"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/code"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/file"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/javascript"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/ssl"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/websocket"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/whois"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
"go.uber.org/multierr"
"gopkg.in/yaml.v2"
)
type Template struct {
ID string `yaml:"id" json:"id" jsonschema:"title=id of the template,description=The Unique ID for the template,required,example=cve-2021-19520,pattern=^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$"`
Info model.Info `yaml:"info" json:"info" jsonschema:"title=info for the template,description=Info contains metadata for the template,required,type=object"`
Flow string `yaml:"flow,omitempty" json:"flow,omitempty" jsonschema:"title=template execution flow in js,description=Flow contains js code which defines how the template should be executed,type=string,example='flow: http(0) && http(1)'"`
RequestsHTTP []*http.Request `yaml:"requests,omitempty" json:"requests,omitempty" jsonschema:"title=http requests to make,description=HTTP requests to make for the template,deprecated=true"`
RequestsWithHTTP []*http.Request `yaml:"http,omitempty" json:"http,omitempty" jsonschema:"title=http requests to make,description=HTTP requests to make for the template"`
RequestsDNS []*dns.Request `yaml:"dns,omitempty" json:"dns,omitempty" jsonschema:"title=dns requests to make,description=DNS requests to make for the template"`
RequestsFile []*file.Request `yaml:"file,omitempty" json:"file,omitempty" jsonschema:"title=file requests to make,description=File requests to make for the template"`
RequestsNetwork []*network.Request `yaml:"network,omitempty" json:"network,omitempty" jsonschema:"title=network requests to make,description=Network requests to make for the template,deprecated=true"`
RequestsWithTCP []*network.Request `yaml:"tcp,omitempty" json:"tcp,omitempty" jsonschema:"title=network(tcp) requests to make,description=Network requests to make for the template"`
RequestsHeadless []*headless.Request `yaml:"headless,omitempty" json:"headless,omitempty" jsonschema:"title=headless requests to make,description=Headless requests to make for the template"`
RequestsSSL []*ssl.Request `yaml:"ssl,omitempty" json:"ssl,omitempty" jsonschema:"title=ssl requests to make,description=SSL requests to make for the template"`
RequestsWebsocket []*websocket.Request `yaml:"websocket,omitempty" json:"websocket,omitempty" jsonschema:"title=websocket requests to make,description=Websocket requests to make for the template"`
RequestsWHOIS []*whois.Request `yaml:"whois,omitempty" json:"whois,omitempty" jsonschema:"title=whois requests to make,description=WHOIS requests to make for the template"`
RequestsCode []*code.Request `yaml:"code,omitempty" json:"code,omitempty" jsonschema:"title=code snippets to make,description=Code snippets"`
RequestsJavascript []*javascript.Request `yaml:"javascript,omitempty" json:"javascript,omitempty" jsonschema:"title=javascript requests to make,description=Javascript requests to make for the template"`
workflows.Workflow `yaml:",inline,omitempty" jsonschema:"title=workflows to run,description=Workflows to run for the template"`
CompiledWorkflow *workflows.Workflow `yaml:"-" json:"-" jsonschema:"-"`
SelfContained bool `yaml:"self-contained,omitempty" json:"self-contained,omitempty" jsonschema:"title=mark requests as self-contained,description=Mark Requests for the template as self-contained"`
StopAtFirstMatch bool `yaml:"stop-at-first-match,omitempty" json:"stop-at-first-match,omitempty" jsonschema:"title=stop at first match,description=Stop at first match for the template"`
Signature http.SignatureTypeHolder `yaml:"signature,omitempty" json:"signature,omitempty" jsonschema:"title=signature is the http request signature method,description=Signature is the HTTP Request signature Method,enum=AWS,deprecated=true"`
Variables variables.Variable `yaml:"variables,omitempty" json:"variables,omitempty" jsonschema:"title=variables for the http request,description=Variables contains any variables for the current request,type=object"`
Constants map[string]interface{} `yaml:"constants,omitempty" json:"constants,omitempty" jsonschema:"title=constant for the template,description=constants contains any constant for the template,type=object"`
TotalRequests int `yaml:"-" json:"-"`
Executer protocols.Executer `yaml:"-" json:"-"`
Path string `yaml:"-" json:"-"`
Verified bool `yaml:"-" json:"-"`
TemplateVerifier string `yaml:"-" json:"-"`
RequestsQueue []protocols.Request `yaml:"-" json:"-"`
ImportedFiles []string `yaml:"-" json:"-"`
}
func (template *Template) Type() types.ProtocolType {
switch {
case len(template.RequestsDNS) > 0:
return types.DNSProtocol
case len(template.RequestsFile) > 0:
return types.FileProtocol
case len(template.RequestsHTTP) > 0:
return types.HTTPProtocol
case len(template.RequestsHeadless) > 0:
return types.HeadlessProtocol
case len(template.RequestsNetwork) > 0:
return types.NetworkProtocol
case len(template.RequestsSSL) > 0:
return types.SSLProtocol
case len(template.RequestsWebsocket) > 0:
return types.WebsocketProtocol
case len(template.RequestsWHOIS) > 0:
return types.WHOISProtocol
case len(template.RequestsCode) > 0:
return types.CodeProtocol
case len(template.RequestsJavascript) > 0:
return types.JavascriptProtocol
case len(template.Workflows) > 0:
return types.WorkflowProtocol
default:
return types.InvalidProtocol
}
}
func (template *Template) IsFuzzing() bool {
if len(template.RequestsHTTP) == 0 && len(template.RequestsHeadless) == 0 {
return false
}
if len(template.RequestsHTTP) > 0 {
for _, request := range template.RequestsHTTP {
if len(request.Fuzzing) > 0 {
return true
}
}
}
if len(template.RequestsHeadless) > 0 {
for _, request := range template.RequestsHeadless {
if len(request.Fuzzing) > 0 {
return true
}
}
}
return false
}
func (template *Template) UsesRequestSignature() bool {
return template.Signature.Value.String() != ""
}
func (template *Template) HasCodeProtocol() bool {
return len(template.RequestsCode) > 0
}
func (template *Template) validateAllRequestIDs() {
if len(template.RequestsCode) > 1 {
for i, req := range template.RequestsCode {
if req.ID == "" {
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
}
}
}
if len(template.RequestsDNS) > 1 {
for i, req := range template.RequestsDNS {
if req.ID == "" {
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
}
}
}
if len(template.RequestsFile) > 1 {
for i, req := range template.RequestsFile {
if req.ID == "" {
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
}
}
}
if len(template.RequestsHTTP) > 1 {
for i, req := range template.RequestsHTTP {
if req.ID == "" {
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
}
}
}
if len(template.RequestsHeadless) > 1 {
for i, req := range template.RequestsHeadless {
if req.ID == "" {
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
}
}
}
if len(template.RequestsNetwork) > 1 {
for i, req := range template.RequestsNetwork {
if req.ID == "" {
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
}
}
}
if len(template.RequestsSSL) > 1 {
for i, req := range template.RequestsSSL {
if req.ID == "" {
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
}
}
}
if len(template.RequestsWebsocket) > 1 {
for i, req := range template.RequestsWebsocket {
if req.ID == "" {
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
}
}
}
if len(template.RequestsWHOIS) > 1 {
for i, req := range template.RequestsWHOIS {
if req.ID == "" {
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
}
}
}
if len(template.RequestsJavascript) > 1 {
for i, req := range template.RequestsJavascript {
if req.ID == "" {
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
}
}
}
}
func (template *Template) MarshalYAML() ([]byte, error) {
out, marshalErr := yaml.Marshal(template)
errValidate := tplValidator.Struct(template)
return out, multierr.Append(marshalErr, errValidate)
}
func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error {
type Alias Template
alias := &Alias{}
err := unmarshal(alias)
if err != nil {
return err
}
*template = Template(*alias)
if !ReTemplateID.MatchString(template.ID) {
return errkit.New("template id must match expression %v", ReTemplateID, "tag", "invalid_template")
}
info := template.Info
if utils.IsBlank(info.Name) {
return errkit.New("no template name field provided", "tag", "invalid_template")
}
if info.Authors.IsEmpty() {
return errkit.New("no template author field provided", "tag", "invalid_template")
}
if len(template.RequestsHTTP) > 0 || len(template.RequestsNetwork) > 0 {
_ = deprecatedProtocolNameTemplates.Set(template.ID, true)
}
if len(alias.RequestsHTTP) > 0 && len(alias.RequestsWithHTTP) > 0 {
return errkit.New("use http or requests, both are not supported", "tag", "invalid_template")
}
if len(alias.RequestsNetwork) > 0 && len(alias.RequestsWithTCP) > 0 {
return errkit.New("use tcp or network, both are not supported", "tag", "invalid_template")
}
if len(alias.RequestsWithHTTP) > 0 {
template.RequestsHTTP = alias.RequestsWithHTTP
}
if len(alias.RequestsWithTCP) > 0 {
template.RequestsNetwork = alias.RequestsWithTCP
}
err = tplValidator.Struct(template)
if err != nil {
return err
}
if template.hasMultipleRequests() {
var tempmap yaml.MapSlice
err = unmarshal(&tempmap)
if err != nil {
return errkit.Wrapf(err, "failed to unmarshal multi protocol template %s", template.ID)
}
arr := []string{}
for _, v := range tempmap {
key, ok := v.Key.(string)
if !ok {
continue
}
arr = append(arr, key)
}
template.addRequestsToQueue(arr...)
}
return nil
}
func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) error {
var errs []error
loadFile := func(source string) (string, bool) {
data, err := options.Options.LoadHelperFile(source, options.TemplatePath, options.Catalog)
if err == nil {
defer func() {
_ = data.Close()
}()
bin, err := io.ReadAll(data)
if err == nil {
return string(bin), true
} else {
errs = append(errs, err)
}
} else {
errs = append(errs, err)
}
return "", false
}
for _, request := range template.RequestsCode {
if len(strings.Split(request.Source, "\n")) == 1 && fileutil.FileExists(request.Source) {
if val, ok := loadFile(request.Source); ok {
template.ImportedFiles = append(template.ImportedFiles, request.Source)
request.Source = val
}
}
}
for _, request := range template.RequestsJavascript {
if len(strings.Split(request.Code, "\n")) == 1 && fileutil.FileExists(request.Code) {
if val, ok := loadFile(request.Code); ok {
template.ImportedFiles = append(template.ImportedFiles, request.Code)
request.Code = val
}
}
}
if template.Flow != "" {
if len(template.Flow) > 0 && filepath.Ext(template.Flow) == ".js" && fileutil.FileExists(template.Flow) {
if val, ok := loadFile(template.Flow); ok {
template.ImportedFiles = append(template.ImportedFiles, template.Flow)
template.Flow = val
}
}
options.Flow = template.Flow
}
if len(template.RequestsQueue) > 0 && template.Flow == "" {
for _, req := range template.RequestsQueue {
if req.Type() == types.CodeProtocol {
request := req.(*code.Request)
if len(strings.Split(request.Source, "\n")) == 1 && fileutil.FileExists(request.Source) {
if val, ok := loadFile(request.Source); ok {
template.ImportedFiles = append(template.ImportedFiles, request.Source)
request.Source = val
}
}
}
}
for _, req := range template.RequestsQueue {
if req.Type() == types.JavascriptProtocol {
request := req.(*javascript.Request)
if len(strings.Split(request.Code, "\n")) == 1 && fileutil.FileExists(request.Code) {
if val, ok := loadFile(request.Code); ok {
template.ImportedFiles = append(template.ImportedFiles, request.Code)
request.Code = val
}
}
}
}
}
return multierr.Combine(errs...)
}
func (template *Template) GetFileImports() []string {
return template.ImportedFiles
}
func (template *Template) addRequestsToQueue(keys ...string) {
for _, key := range keys {
switch key {
case types.DNSProtocol.String():
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsDNS)...)
case types.FileProtocol.String():
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsFile)...)
case types.HTTPProtocol.String():
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)
case types.HeadlessProtocol.String():
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHeadless)...)
case types.NetworkProtocol.String():
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)
case types.SSLProtocol.String():
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsSSL)...)
case types.WebsocketProtocol.String():
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsWebsocket)...)
case types.WHOISProtocol.String():
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsWHOIS)...)
case types.CodeProtocol.String():
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsCode)...)
case types.JavascriptProtocol.String():
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsJavascript)...)
case "requests":
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)
case "network":
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)
}
}
}
func (template *Template) hasMultipleRequests() bool {
counter := len(template.RequestsDNS) + len(template.RequestsFile) +
len(template.RequestsHTTP) + len(template.RequestsHeadless) +
len(template.RequestsNetwork) + len(template.RequestsSSL) +
len(template.RequestsWebsocket) + len(template.RequestsWHOIS) +
len(template.RequestsCode) + len(template.RequestsJavascript)
return counter > 1
}
func (template *Template) MarshalJSON() ([]byte, error) {
type TemplateAlias Template
out, marshalErr := json.Marshal((*TemplateAlias)(template))
errValidate := tplValidator.Struct(template)
return out, multierr.Append(marshalErr, errValidate)
}
func (template *Template) UnmarshalJSON(data []byte) error {
type Alias Template
alias := &Alias{}
err := json.Unmarshal(data, alias)
if err != nil {
return err
}
*template = Template(*alias)
err = tplValidator.Struct(template)
if err != nil {
return err
}
if template.hasMultipleRequests() {
var tempMap map[string]interface{}
err = json.Unmarshal(data, &tempMap)
if err != nil {
return errkit.Wrapf(err, "failed to unmarshal multi protocol template %s", template.ID)
}
arr := []string{}
for k := range tempMap {
arr = append(arr, k)
}
template.addRequestsToQueue(arr...)
}
return nil
}
func (template *Template) HasFileProtocol() bool {
return len(template.RequestsFile) > 0
}