Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/headless/engine/engine.go
2072 views
1
package engine
2
3
import (
4
"fmt"
5
"net/http"
6
"os"
7
"strings"
8
"sync"
9
10
"github.com/go-rod/rod"
11
"github.com/go-rod/rod/lib/launcher"
12
"github.com/go-rod/rod/lib/launcher/flags"
13
"github.com/pkg/errors"
14
15
"github.com/projectdiscovery/nuclei/v3/pkg/types"
16
fileutil "github.com/projectdiscovery/utils/file"
17
osutils "github.com/projectdiscovery/utils/os"
18
processutil "github.com/projectdiscovery/utils/process"
19
)
20
21
// Browser is a browser structure for nuclei headless module
22
type Browser struct {
23
customAgent string
24
defaultHeaders map[string]string
25
tempDir string
26
previousPIDs map[int32]struct{} // track already running PIDs
27
engine *rod.Browser
28
options *types.Options
29
launcher *launcher.Launcher
30
31
// use getHTTPClient to get the http client
32
httpClient *http.Client
33
httpClientOnce *sync.Once
34
}
35
36
// New creates a new nuclei headless browser module
37
func New(options *types.Options) (*Browser, error) {
38
dataStore, err := os.MkdirTemp("", "nuclei-*")
39
if err != nil {
40
return nil, errors.Wrap(err, "could not create temporary directory")
41
}
42
previousPIDs := processutil.FindProcesses(processutil.IsChromeProcess)
43
44
chromeLauncher := launcher.New().
45
Leakless(false).
46
Set("disable-gpu", "true").
47
Set("ignore-certificate-errors", "true").
48
Set("ignore-certificate-errors", "1").
49
Set("disable-crash-reporter", "true").
50
Set("disable-notifications", "true").
51
Set("hide-scrollbars", "true").
52
Set("window-size", fmt.Sprintf("%d,%d", 1080, 1920)).
53
Set("mute-audio", "true").
54
Set("incognito", "true").
55
Delete("use-mock-keychain").
56
UserDataDir(dataStore)
57
58
if MustDisableSandbox() {
59
chromeLauncher = chromeLauncher.NoSandbox(true)
60
}
61
62
executablePath, err := os.Executable()
63
if err != nil {
64
return nil, err
65
}
66
67
// if musl is used, most likely we are on alpine linux which is not supported by go-rod, so we fallback to default chrome
68
useMusl, _ := fileutil.UseMusl(executablePath)
69
if options.UseInstalledChrome || useMusl {
70
if chromePath, hasChrome := launcher.LookPath(); hasChrome {
71
chromeLauncher.Bin(chromePath)
72
} else {
73
return nil, errors.New("the chrome browser is not installed")
74
}
75
}
76
77
if options.ShowBrowser {
78
chromeLauncher = chromeLauncher.Headless(false)
79
} else {
80
chromeLauncher = chromeLauncher.Headless(true)
81
}
82
if options.AliveHttpProxy != "" {
83
chromeLauncher = chromeLauncher.Proxy(options.AliveHttpProxy)
84
}
85
86
for k, v := range options.ParseHeadlessOptionalArguments() {
87
chromeLauncher.Set(flags.Flag(k), v)
88
}
89
90
launcherURL, err := chromeLauncher.Launch()
91
if err != nil {
92
return nil, err
93
}
94
95
browser := rod.New().ControlURL(launcherURL)
96
if browserErr := browser.Connect(); browserErr != nil {
97
return nil, browserErr
98
}
99
defaultHeaders := make(map[string]string)
100
customAgent := ""
101
for _, option := range options.CustomHeaders {
102
parts := strings.SplitN(option, ":", 2)
103
if len(parts) != 2 {
104
continue
105
}
106
if strings.EqualFold(parts[0], "User-Agent") {
107
customAgent = parts[1]
108
} else {
109
k := strings.TrimSpace(parts[0])
110
v := strings.TrimSpace(parts[1])
111
if k == "" || v == "" {
112
continue
113
}
114
defaultHeaders[k] = v
115
}
116
}
117
118
engine := &Browser{
119
tempDir: dataStore,
120
customAgent: customAgent,
121
defaultHeaders: defaultHeaders,
122
engine: browser,
123
options: options,
124
httpClientOnce: &sync.Once{},
125
launcher: chromeLauncher,
126
}
127
engine.previousPIDs = previousPIDs
128
return engine, nil
129
}
130
131
// MustDisableSandbox determines if the current os and user needs sandbox mode disabled
132
func MustDisableSandbox() bool {
133
// linux with root user needs "--no-sandbox" option
134
// https://github.com/chromium/chromium/blob/c4d3c31083a2e1481253ff2d24298a1dfe19c754/chrome/test/chromedriver/client/chromedriver.py#L209
135
return osutils.IsLinux()
136
}
137
138
// SetUserAgent sets custom user agent to the browser
139
func (b *Browser) SetUserAgent(customUserAgent string) {
140
b.customAgent = customUserAgent
141
}
142
143
// UserAgent fetch the currently set custom user agent
144
func (b *Browser) UserAgent() string {
145
return b.customAgent
146
}
147
148
// applyDefaultHeaders setsheaders passed via cli -H flag
149
func (b *Browser) applyDefaultHeaders(p *rod.Page) error {
150
pairs := make([]string, 0, len(b.defaultHeaders)*2+2)
151
152
hasAcceptLanguage := false
153
for k := range b.defaultHeaders {
154
if strings.EqualFold(k, "Accept-Language") {
155
hasAcceptLanguage = true
156
break
157
}
158
}
159
if !hasAcceptLanguage {
160
pairs = append(pairs, "Accept-Language", "en, en-GB, en-us;")
161
}
162
for k, v := range b.defaultHeaders {
163
pairs = append(pairs, k, v)
164
}
165
if len(pairs) == 0 {
166
return nil
167
}
168
_, err := p.SetExtraHeaders(pairs)
169
return err
170
}
171
172
func (b *Browser) getHTTPClient() (*http.Client, error) {
173
var err error
174
b.httpClientOnce.Do(func() {
175
b.httpClient, err = newHttpClient(b.options)
176
})
177
return b.httpClient, err
178
}
179
180
// Close closes the browser engine
181
func (b *Browser) Close() {
182
_ = b.engine.Close()
183
b.launcher.Kill()
184
_ = os.RemoveAll(b.tempDir)
185
processutil.CloseProcesses(processutil.IsChromeProcess, b.previousPIDs)
186
}
187
188