# Variant RCA Report: CVE-2026-50229

## Summary

No bypass or distinct alternate trigger was found for CVE-2026-50229. The original Apache fix in `webapps/examples/jsp/num/numguess.jsp` replaces the wildcard bean property binding (`<jsp:setProperty name="numguess" property="*"/>`) with an explicit binding to only the `guess` parameter. This prevents attacker-controlled request parameters from reaching the `setHint()` setter and, consequently, the unescaped `<%= numguess.getHint() %>` sink. A systematic search of the bundled `examples` web application found two other JSPs that still use wildcard binding (`colors/colrs.jsp` and `sessions/carts.jsp`), but source inspection and runtime testing on the fixed Tomcat 10.1.56 release showed that neither renders attacker-controlled string properties unescaped. Therefore, the fixed version does not reproduce the original XSS and no new variant was confirmed.

## Fix Coverage / Assumptions

The original fix relies on the following invariants:

1. Only the `guess` parameter is intended to influence the `NumberGuessBean` from the request.
2. The `hint` property is internal and should never be populated from an untrusted request parameter.
3. Because `setGuess()` only ever assigns one of the safe strings `higher`, `lower`, or `a number next time`, the unescaped `getHint()` output is safe once the wildcard binding is removed.

The fix explicitly covers the code path in `webapps/examples/jsp/num/numguess.jsp` where the JSP container binds request parameters to the session bean. It does **not** modify any other example JSP, nor does it add output escaping to the `getHint()` rendering.

## Variant / Alternate Trigger

Three candidate entry points were examined in the bundled `examples` web application:

1. **Original entry point (tested for regression):** `GET /examples/jsp/num/numguess.jsp?hint=<script>alert(1)</script>` — no longer reproduces on the fixed version.
2. **Candidate A:** `GET /examples/jsp/colors/colrs.jsp?color1=...&color2=...` — still uses wildcard binding (`<jsp:setProperty name="cb" property="*" />`). The `ColorGameBean` exposes `color1` and `color2` setters, but the JSP renders `getColor1()` and `getColor2()`, which return the constrained `foreground` and `background` fields. `processRequest()` only accepts the literal values `black` or `cyan` for those fields. Arbitrary payloads stored in `color1`/`color2` are not reflected.
3. **Candidate B:** `GET /examples/jsp/sessions/carts.jsp?itemId=...&submit=...` — still uses wildcard binding on `sessions.DummyCart`. The `submit` property is not reflected anywhere; the `itemId` is used to select an `Item` enum value whose title is rendered through `util.HTMLFilter.filter()`.

None of the candidate paths produced unescaped attacker-controlled markup on the fixed release.

## Impact

- **Product / Component:** Apache Tomcat bundled `examples` web application (`webapps/examples/jsp/num/numguess.jsp`, `colors/colrs.jsp`, `sessions/carts.jsp`).
- **Versions tested:** 10.1.55 (vulnerable) and 10.1.56 (fixed) binary distributions from `https://archive.apache.org/dist/tomcat/`.
- **Risk level / consequences:** The original CVE is a medium/low-severity XSS in the examples application. No additional or bypassed XSS entry point was confirmed, so the effective risk is unchanged by this variant analysis.

## Impact Parity

- **Disclosed / claimed maximum impact:** Reflected/stored XSS in the bundled Tomcat examples web application via attacker-controlled request parameters.
- **Reproduced impact from this variant run:** The original `numguess.jsp` XSS was reproduced on Tomcat 10.1.55 and was absent on Tomcat 10.1.56. The two alternate JSP candidates did not render unescaped attacker-controlled markup on the fixed version.
- **Parity:** `none` for any variant; the original claim parity is `full` on the vulnerable version but not a bypass.
- **Not demonstrated:** A distinct bypass or alternate XSS trigger on the fixed version.

## Root Cause

The original root cause is the combination of wildcard JavaBean property binding and unescaped scriptlet output in the same JSP:

```jsp
<jsp:setProperty name="numguess" property="*"/>
...
Good guess, but nope.  Try <b><%= numguess.getHint() %></b>.
```

The fix removes the wildcard binding, so the attacker can no longer populate the `hint` property. The same underlying pattern (wildcard binding + unescaped output) exists in two other JSPs, but those beans validate or filter the rendered values, preventing the same XSS outcome.

Fix commit (10.1 branch): `0d5bdd5b0dd964e9f73e530b7d753462b9bfd1d0` — "Minor optimisation. Only need to set 1 property so don't use wild card."

## Reproduction Steps

1. Run `bundle/vuln_variant/reproduction_steps.sh`.
2. The script reuses the cached Apache Tomcat 10.1.55 and 10.1.56 binary distributions (or downloads them if missing), starts each instance in turn on port `18080`, and performs the following checks:
   - Original `numguess.jsp` XSS on the vulnerable version (`?hint=<script>alert(1)</script>`).
   - Original `numguess.jsp` XSS on the fixed version.
   - `colrs.jsp` wildcard-binding candidate on the fixed version (`?color1=black"><script>alert(1)</script>&color2=cyan`).
   - `carts.jsp` wildcard-binding candidate on the fixed version (`?itemId=0&submit=<script>alert(1)</script>`).
3. Expected evidence:
   - Vulnerable `numguess.jsp` response contains the literal `<script>alert(1)</script>`.
   - Fixed `numguess.jsp` response does not contain the payload and shows the internal hint.
   - `colrs.jsp` and `carts.jsp` responses do not contain the literal payload.

## Evidence

- `bundle/logs/vuln_variant.log` — full script execution log from both runs.
- `bundle/logs/variant_vulnerable_numguess_*.log` / `bundle/vuln_variant/vulnerable_numguess_resp2.html` — original CVE reproduced on 10.1.55.
- `bundle/logs/variant_fixed_numguess_*.log` / `bundle/vuln_variant/fixed_numguess_resp2.html` — original payload absent on 10.1.56.
- `bundle/logs/variant_fixed_colors_*.log` / `bundle/vuln_variant/fixed_colors_resp1.html` — `colrs.jsp` candidate did not reflect payload.
- `bundle/logs/variant_fixed_carts_*.log` / `bundle/vuln_variant/fixed_carts_resp1.html` — `carts.jsp` candidate did not reflect payload.

Key excerpts from the fixed `numguess.jsp` response:
```html
Good guess, but nope.  Try <b>a number next time</b>.
```

Key excerpt from the `colrs.jsp` candidate response:
```html
<body bgcolor=cyan>
<font size=6 color=yellow>
```
The attacker-supplied quote/script string was stored in the bean but never reached the rendered HTML.

Environment:
- OpenJDK 25.0.3 (via `default-jdk`)
- Apache Tomcat 10.1.55 and 10.1.56 binary distributions

## Recommendations / Next Steps

- **Confirm the fix is sufficient** for the original CVE: it is. The `numguess.jsp` change correctly blocks the attacker-controlled `hint` path.
- **Harden the remaining wildcard-binding JSPs** (`colrs.jsp`, `carts.jsp`) by switching to explicit property binding, even though they are not currently exploitable. This removes the latent risk that a future bean change reintroduces the same vulnerability pattern.
- **Add output escaping** in `numguess.jsp` as defense-in-depth, so that even if a new binding path is introduced later, the sink cannot render raw HTML/JavaScript.
- **Review the examples webapp** for any other JSPs that combine `<jsp:setProperty property="*" />` with unescaped `<%= ... %>` scriptlet output.

## Additional Notes

- The reproduction script is idempotent: it stops any running Tomcat instances, clears the `work`/`logs`/`temp` directories, and starts each version with a fresh cookie jar.
- The script was run twice consecutively and produced the same negative variant result both times.
- The Apache Tomcat security model accepts reports for vulnerabilities in bundled web applications, so the `examples` webapp is within scope. Administrative users and user-deployed applications are out of scope, which does not affect this finding.
- Source inspection of the fixed release confirms that `webapps/examples/jsp/num/numguess.jsp` contains `<jsp:setProperty name="numguess" property="guess"/>`, and the compiled servlet uses `JspRuntimeLibrary.introspecthelper(..., "guess", request.getParameter("guess"), request, "guess", false)`.
