Path: blob/dev/pkg/protocols/http/request_annotations.go
2070 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*$`)3132// ErrTimeoutAnnotationDeadline is the error returned when a specific amount of time was exceeded for a request33// which was alloted using @timeout annotation this usually means that vulnerability was not found34// in rare case it could also happen due to network congestion35// the assigned class is TemplateLogic since this in almost every case means that server is not vulnerable36ErrTimeoutAnnotationDeadline = errkit.New("timeout annotation deadline exceeded").SetKind(nucleierr.ErrTemplateLogic).Build()37// ErrRequestTimeoutDeadline is the error returned when a specific amount of time was exceeded for a request38// this happens when the request execution exceeds alloted time39ErrRequestTimeoutDeadline = errkit.New("request timeout deadline exceeded when notimeout is set").SetKind(errkit.ErrKindDeadline).Build()40)4142type flowMark int4344const (45Once flowMark = iota46)4748// parseFlowAnnotations and override requests flow49func parseFlowAnnotations(rawRequest string) (flowMark, bool) {50var fm flowMark51// parse request for known override annotations52var hasFlowOverride bool53// @once54if reOnceAnnotation.MatchString(rawRequest) {55fm = Once56hasFlowOverride = true57}5859return fm, hasFlowOverride60}6162type annotationOverrides struct {63request *retryablehttp.Request64cancelFunc context.CancelFunc65interactshURLs []string66}6768// parseAnnotations and override requests settings69func (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Request) (overrides annotationOverrides, modified bool) {70// parse request for known override annotations7172// @Host:target73if hosts := reHostAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {74value := strings.TrimSpace(hosts[1])75// handle scheme76switch {77case stringsutil.HasPrefixI(value, "http://"):78request.Scheme = "http"79case stringsutil.HasPrefixI(value, "https://"):80request.Scheme = "https"81}8283value = stringsutil.TrimPrefixAny(value, "http://", "https://")8485if isHostPort(value) {86request.URL.Host = value87} else {88hostPort := value89port := request.Port()90if port != "" {91hostPort = net.JoinHostPort(hostPort, port)92}93request.URL.Host = hostPort94}95modified = true96}9798// @tls-sni:target99if hosts := reSniAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {100value := strings.TrimSpace(hosts[1])101value = stringsutil.TrimPrefixAny(value, "http://", "https://")102103var literal bool104switch value {105case "request.host":106value = request.Host107case "interactsh-url":108if interactshURL, err := r.options.Interactsh.NewURLWithData("interactsh-url"); err == nil {109value = interactshURL110}111overrides.interactshURLs = append(overrides.interactshURLs, value)112default:113literal = true114}115ctx := context.WithValue(request.Context(), fastdialer.SniName, value)116request = request.Clone(ctx)117118if literal {119request.TLS = &tls.ConnectionState{ServerName: value}120}121modified = true122}123124// @timeout:duration125if r.connConfiguration.NoTimeout {126modified = true127var ctx context.Context128129if duration := reTimeoutAnnotation.FindStringSubmatch(rawRequest); len(duration) > 0 {130value := strings.TrimSpace(duration[1])131if parsed, err := time.ParseDuration(value); err == nil {132// to avoid dos via timeout request annotation in http template we set it to maximum of 2 minutes133if parsed > 2*time.Minute {134parsed = 2 * time.Minute135}136//nolint:govet // cancelled automatically by withTimeout137// global timeout is overridden by annotation by replacing context138ctx, overrides.cancelFunc = context.WithTimeoutCause(context.TODO(), parsed, ErrTimeoutAnnotationDeadline)139// add timeout value to context140ctx = context.WithValue(ctx, httpclientpool.WithCustomTimeout{}, httpclientpool.WithCustomTimeout{Timeout: parsed})141request = request.Clone(ctx)142}143} else {144//nolint:govet // cancelled automatically by withTimeout145// global timeout is overridden by annotation by replacing context146ctx, overrides.cancelFunc = context.WithTimeoutCause(context.TODO(), r.options.Options.GetTimeouts().HttpTimeout, ErrRequestTimeoutDeadline)147request = request.Clone(ctx)148}149}150151overrides.request = request152153return154}155156func isHostPort(value string) bool {157_, port, err := net.SplitHostPort(value)158if err != nil {159return false160}161if !iputil.IsPort(port) {162return false163}164return true165}166167168