Skip to content

Commit 6bc51b4

Browse files
authored
Merge pull request #2220 from Serizao/add-network-logging
Add network logging
2 parents 8f6a1ca + bf076a4 commit 6bc51b4

File tree

3 files changed

+123
-8
lines changed

3 files changed

+123
-8
lines changed

runner/headless.go

Lines changed: 119 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,20 @@ import (
1212
"github.com/go-rod/rod/lib/proto"
1313
"github.com/pkg/errors"
1414
fileutil "github.com/projectdiscovery/utils/file"
15+
mapsutil "github.com/projectdiscovery/utils/maps"
1516
osutils "github.com/projectdiscovery/utils/os"
17+
sliceutil "github.com/projectdiscovery/utils/slice"
18+
stringsutil "github.com/projectdiscovery/utils/strings"
1619
)
1720

21+
type NetworkRequest struct {
22+
RequestID string
23+
URL string
24+
Method string
25+
StatusCode int
26+
ErrorType string
27+
}
28+
1829
// MustDisableSandbox determines if the current os and user needs sandbox mode disabled
1930
func MustDisableSandbox() bool {
2031
// linux with root user needs "--no-sandbox" option
@@ -99,12 +110,62 @@ func NewBrowser(proxy string, useLocal bool, optionalArgs map[string]string) (*B
99110
return engine, nil
100111
}
101112

102-
func (b *Browser) ScreenshotWithBody(url string, timeout time.Duration, idle time.Duration, headers []string, fullPage bool) ([]byte, string, error) {
113+
func (b *Browser) ScreenshotWithBody(url string, timeout time.Duration, idle time.Duration, headers []string, fullPage bool) ([]byte, string, []NetworkRequest, error) {
103114
page, err := b.engine.Page(proto.TargetCreateTarget{})
104115
if err != nil {
105-
return nil, "", err
116+
return nil, "", []NetworkRequest{}, err
106117
}
107118

119+
// Enable network
120+
page.EnableDomain(proto.NetworkEnable{})
121+
122+
networkRequests := sliceutil.NewSyncSlice[NetworkRequest]()
123+
requestsMap := mapsutil.NewSyncLockMap[string, *NetworkRequest]()
124+
125+
// Intercept outbound requests
126+
go page.EachEvent(func(e *proto.NetworkRequestWillBeSent) {
127+
if !stringsutil.HasPrefixAnyI(e.Request.URL, "http://", "https://") {
128+
return
129+
}
130+
req := &NetworkRequest{
131+
RequestID: string(e.RequestID),
132+
URL: e.Request.URL,
133+
Method: e.Request.Method,
134+
StatusCode: -1,
135+
ErrorType: "QUIT_BEFORE_RESOURCE_LOADING_END",
136+
}
137+
_ = requestsMap.Set(string(e.RequestID), req)
138+
})()
139+
// Intercept inbound responses
140+
go page.EachEvent(func(e *proto.NetworkResponseReceived) {
141+
if requestsMap.Has(string(e.RequestID)) {
142+
req, _ := requestsMap.Get(string(e.RequestID))
143+
req.StatusCode = e.Response.Status
144+
}
145+
})()
146+
// Intercept network end requests
147+
go page.EachEvent(func(e *proto.NetworkLoadingFinished) {
148+
if requestsMap.Has(string(e.RequestID)) {
149+
req, _ := requestsMap.Get(string(e.RequestID))
150+
if req.StatusCode > 0 {
151+
req.ErrorType = ""
152+
}
153+
networkRequests.Append(*req)
154+
}
155+
})()
156+
// Intercept failed request
157+
go page.EachEvent(func(e *proto.NetworkLoadingFailed) {
158+
if requestsMap.Has(string(e.RequestID)) {
159+
req, _ := requestsMap.Get(string(e.RequestID))
160+
req.StatusCode = 0 // mark to zero
161+
req.ErrorType = getSimpleErrorType(e.ErrorText, string(e.Type), string(e.BlockedReason))
162+
if stringsutil.HasPrefixAnyI(req.URL, "http://", "https://") {
163+
networkRequests.Append(*req)
164+
}
165+
}
166+
})()
167+
168+
// Handle any popup dialogs
108169
go page.EachEvent(func(e *proto.PageJavascriptDialogOpening) {
109170
_ = proto.PageHandleJavaScriptDialog{
110171
Accept: true,
@@ -128,31 +189,82 @@ func (b *Browser) ScreenshotWithBody(url string, timeout time.Duration, idle tim
128189
}()
129190

130191
if err := page.Navigate(url); err != nil {
131-
return nil, "", err
192+
return nil, "", networkRequests.Slice, err
132193
}
133194

134195
page.Timeout(5 * time.Second).WaitNavigation(proto.PageLifecycleEventNameFirstMeaningfulPaint)()
135196

136197
if err := page.WaitLoad(); err != nil {
137-
return nil, "", err
198+
return nil, "", networkRequests.Slice, err
138199
}
139200
_ = page.WaitIdle(idle)
140201

141202
screenshot, err := page.Screenshot(fullPage, &proto.PageCaptureScreenshot{})
142203
if err != nil {
143-
return nil, "", err
204+
return nil, "", networkRequests.Slice, err
144205
}
145206

146207
body, err := page.HTML()
147208
if err != nil {
148-
return screenshot, "", err
209+
return screenshot, "", networkRequests.Slice, err
149210
}
150211

151-
return screenshot, body, nil
212+
return screenshot, body, networkRequests.Slice, nil
152213
}
153214

154215
func (b *Browser) Close() {
155216
_ = b.engine.Close()
156217
_ = os.RemoveAll(b.tempDir)
157218
// processutil.CloseProcesses(processutil.IsChromeProcess, b.pids)
158219
}
220+
func getSimpleErrorType(errorText, errorType, blockedReason string) string {
221+
switch blockedReason {
222+
case "csp":
223+
return "CSP_BLOCKED"
224+
case "mixed-content":
225+
return "MIXED_CONTENT"
226+
case "origin":
227+
return "CORS_BLOCKED"
228+
case "subresource-filter":
229+
return "AD_BLOCKED"
230+
}
231+
switch {
232+
case strings.Contains(errorText, "net::ERR_NAME_NOT_RESOLVED"):
233+
return "DNS_ERROR"
234+
case strings.Contains(errorText, "net::ERR_CONNECTION_REFUSED"):
235+
return "CONNECTION_REFUSED"
236+
case strings.Contains(errorText, "net::ERR_CONNECTION_TIMED_OUT"):
237+
return "TIMEOUT"
238+
case strings.Contains(errorText, "net::ERR_CERT_"):
239+
return "SSL_ERROR"
240+
case strings.Contains(errorText, "net::ERR_BLOCKED_BY_CLIENT"):
241+
return "CLIENT_BLOCKED"
242+
case strings.Contains(errorText, "net::ERR_EMPTY_RESPONSE"):
243+
return "EMPTY_RESPONSE"
244+
}
245+
switch errorType {
246+
case "Failed":
247+
return "NETWORK_FAILED"
248+
case "Aborted":
249+
return "ABORTED"
250+
case "TimedOut":
251+
return "TIMEOUT"
252+
case "AccessDenied":
253+
return "ACCESS_DENIED"
254+
case "ConnectionClosed":
255+
return "CONNECTION_CLOSED"
256+
case "ConnectionReset":
257+
return "CONNECTION_RESET"
258+
case "ConnectionRefused":
259+
return "CONNECTION_REFUSED"
260+
case "NameNotResolved":
261+
return "DNS_ERROR"
262+
case "BlockedByClient":
263+
return "CLIENT_BLOCKED"
264+
}
265+
// Fallback
266+
if errorText != "" {
267+
return "OTHER_ERROR"
268+
}
269+
return "UNKNOWN"
270+
}

runner/runner.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1890,6 +1890,7 @@ retry:
18901890
request string
18911891
rawResponseHeaders string
18921892
responseHeaders map[string]interface{}
1893+
linkRequest []NetworkRequest
18931894
)
18941895

18951896
if scanopts.ResponseHeadersInStdout {
@@ -2242,7 +2243,7 @@ retry:
22422243
var pHash uint64
22432244
if scanopts.Screenshot {
22442245
var err error
2245-
screenshotBytes, headlessBody, err = r.browser.ScreenshotWithBody(fullURL, scanopts.ScreenshotTimeout, scanopts.ScreenshotIdle, r.options.CustomHeaders, scanopts.IsScreenshotFullPage())
2246+
screenshotBytes, headlessBody, linkRequest, err = r.browser.ScreenshotWithBody(fullURL, scanopts.ScreenshotTimeout, scanopts.ScreenshotIdle, r.options.CustomHeaders, scanopts.IsScreenshotFullPage())
22462247
if err != nil {
22472248
gologger.Warning().Msgf("Could not take screenshot '%s': %s", fullURL, err)
22482249
} else {
@@ -2287,6 +2288,7 @@ retry:
22872288
result := Result{
22882289
Timestamp: time.Now(),
22892290
Request: request,
2291+
LinkRequest: linkRequest,
22902292
ResponseHeaders: responseHeaders,
22912293
RawHeaders: rawResponseHeaders,
22922294
Scheme: parsed.Scheme,

runner/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func (o AsnResponse) String() string {
3434
// Result of a scan
3535
type Result struct {
3636
Timestamp time.Time `json:"timestamp,omitempty" csv:"timestamp" mapstructure:"timestamp"`
37+
LinkRequest []NetworkRequest `json:"link_request,omitempty" csv:"link_request" mapstructure:"link_request"`
3738
ASN *AsnResponse `json:"asn,omitempty" csv:"-" mapstructure:"asn"`
3839
Err error `json:"-" csv:"-" mapstructure:"-"`
3940
CSPData *httpx.CSPData `json:"csp,omitempty" csv:"-" mapstructure:"csp"`

0 commit comments

Comments
 (0)