# REPRO-2026-00280: Apache Tomcat examples app XSS in numguess.jsp ## Summary Status: published Severity: low Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00280 CVE: CVE-2026-50229 ## Package Name: apache/tomcat Ecosystem: github Affected: Apache Tomcat 11.0.0-M1 through 11.0.22; 10.1.0-M1 through 10.1.55; 9.0.0.M1 through 9.0.118; 8.5.0 through 8.5.100; 7.0.0 through 7.0.109 Fixed: Unknown ## Root Cause # Root Cause Analysis: CVE-2026-50229 ## Summary CVE-2026-50229 is a reflected/stored cross-site scripting (XSS) vulnerability in the bundled `examples` web application of Apache Tomcat. The vulnerable JSP page `webapps/examples/jsp/num/numguess.jsp` uses wildcard bean property binding (``) against a session-scoped `NumberGuessBean`. Because the `NumberGuessBean` exposes a `hint` property, an attacker can supply a `hint` request parameter that overwrites the bean's intended hint value. The page later renders the bean's `hint` value directly into the HTML response using `<%= numguess.getHint() %>`, which does not escape the content. This allows an attacker to inject arbitrary JavaScript/HTML into the response. ## Impact - **Product / Component:** Apache Tomcat, bundled `examples` web application (`webapps/examples/jsp/num/numguess.jsp`) - **Affected versions:** 7.0.0 through 7.0.109, 8.5.0 through 8.5.100, 9.0.0.M1 through 9.0.118, 10.1.0-M1 through 10.1.55, and 11.0.0-M1 through 11.0.22 - **Fixed versions:** 11.0.23, 10.1.56, 9.0.119 (8.5 and 7.0 are end-of-life) - **Risk level / consequences:** Medium. An attacker who can trick a user into visiting a crafted URL can execute JavaScript in the user's browser session in the context of the Tomcat examples application. Because the bean is session-scoped, the payload is also persisted until the session is reset, giving a stored-like behavior. ## Impact Parity - **Disclosed / claimed maximum impact:** XSS in the bundled Tomcat examples web application via attacker-controlled request parameters. - **Reproduced impact from this run:** A live HTTP request to the `numguess.jsp` endpoint on Tomcat 10.1.55 rendered the unescaped `` payload in the HTML response. The same request against Tomcat 10.1.56 did not render the payload because the `hint` parameter is no longer bound into the session bean. - **Parity:** `full` — the reproduced behavior matches the disclosed XSS impact. ## Root Cause The root cause is the combination of two design choices in `numguess.jsp`: 1. **Wildcard bean property binding:** ```jsp ``` `property="*"` binds every request parameter to a bean property of the same name. This exposes properties other than the intended `guess` parameter (e.g., `hint`, `answer`, `success`, `numGuesses`). 2. **Unescaped output:** ```jsp Good guess, but nope. Try <%= numguess.getHint() %>. ``` The `hint` value is emitted directly into the HTML without escaping. When an attacker sets `hint` to HTML/JavaScript, the browser parses and executes it. Apache fixed this by restricting the property binding to the intended `guess` parameter: ```diff - + ``` Fix commit: `0d5bdd5b0dd964e9f73e530b7d753462b9bfd1d0` ("Minor optimisation. Only need to set 1 property so don't use wild card.") ## Reproduction Steps 1. Run `bundle/repro/reproduction_steps.sh`. 2. The script installs Java if needed, downloads and extracts Apache Tomcat 10.1.55 (vulnerable) and 10.1.56 (fixed), then configures and starts each instance in turn on port `18080`. 3. For each version, the script performs two requests in the same HTTP session: - First request: `GET /examples/jsp/num/numguess.jsp?guess=abc` — establishes a session and advances the game state so the hint branch is rendered. - Second request: `GET /examples/jsp/num/numguess.jsp?hint=%3Cscript%3Ealert(1)%3C%2Fscript%3E` — attempts to bind the attacker-controlled `hint` parameter into the session bean. 4. The script inspects the second response for the literal string ``. Expected evidence: - **Vulnerable (10.1.55):** the second response contains `Good guess, but nope. Try .` - **Fixed (10.1.56):** the second response contains the default hint (e.g., `a number next time`) and no attacker-controlled markup. ## Evidence - `bundle/logs/reproduction_steps.log` — full script execution log. - `bundle/repro/vulnerable_resp2.html` — HTTP response from the vulnerable Tomcat 10.1.55 showing the unescaped payload. - `bundle/repro/fixed_resp2.html` — HTTP response from the fixed Tomcat 10.1.56 showing the payload is absent. - `bundle/repro/runtime_manifest.json` — runtime evidence manifest. Key excerpt from `vulnerable_resp2.html`: ```html Good guess, but nope. Try . ``` Key excerpt from `fixed_resp2.html`: ```html Good guess, but nope. Try a number next time. ``` Environment: - OpenJDK 25.0.3 (installed via `default-jdk` package) - Apache Tomcat 10.1.55 and 10.1.56 binary distributions from `https://archive.apache.org/dist/tomcat/` ## Recommendations / Next Steps - **Upgrade** to a fixed Tomcat version (10.1.56+, 9.0.119+, or 11.0.23+). - **Remove or disable** the `examples` web application in production environments; it is intended for demonstration only. - **Avoid wildcard bean property binding** (`property="*"`) when the request can contain untrusted parameters; explicitly whitelist the parameters the application intends to bind. - **Escape output** when rendering dynamic content, or use a framework that escapes expression-language output by default. - Add regression tests that verify untrusted request parameters are not bound into session beans and are not reflected unescaped in responses. ## Additional Notes - The reproduction script is idempotent: it reuses cached Tomcat archives, reconfigures ports deterministically, and cleanly stops each instance before starting the next one. - The script was run twice consecutively and produced the same confirmed result both times. - The examples application is typically not deployed in production, which limits real-world exploitability, but the vulnerability is real in the shipped product. ## Reproduction Details Reproduced: 2026-07-09T19:34:04.388Z Duration: 966 seconds Tool calls: 146 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00280 pruva-verify CVE-2026-50229 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00280&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00280/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-50229 - Source: https://tomcat.apache.org/security-11.html ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 6053 bytes) - bundle/repro/rca_report.md (analysis, 6053 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 8725 bytes) - bundle/vuln_variant/rca_report.md (analysis, 8326 bytes) - bundle/artifact_promotion_manifest.json (other, 11661 bytes) - bundle/artifact_promotion_report.json (other, 11679 bytes) - bundle/vuln_variant/source_identity.json (other, 892 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1195 bytes) - bundle/repro/validation_verdict.json (other, 718 bytes) - bundle/repro/runtime_manifest.json (other, 576 bytes) - bundle/logs/vulnerable_startup.log (log, 48 bytes) - bundle/logs/vulnerable_curl.log (log, 0 bytes) - bundle/repro/vulnerable_resp2.html (other, 384 bytes) - bundle/repro/fixed_resp2.html (other, 377 bytes) - bundle/logs/vuln_variant.log (log, 8577 bytes) - bundle/vuln_variant/vulnerable_numguess_resp2.html (other, 384 bytes) - bundle/vuln_variant/fixed_numguess_resp2.html (other, 377 bytes) - bundle/vuln_variant/fixed_colors_resp1.html (other, 383 bytes) - bundle/vuln_variant/fixed_carts_resp1.html (other, 795 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 5688 bytes) - bundle/vuln_variant/variant_manifest.json (other, 3424 bytes) - bundle/vuln_variant/validation_verdict.json (other, 1132 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 1259 bytes) - bundle/logs/variant_vulnerable_numguess_startup.log (log, 48 bytes) - bundle/logs/variant_vulnerable_numguess_curl.log (log, 0 bytes) - bundle/logs/variant_fixed_numguess_startup.log (log, 48 bytes) - bundle/logs/variant_fixed_numguess_curl.log (log, 0 bytes) - bundle/logs/variant_fixed_colors_startup.log (log, 48 bytes) - bundle/logs/variant_fixed_colors_curl.log (log, 0 bytes) - bundle/logs/variant_fixed_carts_startup.log (log, 48 bytes) - bundle/logs/variant_fixed_carts_curl.log (log, 0 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00280 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00280/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00280 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev