high · 7.5Jul 24, 2026

OmniFaces: Forged Resource IDs, XSS via o:hashParam, and Push-Channel Replay

Rohit Hatagale
AI Security Researcher, SecureLayer7

Multiple flaws in OmniFaces let an attacker forge combined-resource identifiers to exhaust server memory, inject JavaScript through unescaped URL-fragment values, and replay WebSocket push messages…

Packageorg.omnifaces:omnifaces
Ecosystemmaven
Affected< 1.14.3
Fixed in1.14.3
OmniFaces: Forged Resource IDs, XSS via o:hashParam, and Push-Channel Replay

The problem

OmniFaces before 1.14.3 accepts attacker-supplied combined-resource IDs with no authenticity check. A crafted ID inflates at roughly 770:1 during decoding and populates an unbounded static cache, burning heap memory with no server-side limit.

A separate flaw in o:hashParam writes the URL fragment value directly into an Ajax CDATA callback script without JavaScript-string escaping. Any page using o:hashParam and a follow-up Ajax render is a stored-then-reflected XSS sink. A third issue lets a WebSocket client reuse a victim's session-scoped push-channel ID with no HTTP-session binding check.

Proof of concept

A working proof-of-concept for this issue in org.omnifaces:omnifaces, with the exact payload below.

http
# 1. o:hashParam XSS
# Navigate to a page with <o:hashParam> using this fragment:
https://target.example.com/page.jsf#foo='-alert(1)-'__omniXss=1337

# The server reads the fragment value on the follow-up Ajax request
# and writes it unescaped into the CDATA callback block:
# <![CDATA[ omnifaces.HashParam.update('foo', ''-alert(1)-''__omniXss=1337'); ]]>
# The single-quote closes the JS string; the payload executes.

# 2. Combined-resource ID heap exhaustion (DoS)
# Craft a Base64-encoded resource-list ID that expands to ~16 MB when decoded.
# Send it to the combined-resource endpoint:
GET /jakarta.faces.resource/omnifaces:combined.js.xhtml?v=<crafted-base64-id> HTTP/1.1
Host: target.example.com
# 20 754 encoded bytes -> ~16 000 000 decoded chars (~49 MB heap delta per request).
# Each unique ID is also permanently cached; 200 requests = 200 permanent cache entries.

# 3. Push-channel session replay
# Obtain a victim's session-scoped channel UUID (e.g. via XSS or log leak).
# Open a fresh WebSocket with no session cookie and subscribe to that UUID.
# The server checks only whether the UUID exists application-wide, not whether
# the connecting client owns the HTTP session, so the next push to that
# channel is delivered to the attacker's socket.

The XSS root cause is missing JavaScript-string and CDATA-safe encoding in the HashParam render path: the fragment value was interpolated verbatim into the callback script. A single quote in the value terminates the JS string literal, allowing arbitrary code execution.

The advisory confirmed real Chrome execution of the canary window.__omniXss=1337.

The heap-exhaustion root cause is that CombinedResourceInfo.fromId() decoded attacker-supplied IDs without an output-size cap and inserted every successfully parsed ID into a static, unbounded Map. The fix in commit d5cae243 adds HMAC-signed IDs (only server-issued IDs are accepted) and caps decoded output.

Commit c43eef01 adds proper JS-string escaping to HashParam. Commit 59d6c518 binds the WebSocket handshake to the originating HTTP session. These map to CWE-79, CWE-345, CWE-770, and CWE-862 respectively.

The fix

Upgrade to OmniFaces 1.14.3 (patch commits 59d6c518, a52b9246, aa42da36, c43eef01, d5cae243). The release signs combined-resource IDs with a per-deployment HMAC secret, caps decoded output size, bounds the source-map cache, escapes HashParam callback values for JavaScript/CDATA contexts, and verifies HTTP-session ownership during WebSocket handshake.

Reported by Daniel Birtwhistle.

References: [1][2][3][4][5][6]

Related research