github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/common/protocolstate/headless.go (about)

     1  package protocolstate
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/go-rod/rod"
     7  	"github.com/go-rod/rod/lib/proto"
     8  	"github.com/projectdiscovery/networkpolicy"
     9  	errorutil "github.com/projectdiscovery/utils/errors"
    10  	stringsutil "github.com/projectdiscovery/utils/strings"
    11  	urlutil "github.com/projectdiscovery/utils/url"
    12  	"go.uber.org/multierr"
    13  )
    14  
    15  // initalize state of headless protocol
    16  
    17  var (
    18  	ErrURLDenied         = errorutil.NewWithFmt("headless: url %v dropped by rule: %v")
    19  	networkPolicy        *networkpolicy.NetworkPolicy
    20  	allowLocalFileAccess bool
    21  )
    22  
    23  // ValidateNFailRequest validates and fails request
    24  // if the request does not respect the rules, it will be canceled with reason
    25  func ValidateNFailRequest(page *rod.Page, e *proto.FetchRequestPaused) error {
    26  	reqURL := e.Request.URL
    27  	normalized := strings.ToLower(reqURL)      // normalize url to lowercase
    28  	normalized = strings.TrimSpace(normalized) // trim leading & trailing whitespaces
    29  	if !allowLocalFileAccess && stringsutil.HasPrefixI(normalized, "file:") {
    30  		return multierr.Combine(FailWithReason(page, e), ErrURLDenied.Msgf(reqURL, "use of file:// protocol disabled use '-lfa' to enable"))
    31  	}
    32  	// validate potential invalid schemes
    33  	// javascript protocol is allowed for xss fuzzing
    34  	if stringsutil.HasPrefixAnyI(normalized, "ftp:", "externalfile:", "chrome:", "chrome-extension:") {
    35  		return multierr.Combine(FailWithReason(page, e), ErrURLDenied.Msgf(reqURL, "protocol blocked by network policy"))
    36  	}
    37  	if !isValidHost(reqURL) {
    38  		return multierr.Combine(FailWithReason(page, e), ErrURLDenied.Msgf(reqURL, "address blocked by network policy"))
    39  	}
    40  	return nil
    41  }
    42  
    43  // FailWithReason fails request with AccessDenied reason
    44  func FailWithReason(page *rod.Page, e *proto.FetchRequestPaused) error {
    45  	m := proto.FetchFailRequest{
    46  		RequestID:   e.RequestID,
    47  		ErrorReason: proto.NetworkErrorReasonAccessDenied,
    48  	}
    49  	return m.Call(page)
    50  }
    51  
    52  // InitHeadless initializes headless protocol state
    53  func InitHeadless(RestrictLocalNetworkAccess bool, localFileAccess bool) {
    54  	allowLocalFileAccess = localFileAccess
    55  	if !RestrictLocalNetworkAccess {
    56  		return
    57  	}
    58  	networkPolicy, _ = networkpolicy.New(networkpolicy.Options{
    59  		DenyList: append(networkpolicy.DefaultIPv4DenylistRanges, networkpolicy.DefaultIPv6DenylistRanges...),
    60  	})
    61  }
    62  
    63  // isValidHost checks if the host is valid (only limited to http/https protocols)
    64  func isValidHost(targetUrl string) bool {
    65  	if !stringsutil.HasPrefixAny(targetUrl, "http:", "https:") {
    66  		return true
    67  	}
    68  	if networkPolicy == nil {
    69  		return true
    70  	}
    71  	urlx, err := urlutil.Parse(targetUrl)
    72  	if err != nil {
    73  		// not a valid url
    74  		return false
    75  	}
    76  	targetUrl = urlx.Hostname()
    77  	_, ok := networkPolicy.ValidateHost(targetUrl)
    78  	return ok
    79  }