Path: blob/dev/pkg/protocols/headless/engine/engine.go
2839 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) {37var launcherURL, dataStore string38var previousPIDs map[int32]struct{}39var err error4041chromeLauncher := launcher.New()4243if options.CDPEndpoint == "" {44previousPIDs = processutil.FindProcesses(processutil.IsChromeProcess)4546dataStore, err = os.MkdirTemp("", "nuclei-*")47if err != nil {48return nil, errors.Wrap(err, "could not create temporary directory")49}5051chromeLauncher = chromeLauncher.52Leakless(false).53Set("disable-crash-reporter").54Set("disable-gpu").55Set("disable-notifications").56Set("hide-scrollbars").57Set("ignore-certificate-errors").58Set("ignore-ssl-errors").59Set("incognito").60Set("mute-audio").61Set("window-size", fmt.Sprintf("%d,%d", 1080, 1920)).62Delete("use-mock-keychain").63UserDataDir(dataStore)6465if MustDisableSandbox() {66chromeLauncher = chromeLauncher.NoSandbox(true)67}6869executablePath, err := os.Executable()70if err != nil {71return nil, err72}7374// if musl is used, most likely we are on alpine linux which is not supported by go-rod, so we fallback to default chrome75useMusl, _ := fileutil.UseMusl(executablePath)76if options.UseInstalledChrome || useMusl {77if chromePath, hasChrome := launcher.LookPath(); hasChrome {78chromeLauncher.Bin(chromePath)79} else {80return nil, errors.New("the chrome browser is not installed")81}82}8384if options.ShowBrowser {85chromeLauncher = chromeLauncher.Headless(false)86} else {87chromeLauncher = chromeLauncher.Headless(true)88}89if options.AliveHttpProxy != "" {90chromeLauncher = chromeLauncher.Proxy(options.AliveHttpProxy)91}9293for k, v := range options.ParseHeadlessOptionalArguments() {94chromeLauncher.Set(flags.Flag(k), v)95}9697launcherURL, err = chromeLauncher.Launch()98if err != nil {99return nil, err100}101} else {102launcherURL = options.CDPEndpoint103}104105browser := rod.New().ControlURL(launcherURL)106if browserErr := browser.Connect(); browserErr != nil {107return nil, browserErr108}109defaultHeaders := make(map[string]string)110customAgent := ""111for _, option := range options.CustomHeaders {112parts := strings.SplitN(option, ":", 2)113if len(parts) != 2 {114continue115}116if strings.EqualFold(parts[0], "User-Agent") {117customAgent = parts[1]118} else {119k := strings.TrimSpace(parts[0])120v := strings.TrimSpace(parts[1])121if k == "" || v == "" {122continue123}124defaultHeaders[k] = v125}126}127128engine := &Browser{129tempDir: dataStore,130customAgent: customAgent,131defaultHeaders: defaultHeaders,132engine: browser,133options: options,134httpClientOnce: &sync.Once{},135launcher: chromeLauncher,136}137engine.previousPIDs = previousPIDs138return engine, nil139}140141// MustDisableSandbox determines if the current os and user needs sandbox mode disabled142func MustDisableSandbox() bool {143// linux with root user needs "--no-sandbox" option144// https://github.com/chromium/chromium/blob/c4d3c31083a2e1481253ff2d24298a1dfe19c754/chrome/test/chromedriver/client/chromedriver.py#L209145return osutils.IsLinux()146}147148// SetUserAgent sets custom user agent to the browser149func (b *Browser) SetUserAgent(customUserAgent string) {150b.customAgent = customUserAgent151}152153// UserAgent fetch the currently set custom user agent154func (b *Browser) UserAgent() string {155return b.customAgent156}157158// applyDefaultHeaders setsheaders passed via cli -H flag159func (b *Browser) applyDefaultHeaders(p *rod.Page) error {160pairs := make([]string, 0, len(b.defaultHeaders)*2+2)161162hasAcceptLanguage := false163for k := range b.defaultHeaders {164if strings.EqualFold(k, "Accept-Language") {165hasAcceptLanguage = true166break167}168}169if !hasAcceptLanguage {170pairs = append(pairs, "Accept-Language", "en, en-GB, en-us;")171}172for k, v := range b.defaultHeaders {173pairs = append(pairs, k, v)174}175if len(pairs) == 0 {176return nil177}178_, err := p.SetExtraHeaders(pairs)179return err180}181182func (b *Browser) getHTTPClient() (*http.Client, error) {183var err error184b.httpClientOnce.Do(func() {185b.httpClient, err = newHttpClient(b.options)186})187return b.httpClient, err188}189190// Close closes the browser engine191//192// When connected over CDP, it does NOT close the browsers.193func (b *Browser) Close() {194if b.options.CDPEndpoint != "" {195return196}197198_ = b.engine.Close()199b.launcher.Kill()200_ = os.RemoveAll(b.tempDir)201processutil.CloseProcesses(processutil.IsChromeProcess, b.previousPIDs)202}203204205