## Summary

CVE-2026-57527 affects the ZAP ViewState add-on before the fix in commit `ac6c3f94d38505bc0facea286a4d3728044c6e5c`. The add-on's response ViewState panel registered the JSF parameter name `javax.faces.ViewState`; when a proxied HTTP response contained that hidden field, the original ZAP add-on path created a `JSFViewState` and decoded it with Java `ObjectInputStream.readObject()` without a deserialization filter or type allowlist. A malicious web server controlling a response viewed/proxied through ZAP can therefore supply a Base64-encoded serialized Java object that is deserialized inside the ZAP JVM. The current reproduction confirms arbitrary command execution through the actual built ZAP ViewState add-on component path, not a reimplemented handler.

## Impact

- **Package/component affected:** `zap-extensions`, add-on `addOns/viewstate` / ZAP ViewState add-on.
- **Affected versions:** ViewState add-on versions before version 4 / before fixed commit `ac6c3f94d38505bc0facea286a4d3728044c6e5c`. The verified vulnerable checkout is the fixed commit parent `7e686c0900e82bb73b57880c0328d012269f8741`.
- **Risk level and consequences:** High. An attacker-controlled proxied web server can return an HTML response containing a malicious `javax.faces.ViewState` value. When the vulnerable ZAP ViewState add-on response panel processes that response, attacker-controlled Java deserialization occurs in the ZAP process and can execute commands with the privileges of the ZAP user.

## Impact Parity

- **Disclosed/claimed maximum impact:** Arbitrary code execution / RCE through ZAP processing a proxied malicious ViewState response.
- **Reproduced impact from this run:** Code execution. The payload's `readObject()` ran `/bin/sh -c "id > ...; echo RCE_VIA_ZAP_VIEWSTATE_PRODUCT_PATH >> ..."` inside the deserializing JVM, producing marker files and command output.
- **Parity:** `full`.
- **Not demonstrated:** A full desktop browser GUI session was not used. Instead, the proof builds the actual vulnerable and fixed `.zap` add-on archives and drives the original response-panel product component boundary with a real ZAP `HttpMessage` populated from bytes read across a TCP socket. This reaches the original ZAP add-on path (`ExtensionHttpPanelViewStateView$ResponseSplitBodyViewStateViewFactory -> HttpPanelViewStateView -> ViewStateModel -> JSFViewState.decode`) and demonstrates the claimed code-execution impact.

## Root Cause

The vulnerable `ViewStateModel` registered JSF ViewState fields as parseable ViewState parameters:

```java
viewstateParams.add(new ViewState(null, JSFViewState.KEY, "javax.faces.ViewState"));
```

For response bodies, `ViewStateModel.getData()` calls `getViewStateParam()` on the HTTP response body. If `javax.faces.ViewState` is found, `getViewStateParam()` constructs a `JSFViewState` object. `ViewState.getDecodedValue()` then calls `JSFViewState.decode()`, whose vulnerable implementation Base64-decodes the supplied value and passes it directly to `ObjectInputStream.readObject()`:

```java
ObjectInputStream ois = new ObjectInputStream(bais);
return (T) ois.readObject();
```

Because the serialized stream is attacker-controlled and no Java serialization filter, class allowlist, or safe parser is used, a gadget or attacker-defined serializable class available to the deserializing JVM can execute code during `readObject()`.

The known fix is commit `ac6c3f94d38505bc0facea286a4d3728044c6e5c` (`https://github.com/zaproxy/zap-extensions/commit/ac6c3f94d38505bc0facea286a4d3728044c6e5c`), which disables the JSF ViewState registration in `ViewStateModel`:

```java
// viewstateParams.add(new ViewState(null, JSFViewState.KEY, "javax.faces.ViewState"));
```

With that change, the fixed add-on does not create `JSFViewState` for the same response field and therefore does not reach `JSFViewState.decode()` for the malicious input.

## Reproduction Steps

1. Use `bundle/repro/reproduction_steps.sh`.
2. The script:
   - Locates the prepared `zap-extensions` repository from `bundle/project_cache_context.json` or clones a fallback checkout.
   - Resolves the fixed commit `ac6c3f94d38505bc0facea286a4d3728044c6e5c` and vulnerable parent `7e686c0900e82bb73b57880c0328d012269f8741`.
   - Creates clean worktrees for both versions and verifies the JSF registration is active in the vulnerable checkout and commented out in the fixed checkout.
   - Builds the actual ViewState add-on `.zap` archive from each checkout with Gradle task `:addOns:viewstate:jarZapAddOn`.
   - Starts a real malicious TCP HTTP server that serves an HTML page with a hidden `javax.faces.ViewState` field containing a Base64 serialized Java object.
   - Runs a Java product-component harness that reads the malicious HTTP response from the TCP socket, creates a real ZAP `HttpMessage`, instantiates the original `ExtensionHttpPanelViewStateView$ResponseSplitBodyViewStateViewFactory`, obtains the original `HttpPanelViewStateView`, and triggers the normal `HttpPanelViewModel.setMessage(HttpMessage)` listener path.
   - Performs two vulnerable attempts and two fixed negative-control attempts.
3. Expected evidence:
   - Vulnerable attempts log that `ViewStateModel` and `HttpPanelViewStateView` were loaded from the vulnerable built `.zap`, show `ViewStateModel.has_jsf_registration=true`, and create `vuln_marker_1.txt`/`vuln_marker_2.txt` plus `vuln_rce_output_1.txt`/`vuln_rce_output_2.txt`.
   - The marker stack traces include `JSFViewState.decode`, `ViewStateModel.getData`, `HttpPanelViewStateView.dataChanged`, and `DefaultHttpPanelViewModel.setMessage`.
   - Fixed attempts log that classes were loaded from the fixed built `.zap`, show `ViewStateModel.has_jsf_registration=false`, and do not create marker/RCE files.

## Evidence

Primary evidence files:

- `bundle/repro/reproduction_steps.sh` — verified twice consecutively.
- `bundle/repro/runtime_manifest.json` — runtime manifest with `entrypoint_kind="tcp_peer"`, `service_started=true`, `healthcheck_passed=true`, and `target_path_reached=true`.
- `bundle/logs/reproduction_steps.log` — full build and runtime transcript.
- `bundle/logs/vuln_harness_1.log`, `bundle/logs/vuln_harness_2.log` — vulnerable product-path harness logs.
- `bundle/logs/fixed_harness_1.log`, `bundle/logs/fixed_harness_2.log` — fixed negative-control harness logs.
- `bundle/logs/server_vuln_1.log`, `bundle/logs/server_vuln_2.log`, `bundle/logs/server_fixed_1.log`, `bundle/logs/server_fixed_2.log` — malicious TCP server logs proving accepted HTTP connections.
- `bundle/repro/artifacts/vuln_marker_1.txt`, `bundle/repro/artifacts/vuln_marker_2.txt` — deserialization marker files containing stack traces.
- `bundle/repro/artifacts/vuln_rce_output_1.txt`, `bundle/repro/artifacts/vuln_rce_output_2.txt` — command output files.
- `bundle/repro/artifacts/source_identity.txt` — vulnerable/fixed commit IDs and SHA-256 hashes of the built `.zap` archives.
- `bundle/repro/artifacts/fix.patch` — patch diff for the ViewState add-on.

Key excerpts from the successful run:

```text
[setup] Vulnerable commit (fixed parent): 7e686c0900e82bb73b57880c0328d012269f8741
[setup] Fixed commit:                    ac6c3f94d38505bc0facea286a4d3728044c6e5c
[verify] Vulnerable ViewStateModel JSF registration:
... ViewStateModel.java:62:        viewstateParams.add(new ViewState(null, JSFViewState.KEY, "javax.faces.ViewState"));
[verify] Fixed ViewStateModel JSF registration is disabled:
... ViewStateModel.java:62:        // viewstateParams.add(new ViewState(null, JSFViewState.KEY, "javax.faces.ViewState"));
```

The vulnerable runtime path loaded the actual vulnerable add-on and saw the JSF registration:

```text
[vuln-1] view_loaded_from=file:.../worktrees/vulnerable/addOns/viewstate/build/zapAddOn/bin/viewstate-alpha-4.zap
ViewStateModel.loaded_from=file:.../worktrees/vulnerable/addOns/viewstate/build/zapAddOn/bin/viewstate-alpha-4.zap
ViewStateModel.param name=javax.faces.ViewState type=JSF
ViewStateModel.has_jsf_registration=true
[vuln-1] invoking real HttpPanelViewModel.setMessage(HttpMessage)
[vuln-1] marker_exists=true
[vuln-1] rce_output_exists=true
```

The RCE marker confirms deserialization and shows the original ZAP add-on call stack:

```text
DESERIALIZATION_RCE_CONFIRMED
command=id > '.../vuln_rce_output_1.txt'; echo RCE_VIA_ZAP_VIEWSTATE_PRODUCT_PATH >> '.../vuln_rce_output_1.txt'
...
  at org.zaproxy.zap.extension.viewstate.zap.utils.JSFViewState.decode(JSFViewState.java:92)
  at org.zaproxy.zap.extension.viewstate.zap.utils.JSFViewState.decode(JSFViewState.java:76)
  at org.zaproxy.zap.extension.viewstate.zap.utils.ViewState.getDecodedValue(ViewState.java:64)
  at org.zaproxy.zap.extension.viewstate.ViewStateModel.getData(ViewStateModel.java:183)
  at org.zaproxy.zap.extension.viewstate.HttpPanelViewStateView.dataChanged(HttpPanelViewStateView.java:189)
  at org.zaproxy.zap.extension.httppanel.view.DefaultHttpPanelViewModel.setMessage(DefaultHttpPanelViewModel.java:42)
```

The command output proves attacker-controlled code ran in the deserializing JVM:

```text
uid=1000(vscode) gid=1000(vscode) groups=1000(vscode),969(969)
RCE_VIA_ZAP_VIEWSTATE_PRODUCT_PATH
```

The fixed negative controls process the same malicious response through the fixed built add-on but do not deserialize it:

```text
[fixed-1] view_loaded_from=file:.../worktrees/fixed/addOns/viewstate/build/zapAddOn/bin/viewstate-alpha-4.zap
ViewStateModel.param_count=2
ViewStateModel.param name=__VIEWSTATE type=ASP
ViewStateModel.param name=__VIEWSTATEFIELDCOUNT type=ASP
ViewStateModel.has_jsf_registration=false
[fixed-1] marker_exists=false
[fixed-1] rce_output_exists=false
```

Environment details captured by the script include OpenJDK version, exact commits, built archive paths, SHA-256 hashes, TCP server logs, and runtime manifest metadata.

## Recommendations / Next Steps

- Keep the JSF ViewState parser disabled unless it is replaced with a safe, non-deserializing parser.
- If JSF ViewState support is restored, do not call unrestricted `ObjectInputStream.readObject()` on data from HTTP responses. Use a strictly bounded parser or, at minimum, a JEP 290 `ObjectInputFilter` with a tight allowlist and resource limits.
- Release and deploy ViewState add-on version 4 or later containing commit `ac6c3f94d38505bc0facea286a4d3728044c6e5c`.
- Add regression tests that feed a serialized-object-looking `javax.faces.ViewState` response through the actual response-panel model path and assert that `JSFViewState.decode()`/`ObjectInputStream.readObject()` is not reached.
- Consider auditing other add-ons for Java deserialization of proxied request/response data.

## Additional Notes

- Idempotency confirmed: `bundle/repro/reproduction_steps.sh` was run twice consecutively and passed both times.
- The proof uses a headless product-component boundary rather than launching the full Swing desktop. It still loads the actual built `.zap` add-on classes and drives the original response-panel listener/model path with a real ZAP `HttpMessage` created from bytes received across a TCP socket.
- The harness only initializes minimal Swing/I18N state needed for headless execution; it does not reimplement ViewState selection or toggle the fix. The vulnerable/fixed comparison is produced by building and loading different add-on archives from the vulnerable and fixed commits.
- No sanitizer was used; the observed impact is real Java command execution during deserialization.
