2021-02-21 16:31:34 +05:30
|
|
|
package engine
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
2021-12-05 20:14:16 +05:30
|
|
|
"runtime"
|
2021-02-21 16:31:34 +05:30
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/go-rod/rod"
|
|
|
|
|
"github.com/go-rod/rod/lib/launcher"
|
|
|
|
|
"github.com/pkg/errors"
|
2021-09-07 17:31:46 +03:00
|
|
|
ps "github.com/shirou/gopsutil/v3/process"
|
|
|
|
|
|
2022-04-28 01:50:22 +02:00
|
|
|
"github.com/projectdiscovery/fileutil"
|
2021-02-21 16:31:34 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
2021-06-21 10:51:52 +02:00
|
|
|
"github.com/projectdiscovery/stringsutil"
|
2021-02-21 16:31:34 +05:30
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Browser is a browser structure for nuclei headless module
|
|
|
|
|
type Browser struct {
|
|
|
|
|
customAgent string
|
|
|
|
|
tempDir string
|
2021-11-25 18:54:16 +02:00
|
|
|
previousPIDs map[int32]struct{} // track already running PIDs
|
2021-02-21 16:31:34 +05:30
|
|
|
engine *rod.Browser
|
|
|
|
|
httpclient *http.Client
|
|
|
|
|
options *types.Options
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New creates a new nuclei headless browser module
|
|
|
|
|
func New(options *types.Options) (*Browser, error) {
|
2022-08-25 13:22:08 +02:00
|
|
|
dataStore, err := os.MkdirTemp("", "nuclei-*")
|
2021-02-21 16:31:34 +05:30
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "could not create temporary directory")
|
|
|
|
|
}
|
2021-11-25 18:54:16 +02:00
|
|
|
previousPIDs := findChromeProcesses()
|
2021-09-26 07:56:35 +02:00
|
|
|
|
2021-02-26 13:13:11 +05:30
|
|
|
chromeLauncher := launcher.New().
|
2021-02-21 16:31:34 +05:30
|
|
|
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)
|
|
|
|
|
|
2021-12-05 20:14:16 +05:30
|
|
|
if MustDisableSandbox() {
|
|
|
|
|
chromeLauncher = chromeLauncher.NoSandbox(true)
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-28 01:50:22 +02:00
|
|
|
executablePath, err := os.Executable()
|
|
|
|
|
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 {
|
2021-09-26 07:56:35 +02:00
|
|
|
if chromePath, hasChrome := launcher.LookPath(); hasChrome {
|
|
|
|
|
chromeLauncher.Bin(chromePath)
|
|
|
|
|
} else {
|
|
|
|
|
return nil, errors.New("the chrome browser is not installed")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 16:31:34 +05:30
|
|
|
if options.ShowBrowser {
|
2021-02-26 13:13:11 +05:30
|
|
|
chromeLauncher = chromeLauncher.Headless(false)
|
2021-02-21 16:31:34 +05:30
|
|
|
} else {
|
2021-02-26 13:13:11 +05:30
|
|
|
chromeLauncher = chromeLauncher.Headless(true)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
2021-11-10 10:00:03 -06:00
|
|
|
if types.ProxyURL != "" {
|
|
|
|
|
chromeLauncher = chromeLauncher.Proxy(types.ProxyURL)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
2021-02-26 13:13:11 +05:30
|
|
|
launcherURL, err := chromeLauncher.Launch()
|
2021-02-21 16:31:34 +05:30
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
browser := rod.New().ControlURL(launcherURL)
|
2021-02-26 13:13:11 +05:30
|
|
|
if browserErr := browser.Connect(); browserErr != nil {
|
|
|
|
|
return nil, browserErr
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
customAgent := ""
|
|
|
|
|
for _, option := range options.CustomHeaders {
|
|
|
|
|
parts := strings.SplitN(option, ":", 2)
|
|
|
|
|
if len(parts) != 2 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if strings.EqualFold(parts[0], "User-Agent") {
|
|
|
|
|
customAgent = parts[1]
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-11-10 18:12:49 +01:00
|
|
|
|
2021-11-25 18:54:16 +02:00
|
|
|
httpclient, err := newHttpClient(options)
|
2021-11-10 18:12:49 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 16:31:34 +05:30
|
|
|
engine := &Browser{
|
|
|
|
|
tempDir: dataStore,
|
|
|
|
|
customAgent: customAgent,
|
|
|
|
|
engine: browser,
|
|
|
|
|
httpclient: httpclient,
|
|
|
|
|
options: options,
|
|
|
|
|
}
|
2021-11-25 18:54:16 +02:00
|
|
|
engine.previousPIDs = previousPIDs
|
2021-02-21 16:31:34 +05:30
|
|
|
return engine, nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 20:14:16 +05:30
|
|
|
// MustDisableSandbox determines if the current os and user needs sandbox mode disabled
|
|
|
|
|
func MustDisableSandbox() bool {
|
|
|
|
|
// linux with root user needs "--no-sandbox" option
|
|
|
|
|
// https://github.com/chromium/chromium/blob/c4d3c31083a2e1481253ff2d24298a1dfe19c754/chrome/test/chromedriver/client/chromedriver.py#L209
|
|
|
|
|
return runtime.GOOS == "linux" && os.Geteuid() == 0
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-24 12:31:08 +05:30
|
|
|
// SetUserAgent sets custom user agent to the browser
|
|
|
|
|
func (b *Browser) SetUserAgent(customUserAgent string) {
|
|
|
|
|
b.customAgent = customUserAgent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UserAgent fetch the currently set custom user agent
|
|
|
|
|
func (b *Browser) UserAgent() string {
|
|
|
|
|
return b.customAgent
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 16:31:34 +05:30
|
|
|
// Close closes the browser engine
|
|
|
|
|
func (b *Browser) Close() {
|
|
|
|
|
b.engine.Close()
|
|
|
|
|
os.RemoveAll(b.tempDir)
|
|
|
|
|
b.killChromeProcesses()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// killChromeProcesses any and all new chrome processes started after
|
|
|
|
|
// headless process launch.
|
|
|
|
|
func (b *Browser) killChromeProcesses() {
|
2021-06-21 10:51:52 +02:00
|
|
|
processes, _ := ps.Processes()
|
2021-06-05 18:01:08 +05:30
|
|
|
|
2021-06-21 10:51:52 +02:00
|
|
|
for _, process := range processes {
|
2021-09-07 17:31:46 +03:00
|
|
|
// skip non-chrome processes
|
2021-06-21 10:51:52 +02:00
|
|
|
if !isChromeProcess(process) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// skip chrome processes that were already running
|
2021-11-25 18:54:16 +02:00
|
|
|
if _, ok := b.previousPIDs[process.Pid]; ok {
|
2021-02-21 16:31:34 +05:30
|
|
|
continue
|
|
|
|
|
}
|
2021-07-05 17:29:45 +05:30
|
|
|
_ = process.Kill()
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// findChromeProcesses finds chrome process running on host
|
2021-06-21 10:51:52 +02:00
|
|
|
func findChromeProcesses() map[int32]struct{} {
|
2021-02-21 16:31:34 +05:30
|
|
|
processes, _ := ps.Processes()
|
2021-06-21 10:51:52 +02:00
|
|
|
list := make(map[int32]struct{})
|
2021-02-21 16:31:34 +05:30
|
|
|
for _, process := range processes {
|
2021-06-21 10:51:52 +02:00
|
|
|
if isChromeProcess(process) {
|
|
|
|
|
list[process.Pid] = struct{}{}
|
|
|
|
|
if ppid, err := process.Ppid(); err == nil {
|
|
|
|
|
list[ppid] = struct{}{}
|
|
|
|
|
}
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return list
|
|
|
|
|
}
|
2021-06-21 10:51:52 +02:00
|
|
|
|
|
|
|
|
// isChromeProcess checks if a process is chrome/chromium
|
|
|
|
|
func isChromeProcess(process *ps.Process) bool {
|
|
|
|
|
name, _ := process.Name()
|
|
|
|
|
executable, _ := process.Exe()
|
|
|
|
|
return stringsutil.ContainsAny(name, "chrome", "chromium") || stringsutil.ContainsAny(executable, "chrome", "chromium")
|
|
|
|
|
}
|