# 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