README.md
Rendering markdown...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Unauthenticated Information Leak to Full Admin Compromise on ZTE ZXHN H168N (CVE-2021-21735)</title>
<meta name="description" content="A first-person disclosure analysis of CVE-2021-21735 on the ZTE ZXHN H168N V3.5, covering the wizard-page leak, firmware whitelist failure, and the included bulk PoC.">
<link rel="canonical" href="https://minanagehsalalma.github.io/cve-2021-21735-zte-zxhn-h168n-admin-compromise/">
<meta property="og:type" content="article">
<meta property="og:site_name" content="Mina Zekry">
<meta property="og:url" content="https://minanagehsalalma.github.io/cve-2021-21735-zte-zxhn-h168n-admin-compromise/">
<meta property="og:title" content="Unauthenticated Information Leak to Full Admin Compromise on ZTE ZXHN H168N (CVE-2021-21735)">
<meta property="og:description" content="A first-person disclosure analysis of CVE-2021-21735 on the ZTE ZXHN H168N V3.5, covering the wizard-page leak, firmware whitelist failure, and the included bulk PoC.">
<meta property="og:image" content="https://minanagehsalalma.github.io/cve-2021-21735-zte-zxhn-h168n-admin-compromise/hero.png?v=20260525">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1536">
<meta property="og:image:height" content="1024">
<meta property="og:image:alt" content="Hero image for the CVE-2021-21735 ZTE ZXHN H168N article by Mina Zekry.">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Unauthenticated Information Leak to Full Admin Compromise on ZTE ZXHN H168N (CVE-2021-21735)">
<meta name="twitter:description" content="A first-person disclosure analysis of CVE-2021-21735 on the ZTE ZXHN H168N V3.5, covering the wizard-page leak, firmware whitelist failure, and the included bulk PoC.">
<meta name="twitter:image" content="https://minanagehsalalma.github.io/cve-2021-21735-zte-zxhn-h168n-admin-compromise/hero.png?v=20260525">
<meta name="twitter:image:alt" content="Hero image for the CVE-2021-21735 ZTE ZXHN H168N article by Mina Zekry.">
<meta name="theme-color" content="#06090f">
<link rel="icon" type="image/svg+xml" href="favicon.svg?v=20260522">
<style>
@import url("https://fonts.googleapis.com/css2?family=Syne:wght@500;700;800&family=IBM+Plex+Mono:wght@400;500;700&family=Instrument+Sans:wght@400;500;600;700&display=swap");
:root {
--bg: #06090f;
--bg-2: #0a0f18;
--panel: rgba(255, 255, 255, 0.03);
--line: rgba(255, 255, 255, 0.09);
--line-strong: rgba(255, 255, 255, 0.16);
--text: #eef4ff;
--soft: #a6b2c7;
--dim: #738099;
--cyan: #79e6ff;
--ice: #d6fbff;
--red: #ff5c7a;
--amber: #ffbf72;
--green: #7dffb5;
--shadow: 0 30px 80px rgba(0, 0, 0, 0.45);
--radius: 26px;
--max: 1240px;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
color: var(--text);
font-family: "Instrument Sans", "Segoe UI", sans-serif;
line-height: 1.75;
background:
radial-gradient(circle at 15% 0%, rgba(121, 230, 255, 0.14), transparent 24%),
radial-gradient(circle at 85% 10%, rgba(255, 92, 122, 0.12), transparent 20%),
linear-gradient(180deg, #05080d 0%, #080c13 54%, #0a0f18 100%);
min-height: 100vh;
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: -1;
background:
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: linear-gradient(180deg, rgba(255, 255, 255, 0.3), transparent 88%);
}
a {
color: var(--cyan);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
img {
display: block;
width: 100%;
max-width: 100%;
border-radius: 18px;
}
code,
pre,
.mono,
.meta,
.overline,
.label,
.date {
font-family: "IBM Plex Mono", Consolas, monospace;
}
.page {
width: min(calc(100% - 36px), var(--max));
margin: 0 auto;
padding: 28px 0 100px;
}
.masthead {
display: flex;
justify-content: space-between;
gap: 20px;
align-items: center;
padding: 16px 0 26px;
border-bottom: 1px solid var(--line);
color: var(--soft);
font-size: 0.92rem;
}
.masthead strong {
color: var(--ice);
font-weight: 700;
}
.hero {
position: relative;
padding: 36px 0 30px;
}
.hero::after {
content: "";
position: absolute;
inset: 28px 0 auto auto;
width: 280px;
height: 280px;
border-radius: 50%;
background: radial-gradient(circle, rgba(121, 230, 255, 0.12), transparent 72%);
filter: blur(10px);
pointer-events: none;
}
.overline {
display: inline-flex;
gap: 10px;
align-items: center;
color: var(--green);
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 0.76rem;
margin-bottom: 24px;
}
.overline::before {
content: "";
width: 42px;
height: 1px;
background: rgba(125, 255, 181, 0.5);
}
.hero-grid {
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(320px, 380px);
gap: 24px;
align-items: start;
}
.hero-badges {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 18px;
}
.hero-badge {
display: inline-flex;
align-items: center;
padding: 9px 12px;
border: 1px solid var(--line);
border-radius: 999px;
background: rgba(255, 255, 255, 0.025);
color: var(--ice);
font-size: 0.8rem;
letter-spacing: 0.02em;
}
.hero-grid > *,
.layout > *,
.standfirst > *,
.compare > *,
.visuals > *,
.findings > * {
min-width: 0;
}
h1,
h2,
h3 {
margin: 0;
letter-spacing: -0.04em;
line-height: 1.02;
}
h1 {
font-family: "Syne", "Instrument Sans", sans-serif;
font-weight: 800;
font-size: clamp(1.5rem, 2.85vw, 2.38rem);
line-height: 0.94;
letter-spacing: -0.04em;
max-width: 14.2ch;
}
.hero-title-line {
display: block;
}
.hero-title-line-last {
white-space: nowrap;
}
h2 {
font-family: "Syne", "Instrument Sans", sans-serif;
font-weight: 700;
font-size: clamp(1.8rem, 4vw, 3.2rem);
margin-bottom: 18px;
}
h3 {
font-family: "Syne", "Instrument Sans", sans-serif;
font-weight: 700;
font-size: 1.16rem;
margin-bottom: 12px;
}
.hero-image {
width: min(100%, 300px);
margin: 16px 0 0;
border: 1px solid rgba(121, 230, 255, 0.14);
border-radius: 20px;
box-shadow: 0 16px 34px rgba(0, 0, 0, 0.24);
filter: saturate(0.9) contrast(1.02);
opacity: 0.96;
}
.dek {
max-width: 58ch;
margin: 18px 0 0;
font-size: 1rem;
color: #dae4f4;
}
.hero-meta {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 22px;
}
.handlebar {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 20px;
}
.tag {
padding: 10px 13px;
border: 1px solid var(--line);
border-radius: 999px;
background: rgba(255, 255, 255, 0.025);
color: #dfe8f8;
font-size: 0.84rem;
}
.handle {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 11px 14px;
border: 1px solid rgba(121, 230, 255, 0.14);
border-radius: 999px;
background: linear-gradient(180deg, rgba(121, 230, 255, 0.08), rgba(255, 255, 255, 0.03));
color: var(--ice);
font-size: 0.85rem;
transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
}
.handle:hover {
text-decoration: none;
transform: translateY(-1px);
border-color: rgba(121, 230, 255, 0.3);
background: linear-gradient(180deg, rgba(121, 230, 255, 0.12), rgba(255, 255, 255, 0.05));
}
.handle .mono {
color: var(--cyan);
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.1em;
}
.impact-rail {
padding: 20px;
border: 1px solid var(--line);
border-radius: var(--radius);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.05), transparent 35%),
rgba(255, 255, 255, 0.03);
box-shadow: var(--shadow);
backdrop-filter: blur(14px);
margin-top: 8px;
}
.impact-rail .label {
display: block;
color: var(--amber);
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.74rem;
margin-bottom: 14px;
}
.impact-list {
display: grid;
gap: 14px;
}
.impact-item {
padding-top: 14px;
border-top: 1px solid rgba(255, 255, 255, 0.07);
}
.impact-item:first-child {
padding-top: 0;
border-top: 0;
}
.impact-item strong {
display: block;
font-size: 0.86rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--soft);
margin-bottom: 6px;
}
.impact-item span {
display: block;
color: var(--ice);
font-size: 1rem;
}
.hero-stats {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin-top: 18px;
padding-top: 18px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.hero-stat {
padding: 12px 0 0;
border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.hero-stat:nth-child(-n + 2) {
border-top: 0;
padding-top: 0;
}
.hero-stat strong {
display: block;
margin-bottom: 6px;
color: var(--soft);
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.14em;
font-weight: 600;
}
.hero-stat span {
display: block;
color: var(--ice);
font-size: 0.94rem;
}
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 290px;
gap: 42px;
align-items: start;
}
.article {
min-width: 0;
}
.section {
padding: 36px 0;
border-top: 1px solid var(--line);
}
.section:first-child {
border-top: 0;
padding-top: 0;
}
p {
margin: 0 0 16px;
color: #dbe5f5;
font-size: 1.02rem;
}
p:last-child {
margin-bottom: 0;
}
ul,
ol {
margin: 0;
padding-left: 20px;
color: #dbe5f5;
}
li + li {
margin-top: 10px;
}
.standfirst {
display: grid;
grid-template-columns: 1.05fr 0.95fr;
gap: 24px;
margin-top: 24px;
}
.brief,
.callout,
.proof-card,
.source-card {
padding: 22px;
border: 1px solid var(--line);
border-radius: 22px;
background: var(--panel);
box-shadow: var(--shadow);
backdrop-filter: blur(12px);
}
.brief p,
.callout p,
.proof-card p,
.source-card p {
font-size: 0.98rem;
}
.callout {
position: relative;
overflow: hidden;
background:
linear-gradient(135deg, rgba(121, 230, 255, 0.08), transparent 42%),
rgba(255, 255, 255, 0.03);
}
.callout::before {
content: "Impact";
position: absolute;
top: 18px;
right: 20px;
color: rgba(255, 255, 255, 0.11);
font-family: "Syne", sans-serif;
font-size: 2.2rem;
font-weight: 800;
pointer-events: none;
}
.findings {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
margin-top: 26px;
}
.finding {
padding: 18px 0 0;
border-top: 1px solid var(--line);
}
.finding strong {
display: block;
margin-bottom: 8px;
color: var(--cyan);
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.78rem;
}
.terminal {
margin-top: 22px;
padding: 18px 20px;
border: 1px solid rgba(121, 230, 255, 0.16);
border-radius: 22px;
background: #070c14;
overflow-x: auto;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
pre {
margin: 0;
font-size: 0.92rem;
color: #d9fbff;
}
.terminal code {
display: block;
}
.tok-method {
color: var(--red);
font-weight: 700;
}
.tok-path,
.tok-tag {
color: var(--cyan);
}
.tok-header,
.tok-key,
.tok-keyword {
color: var(--amber);
}
.tok-status,
.tok-value {
color: var(--green);
}
.tok-str {
color: #ff9ac2;
}
.tok-note {
color: var(--soft);
}
.rca {
display: grid;
gap: 18px;
margin-top: 24px;
}
.rca-item {
display: grid;
grid-template-columns: 52px 1fr;
gap: 18px;
padding-top: 18px;
border-top: 1px solid var(--line);
}
.rca-item:first-child {
padding-top: 0;
border-top: 0;
}
.rca-num {
width: 52px;
height: 52px;
display: grid;
place-items: center;
border: 1px solid rgba(121, 230, 255, 0.24);
border-radius: 50%;
background: rgba(121, 230, 255, 0.06);
color: var(--cyan);
font-weight: 700;
font-size: 0.98rem;
}
.visuals {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 22px;
margin-top: 24px;
}
figure {
margin: 0;
}
figcaption {
margin-top: 12px;
color: var(--soft);
font-size: 0.92rem;
}
.timeline {
display: grid;
gap: 20px;
margin-top: 26px;
}
.event {
display: grid;
grid-template-columns: 140px 1fr;
gap: 18px;
padding-top: 18px;
border-top: 1px solid var(--line);
}
.event:first-child {
padding-top: 0;
border-top: 0;
}
.date {
color: var(--green);
font-size: 0.82rem;
letter-spacing: 0.12em;
text-transform: uppercase;
padding-top: 4px;
}
.compare {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20px;
margin-top: 24px;
}
.compare .proof-card {
min-height: 100%;
}
.side {
position: sticky;
top: 22px;
display: grid;
gap: 18px;
}
.side blockquote {
margin: 0;
padding: 0;
}
.side .source-card {
padding: 18px 20px;
}
.side .source-card strong {
display: block;
margin-bottom: 10px;
color: var(--ice);
font-size: 0.96rem;
}
.side .source-card ul {
padding-left: 18px;
}
.side .source-card li {
color: #dce5f6;
font-size: 0.94rem;
}
.sources {
display: grid;
gap: 14px;
margin-top: 20px;
}
.source-link {
padding: 16px 18px;
border: 1px solid var(--line);
border-radius: 18px;
background: rgba(255, 255, 255, 0.02);
}
.source-link strong {
display: block;
margin-bottom: 4px;
color: var(--ice);
font-size: 0.98rem;
}
.source-link span {
color: var(--soft);
font-size: 0.92rem;
}
.closing {
margin-top: 20px;
padding-top: 18px;
border-top: 1px solid var(--line);
color: var(--soft);
font-size: 0.98rem;
}
@media (max-width: 1040px) {
.hero-grid,
.layout,
.standfirst,
.compare {
grid-template-columns: 1fr;
}
.findings,
.visuals {
grid-template-columns: 1fr 1fr;
}
.side {
position: static;
}
}
@media (max-width: 720px) {
.page {
width: min(calc(100% - 22px), var(--max));
padding-top: 18px;
overflow-x: clip;
}
.masthead {
flex-direction: column;
align-items: flex-start;
}
.findings,
.visuals,
.compare {
grid-template-columns: 1fr;
}
.event,
.rca-item {
grid-template-columns: 1fr;
}
.side,
.side .source-card,
.terminal,
.brief,
.callout,
.proof-card {
width: 100%;
max-width: 100%;
}
.date {
padding-top: 0;
}
h1 {
max-width: none;
font-size: clamp(1.35rem, 6.9vw, 1.9rem);
line-height: 0.98;
letter-spacing: -0.04em;
}
.hero-title-line-last {
white-space: normal;
}
.dek {
font-size: 0.98rem;
}
.hero-image {
width: min(100%, 180px);
margin-top: 14px;
}
.hero-badges,
.hero-meta,
.handlebar {
gap: 10px;
}
.hero-stats {
grid-template-columns: 1fr;
}
.hero-stat {
border-top: 1px solid rgba(255, 255, 255, 0.06);
padding-top: 12px;
}
.hero-stat:first-child {
border-top: 0;
padding-top: 0;
}
pre,
code {
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
}
}
</style>
</head>
<body>
<div class="page">
<div class="masthead">
<div><strong>Mina Zekry</strong> • vulnerability research / disclosure analysis</div>
<div class="mono">ZTE ZXHN H168N V3.5 • CVE-2021-21735</div>
</div>
<header class="hero">
<div class="overline">Threat Intel Aesthetic • Firmware Routing Failure • 2021 Case File</div>
<div class="hero-grid">
<div>
<div class="hero-badges">
<span class="hero-badge mono">CVE-2021-21735</span>
<span class="hero-badge">ZTE ZXHN H168N V3.5</span>
<span class="hero-badge">NVD 6.5 Medium</span>
</div>
<h1>
<span class="hero-title-line">Unauthenticated Information Leak</span>
<span class="hero-title-line">Leads to Full Admin Compromise</span>
<span class="hero-title-line hero-title-line-last">on ZTE ZXHN H168N</span>
</h1>
<img class="hero-image" src="hero.png?v=20260519-143119" alt="Stylized hero graphic for the ZXHN H168N writeup.">
<p class="dek">
CVE-2021-21735 is publicly cataloged as an information leak. The underlying failure is more dangerous than that label suggests. Wizard endpoints under <code>/wizard_page/</code> exposed PPPoE and WLAN material through a brittle whitelist decision, and in real ISP deployments that leaked data could be repurposed straight into administrator access and wireless compromise.
</p>
<div class="hero-meta">
<span class="tag">By Mina Zekry</span>
<span class="tag">2021 disclosure by Mina Nageh Salama, acknowledged by ZTE</span>
</div>
<div class="handlebar">
<a class="handle" href="https://github.com/minanagehsalalma/cve-2021-21735-zte-zxhn-h168n-admin-compromise">
<span class="mono">GitHub</span>
<span>Repo + PoC</span>
</a>
<a class="handle" href="https://www.linkedin.com/in/minanagehzekry">
<span class="mono">LinkedIn</span>
<span>@minanagehzekry</span>
</a>
</div>
</div>
<div class="impact-rail">
<span class="label">At A Glance</span>
<div class="impact-list">
<div class="impact-item">
<strong>Affected</strong>
<span><code>V3.5.0_EG1T4_TE</code> and earlier</span>
</div>
<div class="impact-item">
<strong>Resolved</strong>
<span><code>V3.5.0_EG1T10_ETS</code></span>
</div>
<div class="impact-item">
<strong>Leakage</strong>
<span>PPPoE identifiers, SSID data, Wi-Fi passphrase</span>
</div>
<div class="impact-item">
<strong>Operational Result</strong>
<span>Admin takeover and WLAN compromise</span>
</div>
<div class="impact-item">
<strong>PoC Script</strong>
<span><code>zte_zxhn_h168n_bulk_poc.py</code></span>
</div>
</div>
<div class="hero-stats">
<div class="hero-stat">
<strong>ZTE Rating</strong>
<span><code>3.5 Low</code></span>
</div>
<div class="hero-stat">
<strong>NVD Rating</strong>
<span><code>6.5 Medium</code></span>
</div>
<div class="hero-stat">
<strong>Key Route</strong>
<span><code>/wizard_page/</code></span>
</div>
<div class="hero-stat">
<strong>Disclosure Opened</strong>
<span>February 12, 2021</span>
</div>
</div>
</div>
</div>
</header>
<div class="layout">
<main class="article">
<section class="section">
<h2>What Makes This One Serious</h2>
<div class="standfirst">
<div class="brief">
<p>
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.
</p>
</div>
<div class="callout">
<p>
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.
</p>
</div>
</div>
<div class="findings">
<div class="finding">
<strong>Exposure</strong>
PPPoE and WLAN endpoints responded before a meaningful authorization boundary.
</div>
<div class="finding">
<strong>Escalation</strong>
Leaked PPPoE data could be reused as the effective admin secret in some ISP deployments.
</div>
<div class="finding">
<strong>Breakout</strong>
A Wi-Fi passphrase leak collapses WPA2 strength regardless of password complexity.
</div>
</div>
</section>
<section class="section">
<h2>Exploit Path</h2>
<p>
My disclosure focused on two wizard handlers: <code>wizard_pppoe_lua.lua</code> and <code>wizard_wlan_config_lua.lua</code>. 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.
</p>
<div class="terminal">
<pre><code><span class="tok-method">GET</span> <span class="tok-path">/wizard_page/wizard_pppoe_lua.lua</span>
<span class="tok-method">GET</span> <span class="tok-path">/wizard_page/wizard_wlan_config_lua.lua</span>
<span class="tok-method">POST</span> <span class="tok-path">/wizard_page/wizard_wlan_config_lua.lua</span>
<span class="tok-key">IF_ACTION</span>=<span class="tok-value">GetPassword</span>
<span class="tok-key">&_InstID_PASS</span>=<span class="tok-value">DEV.WIFI.AP1.PSK1</span>
<span class="tok-key">&PASSTYPE</span>=<span class="tok-value">PSK</span></code></pre>
</div>
<div class="terminal">
<pre><code><span class="tok-method">POST</span> <span class="tok-path">/wizard_page/wizard_wlan_config_lua.lua</span> <span class="tok-note">HTTP/1.1</span>
<span class="tok-header">Host:</span> <span class="tok-value">router-ip</span>
<span class="tok-header">Content-Type:</span> <span class="tok-value">application/x-www-form-urlencoded; charset=UTF-8</span>
<span class="tok-header">X-Requested-With:</span> <span class="tok-value">XMLHttpRequest</span>
<span class="tok-key">IF_ACTION</span>=<span class="tok-value">GetPassword</span><span class="tok-key">&_InstID_PASS</span>=<span class="tok-value">DEV.WIFI.AP1.PSK1</span><span class="tok-key">&PASSTYPE</span>=<span class="tok-value">PSK</span>
<span class="tok-note">HTTP/1.1</span> <span class="tok-status">200 OK</span>
<span class="tok-header">Content-Type:</span> <span class="tok-value">application/xml</span>
<span class="tok-tag"><ajax_response></span>
<span class="tok-tag"><IF_ERRORSTR></span><span class="tok-status">SUCC</span><span class="tok-tag"></IF_ERRORSTR></span>
<span class="tok-tag"><ParaName></span><span class="tok-value">KeyPassphrase</span><span class="tok-tag"></ParaName></span>
<span class="tok-tag"><ParaValue></span><span class="tok-str">[REDACTED-WIFI-PASSWORD]</span><span class="tok-tag"></ParaValue></span>
<span class="tok-tag"></ajax_response></span></code></pre>
</div>
<p>
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.
</p>
<p>
The accompanying repository includes a bulk PoC script, <a href="https://github.com/minanagehsalalma/cve-2021-21735-zte-zxhn-h168n-admin-compromise/blob/main/zte_zxhn_h168n_bulk_poc.py"><code>zte_zxhn_h168n_bulk_poc.py</code></a>, which reads a host list and checks the exposed wizard endpoints for the PPPoE, SSID, and Wi-Fi passphrase fields shown here.
</p>
<p>
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.
</p>
</section>
<section class="section">
<h2>Root Cause Analysis</h2>
<p>
Firmware files show a helper named <code>pro_urlsafe_IsWhiteList(pageName)</code>, a lookup against <code>OBJ_WEB_QUICKSETUP_ID</code>, and a branch on <code>QuickSetupEnable</code> that appears to whitelist <code>wizard_pppoe_lua.lua</code> and <code>wizard_wlan_config_lua.lua</code>.
</p>
<div class="terminal">
<pre><code><span class="tok-keyword">WizardTable</span> = <span class="tok-value">cmapi.getinst</span>(<span class="tok-str">"OBJ_WEB_QUICKSETUP_ID"</span>, <span class="tok-str">""</span>)
<span class="tok-keyword">WizardFlag</span> = <span class="tok-value">WizardTable.QuickSetupEnable</span>
<span class="tok-keyword">if</span> <span class="tok-value">WizardFlag</span> == <span class="tok-str">"0"</span> <span class="tok-keyword">then</span>
<span class="tok-value">whitelist</span>[<span class="tok-str">"wizardFrame_finishAction.lua"</span>] = <span class="tok-status">1</span>
<span class="tok-value">whitelist</span>[<span class="tok-str">"wizard_pppoe_lua.lua"</span>] = <span class="tok-status">1</span>
<span class="tok-value">whitelist</span>[<span class="tok-str">"wizard_wlan_config_lua.lua"</span>] = <span class="tok-status">1</span>
<span class="tok-keyword">end</span></code></pre>
</div>
<div class="rca">
<div class="rca-item">
<div class="rca-num">01</div>
<div>
<h3>Filename routing was used as an authorization primitive.</h3>
<p>
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.
</p>
</div>
</div>
<div class="rca-item">
<div class="rca-num">02</div>
<div>
<h3>Setup UI and privileged data handlers were treated as the same class of object.</h3>
<p>
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.
</p>
</div>
</div>
<div class="rca-item">
<div class="rca-num">03</div>
<div>
<h3>Global setup state replaced per-request privilege checks.</h3>
<p>
A single flag such as <code>QuickSetupEnable</code> 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.
</p>
</div>
</div>
<div class="rca-item">
<div class="rca-num">04</div>
<div>
<h3>The endpoint appears not to enforce an internal second gate.</h3>
<p>
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.
</p>
</div>
</div>
<div class="rca-item">
<div class="rca-num">05</div>
<div>
<h3>The exploit chain becomes critical because the leaked data is security-significant.</h3>
<p>
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.
</p>
</div>
</div>
</div>
</section>
<section class="section">
<h2>Visual Proof</h2>
<p>
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.
</p>
<div class="visuals">
<figure>
<img src="Codecleaned.png" alt="Firmware logic screenshot showing QuickSetup whitelist entries for wizard routes.">
<figcaption>
The whitelist helper appears to route <code>wizard_pppoe_lua.lua</code> and <code>wizard_wlan_config_lua.lua</code> into a setup-allowed path.
</figcaption>
</figure>
<figure>
<img src="Redacted.png" alt="Screenshot showing extracted router URLs, usernames, ESSIDs, and Wi-Fi passwords.">
<figcaption>
Historical field proof captured in the local directory: URLs, PPPoE identifiers, SSIDs, and Wi-Fi passphrases displayed in bulk.
</figcaption>
</figure>
</div>
</section>
<section class="section">
<h2>Disclosure Timeline</h2>
<p>
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.
</p>
<div class="timeline">
<div class="event">
<div class="date">2021-02-12</div>
<div><p>Initial disclosure sent to ZTE PSIRT describing unauthenticated wizard-page access, PPPoE leakage, and Wi-Fi password retrieval.</p></div>
</div>
<div class="event">
<div class="date">2021-02-15</div>
<div><p>Follow-up notes that the exposed functionality appears capable of modifying settings, not only reading them.</p></div>
</div>
<div class="event">
<div class="date">2021-03-01</div>
<div><p>ZTE confirms the vulnerability exists in H168N V3.5 and offers an XX bounty.</p></div>
</div>
<div class="event">
<div class="date">2021-03-09</div>
<div><p>ZTE communicates a <code>3.5 Low</code> rating using <code>AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N</code>.</p></div>
</div>
<div class="event">
<div class="date">2021-03-30</div>
<div><p>ZTE states it will not disclose or assign the CVE until the vulnerability is fixed.</p></div>
</div>
<div class="event">
<div class="date">2021-06-09</div>
<div><p>ZTE publishes its bulletin and lists <code>V3.5.0_EG1T10_ETS</code> as the resolved version.</p></div>
</div>
<div class="event">
<div class="date">2021-09-02</div>
<div><p>ZTE informs me that <code>CVE-2021-21735</code> has been assigned.</p></div>
</div>
<div class="event">
<div class="date">2021-09-28</div>
<div><p>ZTE confirms payout completion after I resubmitted the case through YesWeHack.</p></div>
</div>
</div>
</section>
<section class="section">
<h2>Severity Split</h2>
<p>
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.
</p>
<div class="compare">
<div class="proof-card">
<h3>ZTE View</h3>
<p>
<code>3.5 Low</code> with adjacent network vector and low confidentiality impact. This reads like a narrow exposure scenario.
</p>
</div>
<div class="proof-card">
<h3>NVD View</h3>
<p>
<code>6.5 Medium</code> with network vector and high confidentiality impact. This better matches a case where leaked data can materially unlock admin and WLAN access.
</p>
</div>
</div>
<p class="closing">
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.
</p>
</section>
<section class="section">
<h2>References</h2>
<div class="sources">
<a class="source-link" href="https://support.zte.com.cn/support/news/LoopholeInfoDetail.aspx?newsId=1015924">
<strong>ZTE Security Bulletin</strong>
<span>Vendor advisory for CVE-2021-21735 and resolved firmware version details.</span>
</a>
<a class="source-link" href="https://nvd.nist.gov/vuln/detail/CVE-2021-21735">
<strong>NVD Entry</strong>
<span>Public CVE record with enriched severity and affected-version metadata.</span>
</a>
<a class="source-link" href="https://www.cve.org/CVERecord?id=CVE-2021-21735">
<strong>CVE Record</strong>
<span>Canonical CVE assignment record for CVE-2021-21735.</span>
</a>
</div>
</section>
</main>
<aside class="side">
<div class="source-card">
<strong>Article Angle</strong>
<p>
This writeup treats the bug as a compromise chain rooted in route-level authorization failure, not as a sterile database entry.
</p>
</div>
<div class="source-card">
<strong>Core Endpoints</strong>
<ul>
<li><code>/wizard_page/wizard_pppoe_lua.lua</code></li>
<li><code>/wizard_page/wizard_wlan_config_lua.lua</code></li>
<li><code>IF_ACTION=GetPassword</code></li>
</ul>
</div>
</aside>
</div>
</div>
</body>
</html>