Path: blob/dev/pkg/protocols/common/protocolstate/state.go
2845 views
package protocolstate12import (3"context"4"fmt"5"net"6"net/url"7"time"89"github.com/go-sql-driver/mysql"10"github.com/pkg/errors"11"golang.org/x/net/proxy"1213"github.com/projectdiscovery/fastdialer/fastdialer"14"github.com/projectdiscovery/mapcidr/asn"15"github.com/projectdiscovery/networkpolicy"16"github.com/projectdiscovery/nuclei/v3/pkg/types"17"github.com/projectdiscovery/nuclei/v3/pkg/utils/expand"18"github.com/projectdiscovery/retryablehttp-go"19mapsutil "github.com/projectdiscovery/utils/maps"20)2122var (23dialers *mapsutil.SyncLockMap[string, *Dialers]24)2526func init() {27dialers = mapsutil.NewSyncLockMap[string, *Dialers]()28}2930func GetDialers(ctx context.Context) *Dialers {31executionContext := GetExecutionContext(ctx)32dialers, ok := dialers.Get(executionContext.ExecutionID)33if !ok {34return nil35}36return dialers37}3839func GetDialersWithId(id string) *Dialers {40dialers, ok := dialers.Get(id)41if !ok {42return nil43}44return dialers45}4647func ShouldInit(id string) bool {48dialer, ok := dialers.Get(id)49if !ok {50return true51}52return dialer == nil53}5455// Init creates the Dialers instance based on user configuration56func Init(options *types.Options) error {57if GetDialersWithId(options.ExecutionId) != nil {58return nil59}6061return initDialers(options)62}6364// initDialers is the internal implementation of Init65func initDialers(options *types.Options) error {66opts := fastdialer.DefaultOptions67opts.DialerTimeout = options.GetTimeouts().DialTimeout68if options.DialerKeepAlive > 0 {69opts.DialerKeepAlive = options.DialerKeepAlive70}7172var expandedDenyList []string73for _, excludeTarget := range options.ExcludeTargets {74switch {75case asn.IsASN(excludeTarget):76expandedDenyList = append(expandedDenyList, expand.ASN(excludeTarget)...)77default:78expandedDenyList = append(expandedDenyList, excludeTarget)79}80}8182if options.RestrictLocalNetworkAccess {83expandedDenyList = append(expandedDenyList, networkpolicy.DefaultIPv4DenylistRanges...)84expandedDenyList = append(expandedDenyList, networkpolicy.DefaultIPv6DenylistRanges...)85}86npOptions := &networkpolicy.Options{87DenyList: expandedDenyList,88}89opts.WithNetworkPolicyOptions = npOptions9091switch {92case options.SourceIP != "" && options.Interface != "":93isAssociated, err := isIpAssociatedWithInterface(options.SourceIP, options.Interface)94if err != nil {95return err96}97if isAssociated {98opts.Dialer = &net.Dialer{99LocalAddr: &net.TCPAddr{100IP: net.ParseIP(options.SourceIP),101},102}103} else {104return fmt.Errorf("source ip (%s) is not associated with the interface (%s)", options.SourceIP, options.Interface)105}106case options.SourceIP != "":107isAssociated, err := isIpAssociatedWithInterface(options.SourceIP, "any")108if err != nil {109return err110}111if isAssociated {112opts.Dialer = &net.Dialer{113LocalAddr: &net.TCPAddr{114IP: net.ParseIP(options.SourceIP),115},116}117} else {118return fmt.Errorf("source ip (%s) is not associated with any network interface", options.SourceIP)119}120case options.Interface != "":121ifadrr, err := interfaceAddress(options.Interface)122if err != nil {123return err124}125opts.Dialer = &net.Dialer{126LocalAddr: &net.TCPAddr{127IP: ifadrr,128},129}130}131if options.AliveSocksProxy != "" {132proxyURL, err := url.Parse(options.AliveSocksProxy)133if err != nil {134return err135}136var forward *net.Dialer137if opts.Dialer != nil {138forward = opts.Dialer139} else {140forward = &net.Dialer{141Timeout: opts.DialerTimeout,142KeepAlive: opts.DialerKeepAlive,143DualStack: true,144}145}146dialer, err := proxy.FromURL(proxyURL, forward)147if err != nil {148return err149}150opts.ProxyDialer = &dialer151}152153if options.SystemResolvers {154opts.ResolversFile = true155opts.EnableFallback = true156}157if len(options.InternalResolversList) > 0 {158opts.BaseResolvers = options.InternalResolversList159}160161opts.Deny = append(opts.Deny, expandedDenyList...)162163opts.WithDialerHistory = true164opts.SNIName = options.SNI165// this instance is used in javascript protocol libraries and166// dial history is required to get dialed ip of a host167opts.WithDialerHistory = true168169// fastdialer now by default fallbacks to ztls when there are tls related errors170dialer, err := fastdialer.NewDialer(opts)171if err != nil {172return errors.Wrap(err, "could not create dialer")173}174175networkPolicy, _ := networkpolicy.New(*npOptions)176177httpClientPool := mapsutil.NewSyncLockMap(178// evicts inactive httpclientpool entries after 24 hours179// of inactivity (long running instances)180mapsutil.WithEviction[string, *retryablehttp.Client](24*time.Hour, 12*time.Hour),181)182183dialersInstance := &Dialers{184Fastdialer: dialer,185NetworkPolicy: networkPolicy,186HTTPClientPool: httpClientPool,187LocalFileAccessAllowed: options.AllowLocalFileAccess,188}189190_ = dialers.Set(options.ExecutionId, dialersInstance)191192// Set a custom dialer for the "nucleitcp" protocol. This is just plain TCP, but it's registered193// with a different name so that we do not clobber the "tcp" dialer in the event that nuclei is194// being included as a package in another application.195mysql.RegisterDialContext("nucleitcp", func(ctx context.Context, addr string) (net.Conn, error) {196// Because we're not using the default TCP workflow, quietly add the default port197// number if no port number was specified.198if _, _, err := net.SplitHostPort(addr); err != nil {199addr += ":3306"200}201202var executionId string203if val := ctx.Value("executionId"); val != nil {204executionId = val.(string)205}206dialer := GetDialersWithId(executionId)207if dialer == nil {208return nil, fmt.Errorf("dialers not initialized for %s", executionId)209}210return dialer.Fastdialer.Dial(ctx, "tcp", addr)211})212213StartActiveMemGuardian(context.Background())214215SetLfaAllowed(options)216217return nil218}219220// isIpAssociatedWithInterface checks if the given IP is associated with the given interface.221func isIpAssociatedWithInterface(sourceIP, interfaceName string) (bool, error) {222addrs, err := interfaceAddresses(interfaceName)223if err != nil {224return false, err225}226for _, addr := range addrs {227if ipnet, ok := addr.(*net.IPNet); ok {228if ipnet.IP.String() == sourceIP {229return true, nil230}231}232}233return false, nil234}235236// interfaceAddress returns the first IPv4 address of the given interface.237func interfaceAddress(interfaceName string) (net.IP, error) {238addrs, err := interfaceAddresses(interfaceName)239if err != nil {240return nil, err241}242var address net.IP243for _, addr := range addrs {244if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {245if ipnet.IP.To4() != nil {246address = ipnet.IP247break248}249}250}251if address == nil {252return nil, fmt.Errorf("no suitable address found for interface: `%s`", interfaceName)253}254return address, nil255}256257// interfaceAddresses returns all interface addresses.258func interfaceAddresses(interfaceName string) ([]net.Addr, error) {259if interfaceName == "any" {260return net.InterfaceAddrs()261}262ief, err := net.InterfaceByName(interfaceName)263if err != nil {264return nil, errors.Wrapf(err, "failed to get interface: `%s`", interfaceName)265}266addrs, err := ief.Addrs()267if err != nil {268return nil, errors.Wrapf(err, "failed to get interface addresses for: `%s`", interfaceName)269}270return addrs, nil271}272273// Close closes the global shared fastdialer274func Close(executionId string) {275dialersInstance, ok := dialers.Get(executionId)276if !ok {277return278}279280if dialersInstance != nil {281dialersInstance.Fastdialer.Close()282}283284dialers.Delete(executionId)285286if dialers.IsEmpty() {287StopActiveMemGuardian()288}289}290291292