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

     1  package engine
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/go-rod/rod"
    10  	"github.com/go-rod/rod/lib/launcher"
    11  	"github.com/go-rod/rod/lib/launcher/flags"
    12  	"github.com/pkg/errors"
    13  
    14  	"github.com/projectdiscovery/nuclei/v2/pkg/types"
    15  	fileutil "github.com/projectdiscovery/utils/file"
    16  	osutils "github.com/projectdiscovery/utils/os"
    17  	processutil "github.com/projectdiscovery/utils/process"
    18  )
    19  
    20  // Browser is a browser structure for nuclei headless module
    21  type Browser struct {
    22  	customAgent  string
    23  	tempDir      string
    24  	previousPIDs map[int32]struct{} // track already running PIDs
    25  	engine       *rod.Browser
    26  	httpclient   *http.Client
    27  	options      *types.Options
    28  }
    29  
    30  // New creates a new nuclei headless browser module
    31  func New(options *types.Options) (*Browser, error) {
    32  	dataStore, err := os.MkdirTemp("", "nuclei-*")
    33  	if err != nil {
    34  		return nil, errors.Wrap(err, "could not create temporary directory")
    35  	}
    36  	previousPIDs := processutil.FindProcesses(processutil.IsChromeProcess)
    37  
    38  	chromeLauncher := launcher.New().
    39  		Leakless(false).
    40  		Set("disable-gpu", "true").
    41  		Set("ignore-certificate-errors", "true").
    42  		Set("ignore-certificate-errors", "1").
    43  		Set("disable-crash-reporter", "true").
    44  		Set("disable-notifications", "true").
    45  		Set("hide-scrollbars", "true").
    46  		Set("window-size", fmt.Sprintf("%d,%d", 1080, 1920)).
    47  		Set("mute-audio", "true").
    48  		Set("incognito", "true").
    49  		Delete("use-mock-keychain").
    50  		UserDataDir(dataStore)
    51  
    52  	if MustDisableSandbox() {
    53  		chromeLauncher = chromeLauncher.NoSandbox(true)
    54  	}
    55  
    56  	executablePath, err := os.Executable()
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	// if musl is used, most likely we are on alpine linux which is not supported by go-rod, so we fallback to default chrome
    62  	useMusl, _ := fileutil.UseMusl(executablePath)
    63  	if options.UseInstalledChrome || useMusl {
    64  		if chromePath, hasChrome := launcher.LookPath(); hasChrome {
    65  			chromeLauncher.Bin(chromePath)
    66  		} else {
    67  			return nil, errors.New("the chrome browser is not installed")
    68  		}
    69  	}
    70  
    71  	if options.ShowBrowser {
    72  		chromeLauncher = chromeLauncher.Headless(false)
    73  	} else {
    74  		chromeLauncher = chromeLauncher.Headless(true)
    75  	}
    76  	if types.ProxyURL != "" {
    77  		chromeLauncher = chromeLauncher.Proxy(types.ProxyURL)
    78  	}
    79  
    80  	for k, v := range options.ParseHeadlessOptionalArguments() {
    81  		chromeLauncher.Set(flags.Flag(k), v)
    82  	}
    83  
    84  	launcherURL, err := chromeLauncher.Launch()
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	browser := rod.New().ControlURL(launcherURL)
    90  	if browserErr := browser.Connect(); browserErr != nil {
    91  		return nil, browserErr
    92  	}
    93  	customAgent := ""
    94  	for _, option := range options.CustomHeaders {
    95  		parts := strings.SplitN(option, ":", 2)
    96  		if len(parts) != 2 {
    97  			continue
    98  		}
    99  		if strings.EqualFold(parts[0], "User-Agent") {
   100  			customAgent = parts[1]
   101  		}
   102  	}
   103  
   104  	httpclient, err := newHttpClient(options)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	engine := &Browser{
   110  		tempDir:     dataStore,
   111  		customAgent: customAgent,
   112  		engine:      browser,
   113  		httpclient:  httpclient,
   114  		options:     options,
   115  	}
   116  	engine.previousPIDs = previousPIDs
   117  	return engine, nil
   118  }
   119  
   120  // MustDisableSandbox determines if the current os and user needs sandbox mode disabled
   121  func MustDisableSandbox() bool {
   122  	// linux with root user needs "--no-sandbox" option
   123  	// https://github.com/chromium/chromium/blob/c4d3c31083a2e1481253ff2d24298a1dfe19c754/chrome/test/chromedriver/client/chromedriver.py#L209
   124  	return osutils.IsLinux() && os.Geteuid() == 0
   125  }
   126  
   127  // SetUserAgent sets custom user agent to the browser
   128  func (b *Browser) SetUserAgent(customUserAgent string) {
   129  	b.customAgent = customUserAgent
   130  }
   131  
   132  // UserAgent fetch the currently set custom user agent
   133  func (b *Browser) UserAgent() string {
   134  	return b.customAgent
   135  }
   136  
   137  // Close closes the browser engine
   138  func (b *Browser) Close() {
   139  	b.engine.Close()
   140  	os.RemoveAll(b.tempDir)
   141  	processutil.CloseProcesses(processutil.IsChromeProcess, b.previousPIDs)
   142  }