Path: blob/dev/pkg/protocols/headless/engine/engine.go
2072 views
package engine12import (3"fmt"4"net/http"5"os"6"strings"7"sync"89"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"1314"github.com/projectdiscovery/nuclei/v3/pkg/types"15fileutil "github.com/projectdiscovery/utils/file"16osutils "github.com/projectdiscovery/utils/os"17processutil "github.com/projectdiscovery/utils/process"18)1920// Browser is a browser structure for nuclei headless module21type Browser struct {22customAgent string23defaultHeaders map[string]string24tempDir string25previousPIDs map[int32]struct{} // track already running PIDs26engine *rod.Browser27options *types.Options28launcher *launcher.Launcher2930// use getHTTPClient to get the http client31httpClient *http.Client32httpClientOnce *sync.Once33}3435// New creates a new nuclei headless browser module36func New(options *types.Options) (*Browser, error) {37dataStore, err := os.MkdirTemp("", "nuclei-*")38if err != nil {39return nil, errors.Wrap(err, "could not create temporary directory")40}41previousPIDs := processutil.FindProcesses(processutil.IsChromeProcess)4243chromeLauncher := launcher.New().44Leakless(false).45Set("disable-gpu", "true").46Set("ignore-certificate-errors", "true").47Set("ignore-certificate-errors", "1").48Set("disable-crash-reporter", "true").49Set("disable-notifications", "true").50Set("hide-scrollbars", "true").51Set("window-size", fmt.Sprintf("%d,%d", 1080, 1920)).52Set("mute-audio", "true").53Set("incognito", "true").54Delete("use-mock-keychain").55UserDataDir(dataStore)5657if MustDisableSandbox() {58chromeLauncher = chromeLauncher.NoSandbox(true)59}6061executablePath, err := os.Executable()62if err != nil {63return nil, err64}6566// if musl is used, most likely we are on alpine linux which is not supported by go-rod, so we fallback to default chrome67useMusl, _ := fileutil.UseMusl(executablePath)68if options.UseInstalledChrome || useMusl {69if chromePath, hasChrome := launcher.LookPath(); hasChrome {70chromeLauncher.Bin(chromePath)71} else {72return nil, errors.New("the chrome browser is not installed")73}74}7576if options.ShowBrowser {77chromeLauncher = chromeLauncher.Headless(false)78} else {79chromeLauncher = chromeLauncher.Headless(true)80}81if options.AliveHttpProxy != "" {82chromeLauncher = chromeLauncher.Proxy(options.AliveHttpProxy)83}8485for k, v := range options.ParseHeadlessOptionalArguments() {86chromeLauncher.Set(flags.Flag(k), v)87}8889launcherURL, err := chromeLauncher.Launch()90if err != nil {91return nil, err92}9394browser := rod.New().ControlURL(launcherURL)95if browserErr := browser.Connect(); browserErr != nil {96return nil, browserErr97}98defaultHeaders := make(map[string]string)99customAgent := ""100for _, option := range options.CustomHeaders {101parts := strings.SplitN(option, ":", 2)102if len(parts) != 2 {103continue104}105if strings.EqualFold(parts[0], "User-Agent") {106customAgent = parts[1]107} else {108k := strings.TrimSpace(parts[0])109v := strings.TrimSpace(parts[1])110if k == "" || v == "" {111continue112}113defaultHeaders[k] = v114}115}116117engine := &Browser{118tempDir: dataStore,119customAgent: customAgent,120defaultHeaders: defaultHeaders,121engine: browser,122options: options,123httpClientOnce: &sync.Once{},124launcher: chromeLauncher,125}126engine.previousPIDs = previousPIDs127return engine, nil128}129130// MustDisableSandbox determines if the current os and user needs sandbox mode disabled131func MustDisableSandbox() bool {132// linux with root user needs "--no-sandbox" option133// https://github.com/chromium/chromium/blob/c4d3c31083a2e1481253ff2d24298a1dfe19c754/chrome/test/chromedriver/client/chromedriver.py#L209134return osutils.IsLinux()135}136137// SetUserAgent sets custom user agent to the browser138func (b *Browser) SetUserAgent(customUserAgent string) {139b.customAgent = customUserAgent140}141142// UserAgent fetch the currently set custom user agent143func (b *Browser) UserAgent() string {144return b.customAgent145}146147// applyDefaultHeaders setsheaders passed via cli -H flag148func (b *Browser) applyDefaultHeaders(p *rod.Page) error {149pairs := make([]string, 0, len(b.defaultHeaders)*2+2)150151hasAcceptLanguage := false152for k := range b.defaultHeaders {153if strings.EqualFold(k, "Accept-Language") {154hasAcceptLanguage = true155break156}157}158if !hasAcceptLanguage {159pairs = append(pairs, "Accept-Language", "en, en-GB, en-us;")160}161for k, v := range b.defaultHeaders {162pairs = append(pairs, k, v)163}164if len(pairs) == 0 {165return nil166}167_, err := p.SetExtraHeaders(pairs)168return err169}170171func (b *Browser) getHTTPClient() (*http.Client, error) {172var err error173b.httpClientOnce.Do(func() {174b.httpClient, err = newHttpClient(b.options)175})176return b.httpClient, err177}178179// Close closes the browser engine180func (b *Browser) Close() {181_ = b.engine.Close()182b.launcher.Kill()183_ = os.RemoveAll(b.tempDir)184processutil.CloseProcesses(processutil.IsChromeProcess, b.previousPIDs)185}186187188