Reflected XSS on promo.php via unescaped mysqli_error echo (GET-link delivered)
/promo.phpAttack Path
Business Impact
An attacker can send victims a malicious promo.php link that executes arbitrary JavaScript on the site's origin, enabling session token theft, credential phishing, or silent action forgery against visitors. The same unescaped error-echo sink also enables error-based extraction of database contents via extractvalue() concatenation, exposing promo code and potentially other database data. Because the page is promoted for checking promo codes, link-delivered exploitation could erode customer trust and trigger compliance issues around customer data protection.
Description
When a query against promo_codes fails, promo.php echoes the raw mysqli_error string into an alert alert-danger div WITHOUT htmlspecialchars - unlike the code param's value="" reflection, which is correctly escaped (" < >). Attacker-controlled text reaches the error message through extractvalue()'s XPATH error (payload must fit the ~32-char XPATH truncation window), producing a link-delivered reflected XSS: ?code=' AND extractvalue(1,concat(0x7e,'<img src=x onerror=alert(1)>'))-- - renders the img tag live in the victim's browser. Confirmed by headless Chromium render: alertCalled: true.
Proof of Concept
Setup
No authentication required. The value="" attribute reflection of ?code= IS escaped (double quotes become "), but the database-error path echoes mysqli_error unescaped. Payload text is embedded inside the SQL string and surfaces via the XPATH error message, truncated at 32 chars - so a short payload is required.
Exploit
GET /promo.php?code=' AND extractvalue(1,concat(0x7e,'<img src=x onerror=alert(1)>'))-- - HTTP/1.1
Host: storefront.example
(URL-encoded when sent as a link to a victim.)
Verification
Response body contains the payload UNESCAPED in the error div:
<div class="alert alert-danger mt-4">
<i class="fas fa-times-circle"></i> Database Error: XPATH syntax error: '~<img src="x" onerror="alert(1)">'
</div>
Headless Chromium render of the same URL: signals.alertCalled = true (script execution confirmed). Contrast: ?code="><img src=x onerror=alert(1)> reflects escaped as value=""><img src=x onerror=alert(1)>" - no execution - proving the error echo is the unescaped sink.
Cleanup
None - reflected, no state persisted.
Observed Behavior
HTTP GET with the extractvalue payload returned 200 with Database Error: XPATH syntax error: '~<img src=x onerror=alert(1)>' rendered raw in the alert-danger div (the same payload in the value="" attribute came back HTML-escaped). browser_render of the identical URL reported alertCalled: true, evalCalled: true - real JavaScript execution in the browser. Longer marker payload <img src=x onerror=window.__xss_fired=true> was truncated at the 32-char XPATH window (onerror="window.__xss), confirming the payload-length constraint.
Attack Scenarios
- 1
Attacker sends a victim a crafted promo.php link (e.g. via email/chat - page advertises promo checking); JavaScript executes in the victim's browser on the site origin
- 2
Same error-echo sink also enables error-based data extraction (extractvalue concatenation), compounding the SQLi finding
Triage & Management
Validation Status
Exploitability
Asset Information
Anonymized staging evaluation. Explore the full detail view; management actions are available in your own workspace.
Find issues in your app