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