Path: blob/dev/pkg/protocols/network/network.go
2070 views
package network12import (3"strconv"4"strings"56"github.com/pkg/errors"78"github.com/projectdiscovery/fastdialer/fastdialer"9"github.com/projectdiscovery/nuclei/v3/pkg/operators"10"github.com/projectdiscovery/nuclei/v3/pkg/protocols"11"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"12"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"13"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool"14"github.com/projectdiscovery/utils/errkit"15fileutil "github.com/projectdiscovery/utils/file"16)1718// Request contains a Network protocol request to be made from a template19type Request struct {20// ID is the optional id of the request21ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the request,description=ID of the network request"`2223// description: |24// Host to send network requests to.25//26// Usually it's set to `{{Hostname}}`. If you want to enable TLS for27// TCP Connection, you can use `tls://{{Hostname}}`.28// examples:29// - value: |30// []string{"{{Hostname}}"}31Address []string `yaml:"host,omitempty" json:"host,omitempty" jsonschema:"title=host to send requests to,description=Host to send network requests to"`32addresses []addressKV3334// description: |35// Attack is the type of payload combinations to perform.36//37// Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates38// permutations and combinations for all payloads.39AttackType generators.AttackTypeHolder `yaml:"attack,omitempty" json:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb"`40// description: |41// Payloads contains any payloads for the current request.42//43// Payloads support both key-values combinations where a list44// of payloads is provided, or optionally a single file can also45// be provided as payload which will be read on run-time.46Payloads map[string]interface{} `yaml:"payloads,omitempty" json:"payloads,omitempty" jsonschema:"title=payloads for the network request,description=Payloads contains any payloads for the current request"`47// description: |48// Threads specifies number of threads to use sending requests. This enables Connection Pooling.49//50// Connection: Close attribute must not be used in request while using threads flag, otherwise51// pooling will fail and engine will continue to close connections after requests.52// examples:53// - name: Send requests using 10 concurrent threads54// value: 1055Threads int `yaml:"threads,omitempty" json:"threads,omitempty" jsonschema:"title=threads for sending requests,description=Threads specifies number of threads to use sending requests. This enables Connection Pooling"`5657// description: |58// Inputs contains inputs for the network socket59Inputs []*Input `yaml:"inputs,omitempty" json:"inputs,omitempty" jsonschema:"title=inputs for the network request,description=Inputs contains any input/output for the current request"`60// description: |61// Port is the port to send network requests to. this acts as default port but is overriden if target/input contains62// non-http(s) ports like 80,8080,8081 etc63Port string `yaml:"port,omitempty" json:"port,omitempty" jsonschema:"title=port to send requests to,description=Port to send network requests to,oneof_type=string;integer"`6465// description: |66// ExcludePorts is the list of ports to exclude from being scanned . It is intended to be used with `Port` field and contains a list of ports which are ignored/skipped67ExcludePorts string `yaml:"exclude-ports,omitempty" json:"exclude-ports,omitempty" jsonschema:"title=exclude ports from being scanned,description=Exclude ports from being scanned"`68// description: |69// ReadSize is the size of response to read at the end70//71// Default value for read-size is 1024.72// examples:73// - value: "2048"74ReadSize int `yaml:"read-size,omitempty" json:"read-size,omitempty" jsonschema:"title=size of network response to read,description=Size of response to read at the end. Default is 1024 bytes"`75// description: |76// ReadAll determines if the data stream should be read till the end regardless of the size77//78// Default value for read-all is false.79// examples:80// - value: false81ReadAll bool `yaml:"read-all,omitempty" json:"read-all,omitempty" jsonschema:"title=read all response stream,description=Read all response stream till the server stops sending"`8283// description: |84// SelfContained specifies if the request is self-contained.85SelfContained bool `yaml:"-" json:"-"`8687// description: |88// StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.89StopAtFirstMatch bool `yaml:"stop-at-first-match,omitempty" json:"stop-at-first-match,omitempty" jsonschema:"title=stop at first match,description=Stop the execution after a match is found"`9091// description: |92// ports is post processed list of ports to scan (obtained from Port)93ports []string `yaml:"-" json:"-"`9495// Operators for the current request go here.96operators.Operators `yaml:",inline,omitempty"`97CompiledOperators *operators.Operators `yaml:"-" json:"-"`9899generator *generators.PayloadGenerator100// cache any variables that may be needed for operation.101dialer *fastdialer.Dialer102options *protocols.ExecutorOptions103}104105// RequestPartDefinitions contains a mapping of request part definitions and their106// description. Multiple definitions are separated by commas.107// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.108var RequestPartDefinitions = map[string]string{109"template-id": "ID of the template executed",110"template-info": "Info Block of the template executed",111"template-path": "Path of the template executed",112"host": "Host is the input to the template",113"matched": "Matched is the input which was matched upon",114"type": "Type is the type of request made",115"request": "Network request made from the client",116"body,all,data": "Network response received from server (default)",117"raw": "Full Network protocol data",118}119120type addressKV struct {121address string122tls bool123}124125// Input is the input to send on the network126type Input struct {127// description: |128// Data is the data to send as the input.129//130// It supports DSL Helper Functions as well as normal expressions.131// examples:132// - value: "\"TEST\""133// - value: "\"hex_decode('50494e47')\""134Data string `yaml:"data,omitempty" json:"data,omitempty" jsonschema:"title=data to send as input,description=Data is the data to send as the input,oneof_type=string;integer"`135// description: |136// Type is the type of input specified in `data` field.137//138// Default value is text, but hex can be used for hex formatted data.139// values:140// - "hex"141// - "text"142Type NetworkInputTypeHolder `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"title=type is the type of input data,description=Type of input specified in data field,enum=hex,enum=text"`143// description: |144// Read is the number of bytes to read from socket.145//146// This can be used for protocols which expect an immediate response. You can147// read and write responses one after another and eventually perform matching148// on every data captured with `name` attribute.149//150// The [network docs](https://nuclei.projectdiscovery.io/templating-guide/protocols/network/) highlight more on how to do this.151// examples:152// - value: "1024"153Read int `yaml:"read,omitempty" json:"read,omitempty" jsonschema:"title=bytes to read from socket,description=Number of bytes to read from socket"`154// description: |155// Name is the optional name of the data read to provide matching on.156// examples:157// - value: "\"prefix\""158Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"title=optional name for data read,description=Optional name of the data read to provide matching on"`159}160161// GetID returns the unique ID of the request if any.162func (request *Request) GetID() string {163return request.ID164}165166// Compile compiles the protocol request for further execution.167func (request *Request) Compile(options *protocols.ExecutorOptions) error {168var shouldUseTLS bool169var err error170171request.options = options172for _, address := range request.Address {173// check if the connection should be encrypted174if strings.HasPrefix(address, "tls://") {175shouldUseTLS = true176address = strings.TrimPrefix(address, "tls://")177}178request.addresses = append(request.addresses, addressKV{address: address, tls: shouldUseTLS})179}180// Pre-compile any input dsl functions before executing the request.181for _, input := range request.Inputs {182if input.Type.String() != "" {183continue184}185if compiled, evalErr := expressions.Evaluate(input.Data, map[string]interface{}{}); evalErr == nil {186input.Data = compiled187}188}189190// parse ports and validate191if request.Port != "" {192for _, port := range strings.Split(request.Port, ",") {193if port == "" {194continue195}196portInt, err := strconv.Atoi(port)197if err != nil {198return errkit.Wrapf(err, "could not parse port %v from '%s'", port, request.Port)199}200if portInt < 1 || portInt > 65535 {201return errkit.Newf("port %v is not in valid range", portInt)202}203request.ports = append(request.ports, port)204}205}206207// Resolve payload paths from vars if they exists208for name, payload := range request.options.Options.Vars.AsMap() {209payloadStr, ok := payload.(string)210// check if inputs contains the payload211var hasPayloadName bool212for _, input := range request.Inputs {213if input.Type.String() != "" {214continue215}216if expressions.ContainsVariablesWithNames(map[string]interface{}{name: payload}, input.Data) == nil {217hasPayloadName = true218break219}220}221if ok && hasPayloadName && fileutil.FileExists(payloadStr) {222if request.Payloads == nil {223request.Payloads = make(map[string]interface{})224}225request.Payloads[name] = payloadStr226}227}228229if len(request.Payloads) > 0 {230request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog, request.options.Options.AttackType, request.options.Options)231if err != nil {232return errors.Wrap(err, "could not parse payloads")233}234// if we have payloads, adjust threads if none specified235request.Threads = options.GetThreadsForNPayloadRequests(request.Requests(), request.Threads)236}237238// Create a client for the class239client, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{240CustomDialer: options.CustomFastdialer,241})242if err != nil {243return errors.Wrap(err, "could not get network client")244}245request.dialer = client246247if len(request.Matchers) > 0 || len(request.Extractors) > 0 {248compiled := &request.Operators249compiled.ExcludeMatchers = options.ExcludeMatchers250compiled.TemplateID = options.TemplateID251if err := compiled.Compile(); err != nil {252return errors.Wrap(err, "could not compile operators")253}254request.CompiledOperators = compiled255}256return nil257}258259// Requests returns the total number of requests the YAML rule will perform260func (request *Request) Requests() int {261return len(request.Address)262}263264func (request *Request) SetDialer(dialer *fastdialer.Dialer) {265request.dialer = dialer266}267268// UpdateOptions replaces this request's options with a new copy269func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {270r.options.ApplyNewEngineOptions(opts)271}272273274