CVE-2021-35036 • Zyxel Credential Exposure

Super-Admin Password Leak Affecting Zyxel CPE/ONT/LTE Fleet

Poster-style advisory image for CVE-2021-35036 summarizing impact, scope, timeline, and the genpass lab context.

The issue was first reported against VMG3625-T50B firmware V5.50(ABTL.0)b2k and later expanded into a broader product-line issue: authenticated low-privilege sessions could reach backend DAL endpoints that returned administrator, supervisor, FTPS, and TR-069 secrets in cleartext. Later Zyxel advisories expanded the scope well beyond a single ISP-customized router image.

Initial report: October 2021 Public advisory: September 27, 2022 Primary CWE: CWE-312
Impact
Privilege escalation by disclosure

User-level access was enough to recover passwords for higher-privilege local accounts and management services.

Scope
Shared management stack

The firmware tree shows the exposure lived in shared libzcfg_fe_dal management code, not in a one-off UI template.

Lesson
Presentation masking is not access control

Several later fixes merely hid values in CLI or TR-98 presentation paths after the backend had already loaded the raw secrets.

What Was Reported

Original finding

A user-privileged account could browse directly to:

  • /cgi-bin/DAL?oid=login_privilege
  • /cgi-bin/DAL?oid=tr69

and obtain cleartext values for local accounts and TR-069 credentials. Disclosure material also shows a related /getDefaultInformation response leaking default passwords for root, supervisor, admin, admin1, and ftps.

The issue was not just that secrets existed in storage. An authenticated low-privilege session could retrieve them through ordinary web-facing DAL handlers.
Intercepted Zyxel login_privilege response with leaked account data.
Intercept evidence captured during disclosure.
Public scope

From one ISP image to a larger fleet

Zyxel first assigned the CVE for VMG3625-T50B, but its later public advisory broadened affected scope across multiple product families.

  • DSL / Ethernet CPE: VMG3625-T50B, VMG3927-T50K, VMG8623-T50B, VMG8825-T50K, EMG3525-T50B, EMG5523-T50B, EMG5723-T50K, DX3301-T0, DX5401-B0, EX5401-B0, EX5501-B0
  • Fiber ONT: AX7501-B series, EP240P, PMG5617GA, PMG5622GA, PMG5317-T20B, PMG5617-T20B2, PM7300-T0
  • 4G / 5G CPE: LTE3301-PLUS, LTE5388 family, LTE7480 family, LTE7490-M804, NR5101, NR7101, NR7102

That matters because the exposed code belongs to a shared management framework, which matches the later vendor-side scope expansion.

Root Cause

Why the leak existed

1. Login privilege GET returned raw passwords

In the firmware source, zcfgFeDal_LoginPrivilege_Get iterates through RDM_OID_ZY_LOG_CFG_GP_ACCOUNT and copies each account's Password field directly into the outgoing JSON array.

json_object_object_add(paramJobj, "Username",
  JSON_OBJ_COPY(json_object_object_get(loginPrivilegeObj, "Username")));
json_object_object_add(paramJobj, "Password",
  JSON_OBJ_COPY(json_object_object_get(loginPrivilegeObj, "Password")));

The DAL command registration then exposes login_privilege as edit|get with an empty privilege string, even though the inline comment still says root_only.

2. TR-069 GET copied the whole management object

The TR-069 DAL getter uses a generic copy path across the management parameter list. That includes Password and ConnectionRequestPassword, which are copied out unless another layer explicitly strips them.

else {
  json_object_object_add(pramJobj, paraName,
    JSON_OBJ_COPY(json_object_object_get(mgmtJobj, paraName)));
}

The corresponding DAL registration exposes tr69 as get|edit.

3. Cosmetic hiding arrived after the backend exposure

Later patches show Zyxel masking values in display code by printing ******** for ACS and SIP passwords. That is a UI / CLI presentation fix, not a backend access-control fix.

printf("%-45s %s\n", "ACS Password", "********");

4. Subsequent metadata fixes confirm the sensitivity

Other later patches add PARAMETER_ATTR_PASSWORD to fields like Password, ConnectionRequestPassword, DefaultPassword, and PasswordHash. That change acknowledges these values should not have been returned verbatim in getter flows.

Timeline

Disclosure path

October 5, 2021 The issue is reported to Zyxel as a cleartext credential exposure reachable from a low-privilege account.
October 18, 2021 Zyxel assigns CVE-2021-35036 for the VMG3625-T50B case.
March 2022 Planned disclosure is delayed again while patches are prepared.
September 27, 2022 Zyxel notifies the researcher that the CVE and advisory will be published.
September 28, 2022 Zyxel confirms public advisory publication and Hall of Fame acknowledgment.
Genpass Lab

The VMG8825 genpass clue

The repository also includes an adapted copy of the public Zyxel VMG8825-T50 keygen work by boginw, tailored for the VMG8825-B50B because the original version did not fit this router model directly. It demonstrates that password material was embedded in reusable vendor logic and could be regenerated from device identity inputs instead of being treated like narrowly scoped operator secrets.

run-qemu.bat launches the ARM guest, rootfs.ext2 carries the extracted filesystem, and the tracked adapted-runtime/opt/genpass/genpass wrapper is the B50B-specific version included in the repository. That wrapper accepts serials whose fifth character is V, Y, or H before calling into vendor password-generation code under /opt/zyxel.

  1. Launch the emulator from the repo with .\zyxel-vmg8825-b50b-keygen-lab\run-qemu.bat.
  2. At the guest login, authenticate with root / root.
  3. Run genpass S182V12345678 to derive the supervisor and admin password families from a sample modem serial.
# host
PS> .\zyxel-vmg8825-b50b-keygen-lab\run-qemu.bat

# inside the bundled emulator
root@VMG8825-B50B-emul:~# genpass S182V12345678

Old algorithm supervisor password.............. 789630c0
New algorithm supervisor password.............. dEfczwP8Sy
...
additional admin and Wi-Fi outputs continue below

That does not prove CVE-2021-35036 by itself, but it reinforces the design pattern behind the analysis: password material was treated as reusable product data that multiple components could fetch, transform, display, or restore. Once the DAL layer exposed those backing objects too broadly, the rest of the stack never had a chance.

Reverse Engineering

How genpass works internally

1. The shell script is only a front-end

Reverse engineering starts with the extracted genpass shell wrapper, not the password algorithm itself. The script validates the serial format, exports it as SERIAL, sets LD_LIBRARY_PATH to Zyxel's extracted libraries, preloads libhook.so, and then invokes the real worker binary: /opt/genpass/getpassword.

That matters because the visible script does not derive passwords on its own. It acts as an execution harness that feeds controlled input into vendor code that was originally meant to run inside the router environment.

2. libhook.so replaces the router's serial source

The small preload library is the most revealing part of the setup. Static analysis of its exported symbols shows that it overrides zyUtilIGetSerialNumber and zcfgBeCommonIsApplyRandomSupervisorPasswordNewAlgorithm.

In the first hook, the code calls getenv("SERIAL"). If the environment variable is present, it copies that value into the caller's buffer. If not, it falls back to the original function via dlsym(RTLD_NEXT, "zyUtilIGetSerialNumber"). In the second hook, it simply returns 1, forcing the "new random supervisor password" path on during emulation.

3. getpassword is an orchestrator, not the algorithm

String and symbol analysis of getpassword shows that it dynamically resolves the real generators at runtime. Its string table contains libzcfg_be.so, libzcfg_be_wind.so, zcfgBeCommonGenKeyBySerialNumMethod2, zcfgBeCommonGenKeyBySerialNumMethod3, zcfgBeCommonGenKeyBySerialNumConfigLength, zcfgBeCommonGenKeyBySerialNumConfigLengthOld, and zcfgBeWlanGenDefaultKey.

That is a strong indicator that the binary does not embed one monolithic algorithm. Instead, it loads vendor library entry points, asks them for the required buffer sizes, invokes multiple generation routines, and prints the results under labels such as old/new supervisor, old/new admin, WIND-specific admin variants, and several Wi-Fi key families.

4. The emulator recreates enough of the firmware to make the vendor code run

The reason this works in QEMU is that the extracted filesystem still contains the same userland and shared objects the original firmware expected. The wrapper points LD_LIBRARY_PATH at Zyxel's library directories, the preload hook substitutes missing hardware-derived state, and the vendor password functions execute as if they were running on the device.

From a reverse-engineering perspective, that makes genpass a harness around reusable product logic rather than a standalone cracking tool. The lab setup does not reimplement Zyxel's algorithms; it restores just enough runtime context to call them directly.

5. The supervisor routines are deterministic and portable

Disassembly of zcfgBeCommonGenKeyBySerialNumMethod2 and zcfgBeCommonGenKeyBySerialNumMethod3 shows two exact supervisor paths. Method2 computes MD5(serial), renders each digest byte as vendor-style hex where one-nibble bytes are duplicated instead of zero-padded, hashes that string again, and then samples every third character to build the old supervisor password.

Method3 reuses that same double-hash stream, uppercases it, derives a 16-bit seed from bytes 1 and 2 of MD5(serial), increments the seed until a ten-slot mod-3 schedule contains uppercase, lowercase, and digit classes, and then maps each sampled byte into safe alphabets with explicit substitutions for I, O, l, o, 1, and 0. That is why the generator can be ported cleanly into browser JavaScript: the firmware logic is deterministic and table driven, not server backed.

Exact Browser Port

Supervisor generator for Method2 and Method3

This widget is an exact browser-side port of the two supervisor routines exposed by the bundled getpassword runtime. It validates the B50B serial format accepted by this repository's wrapper, reproduces Zyxel's double-hash quirk, and replays the final Method3 slot mapping.

Accepted format for the tracked B50B wrapper: S + 3 digits + V/Y/H + 8 digits.

Vendor double-hash
MD5(serial) in vendor hex
MD5(round1) in vendor hex
Every third character tap

Method2 returns the first eight tapped characters. Method3 uppercases the ten-character tap before class mapping.

Method3 slot schedule

The scheduler advances the seed until the ten slots contain uppercase, lowercase, and digit classes at least once.

Method3 output replay

Ready. Generate a result or replay the slot-by-slot mapping.

Exact supervisor outputs
Old supervisor / Method2
New supervisor / Method3

This matches the call order inside getpassword: old supervisor is Method2, new supervisor is Method3.

The important takeaway is architectural: password generation lived in callable shared libraries, serial-number retrieval could be intercepted cleanly, and the surrounding wrapper needed only a small amount of glue to expose Zyxel's own deterministic serial-based generators as a repeatable offline workflow.
Why This CVE Matters

Not just “passwords in config”

Reducing this case to “cleartext storage” understates the practical impact: secrets belonging to higher-privilege accounts and remote-management channels were retrievable from web-exposed DAL endpoints by a weaker account. In real deployments, that turns credential disclosure into a pivot for privilege escalation, service abuse, and post-auth compromise.

The later patch trail is also revealing. Instead of one clean architectural fix, the source history shows several incremental attempts: exposing getters, adding masking in display functions, and only later marking parameters as password-type data. That progression is exactly what makes this class of bug dangerous in shared OEM / ISP firmware stacks.

References

Primary references

  1. Zyxel advisory: cleartext storage of information vulnerability
  2. NVD entry for CVE-2021-35036
  3. Boginw: Zyxel VMG8825-T50 Supervisor Keygen