Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/headless/engine/page.go
2843 views
1
package engine
2
3
import (
4
"bufio"
5
"fmt"
6
"net/http"
7
"net/url"
8
"strings"
9
"sync"
10
"time"
11
12
"github.com/go-rod/rod"
13
"github.com/go-rod/rod/lib/proto"
14
"github.com/projectdiscovery/gologger"
15
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
16
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
17
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
18
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
19
httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http"
20
"github.com/projectdiscovery/nuclei/v3/pkg/types"
21
"github.com/projectdiscovery/utils/errkit"
22
urlutil "github.com/projectdiscovery/utils/url"
23
)
24
25
// Page is a single page in an isolated browser instance
26
type Page struct {
27
ctx *contextargs.Context
28
inputURL *urlutil.URL
29
options *Options
30
page *rod.Page
31
rules []rule
32
instance *Instance
33
hijackRouter *rod.HijackRouter
34
hijackNative *Hijack
35
mutex *sync.RWMutex
36
History []HistoryData
37
InteractshURLs []string
38
payloads map[string]interface{}
39
variables map[string]interface{}
40
lastActionNavigate *Action
41
}
42
43
// HistoryData contains the page request/response pairs
44
type HistoryData struct {
45
RawRequest string
46
RawResponse string
47
}
48
49
// Options contains additional configuration options for the browser instance
50
type Options struct {
51
Timeout time.Duration
52
DisableCookie bool
53
Options *types.Options
54
}
55
56
// Run runs a list of actions by creating a new page in the browser.
57
func (i *Instance) Run(ctx *contextargs.Context, actions []*Action, payloads map[string]interface{}, options *Options) (ActionData, *Page, error) {
58
page, err := i.engine.Page(proto.TargetCreateTarget{})
59
if err != nil {
60
return nil, nil, err
61
}
62
page = page.Timeout(options.Timeout)
63
64
if err = i.browser.applyDefaultHeaders(page); err != nil {
65
_ = page.Close()
66
return nil, nil, err
67
}
68
69
if i.browser.customAgent != "" {
70
if userAgentErr := page.SetUserAgent(&proto.NetworkSetUserAgentOverride{UserAgent: i.browser.customAgent}); userAgentErr != nil {
71
_ = page.Close()
72
return nil, nil, userAgentErr
73
}
74
}
75
76
payloads = generators.MergeMaps(payloads,
77
generators.BuildPayloadFromOptions(i.browser.options),
78
)
79
80
target := ctx.MetaInput.Input
81
input, err := urlutil.Parse(target)
82
if err != nil {
83
_ = page.Close()
84
return nil, nil, errkit.Wrapf(err, "could not parse URL %s", target)
85
}
86
87
hasTrailingSlash := httputil.HasTrailingSlash(target)
88
variables := utils.GenerateVariables(input, hasTrailingSlash, contextargs.GenerateVariables(ctx))
89
variables = generators.MergeMaps(variables, payloads)
90
91
if vardump.EnableVarDump {
92
gologger.Debug().Msgf("Headless Protocol request variables: %s\n", vardump.DumpVariables(variables))
93
}
94
95
createdPage := &Page{
96
options: options,
97
page: page,
98
ctx: ctx,
99
instance: i,
100
mutex: &sync.RWMutex{},
101
payloads: payloads,
102
variables: variables,
103
inputURL: input,
104
}
105
106
successfulPageCreation := false
107
defer func() {
108
if !successfulPageCreation {
109
// to avoid leaking pages in case of errors
110
createdPage.Close()
111
}
112
}()
113
114
httpclient, err := i.browser.getHTTPClient()
115
if err != nil {
116
return nil, nil, err
117
}
118
119
// in case the page has request/response modification rules - enable global hijacking
120
if createdPage.hasModificationRules() || containsModificationActions(actions...) {
121
hijackRouter := page.HijackRequests()
122
if err := hijackRouter.Add("*", "", createdPage.routingRuleHandler(httpclient)); err != nil {
123
return nil, nil, err
124
}
125
createdPage.hijackRouter = hijackRouter
126
go hijackRouter.Run()
127
} else {
128
hijackRouter := NewHijack(page)
129
hijackRouter.SetPattern(&proto.FetchRequestPattern{
130
URLPattern: "*",
131
RequestStage: proto.FetchRequestStageResponse,
132
})
133
createdPage.hijackNative = hijackRouter
134
hijackRouterHandler := hijackRouter.Start(createdPage.routingRuleHandlerNative)
135
go func() {
136
_ = hijackRouterHandler()
137
}()
138
}
139
140
if err := page.SetViewport(&proto.EmulationSetDeviceMetricsOverride{Viewport: &proto.PageViewport{
141
Scale: 1,
142
Width: float64(1920),
143
Height: float64(1080),
144
}}); err != nil {
145
return nil, nil, err
146
}
147
148
// inject cookies
149
// each http request is performed via the native go http client
150
// we first inject the shared cookies
151
URL, err := url.Parse(ctx.MetaInput.Input)
152
if err != nil {
153
return nil, nil, err
154
}
155
156
if !options.DisableCookie {
157
if cookies := ctx.CookieJar.Cookies(URL); len(cookies) > 0 {
158
var NetworkCookies []*proto.NetworkCookie
159
for _, cookie := range cookies {
160
networkCookie := &proto.NetworkCookie{
161
Name: cookie.Name,
162
Value: cookie.Value,
163
Domain: cookie.Domain,
164
Path: cookie.Path,
165
HTTPOnly: cookie.HttpOnly,
166
Secure: cookie.Secure,
167
Expires: proto.TimeSinceEpoch(cookie.Expires.Unix()),
168
SameSite: proto.NetworkCookieSameSite(GetSameSite(cookie)),
169
Priority: proto.NetworkCookiePriorityLow,
170
}
171
NetworkCookies = append(NetworkCookies, networkCookie)
172
}
173
params := proto.CookiesToParams(NetworkCookies)
174
for _, param := range params {
175
param.URL = ctx.MetaInput.Input
176
}
177
err := page.SetCookies(params)
178
if err != nil {
179
return nil, nil, err
180
}
181
}
182
}
183
184
data, err := createdPage.ExecuteActions(ctx, actions)
185
if err != nil {
186
return nil, nil, err
187
}
188
189
if !options.DisableCookie {
190
// at the end of actions pull out updated cookies from the browser and inject them into the shared cookie jar
191
if cookies, err := page.Cookies([]string{URL.String()}); !options.DisableCookie && err == nil && len(cookies) > 0 {
192
var httpCookies []*http.Cookie
193
for _, cookie := range cookies {
194
httpCookie := &http.Cookie{
195
Name: cookie.Name,
196
Value: cookie.Value,
197
Domain: cookie.Domain,
198
Path: cookie.Path,
199
HttpOnly: cookie.HTTPOnly,
200
Secure: cookie.Secure,
201
}
202
httpCookies = append(httpCookies, httpCookie)
203
}
204
ctx.CookieJar.SetCookies(URL, httpCookies)
205
}
206
}
207
208
// The first item of history data will contain the very first request from the browser
209
// we assume it's the one matching the initial URL
210
createdPage.mutex.RLock()
211
var firstHistoryItem HistoryData
212
hasHistory := len(createdPage.History) > 0
213
if hasHistory {
214
firstHistoryItem = createdPage.History[0]
215
}
216
createdPage.mutex.RUnlock()
217
218
if hasHistory {
219
if resp, err := http.ReadResponse(bufio.NewReader(strings.NewReader(firstHistoryItem.RawResponse)), nil); err == nil {
220
data["header"] = utils.HeadersToString(resp.Header)
221
data["status_code"] = fmt.Sprint(resp.StatusCode)
222
defer func() {
223
_ = resp.Body.Close()
224
}()
225
}
226
}
227
228
successfulPageCreation = true // avoid closing the page in case of success in deferred function
229
return data, createdPage, nil
230
}
231
232
// Close closes a browser page
233
func (p *Page) Close() {
234
if p.hijackRouter != nil {
235
_ = p.hijackRouter.Stop()
236
}
237
if p.hijackNative != nil {
238
_ = p.hijackNative.Stop()
239
}
240
_ = p.page.Close()
241
}
242
243
// Page returns the current page for the actions
244
func (p *Page) Page() *rod.Page {
245
return p.page
246
}
247
248
// Browser returns the browser that created the current page
249
func (p *Page) Browser() *rod.Browser {
250
return p.instance.engine
251
}
252
253
// URL returns the URL for the current page.
254
func (p *Page) URL() string {
255
info, err := p.page.Info()
256
if err != nil {
257
return ""
258
}
259
return info.URL
260
}
261
262
// DumpHistory returns the full page navigation history
263
func (p *Page) DumpHistory() string {
264
p.mutex.RLock()
265
defer p.mutex.RUnlock()
266
267
var historyDump strings.Builder
268
for _, historyData := range p.History {
269
historyDump.WriteString(historyData.RawRequest)
270
historyDump.WriteString(historyData.RawResponse)
271
}
272
return historyDump.String()
273
}
274
275
// addToHistory adds a request/response pair to the page history
276
func (p *Page) addToHistory(historyData ...HistoryData) {
277
p.mutex.Lock()
278
defer p.mutex.Unlock()
279
280
p.History = append(p.History, historyData...)
281
}
282
283
func (p *Page) addInteractshURL(URLs ...string) {
284
p.mutex.Lock()
285
defer p.mutex.Unlock()
286
287
p.InteractshURLs = append(p.InteractshURLs, URLs...)
288
}
289
290
func (p *Page) hasModificationRules() bool {
291
for _, rule := range p.rules {
292
if containsAnyModificationActionType(rule.Action) {
293
return true
294
}
295
}
296
return false
297
}
298
299
// updateLastNavigatedURL updates the last navigated URL in the instance's
300
// request log.
301
func (p *Page) updateLastNavigatedURL() {
302
if p.lastActionNavigate == nil {
303
return
304
}
305
306
templateURL := p.lastActionNavigate.GetArg("url")
307
p.instance.requestLog[templateURL] = p.URL()
308
}
309
310
func containsModificationActions(actions ...*Action) bool {
311
for _, action := range actions {
312
if containsAnyModificationActionType(action.ActionType.ActionType) {
313
return true
314
}
315
}
316
return false
317
}
318
319
func containsAnyModificationActionType(actionTypes ...ActionType) bool {
320
for _, actionType := range actionTypes {
321
switch actionType {
322
case ActionSetMethod:
323
return true
324
case ActionAddHeader:
325
return true
326
case ActionSetHeader:
327
return true
328
case ActionDeleteHeader:
329
return true
330
case ActionSetBody:
331
return true
332
}
333
}
334
return false
335
}
336
337
func GetSameSite(cookie *http.Cookie) string {
338
switch cookie.SameSite {
339
case http.SameSiteNoneMode:
340
return "none"
341
case http.SameSiteLaxMode:
342
return "lax"
343
case http.SameSiteStrictMode:
344
return "strict"
345
case http.SameSiteDefaultMode:
346
fallthrough
347
default:
348
return ""
349
}
350
}
351
352