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
1+ /*
2+ pphack - The Most Advanced Client-Side Prototype Pollution Scanner
33
4+ This repository is under MIT License https://github.com/edoardottt/pphack/blob/main/LICENSE
5+ */
46package scan
57
68import (
@@ -34,7 +36,7 @@ func GetChromeOptions(r *Runner) []func(*chromedp.ExecAllocator) {
3436}
3537
3638// GetChromeBrowser takes as input the chrome options and returns
37- // the contexts with the associated cancel functions to use the
39+ // the context with the associated cancel functions to use the
3840// headless chrome browser it creates.
3941// Returns ecancel (exec allocator cancel), pctx (parent browser context),
4042// and pcancel (parent context cancel).
@@ -46,7 +48,6 @@ func GetChromeBrowser(copts []func(*chromedp.ExecAllocator)) (context.CancelFunc
4648 pctx , pcancel := chromedp .NewContext (ectx )
4749
4850 // 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.
5051 if err := chromedp .Run (pctx ); err != nil {
5152 ecancel ()
5253 gologger .Fatal ().Msgf ("error starting browser: %s" , err .Error ())
@@ -69,7 +70,7 @@ func buildHeaders(headers map[string]interface{}) chromedp.Tasks {
6970
7071// Scan is the core function that performs the prototype pollution scan.
7172// 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+ // headers, the JavaScript payload (js), the original input value and the
7374// fully constructed target URL.
7475//
7576// Flow:
@@ -90,21 +91,18 @@ func Scan(
9091 resDetection []string
9192 )
9293
93- // Initialize result with the original input value and the constructed scan URL.
9494 resultData := output.ResultData {
9595 TargetURL : value ,
9696 ScanURL : targetURL ,
9797 }
9898
99- // Wrap the parent context with a per-scan timeout so hung pages
100- // don't block the scanner indefinitely.
99+ // Wrap the parent context with a per-scan timeout to avoid blocking.
101100 ctx , ctxCancel := context .WithTimeout (pctx , time .Second * time .Duration (r .Options .Timeout ))
102101 defer ctxCancel ()
103102
104103 // Open a new Chrome tab scoped to the timeout context.
105- // tabCancel explicitly closes the tab when Scan returns,
104+ // tabCancel explicitly closes the tab when Scan returns
106105 // preventing tab accumulation across concurrent scans.
107- // Previously this cancel was silently dropped with _, causing a tab leak.
108106 tabCtx , tabCancel := chromedp .NewContext (ctx )
109107 defer tabCancel ()
110108
@@ -123,14 +121,8 @@ func Scan(
123121 resultData .ScanError = errScan .Error ()
124122 }
125123
126- // Trim and store the JS evaluation result.
127- // This value is reused in the exploit gate below to avoid a redundant TrimSpace call.
128124 resultData .JSEvaluation = strings .TrimSpace (resScan )
129125
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).
134126 if ! r .Options .Exploit || errScan != nil || resultData .JSEvaluation == "" {
135127 return resultData , nil
136128 }
@@ -140,16 +132,12 @@ func Scan(
140132 }
141133
142134 // 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.
146135 fingerprintTasks := chromedp.Tasks {
147136 chromedp .EvaluateAsDevTools (exploit .Fingerprint , & resDetection ),
148137 }
149138
150139 errDetection := chromedp .Run (tabCtx , fingerprintTasks )
151140 if errDetection != nil {
152- // Log detection errors unconditionally - errors are not verbosity-dependent.
153141 gologger .Error ().Msg (errDetection .Error ())
154142 resultData .FingerprintError = errDetection .Error ()
155143 }
@@ -162,9 +150,6 @@ func Scan(
162150 gologger .Info ().Msg (fmt .Sprintf ("Trying to exploit %s" , value ))
163151 }
164152
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.
168153 exploitTasks := buildHeaders (headers )
169154
170155 result , errExploit := exploit .CheckExploit (
@@ -179,8 +164,6 @@ func Scan(
179164 resultData .ExploitURLs = result
180165
181166 if errExploit != nil {
182- // Previously this field was incorrectly set to errDetection.Error(),
183- // masking the actual exploit error. Now correctly uses errExploit.
184167 resultData .ExploitError = errExploit .Error ()
185168 gologger .Error ().Msg (errExploit .Error ())
186169 }
0 commit comments