October 29, 2026
One Payload, Four Targets: How a 10% Confidence Scanner Hit Unlocked Unauthenticated…
A deep technical postmortem of a reflected XSS that lived not in a parameter value, but in the *name* of the parameter itself — and why…

By Fady Basem Zaky
16 min read
One Payload, Four Targets: How a 10% Confidence Scanner Hit Unlocked Unauthenticated JavaScript-Context XSS Across an Entire Application Fleet
A deep technical postmortem of a reflected XSS that lived not in a parameter value, but in the name of the parameter itself — and why HTML-entity encoding was never going to stop it
Introduction — The Hit Nobody Believed
Most bug bounty hunters and penetration testers have a mental filter for automated scanner output. When a tool returns a confidence score of 10 out of 100, the default reaction is to mark it as noise and move on. That is exactly what the scanner itself was telling us: "I found something that looks a little bit like a payload, but I have almost no faith that it will actually execute."
This is the story of what happens when you refuse to accept that verdict.
What began as a single low-confidence reflection on a hardened OAuth2 authorization endpoint evolved, through deliberate pivoting and pattern recognition, into four confirmed, unauthenticated, single-click reflected Cross-Site Scripting (XSS) vulnerabilities spanning two separate production hosts. The root cause was not a missing escape function. It was a classic, and still surprisingly common, context mismatch: the application correctly applied HTML-entity encoding, then placed the "safely" encoded data inside a <script> block where HTML entities have no effect whatsoever.
The payload did not need to break out of a string. It did not need to close a tag. It simply needed to become the name of a query parameter written in the form of an ES6 computed property key. The moment the browser parsed the inline script, the attacker-controlled expression was evaluated with the full privileges of the vulnerable origin.
This write-up is a complete technical reconstruction of that journey: the initial scanner result, the decision to pivot, the discovery of the real target, the precise root-cause analysis, the proof-of-concept variants that confirmed execution without relying on dialog boxes, the mapping of the blast radius across four live endpoints, a careful impact assessment that separates confirmed facts from plausible hypotheses, a provisional CVSS score with explicit rationale, and a set of remediation recommendations that address the architectural flaw rather than just the symptom.
Everything that identifies the specific organization, hostnames, internal product names, or live exploit URLs has been generalized or redacted pending responsible disclosure.
Table of Contents
- Executive Summary
- Background — Starting at the Hardest Target
- The Scanner Result That Almost Got Discarded
- The Pivot: From One Dead End to a Systemic Pattern
- Discovery of the Real Target
- Root Cause Analysis — Why HTML Encoding Failed
- When Execution Happens and When It Does Not
- Proof of Concept — Multiple Confirmation Methods
- From One XSS to Four: Mapping the Full Blast Radius
- Impact Assessment — Confirmed vs. Hypothesized
- CVSS 3.1 Assessment (Provisional)
- Remediation Recommendations
- Lessons for Testers and Builders
- Responsible Disclosure Note
- References
1. Executive Summary
This report documents a single root-cause vulnerability — a JavaScript-context Reflected Cross-Site Scripting (XSS) issue (CWE-79) — that was found replicated, unmodified, across four separate live endpoints spanning two distinct production hosts within the same organization's infrastructure.
The vulnerability requires no authentication, no session cookie, and a single click from a victim. It stems from a dangerous but common engineering pattern: the application HTML-entity-encodes attacker-controlled input (both parameter names and values) and then embeds that encoded data directly inside an inline <script> block as part of a JavaScript object literal. HTML-entity encoding is the correct defense for HTML attribute and text-node contexts. It provides essentially zero protection inside a <script> tag, because the JavaScript engine never decodes HTML entities.
When an attacker supplies a query parameter whose name takes the form of an ES6 computed property key — for example [alert(document.domain)] — the server emits:
queryString: {[alert(document.domain)]: 'x'}queryString: {[alert(document.domain)]: 'x'}Per the ECMAScript specification, the expression inside the brackets of a computed property key is evaluated the moment the surrounding object literal is constructed. That evaluation occurs immediately on page load, before the client-side framework has finished initializing.
What is confirmed by direct testing:
- Unauthenticated, attacker-controlled JavaScript execution in the page's own origin.
- Full DOM read and write access, demonstrated both by writing
document.domaininto a live attribute and by completely overwriting the rendered page content with a static marker. - The same payload, unmodified, succeeding on four distinct URLs across two hosts.
What is not confirmed and is therefore treated as hypothesis only:
- Theft of authentication tokens or session material from client-side storage.
- Ability to issue authenticated requests to backend APIs.
- Successful credential harvesting via a fully weaponized phishing page.
A provisional CVSS 3.1 base score of 5.4 (Medium) is assigned based strictly on the confirmed impact. The score is deliberately conservative; it would need upward revision on Scope and Confidentiality only if follow-up testing demonstrates cross-authority impact or sensitive data exposure.
2. Background — Starting at the Hardest Target
Every serious web application assessment has a natural starting point. For this engagement that starting point was the organization's OAuth2 / OpenID Connect authorization endpoint.
Authorization endpoints are attractive targets for XSS testing for several well-documented reasons:
- They are required by specification to accept a large number of client-supplied parameters (
client_id,redirect_uri,scope,state,nonce,code_challenge,code_challenge_method,resource, and others). - Many of those parameters are reflected back into the response — either for display, for form pre-filling, or for debugging purposes.
- The endpoint is typically public-facing and unauthenticated by design.
- A successful XSS on an identity provider can have outsized impact because of the trust placed in that origin.
The testing methodology was straightforward: construct a fully populated authorization URL, feed it to an automated XSS scanner, and let the tool systematically mutate every parameter while watching for reflections that could be turned into executable payloads.
3. The Scanner Result That Almost Got Discarded
The scan results were almost entirely negative. For the majority of tested parameters the tool reported reflections but explicitly stated:
[-] No vectors were crafted.[-] No vectors were crafted.Eight parameters, eight dead ends.
Then came the single exception: the client_id parameter. After generating and testing more than 1,500 candidate payloads, the scanner surfaced exactly one result:
[+] Payload: <a/+/onmouseovEr%0a=%0aconfirm()%0dx//v3dm0s
[!] Efficiency: 91
[!] Confidence: 10[+] Payload: <a/+/onmouseovEr%0a=%0aconfirm()%0dx//v3dm0s
[!] Efficiency: 91
[!] Confidence: 10Efficiency (91) is a fuzzy-match score indicating how closely the reflected output resembles the expected shape of a working payload. Confidence (10 out of 100) is the number that actually matters. On the tool's own internal scale, a score of 10 is about as low as it goes. In plain language the scanner itself was saying: "I found something that looks vaguely interesting, but I have almost no belief that this will execute."
Experienced testers learn to recognize this class of result as probable noise — a reflection landing inside a context where the characters the tool needs are being filtered, encoded, or neutralized. By every reasonable heuristic this was a result to skip.
Most hunters would have moved on.
4. The Pivot: From One Dead End to a Systemic Pattern
Instead of either (a) burning additional hours trying to force a low-confidence lead on a security-hardened identity provider, or (b) discarding the result and switching to an unrelated part of the attack surface, a third option presented itself: treat the scan not as a final verdict but as reconnaissance.
What the scan had actually demonstrated — independent of whether any individual payload fired — was that this organization's engineering culture reflects raw, client-supplied input into dynamically generated page content, and does so relatively broadly. That is not a fact about one endpoint. It is a fact about how a team builds software. Teams tend to repeat their habits across every application they ship, not just the one that happens to be under the microscope first.
A single low-confidence hit on a hardened authentication endpoint is a weak signal in isolation. The same reflection habit appearing on a smaller, less-scrutinized application elsewhere in the same infrastructure is a very different story. Smaller, internal-feeling utility tools are precisely the places where the same bad pattern tends to go unreviewed, unhardened, and unnoticed for long periods of time.
The next step was therefore not more fuzzing of the authorization endpoint. It was systematic subdomain enumeration and source review of every other application the organization operated, on the theory that wherever this reflection habit existed, it was unlikely to be unique to the single endpoint already tested.
5. Discovery of the Real Target
Enumeration surfaced a cluster of smaller, purpose-built web applications sitting on their own subdomains. These were the classic internal utility tools that rarely receive the same security attention as a public-facing authentication flow: no login walls, no aggressive rate limiting, no obvious WAF presence. Just small, functional applications doing one job each.
One of them was a simple calculator-style tool. It was public, unauthenticated, accepted a modest amount of user input via the query string, and returned a computed result. Viewing the page source immediately revealed something worth a second look: the application was bootstrapping its entire client-side state by serializing the raw incoming query string directly into an inline <script> block as a JavaScript object literal.
A simplified version of the emitted structure looked like this:
<script type="application/javascript">
var APPLICATION_PARAMETERS = {
arguments: {
route: { consumerID: 'ACME', locale: 'en_US' },
queryString: { someParam: 'someValue' }
},
config: { /* ... */ }
};
</script><script type="application/javascript">
var APPLICATION_PARAMETERS = {
arguments: {
route: { consumerID: 'ACME', locale: 'en_US' },
queryString: { someParam: 'someValue' }
},
config: { /* ... */ }
};
</script>The queryString block was not static. It was built dynamically, server-side, from whatever parameters the incoming request actually carried. Critically, on closer inspection it became clear that the application was reflecting not only parameter values but also parameter names into that object.
That single detail transformed the observation from "another reflection" into a genuine finding.
6. Root Cause Analysis — Why HTML Encoding Failed
This is one of the more instructive root causes in the XSS family, because it is not a case of no escaping. It is a case of escaping correctly applied to the wrong context.
Both the parameter values and the parameter names that passed through this pipeline were HTML-entity escaped:
- Single quotes became
' - Double quotes became
" - Angle brackets became
<and>
That is exactly the right defense for an HTML attribute or a text node. It is not the right defense for content that will be parsed by a JavaScript engine inside a <script> tag. HTML entities are a browser HTML-parser concept. The JavaScript engine that executes code inside a <script> block never decodes them. The four characters ' sitting inside inline JavaScript source are simply four inert characters; they will never become a quote mark as far as the JS parser is concerned.
While entity-escaping successfully blocked the classic "break out of a string with a quote" attack, it left an entirely different attack surface completely open. The characters that matter for this class of injection were never HTML-significant to begin with, and therefore were never on the escaping list:
CharacterPurpose in the attack[ ]Declare a computed property key( )Function calls.Member access=Assignment
None of these are touched by standard HTML-entity encoding. All of them pass straight through, unmodified, into live JavaScript source.
Supplying a query parameter whose name is [expr] causes the server to emit:
queryString: {[expr]: 'x'}queryString: {[expr]: 'x'}This is standard ES6 computed property syntax. According to the ECMAScript specification (ECMA-262, Object Initializer section), the expression inside the brackets of a computed property key is evaluated at the moment the surrounding object literal is constructed. That evaluation happens the instant the inline script runs — immediately on page load, before any client-side framework (in this case AngularJS) has finished bootstrapping.
In plain terms: whatever an attacker places as the name of a GET parameter is executed as live JavaScript, with zero authentication required, the moment the victim's browser loads the page.
7. When Execution Happens and When It Does Not
Precision matters. Not every arbitrary parameter name results in code execution. Three conditions must all be true simultaneously:
- The application reflects the parameter's name (not merely its value) into a position in the generated JavaScript where it is not enclosed in quotes — i.e., directly as an object key rather than as a quoted string.
- The reflected name is not passed through any encoder that is script-context-aware (such as
JSON.stringifyover a properly constructed object, or a strict allow-list / character-set filter). Only HTML-entity encoding is applied, which is ineffective in this context. - The attacker-supplied name takes the specific syntactic form of a computed-property expression, i.e., wrapped in square brackets. A plain identifier such as
foois reflected as an ordinary object key and does not execute. It is specifically the unescaped bracket syntax that creates an evaluated expression.
Where any of these three conditions fails, the same attacker input becomes inert. This was demonstrated by a fifth, related build of the same application family that was tested as a negative control. That build serialized parameters using proper JSON key-quoting ("name": "value") rather than raw interpolation. The same payload that succeeded everywhere else produced no execution. Documenting that negative result was as important as documenting the positive ones: it proves the vulnerability is tied to a specific code path / build pipeline, not to every application the organization operates.
8. Proof of Concept — Multiple Confirmation Methods
8.1 Minimal HTTP-level reflection
A minimal request that demonstrates the raw, unmodified reflection:
GET /app/en/?%5Balert(document.domain)%5D=x HTTP/1.1
Host: vulnerable-app.exampleGET /app/en/?%5Balert(document.domain)%5D=x HTTP/1.1
Host: vulnerable-app.exampleURL-decoded, the parameter name is [alert(document.domain)]. The server's response reflects it directly into the live script block:
<script type="application/javascript">
var APPLICATION_PARAMETERS = {
arguments: {
route: { consumerID: 'ACME', locale: 'en_US' },
queryString: {[alert(document.domain)]: 'x'}
},
/* ... */
};
</script><script type="application/javascript">
var APPLICATION_PARAMETERS = {
arguments: {
route: { consumerID: 'ACME', locale: 'en_US' },
queryString: {[alert(document.domain)]: 'x'}
},
/* ... */
};
</script>Loading this URL in any standards-compliant browser triggers an alert() dialog displaying the vulnerable page's own origin — proof that attacker-supplied code executed before the application finished initializing.
8.2 Headless-browser confirmation (dialog-independent)
alert() dialogs are unreliable for automated or headless verification pipelines. Execution was therefore independently confirmed using payloads that write a persistent, directly inspectable marker into the live DOM.
Marker A — read document.domain and write it into the page's <html lang> attribute:
https://vulnerable-app.example/app/en/?%5Bdocument.documentElement.lang%3Ddocument.domain%5D=xhttps://vulnerable-app.example/app/en/?%5Bdocument.documentElement.lang%3Ddocument.domain%5D=xAfter full page execution the resulting DOM contained:
<html lang="vulnerable-app.example" ng-app="application.bootstrap" ...><html lang="vulnerable-app.example" ng-app="application.bootstrap" ...>This is conclusive: the injected JavaScript executed, successfully read document.domain, and wrote the result back into the live DOM — meaning it ran with the full script privileges of the vulnerable page's own origin.
Marker B — full visible page takeover:
https://vulnerable-app.example/app/en/?%5Bdocument.documentElement.innerHTML%3D741923%5D=xhttps://vulnerable-app.example/app/en/?%5Bdocument.documentElement.innerHTML%3D741923%5D=xThe entire rendered page was overwritten with the literal marker string. This was confirmed both visually (screenshot retained) and by examining the raw DOM dump. Full DOM write access is therefore demonstrated, not merely hypothesized.
8.3 Illustrative, non-operational example for public consumption
To avoid publishing a working exploit against a still-unpatched service, the following uses a fictional domain while remaining structurally identical to the confirmed technique:
GET /app/en/?%5Balert(document.domain)%5D=x HTTP/1.1
Host: example-notice-tool.testGET /app/en/?%5Balert(document.domain)%5D=x HTTP/1.1
Host: example-notice-tool.testIn a vulnerable implementation matching the pattern described above, this produces:
queryString: {[alert(document.domain)]: 'x'}queryString: {[alert(document.domain)]: 'x'}9. From One XSS to Four: Mapping the Full Blast Radius
Finding one working instance is where most testers stop. It is also where the real value of understanding root cause, rather than simply chasing a single payload, becomes apparent. A root cause does not live in one URL. It lives in a template, a shared service, or a piece of bootstrap boilerplate that gets copied across every deployment that uses it.
Reasoning from "this is a bootstrap-template bug, not a one-off typo" led directly to checking every locale and every deployment of the same application family. The same exact payload, unmodified, worked identically across four separate live endpoints spanning two distinct hosts:
TargetResultNotes1Primary application, default localeVulnerableInitial discovery point2Same application, secondary locale AVulnerableIdentical payload, zero modification3Same application, secondary locale BVulnerableIdentical payload, zero modification4Second, independently deployed instance on a separate host running the same codebaseVulnerableConfirms the bug is shared across the deployment pipeline, not host-specific
A fifth candidate — a build of the same application hosted on different server infrastructure — was tested as a negative control and found not vulnerable. That build JSON-encodes parameter keys rather than reflecting them raw into an object literal, neutralizing this exact attack technique. Documenting the negative result mattered as much as the positive ones: it confirms the vulnerability is a property of one specific build pipeline, not a universal platform-wide flaw, and it rules out the possibility that the "fix" is already partially deployed everywhere.
The headline point: this is not four independent bugs. It is one bug, in one shared piece of bootstrap logic, instantiated four times because that logic was copied — unchanged — into every deployment built from the same template.
10. Impact Assessment — Confirmed vs. Hypothesized
Unauthenticated, single-click, reflected JavaScript execution on four live public-facing endpoints is not a low-stakes finding. Precision about what has actually been shown versus what remains plausible is essential for credibility.
Confirmed by direct testing:
- Zero authentication barrier. Every affected page returns HTTP 200 with no session, no cookie, and no login wall of any kind.
- Single click is sufficient. Execution happens on page load — before the victim interacts with anything on the page.
- Full DOM read and write access within the vulnerable origin, demonstrated by both the
document.domainattribute write and the complete page-content overwrite. - The applications in question are small, public-facing utility tools of the kind organizations commonly link to from emails, internal portals, and onboarding documentation. A crafted link therefore raises comparatively few red flags for a typical recipient.
Plausible but not independently tested or confirmed (reported here as hypotheses only):
- Access to client-side storage (
localStorage/sessionStorage) that the application may use in that origin, including any authentication material it might keep there. Client-side code was observed to reference OAuth-related configuration and storage keys, which is why this is flagged as the highest-value follow-up; no such data was actually read or exfiltrated during testing. - Ability to issue requests to the application's backend API in an authenticated victim's session context. This was not tested.
- Use as a phishing or credential-harvesting vector via DOM rewriting. This follows logically from the confirmed full-DOM-write capability but was not separately demonstrated against a real victim flow.
Readers should treat the first list as established fact and the second as reasoned hypothesis pending further authorized testing — not as equivalent claims.
11. CVSS 3.1 Assessment (Provisional)
Based strictly on what was directly confirmed — script execution and DOM read/write access, with no demonstrated access to sensitive data, authentication material, or backend systems — the defensible provisional vector is:
AV:N / AC:L / PR:N / UI:R / S:U / C:L / I:L / A:N
Base Score: 5.4 (Medium) — provisionalAV:N / AC:L / PR:N / UI:R / S:U / C:L / I:L / A:N
Base Score: 5.4 (Medium) — provisionalMetricValueRationaleAttack VectorNetworkExploitable remotely over HTTP(S) — confirmedAttack ComplexityLowA single static, reusable URL; no race conditions or special preconditions observedPrivileges RequiredNoneAll confirmed instances are fully unauthenticatedUser InteractionRequiredVictim must load the crafted URLScopeUnchangedNo cross-authority impact (backend API access, session compromise of a separate security boundary) was demonstrated. Scope: Changed would require evidence of exactly thatConfidentialityLowConfirmed impact is DOM read access within the vulnerable origin. No sensitive data disclosure was demonstrated, so High is not currently supportableIntegrityLowConfirmed DOM-write access; no server-side data modification was tested or claimedAvailabilityNoneNo denial-of-service impact observed or claimed
This score should be treated as provisional. It would need to be revised upward — specifically on Scope and Confidentiality — only if follow-up authorized testing confirms that script execution can actually reach session/authentication material or a distinct backend security authority. Absent that confirmation, scoring the finding as High based solely on the theoretical worst case of a reflected XSS would overstate what has actually been shown.
12. Remediation Recommendations
- Stop reflecting arbitrary query-parameter names entirely. Whitelist the small, known set of parameters the application legitimately needs, server-side, and silently drop everything else before constructing the inline bootstrap object. This single change eliminates the vulnerability class.
- Use a JavaScript-safe serializer for script contexts — never HTML-entity encoding alone. Build the parameters object server-side from trusted, sanitized inputs and emit it via a proper encoder (for example
JSON.stringifyon a pre-sanitized object). Additionally escape the characters that remain meaningful inside a<script>context (<, >, &, and the JavaScript line-terminator code points U+2028 and U+2029). Raw, attacker-controlled strings should never be interpolated directly as object keys under any circumstances. - If the raw query string must reach the client for legitimate functional reasons, constrain parameter names to a strict allow-list pattern such as
^[A-Za-z0-9_]+$and restrict values to an equally tight character set, rejecting or stripping anything outside those sets before the string ever reaches template rendering. - Add defense in depth with Content-Security-Policy. None of the four affected endpoints currently return any CSP header. Deploying a policy such as
script-src 'self' 'nonce-<random>'and moving the bootstrap script to a nonce'd<script>tag would provide a meaningful secondary control, independent of whether the primary fix is applied immediately everywhere. CSP is not a substitute for correct serialization; it is a safety net. - Treat this as a single shared-component fix, not four separate patches. Because the same vulnerable bootstrap logic is confirmed across two independently deployed hosts, the most effective remediation path is to patch the underlying shared template or service once, then sweep the full deployment fleet to confirm no other instance built from the same pipeline carries the identical code path forward.
- Add regression tests that specifically assert maliciously structured parameter names cannot alter the shape of the generated JavaScript. A useful test case: submit a parameter name containing [, ], (, ), and =, then parse the generated script and confirm that no unexpected computed keys or function calls appear. Checking only that the response does not contain an unescaped
<script>tag is insufficient.
13. Lessons for Testers and Builders
A few takeaways worth carrying into future work, regardless of how this particular engagement ultimately resolves:
- A scanner's confidence score is a hint about one specific payload, not a verdict on the underlying hypothesis. A result scored 10/100 told us only that the tool's own generic payload set did not fire. It said nothing about whether a hand-crafted, context-aware payload might succeed elsewhere.
- Chase the pattern, not the endpoint. The actual vulnerable application had nothing to do with where testing started. What carried over was not a payload at all — it was the observation that this organization's engineering habits reflect raw input broadly. That is a signal worth hunting across an entire infrastructure footprint, not abandoning after one dead end.
- Escaping must match context, always. HTML-entity escaping defends HTML contexts. It provides no meaningful protection inside a
<script>block, ajavascript:URI, a CSS value, or a JSON-in-attribute context. Each sink needs its own escaping discipline. Mismatches between them remain one of the single most common root causes of real-world XSS. - "No vectors crafted" means "not with this payload set, in this spot" — nothing more. The actual exploit came from hand-building a payload shaped specifically around the real context (a JavaScript object literal using computed-property syntax), not from trusting an automated tool's generic library to cover every case.
- Negative results are part of the evidence, not just the positives. Confirming that one related build was not vulnerable — and understanding precisely why — did as much to establish the credibility and precise scope of this finding as any of the successful proofs of concept.
- One root cause can be worth more than one bug report. Recognizing that four "findings" were actually one shared defect, replicated by a build pipeline, turns a scattered set of individual issues into a single, clear, high-leverage fix recommendation. That is both more useful to the engineering team on the receiving end and a stronger, more defensible submission.
- Precision about confirmed versus hypothesized impact is part of the technical work. A finding is more credible, not less, when confirmed facts and plausible-but-untested consequences are kept clearly separate.
14. Responsible Disclosure Note
Specific organization names, exact subdomains, internal identifiers, tenant or client identifiers, live exploit URLs, and screenshots have been generalized, redacted, or replaced with fictional placeholders throughout this article. The technical demonstration uses non-operational examples rather than working links against any real system. No claim is made about whether the underlying issue has been fixed, accepted, or publicly disclosed by the affected organization at the time of writing. Publication is pending completion of the responsible-disclosure process.
15. References
- OWASP, Cross Site Scripting Prevention Cheat Sheet
- MITRE, CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
- MDN Web Docs, Object initializer — Computed property names
- ECMA International, ECMA-262 ECMAScript Language Specification
- FIRST.org, CVSS v3.1 Specification Document
- OWASP, XSS Filter Evasion Cheat Sheet
This article is intended as a technical case study of methodology, root-cause analysis, and responsible reporting. It is not an exploit guide against any live target.
Welcome to SilentN0va
Thank you for reading. For more analysis, red team technical walkthroughs, and security research, follow my updates here on Medium and connect across my official platforms:
- Linktree: https://linktr.ee/SilentN0va
- LinkedIn: https://linkedin.com/in/fady-basem-b96278340
- GitHub: https://github.com/Fodenbasem
- X (Twitter): https://x.com/____0xsn____
- YouTube: https://youtube.com/@fadybasem.1337