Path: blob/dev/pkg/protocols/common/protocolstate/headless.go
2072 views
package protocolstate12import (3"context"4"net"5"strings"67"github.com/go-rod/rod"8"github.com/go-rod/rod/lib/proto"9"github.com/projectdiscovery/networkpolicy"10"github.com/projectdiscovery/nuclei/v3/pkg/types"11"github.com/projectdiscovery/utils/errkit"12stringsutil "github.com/projectdiscovery/utils/strings"13urlutil "github.com/projectdiscovery/utils/url"14"go.uber.org/multierr"15)1617// initialize state of headless protocol1819var (20ErrURLDenied = errkit.New("headless: url dropped by rule")21ErrHostDenied = errorTemplate{format: "host %v dropped by network policy"}22)2324// errorTemplate provides a way to create formatted errors like the old errorutil.NewWithFmt25type errorTemplate struct {26format string27}2829func (e errorTemplate) Msgf(args ...interface{}) error {30return errkit.Newf(e.format, args...)31}3233func GetNetworkPolicy(ctx context.Context) *networkpolicy.NetworkPolicy {34execCtx := GetExecutionContext(ctx)35if execCtx == nil {36return nil37}38dialers, ok := dialers.Get(execCtx.ExecutionID)39if !ok || dialers == nil {40return nil41}42return dialers.NetworkPolicy43}4445// ValidateNFailRequest validates and fails request46// if the request does not respect the rules, it will be canceled with reason47func ValidateNFailRequest(options *types.Options, page *rod.Page, e *proto.FetchRequestPaused) error {48reqURL := e.Request.URL49normalized := strings.ToLower(reqURL) // normalize url to lowercase50normalized = strings.TrimSpace(normalized) // trim leading & trailing whitespaces51if !IsLfaAllowed(options) && stringsutil.HasPrefixI(normalized, "file:") {52return multierr.Combine(FailWithReason(page, e), errkit.Newf("headless: url %v dropped by rule: %v", reqURL, "use of file:// protocol disabled use '-lfa' to enable"))53}54// validate potential invalid schemes55// javascript protocol is allowed for xss fuzzing56if stringsutil.HasPrefixAnyI(normalized, "ftp:", "externalfile:", "chrome:", "chrome-extension:") {57return multierr.Combine(FailWithReason(page, e), errkit.Newf("headless: url %v dropped by rule: %v", reqURL, "protocol blocked by network policy"))58}59if !isValidHost(options, reqURL) {60return multierr.Combine(FailWithReason(page, e), errkit.Newf("headless: url %v dropped by rule: %v", reqURL, "address blocked by network policy"))61}62return nil63}6465// FailWithReason fails request with AccessDenied reason66func FailWithReason(page *rod.Page, e *proto.FetchRequestPaused) error {67m := proto.FetchFailRequest{68RequestID: e.RequestID,69ErrorReason: proto.NetworkErrorReasonAccessDenied,70}71return m.Call(page)72}7374// InitHeadless initializes headless protocol state75func InitHeadless(options *types.Options) {76dialers, ok := dialers.Get(options.ExecutionId)77if ok && dialers != nil {78dialers.Lock()79dialers.LocalFileAccessAllowed = options.AllowLocalFileAccess80dialers.RestrictLocalNetworkAccess = options.RestrictLocalNetworkAccess81dialers.Unlock()82}83}8485func IsRestrictLocalNetworkAccess(options *types.Options) bool {86dialers, ok := dialers.Get(options.ExecutionId)87if ok && dialers != nil {88dialers.Lock()89defer dialers.Unlock()9091return dialers.RestrictLocalNetworkAccess92}93return false94}9596// isValidHost checks if the host is valid (only limited to http/https protocols)97func isValidHost(options *types.Options, targetUrl string) bool {98if !stringsutil.HasPrefixAny(targetUrl, "http:", "https:") {99return true100}101102dialers, ok := dialers.Get(options.ExecutionId)103if !ok {104return true105}106107np := dialers.NetworkPolicy108if !ok || np == nil {109return true110}111112urlx, err := urlutil.Parse(targetUrl)113if err != nil {114// not a valid url115return false116}117targetUrl = urlx.Hostname()118_, ok = np.ValidateHost(targetUrl)119return ok120}121122// IsHostAllowed checks if the host is allowed by network policy123func IsHostAllowed(executionId string, targetUrl string) bool {124dialers, ok := dialers.Get(executionId)125if !ok {126return true127}128129np := dialers.NetworkPolicy130if !ok || np == nil {131return true132}133134sepCount := strings.Count(targetUrl, ":")135if sepCount > 1 {136// most likely a ipv6 address (parse url and validate host)137return np.Validate(targetUrl)138}139if sepCount == 1 {140host, _, _ := net.SplitHostPort(targetUrl)141if _, ok := np.ValidateHost(host); !ok {142return false143}144return true145}146// just a hostname or ip without port147_, ok = np.ValidateHost(targetUrl)148return ok149}150151152