Skip to content

Commit bc544d9

Browse files
authored
Adding Comments as it is
1 parent c647525 commit bc544d9

1 file changed

Lines changed: 60 additions & 0 deletions

File tree

pkg/scan/chrome.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// pphack - The Most Advanced Client-Side Prototype Pollution Scanner
2+
// This repository is under MIT License https://github.com/edoardottt/pphack/blob/main/LICENSE
3+
14
package scan
25

36
import (
@@ -13,6 +16,10 @@ import (
1316
"github.com/projectdiscovery/gologger"
1417
)
1518

19+
// GetChromeOptions takes as input the runner settings and returns
20+
// the chrome options used to configure the headless browser instance.
21+
// It always disables certificate errors and sets a custom user agent.
22+
// If a proxy is configured in the runner options, it is appended as well.
1623
func GetChromeOptions(r *Runner) []func(*chromedp.ExecAllocator) {
1724
copts := append(chromedp.DefaultExecAllocatorOptions[:],
1825
chromedp.Flag("ignore-certificate-errors", true),
@@ -26,10 +33,20 @@ func GetChromeOptions(r *Runner) []func(*chromedp.ExecAllocator) {
2633
return copts
2734
}
2835

36+
// GetChromeBrowser takes as input the chrome options and returns
37+
// the contexts with the associated cancel functions to use the
38+
// headless chrome browser it creates.
39+
// Returns ecancel (exec allocator cancel), pctx (parent browser context),
40+
// and pcancel (parent context cancel).
41+
// Callers must invoke pcancel before ecancel to ensure correct cleanup order.
42+
// ecancel is also called internally on fatal browser startup failure
43+
// to avoid leaking the exec allocator before the process exits.
2944
func GetChromeBrowser(copts []func(*chromedp.ExecAllocator)) (context.CancelFunc, context.Context, context.CancelFunc) {
3045
ectx, ecancel := chromedp.NewExecAllocator(context.Background(), copts...)
3146
pctx, pcancel := chromedp.NewContext(ectx)
3247

48+
// Run an empty chromedp task to verify the browser starts successfully.
49+
// If it fails, ecancel is called before Fatal to avoid leaking the allocator.
3350
if err := chromedp.Run(pctx); err != nil {
3451
ecancel()
3552
gologger.Fatal().Msgf("error starting browser: %s", err.Error())
@@ -38,6 +55,10 @@ func GetChromeBrowser(copts []func(*chromedp.ExecAllocator)) (context.CancelFunc
3855
return ecancel, pctx, pcancel
3956
}
4057

58+
// buildHeaders is a helper that converts a headers map into a chromedp.Tasks
59+
// slice containing the SetExtraHTTPHeaders action.
60+
// Returns nil if headers is nil, making it safe to append directly onto any
61+
// existing chromedp.Tasks without an extra nil check at the call site.
4162
func buildHeaders(headers map[string]interface{}) chromedp.Tasks {
4263
if headers == nil {
4364
return nil
@@ -46,6 +67,18 @@ func buildHeaders(headers map[string]interface{}) chromedp.Tasks {
4667
return chromedp.Tasks{network.SetExtraHTTPHeaders(network.Headers(headers))}
4768
}
4869

70+
// Scan is the core function that performs the prototype pollution scan.
71+
// It takes a parent browser context (pctx), runner config (r), optional HTTP
72+
// headers, the JavaScript payload (js), the original input value, and the
73+
// fully constructed target URL.
74+
//
75+
// Flow:
76+
// 1. Creates a timeout-scoped context and a dedicated Chrome tab context.
77+
// 2. Navigates to targetURL and evaluates the JS pollution payload.
78+
// 3. If exploit mode is enabled and the payload returned a non-empty result,
79+
// it runs fingerprinting to identify the affected library/sink.
80+
// 4. Attempts exploitation using the fingerprint results.
81+
// 5. Populates and returns a ResultData struct with all findings and errors.
4982
func Scan(
5083
pctx context.Context,
5184
r *Runner,
@@ -57,31 +90,47 @@ func Scan(
5790
resDetection []string
5891
)
5992

93+
// Initialize result with the original input value and the constructed scan URL.
6094
resultData := output.ResultData{
6195
TargetURL: value,
6296
ScanURL: targetURL,
6397
}
6498

99+
// Wrap the parent context with a per-scan timeout so hung pages
100+
// don't block the scanner indefinitely.
65101
ctx, ctxCancel := context.WithTimeout(pctx, time.Second*time.Duration(r.Options.Timeout))
66102
defer ctxCancel()
67103

104+
// Open a new Chrome tab scoped to the timeout context.
105+
// tabCancel explicitly closes the tab when Scan returns,
106+
// preventing tab accumulation across concurrent scans.
107+
// Previously this cancel was silently dropped with _, causing a tab leak.
68108
tabCtx, tabCancel := chromedp.NewContext(ctx)
69109
defer tabCancel()
70110

111+
// Build the scan task list: optionally inject custom HTTP headers,
112+
// navigate to the target, then evaluate the prototype pollution JS payload.
71113
scanTasks := buildHeaders(headers)
72114
scanTasks = append(
73115
scanTasks,
74116
chromedp.Navigate(targetURL),
75117
chromedp.EvaluateAsDevTools(js, &resScan),
76118
)
77119

120+
// Execute the scan tasks inside the dedicated tab context.
78121
errScan := chromedp.Run(tabCtx, scanTasks)
79122
if errScan != nil {
80123
resultData.ScanError = errScan.Error()
81124
}
82125

126+
// Trim and store the JS evaluation result.
127+
// This value is reused in the exploit gate below to avoid a redundant TrimSpace call.
83128
resultData.JSEvaluation = strings.TrimSpace(resScan)
84129

130+
// Early return guard: skip exploit phase entirely if:
131+
// - exploit mode is off, OR
132+
// - the scan itself errored (page unreachable, timeout, etc.), OR
133+
// - the JS payload returned empty (no pollution detected).
85134
if !r.Options.Exploit || errScan != nil || resultData.JSEvaluation == "" {
86135
return resultData, nil
87136
}
@@ -90,23 +139,32 @@ func Scan(
90139
gologger.Info().Label("VULN").Msg(fmt.Sprintf("Target is Vulnerable %s", targetURL))
91140
}
92141

142+
// Run fingerprinting as a separate, isolated task list.
143+
// Previously the fingerprint eval was appended onto scanTasks, which caused
144+
// the full task list (Navigate + JS eval + fingerprint) to re-run from scratch,
145+
// re-navigating the page unnecessarily and potentially corrupting scan state.
93146
fingerprintTasks := chromedp.Tasks{
94147
chromedp.EvaluateAsDevTools(exploit.Fingerprint, &resDetection),
95148
}
96149

97150
errDetection := chromedp.Run(tabCtx, fingerprintTasks)
98151
if errDetection != nil {
152+
// Log detection errors unconditionally - errors are not verbosity-dependent.
99153
gologger.Error().Msg(errDetection.Error())
100154
resultData.FingerprintError = errDetection.Error()
101155
}
102156

157+
// Store fingerprint results and cross-reference known exploit references.
103158
resultData.Fingerprint = resDetection
104159
resultData.References = exploit.GetReferences(resDetection)
105160

106161
if r.Options.Verbose {
107162
gologger.Info().Msg(fmt.Sprintf("Trying to exploit %s", value))
108163
}
109164

165+
// Build exploit-phase headers separately using buildHeaders.
166+
// Previously this was a duplicated inline block; now it uses the shared helper
167+
// for consistency with the scan phase header handling.
110168
exploitTasks := buildHeaders(headers)
111169

112170
result, errExploit := exploit.CheckExploit(
@@ -121,6 +179,8 @@ func Scan(
121179
resultData.ExploitURLs = result
122180

123181
if errExploit != nil {
182+
// Previously this field was incorrectly set to errDetection.Error(),
183+
// masking the actual exploit error. Now correctly uses errExploit.
124184
resultData.ExploitError = errExploit.Error()
125185
gologger.Error().Msg(errExploit.Error())
126186
}

0 commit comments

Comments
 (0)