mirror of https://github.com/daffainfo/nuclei.git
491 lines
20 KiB
Go
491 lines
20 KiB
Go
//go:generate dstdocgen -path "" -structure Template -output templates_doc.go -package templates
|
|
package templates
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
validate "github.com/go-playground/validator/v10"
|
|
"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/workflows"
|
|
errorutil "github.com/projectdiscovery/utils/errors"
|
|
fileutil "github.com/projectdiscovery/utils/file"
|
|
"go.uber.org/multierr"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// Template is a YAML input file which defines all the requests and
|
|
// other metadata for a template.
|
|
type Template struct {
|
|
// description: |
|
|
// ID is the unique id for the template.
|
|
//
|
|
// #### Good IDs
|
|
//
|
|
// A good ID uniquely identifies what the requests in the template
|
|
// are doing. Let's say you have a template that identifies a git-config
|
|
// file on the webservers, a good name would be `git-config-exposure`. Another
|
|
// example name is `azure-apps-nxdomain-takeover`.
|
|
// examples:
|
|
// - name: ID Example
|
|
// value: "\"CVE-2021-19520\""
|
|
ID string `yaml:"id" json:"id" jsonschema:"title=id of the template,description=The Unique ID for the template,example=cve-2021-19520,pattern=^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$"`
|
|
// description: |
|
|
// Info contains metadata information about the template.
|
|
// examples:
|
|
// - value: exampleInfoStructure
|
|
Info model.Info `yaml:"info" json:"info" jsonschema:"title=info for the template,description=Info contains metadata for the template"`
|
|
// description: |
|
|
// Flow contains the execution flow for the template.
|
|
// examples:
|
|
// - flow: |
|
|
// for region in regions {
|
|
// http(0)
|
|
// }
|
|
// for vpc in vpcs {
|
|
// http(1)
|
|
// }
|
|
//
|
|
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"`
|
|
// description: |
|
|
// Requests contains the http request to make in the template.
|
|
// WARNING: 'requests' will be deprecated and will be removed in a future release. Please use 'http' instead.
|
|
// examples:
|
|
// - value: exampleNormalHTTPRequest
|
|
RequestsHTTP []*http.Request `yaml:"requests,omitempty" json:"requests,omitempty" jsonschema:"title=http requests to make,description=HTTP requests to make for the template"`
|
|
// description: |
|
|
// HTTP contains the http request to make in the template.
|
|
// examples:
|
|
// - value: exampleNormalHTTPRequest
|
|
// RequestsWithHTTP is placeholder(internal) only, and should not be used instead use RequestsHTTP
|
|
// Deprecated: Use RequestsHTTP instead.
|
|
RequestsWithHTTP []*http.Request `yaml:"http,omitempty" json:"http,omitempty" jsonschema:"title=http requests to make,description=HTTP requests to make for the template"`
|
|
// description: |
|
|
// DNS contains the dns request to make in the template
|
|
// examples:
|
|
// - value: exampleNormalDNSRequest
|
|
RequestsDNS []*dns.Request `yaml:"dns,omitempty" json:"dns,omitempty" jsonschema:"title=dns requests to make,description=DNS requests to make for the template"`
|
|
// description: |
|
|
// File contains the file request to make in the template
|
|
// examples:
|
|
// - value: exampleNormalFileRequest
|
|
RequestsFile []*file.Request `yaml:"file,omitempty" json:"file,omitempty" jsonschema:"title=file requests to make,description=File requests to make for the template"`
|
|
// description: |
|
|
// Network contains the network request to make in the template
|
|
// WARNING: 'network' will be deprecated and will be removed in a future release. Please use 'tcp' instead.
|
|
// examples:
|
|
// - value: exampleNormalNetworkRequest
|
|
RequestsNetwork []*network.Request `yaml:"network,omitempty" json:"network,omitempty" jsonschema:"title=network requests to make,description=Network requests to make for the template"`
|
|
// description: |
|
|
// TCP contains the network request to make in the template
|
|
// examples:
|
|
// - value: exampleNormalNetworkRequest
|
|
// RequestsWithTCP is placeholder(internal) only, and should not be used instead use RequestsNetwork
|
|
// Deprecated: Use RequestsNetwork instead.
|
|
RequestsWithTCP []*network.Request `yaml:"tcp,omitempty" json:"tcp,omitempty" jsonschema:"title=network(tcp) requests to make,description=Network requests to make for the template"`
|
|
// description: |
|
|
// Headless contains the headless request to make in 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"`
|
|
// description: |
|
|
// SSL contains the SSL request to make in 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"`
|
|
// description: |
|
|
// Websocket contains the Websocket request to make in 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"`
|
|
// description: |
|
|
// WHOIS contains the WHOIS request to make in 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"`
|
|
// description: |
|
|
// Code contains code snippets.
|
|
RequestsCode []*code.Request `yaml:"code,omitempty" json:"code,omitempty" jsonschema:"title=code snippets to make,description=Code snippets"`
|
|
// description: |
|
|
// Javascript contains the javascript request to make in the template.
|
|
RequestsJavascript []*javascript.Request `yaml:"javascript,omitempty" json:"javascript,omitempty" jsonschema:"title=javascript requests to make,description=Javascript requests to make for the template"`
|
|
|
|
// description: |
|
|
// Workflows is a yaml based workflow declaration code.
|
|
workflows.Workflow `yaml:",inline,omitempty" jsonschema:"title=workflows to run,description=Workflows to run for the template"`
|
|
CompiledWorkflow *workflows.Workflow `yaml:"-" json:"-" jsonschema:"-"`
|
|
|
|
// description: |
|
|
// Self Contained marks Requests for the template as self-contained
|
|
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"`
|
|
// description: |
|
|
// Stop execution once first match is found
|
|
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"`
|
|
|
|
// description: |
|
|
// Signature is the request signature method
|
|
// values:
|
|
// - "AWS"
|
|
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"`
|
|
|
|
// description: |
|
|
// Variables contains any variables for the current request.
|
|
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"`
|
|
|
|
// description: |
|
|
// Constants contains any scalar constant for the current template
|
|
Constants map[string]interface{} `yaml:"constants,omitempty" json:"constants,omitempty" jsonschema:"title=constant for the template,description=constants contains any constant for the template"`
|
|
|
|
// TotalRequests is the total number of requests for the template.
|
|
TotalRequests int `yaml:"-" json:"-"`
|
|
// Executer is the actual template executor for running template requests
|
|
Executer protocols.Executer `yaml:"-" json:"-"`
|
|
|
|
Path string `yaml:"-" json:"-"`
|
|
|
|
// Verified defines if the template signature is digitally verified
|
|
Verified bool `yaml:"-" json:"-"`
|
|
|
|
// RequestsQueue contains all template requests in order (both protocol & request order)
|
|
RequestsQueue []protocols.Request `yaml:"-" json:"-"`
|
|
|
|
// ImportedFiles contains list of files whose contents are imported after template was compiled
|
|
ImportedFiles []string `yaml:"-" json:"-"`
|
|
}
|
|
|
|
// Type returns the type of the template
|
|
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.Workflow.Workflows) > 0:
|
|
return types.WorkflowProtocol
|
|
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
|
|
default:
|
|
return types.InvalidProtocol
|
|
}
|
|
}
|
|
|
|
// HasCodeProtocol returns true if the template has a code protocol section
|
|
func (template *Template) HasCodeProtocol() bool {
|
|
return len(template.RequestsCode) > 0
|
|
}
|
|
|
|
// validateAllRequestIDs check if that protocol already has given id if not
|
|
// then is is manually set to proto_index
|
|
func (template *Template) validateAllRequestIDs() {
|
|
// this is required in multiprotocol and flow where we save response variables
|
|
// and all other data in template context if template as two requests in a protocol
|
|
// then it is overwritten to avoid this we use proto_index as request ID
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MarshalYAML forces recursive struct validation during marshal operation
|
|
func (template *Template) MarshalYAML() ([]byte, error) {
|
|
out, marshalErr := yaml.Marshal(template)
|
|
// Review: we are adding requestIDs for templateContext
|
|
// if we are using this method then we might need to purge manually added IDS that start with `templatetype_`
|
|
// this is only applicable if there are more than 1 request fields in protocol
|
|
errValidate := validate.New().Struct(template)
|
|
return out, multierr.Append(marshalErr, errValidate)
|
|
}
|
|
|
|
// MarshalYAML forces recursive struct validation after unmarshal operation
|
|
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 len(template.RequestsHTTP) > 0 || len(template.RequestsNetwork) > 0 {
|
|
_ = deprecatedProtocolNameTemplates.Set(template.ID, true)
|
|
}
|
|
|
|
if len(alias.RequestsHTTP) > 0 && len(alias.RequestsWithHTTP) > 0 {
|
|
return errorutil.New("use http or requests, both are not supported").WithTag("invalid template")
|
|
}
|
|
if len(alias.RequestsNetwork) > 0 && len(alias.RequestsWithTCP) > 0 {
|
|
return errorutil.New("use tcp or network, both are not supported").WithTag("invalid template")
|
|
}
|
|
if len(alias.RequestsWithHTTP) > 0 {
|
|
template.RequestsHTTP = alias.RequestsWithHTTP
|
|
}
|
|
if len(alias.RequestsWithTCP) > 0 {
|
|
template.RequestsNetwork = alias.RequestsWithTCP
|
|
}
|
|
err = validate.New().Struct(template)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check if the template contains more than 1 protocol request
|
|
// if so preserve the order of the protocols and requests
|
|
if template.hasMultipleRequests() {
|
|
var tempmap yaml.MapSlice
|
|
err = unmarshal(&tempmap)
|
|
if err != nil {
|
|
return errorutil.NewWithErr(err).Msgf("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)
|
|
}
|
|
// add protocols to the protocol stack (the idea is to preserve the order of the protocols)
|
|
template.addRequestsToQueue(arr...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ImportFileRefs checks if sensitive fields like `flow` , `source` in code protocol are referencing files
|
|
// instead of actual javascript / engine code if so it loads the file contents and replaces the reference
|
|
func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) error {
|
|
var errs []error
|
|
|
|
loadFile := func(source string) (string, bool) {
|
|
// load file respecting sandbox
|
|
data, err := options.Options.LoadHelperFile(source, options.TemplatePath, options.Catalog)
|
|
if err == nil {
|
|
defer 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 code protocol requests
|
|
for _, request := range template.RequestsCode {
|
|
// simple test to check if source is a file or a snippet
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
// flow code references
|
|
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
|
|
}
|
|
|
|
// for multiprotocol requests
|
|
// mutually exclusive with flow
|
|
if len(template.RequestsQueue) > 0 && template.Flow == "" {
|
|
// this is most likely a multiprotocol template
|
|
for _, req := range template.RequestsQueue {
|
|
if req.Type() == types.CodeProtocol {
|
|
request := req.(*code.Request)
|
|
// simple test to check if source is a file or a snippet
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return multierr.Combine(errs...)
|
|
}
|
|
|
|
// GetFileImports returns a list of files that are imported by the template
|
|
func (template *Template) GetFileImports() []string {
|
|
return template.ImportedFiles
|
|
}
|
|
|
|
// addProtocolsToQueue adds protocol requests to the queue and preserves order of the protocols and requests
|
|
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)...)
|
|
// for deprecated protocols
|
|
case "requests":
|
|
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)
|
|
case "network":
|
|
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// hasMultipleRequests checks if the template has multiple requests
|
|
// if so it preserves the order of the request during compile and execution
|
|
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
|
|
}
|
|
|
|
// MarshalJSON forces recursive struct validation during marshal operation
|
|
func (template *Template) MarshalJSON() ([]byte, error) {
|
|
out, marshalErr := json.Marshal(template)
|
|
errValidate := validate.New().Struct(template)
|
|
return out, multierr.Append(marshalErr, errValidate)
|
|
}
|
|
|
|
// UnmarshalJSON forces recursive struct validation after unmarshal operation
|
|
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 = validate.New().Struct(template)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check if the template contains more than 1 protocol request
|
|
// if so preserve the order of the protocols and requests
|
|
if template.hasMultipleRequests() {
|
|
var tempMap map[string]interface{}
|
|
err = json.Unmarshal(data, &tempMap)
|
|
if err != nil {
|
|
return errorutil.NewWithErr(err).Msgf("failed to unmarshal multi protocol template %s", template.ID)
|
|
}
|
|
arr := []string{}
|
|
for k := range tempMap {
|
|
arr = append(arr, k)
|
|
}
|
|
template.addRequestsToQueue(arr...)
|
|
}
|
|
return nil
|
|
}
|