2023-08-25 18:30:46 +05:30
package protocolstate
import (
2025-07-09 14:47:26 -05:00
"context"
2024-02-06 02:03:33 +05:30
"net"
2023-08-25 18:30:46 +05:30
"strings"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
"github.com/projectdiscovery/networkpolicy"
2025-07-09 14:47:26 -05:00
"github.com/projectdiscovery/nuclei/v3/pkg/types"
2025-08-20 05:28:23 +05:30
"github.com/projectdiscovery/utils/errkit"
2023-08-25 18:30:46 +05:30
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
"go.uber.org/multierr"
)
2025-07-09 14:47:26 -05:00
// initialize state of headless protocol
2023-08-25 18:30:46 +05:30
2025-08-25 15:06:58 +07:00
var (
ErrURLDenied = errkit . New ( "headless: url dropped by rule" )
ErrHostDenied = errorTemplate { format : "host %v dropped by network policy" }
)
// errorTemplate provides a way to create formatted errors like the old errorutil.NewWithFmt
type errorTemplate struct {
format string
2025-08-20 05:28:23 +05:30
}
2025-08-25 15:06:58 +07:00
func ( e errorTemplate ) Msgf ( args ... interface { } ) error {
return errkit . Newf ( e . format , args ... )
2025-08-20 05:28:23 +05:30
}
2023-08-25 18:30:46 +05:30
2025-07-09 14:47:26 -05:00
func GetNetworkPolicy ( ctx context . Context ) * networkpolicy . NetworkPolicy {
execCtx := GetExecutionContext ( ctx )
if execCtx == nil {
return nil
}
dialers , ok := dialers . Get ( execCtx . ExecutionID )
if ! ok || dialers == nil {
return nil
}
return dialers . NetworkPolicy
}
2023-08-25 18:30:46 +05:30
// ValidateNFailRequest validates and fails request
// if the request does not respect the rules, it will be canceled with reason
2025-07-09 14:47:26 -05:00
func ValidateNFailRequest ( options * types . Options , page * rod . Page , e * proto . FetchRequestPaused ) error {
2023-08-25 18:30:46 +05:30
reqURL := e . Request . URL
normalized := strings . ToLower ( reqURL ) // normalize url to lowercase
normalized = strings . TrimSpace ( normalized ) // trim leading & trailing whitespaces
2025-07-09 14:47:26 -05:00
if ! IsLfaAllowed ( options ) && stringsutil . HasPrefixI ( normalized , "file:" ) {
2025-08-25 15:06:58 +07:00
return multierr . Combine ( FailWithReason ( page , e ) , errkit . Newf ( "headless: url %v dropped by rule: %v" , reqURL , "use of file:// protocol disabled use '-lfa' to enable" ) )
2023-08-25 18:30:46 +05:30
}
// validate potential invalid schemes
// javascript protocol is allowed for xss fuzzing
2023-08-28 08:15:30 +00:00
if stringsutil . HasPrefixAnyI ( normalized , "ftp:" , "externalfile:" , "chrome:" , "chrome-extension:" ) {
2025-08-25 15:06:58 +07:00
return multierr . Combine ( FailWithReason ( page , e ) , errkit . Newf ( "headless: url %v dropped by rule: %v" , reqURL , "protocol blocked by network policy" ) )
2023-08-25 18:30:46 +05:30
}
2025-07-09 14:47:26 -05:00
if ! isValidHost ( options , reqURL ) {
2025-08-25 15:06:58 +07:00
return multierr . Combine ( FailWithReason ( page , e ) , errkit . Newf ( "headless: url %v dropped by rule: %v" , reqURL , "address blocked by network policy" ) )
2023-08-25 18:30:46 +05:30
}
return nil
}
// FailWithReason fails request with AccessDenied reason
func FailWithReason ( page * rod . Page , e * proto . FetchRequestPaused ) error {
m := proto . FetchFailRequest {
RequestID : e . RequestID ,
ErrorReason : proto . NetworkErrorReasonAccessDenied ,
}
return m . Call ( page )
}
// InitHeadless initializes headless protocol state
2025-07-09 14:47:26 -05:00
func InitHeadless ( options * types . Options ) {
dialers , ok := dialers . Get ( options . ExecutionId )
if ok && dialers != nil {
dialers . Lock ( )
dialers . LocalFileAccessAllowed = options . AllowLocalFileAccess
dialers . RestrictLocalNetworkAccess = options . RestrictLocalNetworkAccess
dialers . Unlock ( )
2023-08-25 18:30:46 +05:30
}
}
2025-07-09 14:47:26 -05:00
func IsRestrictLocalNetworkAccess ( options * types . Options ) bool {
dialers , ok := dialers . Get ( options . ExecutionId )
if ok && dialers != nil {
dialers . Lock ( )
defer dialers . Unlock ( )
return dialers . RestrictLocalNetworkAccess
}
return false
}
2023-08-25 18:30:46 +05:30
// isValidHost checks if the host is valid (only limited to http/https protocols)
2025-07-09 14:47:26 -05:00
func isValidHost ( options * types . Options , targetUrl string ) bool {
2023-08-25 18:30:46 +05:30
if ! stringsutil . HasPrefixAny ( targetUrl , "http:" , "https:" ) {
return true
}
2025-07-09 14:47:26 -05:00
dialers , ok := dialers . Get ( options . ExecutionId )
if ! ok {
return true
}
np := dialers . NetworkPolicy
if ! ok || np == nil {
2023-08-25 18:30:46 +05:30
return true
}
2025-07-09 14:47:26 -05:00
2023-08-25 18:30:46 +05:30
urlx , err := urlutil . Parse ( targetUrl )
if err != nil {
// not a valid url
return false
}
targetUrl = urlx . Hostname ( )
2025-07-09 14:47:26 -05:00
_ , ok = np . ValidateHost ( targetUrl )
2023-08-25 18:30:46 +05:30
return ok
}
2023-09-16 16:02:17 +05:30
// IsHostAllowed checks if the host is allowed by network policy
2025-07-09 14:47:26 -05:00
func IsHostAllowed ( executionId string , targetUrl string ) bool {
dialers , ok := dialers . Get ( executionId )
if ! ok {
2023-09-16 16:02:17 +05:30
return true
}
2025-07-09 14:47:26 -05:00
np := dialers . NetworkPolicy
if ! ok || np == nil {
return true
}
2024-02-06 02:03:33 +05:30
sepCount := strings . Count ( targetUrl , ":" )
if sepCount > 1 {
// most likely a ipv6 address (parse url and validate host)
2025-07-09 14:47:26 -05:00
return np . Validate ( targetUrl )
2024-02-06 02:03:33 +05:30
}
if sepCount == 1 {
host , _ , _ := net . SplitHostPort ( targetUrl )
2025-07-09 14:47:26 -05:00
if _ , ok := np . ValidateHost ( host ) ; ! ok {
2024-02-06 02:03:33 +05:30
return false
}
return true
}
// just a hostname or ip without port
2025-07-09 14:47:26 -05:00
_ , ok = np . ValidateHost ( targetUrl )
2023-09-16 16:02:17 +05:30
return ok
}