# 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 (`<jsp:setProperty name="numguess" property="*"/>`) 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 `<script>alert(1)</script>` 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
   <jsp:useBean id="numguess" class="num.NumberGuessBean" scope="session"/>
   <jsp:setProperty name="numguess" property="*"/>
   ```
   `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 <b><%= numguess.getHint() %></b>.
   ```
   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
-<jsp:setProperty name="numguess" property="*"/>
+<jsp:setProperty name="numguess" property="guess"/>
```

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 `<script>alert(1)</script>`.

Expected evidence:
- **Vulnerable (10.1.55):** the second response contains `Good guess, but nope. Try <b><script>alert(1)</script></b>.`
- **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 <b><script>alert(1)</script></b>.
```

Key excerpt from `fixed_resp2.html`:
```html
Good guess, but nope.  Try <b>a number next time</b>.
```

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.
