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