{"repro_id":"REPRO-2026-00249","version":6,"title":"ZAP ViewState add-on insecure deserialization RCE","repro_type":"security","status":"published","severity":"high","description":"Zed Attack Proxy (ZAP) ViewState add-on before version 4 contains an insecure deserialization vulnerability. An attacker who controls a proxied web server can embed a malicious serialized Java object in a ViewState response; when ZAP's ViewState add-on parses it, arbitrary code execution occurs. Reproduce: install ZAP with the ViewState add-on <4, run ZAP as a proxy, and browse a malicious page that returns a crafted ViewState payload. The add-on deserializes the payload and executes code.","root_cause":"## Summary\n\nCVE-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.\n\n## Impact\n\n- **Package/component affected:** `zap-extensions`, add-on `addOns/viewstate` / ZAP ViewState add-on.\n- **Affected versions:** ViewState add-on versions before version 4 / before fixed commit `ac6c3f94d38505bc0facea286a4d3728044c6e5c`. The verified vulnerable checkout is the fixed commit parent `7e686c0900e82bb73b57880c0328d012269f8741`.\n- **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.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Arbitrary code execution / RCE through ZAP processing a proxied malicious ViewState response.\n- **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.\n- **Parity:** `full`.\n- **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.\n\n## Root Cause\n\nThe vulnerable `ViewStateModel` registered JSF ViewState fields as parseable ViewState parameters:\n\n```java\nviewstateParams.add(new ViewState(null, JSFViewState.KEY, \"javax.faces.ViewState\"));\n```\n\nFor 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()`:\n\n```java\nObjectInputStream ois = new ObjectInputStream(bais);\nreturn (T) ois.readObject();\n```\n\nBecause 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()`.\n\nThe known fix is commit `ac6c3f94d38505bc0facea286a4d3728044c6e5c` (`https://github.com/zaproxy/zap-extensions/commit/ac6c3f94d38505bc0facea286a4d3728044c6e5c`), which disables the JSF ViewState registration in `ViewStateModel`:\n\n```java\n// viewstateParams.add(new ViewState(null, JSFViewState.KEY, \"javax.faces.ViewState\"));\n```\n\nWith 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.\n\n## Reproduction Steps\n\n1. Use `bundle/repro/reproduction_steps.sh`.\n2. The script:\n   - Locates the prepared `zap-extensions` repository from `bundle/project_cache_context.json` or clones a fallback checkout.\n   - Resolves the fixed commit `ac6c3f94d38505bc0facea286a4d3728044c6e5c` and vulnerable parent `7e686c0900e82bb73b57880c0328d012269f8741`.\n   - Creates clean worktrees for both versions and verifies the JSF registration is active in the vulnerable checkout and commented out in the fixed checkout.\n   - Builds the actual ViewState add-on `.zap` archive from each checkout with Gradle task `:addOns:viewstate:jarZapAddOn`.\n   - 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.\n   - 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.\n   - Performs two vulnerable attempts and two fixed negative-control attempts.\n3. Expected evidence:\n   - 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`.\n   - The marker stack traces include `JSFViewState.decode`, `ViewStateModel.getData`, `HttpPanelViewStateView.dataChanged`, and `DefaultHttpPanelViewModel.setMessage`.\n   - 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.\n\n## Evidence\n\nPrimary evidence files:\n\n- `bundle/repro/reproduction_steps.sh` — verified twice consecutively.\n- `bundle/repro/runtime_manifest.json` — runtime manifest with `entrypoint_kind=\"tcp_peer\"`, `service_started=true`, `healthcheck_passed=true`, and `target_path_reached=true`.\n- `bundle/logs/reproduction_steps.log` — full build and runtime transcript.\n- `bundle/logs/vuln_harness_1.log`, `bundle/logs/vuln_harness_2.log` — vulnerable product-path harness logs.\n- `bundle/logs/fixed_harness_1.log`, `bundle/logs/fixed_harness_2.log` — fixed negative-control harness logs.\n- `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.\n- `bundle/repro/artifacts/vuln_marker_1.txt`, `bundle/repro/artifacts/vuln_marker_2.txt` — deserialization marker files containing stack traces.\n- `bundle/repro/artifacts/vuln_rce_output_1.txt`, `bundle/repro/artifacts/vuln_rce_output_2.txt` — command output files.\n- `bundle/repro/artifacts/source_identity.txt` — vulnerable/fixed commit IDs and SHA-256 hashes of the built `.zap` archives.\n- `bundle/repro/artifacts/fix.patch` — patch diff for the ViewState add-on.\n\nKey excerpts from the successful run:\n\n```text\n[setup] Vulnerable commit (fixed parent): 7e686c0900e82bb73b57880c0328d012269f8741\n[setup] Fixed commit:                    ac6c3f94d38505bc0facea286a4d3728044c6e5c\n[verify] Vulnerable ViewStateModel JSF registration:\n... ViewStateModel.java:62:        viewstateParams.add(new ViewState(null, JSFViewState.KEY, \"javax.faces.ViewState\"));\n[verify] Fixed ViewStateModel JSF registration is disabled:\n... ViewStateModel.java:62:        // viewstateParams.add(new ViewState(null, JSFViewState.KEY, \"javax.faces.ViewState\"));\n```\n\nThe vulnerable runtime path loaded the actual vulnerable add-on and saw the JSF registration:\n\n```text\n[vuln-1] view_loaded_from=file:.../worktrees/vulnerable/addOns/viewstate/build/zapAddOn/bin/viewstate-alpha-4.zap\nViewStateModel.loaded_from=file:.../worktrees/vulnerable/addOns/viewstate/build/zapAddOn/bin/viewstate-alpha-4.zap\nViewStateModel.param name=javax.faces.ViewState type=JSF\nViewStateModel.has_jsf_registration=true\n[vuln-1] invoking real HttpPanelViewModel.setMessage(HttpMessage)\n[vuln-1] marker_exists=true\n[vuln-1] rce_output_exists=true\n```\n\nThe RCE marker confirms deserialization and shows the original ZAP add-on call stack:\n\n```text\nDESERIALIZATION_RCE_CONFIRMED\ncommand=id > '.../vuln_rce_output_1.txt'; echo RCE_VIA_ZAP_VIEWSTATE_PRODUCT_PATH >> '.../vuln_rce_output_1.txt'\n...\n  at org.zaproxy.zap.extension.viewstate.zap.utils.JSFViewState.decode(JSFViewState.java:92)\n  at org.zaproxy.zap.extension.viewstate.zap.utils.JSFViewState.decode(JSFViewState.java:76)\n  at org.zaproxy.zap.extension.viewstate.zap.utils.ViewState.getDecodedValue(ViewState.java:64)\n  at org.zaproxy.zap.extension.viewstate.ViewStateModel.getData(ViewStateModel.java:183)\n  at org.zaproxy.zap.extension.viewstate.HttpPanelViewStateView.dataChanged(HttpPanelViewStateView.java:189)\n  at org.zaproxy.zap.extension.httppanel.view.DefaultHttpPanelViewModel.setMessage(DefaultHttpPanelViewModel.java:42)\n```\n\nThe command output proves attacker-controlled code ran in the deserializing JVM:\n\n```text\nuid=1000(vscode) gid=1000(vscode) groups=1000(vscode),969(969)\nRCE_VIA_ZAP_VIEWSTATE_PRODUCT_PATH\n```\n\nThe fixed negative controls process the same malicious response through the fixed built add-on but do not deserialize it:\n\n```text\n[fixed-1] view_loaded_from=file:.../worktrees/fixed/addOns/viewstate/build/zapAddOn/bin/viewstate-alpha-4.zap\nViewStateModel.param_count=2\nViewStateModel.param name=__VIEWSTATE type=ASP\nViewStateModel.param name=__VIEWSTATEFIELDCOUNT type=ASP\nViewStateModel.has_jsf_registration=false\n[fixed-1] marker_exists=false\n[fixed-1] rce_output_exists=false\n```\n\nEnvironment details captured by the script include OpenJDK version, exact commits, built archive paths, SHA-256 hashes, TCP server logs, and runtime manifest metadata.\n\n## Recommendations / Next Steps\n\n- Keep the JSF ViewState parser disabled unless it is replaced with a safe, non-deserializing parser.\n- 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.\n- Release and deploy ViewState add-on version 4 or later containing commit `ac6c3f94d38505bc0facea286a4d3728044c6e5c`.\n- 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.\n- Consider auditing other add-ons for Java deserialization of proxied request/response data.\n\n## Additional Notes\n\n- Idempotency confirmed: `bundle/repro/reproduction_steps.sh` was run twice consecutively and passed both times.\n- 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.\n- 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.\n- No sanitizer was used; the observed impact is real Java command execution during deserialization.\n","cve_id":"CVE-2026-57527","source_url":"https://github.com/zaproxy/zap-extensions","package":{"name":"zaproxy/zap-extensions","ecosystem":"github","affected_versions":"ViewState add-on versions 1, 2, and 3 (before version 4)","fixed_version":"ViewState add-on version 4"},"reproduced_at":"2026-07-06T08:37:40.452823+00:00","duration_secs":1576.0,"tool_calls":263,"handoffs":3,"total_cost_usd":7.426435439999997,"agent_costs":{"hypothesis_generator":0.0103794,"judge":0.0453875,"repro":4.68591174,"support":0.0710088,"vuln_variant":2.613748},"cost_breakdown":{"hypothesis_generator":{"accounts/fireworks/models/glm-5p2":0.0103794},"judge":{"gpt-5.4-mini":0.0453875},"repro":{"accounts/fireworks/routers/glm-5p2-fast":0.8793017400000002,"gpt-5.5":3.80661},"support":{"accounts/fireworks/routers/glm-5p2-fast":0.0710088},"vuln_variant":{"gpt-5.5":2.613748}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-06T08:38:16.561774+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":27259,"category":"reproduction_script"},{"path":"bundle/repro/artifacts/fix.patch","filename":"fix.patch","size":3026,"category":"patch"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":11496,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":21204,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":11486,"category":"analysis"},{"path":"bundle/artifact_promotion_manifest.json","filename":"artifact_promotion_manifest.json","size":16653,"category":"other"},{"path":"bundle/vuln_variant/source_identity.json","filename":"source_identity.json","size":1827,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":1692,"category":"other"},{"path":"bundle/repro/artifacts/marker_vuln.txt","filename":"marker_vuln.txt","size":318,"category":"other"},{"path":"bundle/repro/artifacts/rce_output.txt","filename":"rce_output.txt","size":97,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1561,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":819,"category":"other"},{"path":"bundle/logs/vuln_harness_1.log","filename":"vuln_harness_1.log","size":3996,"category":"log"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":18212,"category":"log"},{"path":"bundle/repro/artifacts/vuln_marker_1.txt","filename":"vuln_marker_1.txt","size":2425,"category":"other"},{"path":"bundle/repro/artifacts/source_identity.txt","filename":"source_identity.txt","size":632,"category":"other"},{"path":"bundle/logs/build_vuln.log","filename":"build_vuln.log","size":3307,"category":"log"},{"path":"bundle/logs/build_fixed.log","filename":"build_fixed.log","size":3307,"category":"log"},{"path":"bundle/logs/vuln_harness_2.log","filename":"vuln_harness_2.log","size":3996,"category":"log"},{"path":"bundle/logs/fixed_harness_1.log","filename":"fixed_harness_1.log","size":1311,"category":"log"},{"path":"bundle/logs/fixed_harness_2.log","filename":"fixed_harness_2.log","size":1311,"category":"log"},{"path":"bundle/logs/server_vuln_1.log","filename":"server_vuln_1.log","size":181,"category":"log"},{"path":"bundle/logs/server_vuln_2.log","filename":"server_vuln_2.log","size":181,"category":"log"},{"path":"bundle/logs/server_fixed_1.log","filename":"server_fixed_1.log","size":181,"category":"log"},{"path":"bundle/logs/server_fixed_2.log","filename":"server_fixed_2.log","size":181,"category":"log"},{"path":"bundle/repro/artifacts/product_path_harness.java","filename":"product_path_harness.java","size":7067,"category":"other"},{"path":"bundle/repro/artifacts/payload.b64","filename":"payload.b64","size":381,"category":"other"},{"path":"bundle/repro/artifacts/vuln_marker_2.txt","filename":"vuln_marker_2.txt","size":2425,"category":"other"},{"path":"bundle/repro/artifacts/vuln_rce_output_1.txt","filename":"vuln_rce_output_1.txt","size":98,"category":"other"},{"path":"bundle/repro/artifacts/vuln_rce_output_2.txt","filename":"vuln_rce_output_2.txt","size":98,"category":"other"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":7638,"category":"documentation"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":5039,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":3189,"category":"other"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":1504,"category":"other"},{"path":"bundle/logs/vuln_variant_reproduction_steps.log","filename":"vuln_variant_reproduction_steps.log","size":10931,"category":"log"},{"path":"bundle/logs/vuln_variant_request_vuln.log","filename":"vuln_variant_request_vuln.log","size":4005,"category":"log"},{"path":"bundle/logs/vuln_variant_request_fixed.log","filename":"vuln_variant_request_fixed.log","size":1317,"category":"log"},{"path":"bundle/vuln_variant/artifacts/request_vuln_marker.txt","filename":"request_vuln_marker.txt","size":2433,"category":"other"},{"path":"bundle/logs/vuln_variant_build_vuln.log","filename":"vuln_variant_build_vuln.log","size":3307,"category":"log"},{"path":"bundle/logs/vuln_variant_build_fixed.log","filename":"vuln_variant_build_fixed.log","size":3307,"category":"log"},{"path":"bundle/vuln_variant/artifacts/request_variant_harness.java","filename":"request_variant_harness.java","size":5916,"category":"other"},{"path":"bundle/vuln_variant/artifacts/request_payload_vuln.b64","filename":"request_payload_vuln.b64","size":413,"category":"other"},{"path":"bundle/vuln_variant/artifacts/request_payload_fixed.b64","filename":"request_payload_fixed.b64","size":417,"category":"other"},{"path":"bundle/vuln_variant/artifacts/request_vuln_rce_output.txt","filename":"request_vuln_rce_output.txt","size":101,"category":"other"},{"path":"bundle/vuln_variant/artifacts/source_identity_request_variant.txt","filename":"source_identity_request_variant.txt","size":705,"category":"other"}]}