Skip to content

Commit

Permalink
feat(headless): add cdp-endpoint option
Browse files Browse the repository at this point in the history
Signed-off-by: Dwi Siswanto <[email protected]>
  • Loading branch information
dwisiswant0 committed Oct 30, 2024
1 parent 97403c2 commit f2e9f8d
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 49 deletions.
1 change: 1 addition & 0 deletions cmd/nuclei/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.BoolVarP(&options.ShowBrowser, "show-browser", "sb", false, "show the browser on the screen when running templates with headless mode"),
flagSet.StringSliceVarP(&options.HeadlessOptionalArguments, "headless-options", "ho", nil, "start headless chrome with additional options", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.BoolVarP(&options.UseInstalledChrome, "system-chrome", "sc", false, "use local installed Chrome browser instead of nuclei installed"),
flagSet.StringVarP(&options.CDPEndpoint, "cdp-endpoint", "cdpe", "", "use remote browser via Chrome DevTools Protocol (CDP) endpoint"),
flagSet.BoolVarP(&options.ShowActions, "list-headless-action", "lha", false, "list available headless actions"),
)

Expand Down
115 changes: 66 additions & 49 deletions pkg/protocols/headless/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,61 +29,72 @@ type Browser struct {

// New creates a new nuclei headless browser module
func New(options *types.Options) (*Browser, error) {
dataStore, err := os.MkdirTemp("", "nuclei-*")
if err != nil {
return nil, errors.Wrap(err, "could not create temporary directory")
}
previousPIDs := processutil.FindProcesses(processutil.IsChromeProcess)

chromeLauncher := launcher.New().
Leakless(false).
Set("disable-gpu", "true").
Set("ignore-certificate-errors", "true").
Set("ignore-certificate-errors", "1").
Set("disable-crash-reporter", "true").
Set("disable-notifications", "true").
Set("hide-scrollbars", "true").
Set("window-size", fmt.Sprintf("%d,%d", 1080, 1920)).
Set("mute-audio", "true").
Set("incognito", "true").
Delete("use-mock-keychain").
UserDataDir(dataStore)

if MustDisableSandbox() {
chromeLauncher = chromeLauncher.NoSandbox(true)
}
var launcherURL, dataStore string
var previousPIDs map[int32]struct{}
var err error

executablePath, err := os.Executable()
if err != nil {
return nil, err
}
chromeLauncher := launcher.New()

// if musl is used, most likely we are on alpine linux which is not supported by go-rod, so we fallback to default chrome
useMusl, _ := fileutil.UseMusl(executablePath)
if options.UseInstalledChrome || useMusl {
if chromePath, hasChrome := launcher.LookPath(); hasChrome {
chromeLauncher.Bin(chromePath)
} else {
return nil, errors.New("the chrome browser is not installed")
if options.CDPEndpoint == "" {
previousPIDs = processutil.FindProcesses(processutil.IsChromeProcess)

dataStore, err = os.MkdirTemp("", "nuclei-*")
if err != nil {
return nil, errors.Wrap(err, "could not create temporary directory")
}
}

if options.ShowBrowser {
chromeLauncher = chromeLauncher.Headless(false)
} else {
chromeLauncher = chromeLauncher.Headless(true)
}
if types.ProxyURL != "" {
chromeLauncher = chromeLauncher.Proxy(types.ProxyURL)
}
chromeLauncher = chromeLauncher.
Leakless(false).
Set("disable-gpu", "true").
Set("ignore-certificate-errors", "true").
Set("ignore-certificate-errors", "1").
Set("disable-crash-reporter", "true").
Set("disable-notifications", "true").
Set("hide-scrollbars", "true").
Set("window-size", fmt.Sprintf("%d,%d", 1080, 1920)).
Set("mute-audio", "true").
Set("incognito", "true").
Delete("use-mock-keychain").
UserDataDir(dataStore)

if MustDisableSandbox() {
chromeLauncher = chromeLauncher.NoSandbox(true)
}

for k, v := range options.ParseHeadlessOptionalArguments() {
chromeLauncher.Set(flags.Flag(k), v)
}
executablePath, err := os.Executable()
if err != nil {
return nil, err
}

launcherURL, err := chromeLauncher.Launch()
if err != nil {
return nil, err
// if musl is used, most likely we are on alpine linux which is not supported by go-rod, so we fallback to default chrome
useMusl, _ := fileutil.UseMusl(executablePath)
if options.UseInstalledChrome || useMusl {
if chromePath, hasChrome := launcher.LookPath(); hasChrome {
chromeLauncher.Bin(chromePath)
} else {
return nil, errors.New("the chrome browser is not installed")
}
}

if options.ShowBrowser {
chromeLauncher = chromeLauncher.Headless(false)
} else {
chromeLauncher = chromeLauncher.Headless(true)
}
if types.ProxyURL != "" {
chromeLauncher = chromeLauncher.Proxy(types.ProxyURL)
}

for k, v := range options.ParseHeadlessOptionalArguments() {
chromeLauncher.Set(flags.Flag(k), v)
}

launcherURL, err = chromeLauncher.Launch()
if err != nil {
return nil, err
}
} else {
launcherURL = options.CDPEndpoint
}

browser := rod.New().ControlURL(launcherURL)
Expand Down Expand Up @@ -135,7 +146,13 @@ func (b *Browser) UserAgent() string {
}

// Close closes the browser engine
//
// When connected over CDP, it does NOT close the browsers.
func (b *Browser) Close() {
if b.options.CDPEndpoint != "" {
return
}

b.engine.Close()
os.RemoveAll(b.tempDir)
processutil.CloseProcesses(processutil.IsChromeProcess, b.previousPIDs)
Expand Down
2 changes: 2 additions & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ type Options struct {
ForceAttemptHTTP2 bool
// StatsJSON writes stats output in JSON format
StatsJSON bool
// CDPEndpoint specifies the endpoint for Chrome DevTools Protocol (CDP)
CDPEndpoint string
// Headless specifies whether to allow headless mode templates
Headless bool
// ShowBrowser specifies whether the show the browser in headless mode
Expand Down

0 comments on commit f2e9f8d

Please sign in to comment.