nuclei/v2/pkg/protocols/http/request_annotations.go

128 lines
3.5 KiB
Go
Raw Normal View History

2022-04-04 09:32:41 +02:00
package http
import (
"context"
2022-04-13 17:41:02 +02:00
"net"
2022-04-04 09:32:41 +02:00
"net/http"
"regexp"
"strings"
"time"
2022-04-13 17:41:02 +02:00
"github.com/projectdiscovery/fastdialer/fastdialer"
2022-04-13 17:41:02 +02:00
"github.com/projectdiscovery/iputil"
"github.com/projectdiscovery/stringsutil"
"github.com/projectdiscovery/urlutil"
2022-04-04 09:32:41 +02:00
)
var (
// @Host:target overrides the input target with the annotated one (similar to self-contained requests)
reHostAnnotation = regexp.MustCompile(`(?m)^@Host:\s*(.+)\s*$`)
// @tls-sni:target overrides the input target with the annotated one
// special values:
// request.host: takes the value from the host header
// target: overiddes with the specific value
reSniAnnotation = regexp.MustCompile(`(?m)^@tls-sni:\s*(.+)\s*$`)
// @timeout:duration overrides the input timout with a custom duration
reTimeoutAnnotation = regexp.MustCompile(`(?m)^@timeout:\s*(.+)\s*$`)
// @once sets the request to be executed only once for a specific URL
reOnceAnnotation = regexp.MustCompile(`(?m)^@once\s*$`)
)
2022-04-04 09:32:41 +02:00
type flowMark int
const (
Once flowMark = iota
)
// parseFlowAnnotations and override requests flow
func parseFlowAnnotations(rawRequest string) (flowMark, bool) {
var fm flowMark
// parse request for known ovverride annotations
var hasFlowOveride bool
// @once
if reOnceAnnotation.MatchString(rawRequest) {
fm = Once
hasFlowOveride = true
}
return fm, hasFlowOveride
}
2022-04-04 09:32:41 +02:00
// parseAnnotations and override requests settings
func (r *Request) parseAnnotations(rawRequest string, request *http.Request) (*http.Request, bool) {
2022-04-04 09:32:41 +02:00
// parse request for known ovverride annotations
var modified bool
// @Host:target
2022-04-04 09:32:41 +02:00
if hosts := reHostAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {
2022-04-13 17:41:02 +02:00
value := strings.TrimSpace(hosts[1])
// handle scheme
switch {
case stringsutil.HasPrefixI(value, "http://"):
request.URL.Scheme = urlutil.HTTP
case stringsutil.HasPrefixI(value, "https://"):
request.URL.Scheme = urlutil.HTTPS
}
value = stringsutil.TrimPrefixAny(value, "http://", "https://")
if isHostPort(value) {
request.URL.Host = value
2022-04-14 12:59:21 +02:00
} else {
hostPort := value
port := request.URL.Port()
if port != "" {
hostPort = net.JoinHostPort(hostPort, port)
}
request.URL.Host = hostPort
2022-04-13 17:41:02 +02:00
}
modified = true
}
// @tls-sni:target
if hosts := reSniAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {
value := strings.TrimSpace(hosts[1])
value = stringsutil.TrimPrefixAny(value, "http://", "https://")
if idxForwardSlash := strings.Index(value, "/"); idxForwardSlash >= 0 {
value = value[:idxForwardSlash]
}
if stringsutil.EqualFoldAny(value, "request.host") {
value = request.Host
}
ctx := context.WithValue(request.Context(), fastdialer.SniName, value)
request = request.Clone(ctx)
modified = true
2022-04-13 17:41:02 +02:00
}
// @timeout:duration
if r.connConfiguration.NoTimeout {
modified = true
if duration := reTimeoutAnnotation.FindStringSubmatch(rawRequest); len(duration) > 0 {
value := strings.TrimSpace(duration[1])
if parsed, err := time.ParseDuration(value); err == nil {
//nolint:govet // cancelled automatically by withTimeout
ctx, _ := context.WithTimeout(request.Context(), parsed)
request = request.Clone(ctx)
}
} else {
//nolint:govet // cancelled automatically by withTimeout
ctx, _ := context.WithTimeout(request.Context(), time.Duration(r.options.Options.Timeout)*time.Second)
request = request.Clone(ctx)
}
}
return request, modified
2022-04-13 17:41:02 +02:00
}
func isHostPort(value string) bool {
_, port, err := net.SplitHostPort(value)
if err != nil {
return false
}
if !iputil.IsPort(port) {
return false
2022-04-04 09:32:41 +02:00
}
2022-04-13 17:41:02 +02:00
return true
2022-04-04 09:32:41 +02:00
}