Path: blob/dev/pkg/protocols/http/request_annotations.go
2847 views
package http12import (3"context"4"crypto/tls"5"net"6"regexp"7"strings"8"time"910"github.com/projectdiscovery/fastdialer/fastdialer"11"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool"12"github.com/projectdiscovery/nuclei/v3/pkg/types/nucleierr"13"github.com/projectdiscovery/retryablehttp-go"14"github.com/projectdiscovery/utils/errkit"15iputil "github.com/projectdiscovery/utils/ip"16stringsutil "github.com/projectdiscovery/utils/strings"17)1819var (20// @Host:target overrides the input target with the annotated one (similar to self-contained requests)21reHostAnnotation = regexp.MustCompile(`(?m)^@Host:\s*(.+)\s*$`)22// @tls-sni:target overrides the input target with the annotated one23// special values:24// request.host: takes the value from the host header25// target: overrides with the specific value26reSniAnnotation = regexp.MustCompile(`(?m)^@tls-sni:\s*(.+)\s*$`)27// @timeout:duration overrides the input timeout with a custom duration28reTimeoutAnnotation = regexp.MustCompile(`(?m)^@timeout:\s*(.+)\s*$`)29// @once sets the request to be executed only once for a specific URL30reOnceAnnotation = regexp.MustCompile(`(?m)^@once\s*$`)31// maxAnnotationTimeout is the maximum timeout allowed for @timeout32// annotations to prevent DoS attacks via extremely large timeout values.33maxAnnotationTimeout = 5 * time.Minute34// ErrTimeoutAnnotationDeadline is the error returned when a specific amount of time was exceeded for a request35// which was allotted using @timeout annotation this usually means that vulnerability was not found36// in rare case it could also happen due to network congestion37// the assigned class is TemplateLogic since this in almost every case means that server is not vulnerable38ErrTimeoutAnnotationDeadline = errkit.New("timeout annotation deadline exceeded").SetKind(nucleierr.ErrTemplateLogic).Build()39// ErrRequestTimeoutDeadline is the error returned when a specific amount of time was exceeded for a request40// this happens when the request execution exceeds allotted time41ErrRequestTimeoutDeadline = errkit.New("request timeout deadline exceeded when notimeout is set").SetKind(errkit.ErrKindDeadline).Build()42)4344type flowMark int4546const (47Once flowMark = iota48)4950// parseFlowAnnotations and override requests flow51func parseFlowAnnotations(rawRequest string) (flowMark, bool) {52var fm flowMark53// parse request for known override annotations54var hasFlowOverride bool55// @once56if reOnceAnnotation.MatchString(rawRequest) {57fm = Once58hasFlowOverride = true59}6061return fm, hasFlowOverride62}6364type annotationOverrides struct {65request *retryablehttp.Request66cancelFunc context.CancelFunc67interactshURLs []string68}6970// parseAnnotations and override requests settings71func (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Request) (overrides annotationOverrides, modified bool) {72// parse request for known override annotations7374// @Host:target75if hosts := reHostAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {76value := strings.TrimSpace(hosts[1])77// handle scheme78switch {79case stringsutil.HasPrefixI(value, "http://"):80request.Scheme = "http"81case stringsutil.HasPrefixI(value, "https://"):82request.Scheme = "https"83}8485value = stringsutil.TrimPrefixAny(value, "http://", "https://")8687if isHostPort(value) {88request.URL.Host = value89} else {90hostPort := value91port := request.Port()92if port != "" {93hostPort = net.JoinHostPort(hostPort, port)94}95request.URL.Host = hostPort96}97modified = true98}99100// @tls-sni:target101if hosts := reSniAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {102value := strings.TrimSpace(hosts[1])103value = stringsutil.TrimPrefixAny(value, "http://", "https://")104105var literal bool106switch value {107case "request.host":108value = request.Host109case "interactsh-url":110if interactshURL, err := r.options.Interactsh.NewURLWithData("interactsh-url"); err == nil {111value = interactshURL112}113overrides.interactshURLs = append(overrides.interactshURLs, value)114default:115literal = true116}117ctx := context.WithValue(request.Context(), fastdialer.SniName, value)118request = request.Clone(ctx)119120if literal {121request.TLS = &tls.ConnectionState{ServerName: value}122}123modified = true124}125126// @timeout:duration127if r.connConfiguration.NoTimeout {128modified = true129var ctx context.Context130131if duration := reTimeoutAnnotation.FindStringSubmatch(rawRequest); len(duration) > 0 {132value := strings.TrimSpace(duration[1])133if parsedTimeout, err := time.ParseDuration(value); err == nil {134// Cap at maximum allowed timeout to prevent DoS via extremely large timeout values135if parsedTimeout > maxAnnotationTimeout {136parsedTimeout = maxAnnotationTimeout137}138139//nolint:govet // cancelled automatically by withTimeout140// global timeout is overridden by annotation by replacing context141ctx, overrides.cancelFunc = context.WithTimeoutCause(context.TODO(), parsedTimeout, ErrTimeoutAnnotationDeadline)142// add timeout value to context143ctx = context.WithValue(ctx, httpclientpool.WithCustomTimeout{}, httpclientpool.WithCustomTimeout{Timeout: parsedTimeout})144request = request.Clone(ctx)145}146} else {147//nolint:govet // cancelled automatically by withTimeout148// global timeout is overridden by annotation by replacing context149ctx, overrides.cancelFunc = context.WithTimeoutCause(context.TODO(), r.options.Options.GetTimeouts().HttpTimeout, ErrRequestTimeoutDeadline)150request = request.Clone(ctx)151}152}153154overrides.request = request155156return157}158159func isHostPort(value string) bool {160_, port, err := net.SplitHostPort(value)161if err != nil {162return false163}164if !iputil.IsPort(port) {165return false166}167return true168}169170171