github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/http/request_annotations.go (about)

     1  package http
     2  
     3  import (
     4  	"context"
     5  	"net"
     6  	"regexp"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/projectdiscovery/fastdialer/fastdialer"
    11  	"github.com/projectdiscovery/retryablehttp-go"
    12  	iputil "github.com/projectdiscovery/utils/ip"
    13  	stringsutil "github.com/projectdiscovery/utils/strings"
    14  )
    15  
    16  var (
    17  	// @Host:target overrides the input target with the annotated one (similar to self-contained requests)
    18  	reHostAnnotation = regexp.MustCompile(`(?m)^@Host:\s*(.+)\s*$`)
    19  	// @tls-sni:target overrides the input target with the annotated one
    20  	// special values:
    21  	// request.host: takes the value from the host header
    22  	// target: overrides with the specific value
    23  	reSniAnnotation = regexp.MustCompile(`(?m)^@tls-sni:\s*(.+)\s*$`)
    24  	// @timeout:duration overrides the input timeout with a custom duration
    25  	reTimeoutAnnotation = regexp.MustCompile(`(?m)^@timeout:\s*(.+)\s*$`)
    26  	// @once sets the request to be executed only once for a specific URL
    27  	reOnceAnnotation = regexp.MustCompile(`(?m)^@once\s*$`)
    28  )
    29  
    30  type flowMark int
    31  
    32  const (
    33  	Once flowMark = iota
    34  )
    35  
    36  // parseFlowAnnotations and override requests flow
    37  func parseFlowAnnotations(rawRequest string) (flowMark, bool) {
    38  	var fm flowMark
    39  	// parse request for known override annotations
    40  	var hasFlowOverride bool
    41  	// @once
    42  	if reOnceAnnotation.MatchString(rawRequest) {
    43  		fm = Once
    44  		hasFlowOverride = true
    45  	}
    46  
    47  	return fm, hasFlowOverride
    48  }
    49  
    50  type annotationOverrides struct {
    51  	request        *retryablehttp.Request
    52  	cancelFunc     context.CancelFunc
    53  	interactshURLs []string
    54  }
    55  
    56  // parseAnnotations and override requests settings
    57  func (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Request) (overrides annotationOverrides, modified bool) {
    58  	// parse request for known override annotations
    59  
    60  	// @Host:target
    61  	if hosts := reHostAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {
    62  		value := strings.TrimSpace(hosts[1])
    63  		// handle scheme
    64  		switch {
    65  		case stringsutil.HasPrefixI(value, "http://"):
    66  			request.URL.Scheme = "http"
    67  		case stringsutil.HasPrefixI(value, "https://"):
    68  			request.URL.Scheme = "https"
    69  		}
    70  
    71  		value = stringsutil.TrimPrefixAny(value, "http://", "https://")
    72  
    73  		if isHostPort(value) {
    74  			request.URL.Host = value
    75  		} else {
    76  			hostPort := value
    77  			port := request.URL.Port()
    78  			if port != "" {
    79  				hostPort = net.JoinHostPort(hostPort, port)
    80  			}
    81  			request.URL.Host = hostPort
    82  		}
    83  		modified = true
    84  	}
    85  
    86  	// @tls-sni:target
    87  	if hosts := reSniAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {
    88  		value := strings.TrimSpace(hosts[1])
    89  		value = stringsutil.TrimPrefixAny(value, "http://", "https://")
    90  		if idxForwardSlash := strings.Index(value, "/"); idxForwardSlash >= 0 {
    91  			value = value[:idxForwardSlash]
    92  		}
    93  
    94  		switch value {
    95  		case "request.host":
    96  			value = request.Host
    97  		case "interactsh-url":
    98  			if interactshURL, err := r.options.Interactsh.NewURLWithData("interactsh-url"); err == nil {
    99  				value = interactshURL
   100  			}
   101  			overrides.interactshURLs = append(overrides.interactshURLs, value)
   102  		}
   103  		ctx := context.WithValue(request.Context(), fastdialer.SniName, value)
   104  		request = request.Clone(ctx)
   105  		modified = true
   106  	}
   107  
   108  	// @timeout:duration
   109  	if r.connConfiguration.NoTimeout {
   110  		modified = true
   111  		var ctx context.Context
   112  
   113  		if duration := reTimeoutAnnotation.FindStringSubmatch(rawRequest); len(duration) > 0 {
   114  			value := strings.TrimSpace(duration[1])
   115  			if parsed, err := time.ParseDuration(value); err == nil {
   116  				//nolint:govet // cancelled automatically by withTimeout
   117  				ctx, overrides.cancelFunc = context.WithTimeout(context.Background(), parsed)
   118  				request = request.Clone(ctx)
   119  			}
   120  		} else {
   121  			//nolint:govet // cancelled automatically by withTimeout
   122  			ctx, overrides.cancelFunc = context.WithTimeout(context.Background(), time.Duration(r.options.Options.Timeout)*time.Second)
   123  			request = request.Clone(ctx)
   124  		}
   125  	}
   126  
   127  	overrides.request = request
   128  
   129  	return
   130  }
   131  
   132  func isHostPort(value string) bool {
   133  	_, port, err := net.SplitHostPort(value)
   134  	if err != nil {
   135  		return false
   136  	}
   137  	if !iputil.IsPort(port) {
   138  		return false
   139  	}
   140  	return true
   141  }