Skip to content

CVE-2026-50229: Verified Repro With Script Download

CVE-2026-50229: Apache Tomcat examples app XSS in numguess.jsp

CVE-2026-50229 is verified against apache/tomcat · github. Affected versions: 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. Vulnerability class: XSS. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00280.

REPRO-2026-00280 apache/tomcat · github XSS Variant found Jul 9, 2026 CVE entry ↗ .txt
Severity
MEDIUM
CVSS
6.1
Confidence
HIGH
Reproduced in
16m 6s
Tool calls
146
Spend
$1.42
01 · Overview

What Is CVE-2026-50229?

CVE-2026-50229 is a low-severity reflected/stored cross-site scripting (XSS) vulnerability (CWE-80) in the bundled examples web application shipped with Apache Tomcat. Pruva reproduced it (reproduction REPRO-2026-00280).

02 · Severity & CVSS

CVE-2026-50229 Severity & CVSS Score

CVE-2026-50229 is rated medium severity, with a CVSS base score of 6.1 out of 10.

MEDIUM threat level
6.1 / 10 CVSS base
Weakness CWE-80 — Improper Neutralization of Script-Related HTML Tags in a Web Page ('Cross-site Scripting')

Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.

03 · Affected Versions

Affected apache/tomcat Versions

apache/tomcat · github versions 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 are affected.

How to Reproduce CVE-2026-50229

$ pruva-verify REPRO-2026-00280
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00280/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-50229

Remote code execution — reproduced
  • reached the target end-to-end
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

HTTP GET parameter hint containing <script>alert(1)</script>

Attack chain
  1. /examples/jsp/num/numguess.jsp in the bundled Tomcat examples web application
Runnable proof: reproduction_steps.sh
Captured evidence: vulnerable startupvulnerable curlvariant vulnerable numguess startupvariant vulnerable numguess curlvariant fixed numguess startupvariant fixed numguess curlvariant fixed colors startupvariant fixed colors curl
Variants tested

No distinct bypass or alternate XSS trigger found for CVE-2026-50229. The fix in numguess.jsp correctly restricts bean binding to the guess parameter. Two other example JSPs (colrs.jsp, carts.jsp) still use wildcard binding but were confirmed not to render unescaped attacker-controlled markup on the fixed release.

How the agent worked 365 events · 146 tool calls · 16 min
16 minDuration
146Tool calls
106Reasoning steps
365Events
3Dead-ends
Agent activity over 16 min
Support
33
Hypothesis
2
Repro
80
Judge
23
Variant
223
0:0016:06

Root Cause and Exploit Chain for CVE-2026-50229

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.22Fixed: 11.0.23, 10.1.56, 9.0.119 (8.5 and 7.0 are end-of-life)

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.

  • 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: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:

    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:

-<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:

Good guess, but nope.  Try <b><script>alert(1)</script></b>.

Key excerpt from fixed_resp2.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.

Variant Analysis & Alternative Triggers for CVE-2026-50229

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.

  • 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: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.htmlcolrs.jsp candidate did not reflect payload.
  • bundle/logs/variant_fixed_carts_*.log / bundle/vuln_variant/fixed_carts_resp1.htmlcarts.jsp candidate did not reflect payload.

Key excerpts from the fixed numguess.jsp response:

Good guess, but nope.  Try <b>a number next time</b>.

Key excerpt from the colrs.jsp candidate response:

<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).

CVE-2026-50229 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:003:05
0:00
session startedaccounts/fireworks/models/kimi-k2p7-code · CVE-2026-50229 · REPRO-20
0:02
0:03
web search
0:06
0:07
0:18
0:19
0:21
0:22
0:31
0:33
web search
0:34
0:36
0:38
0:39
0:45
0:46
1:08
1:09
web search
2:25
2:25
extract_facts
no facts extracted
2:27
2:27
2:27
supportrepro
3:03
3:05
3:05

Artifacts and Evidence for CVE-2026-50229

Scripts, logs, diffs, and output captured during the reproduction.

08 · How to Fix

How to Fix CVE-2026-50229

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-50229 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-50229

How does the CVE-2026-50229 XSS attack work?

An attacker crafts a URL to the numguess.jsp example page with a hint parameter containing HTML/JavaScript. The wildcard property binding lets this attacker-controlled value overwrite the session-scoped bean's hint field, which is then echoed unescaped into the HTML response, executing the injected script in the victim's browser. Because the bean is session-scoped, the payload persists until the session resets, giving stored-like behavior.

Which Apache Tomcat versions are affected by CVE-2026-50229, and where is it fixed?

Affected versions are 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. It is fixed in 11.0.23, 10.1.56, and 9.0.119; the 8.5 and 7.0 lines are end-of-life and will not receive a fix.

How severe is CVE-2026-50229?

It is rated low severity by Pruva. It requires an attacker to trick a user into visiting a crafted URL against the bundled Tomcat examples application, which is not typically deployed in production.

How can I reproduce CVE-2026-50229?

Download the verified script from this page and run it in an isolated environment against an affected Tomcat build with the examples web application deployed. It submits a crafted hint parameter to numguess.jsp and shows the payload reflected unescaped in the HTML response, then confirms the patched version restricts property binding to the intended guess parameter.
11 · References

References for CVE-2026-50229

Authoritative sources for CVE-2026-50229 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.