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 }