Denial-of-service path
Main clip from my original 2024 validation set showing the denial-of-service path.
A pre-auth oversized application/x-www-form-urlencoded POST can drive the router web interface into
denial of service. The root cause sits in the request-body handling path: attacker-controlled POST data reaches the
CGILua parser before authentication, and the parser eagerly reads and processes request bodies that are still within
the configured application-level input budget.
An unauthenticated denial of service is triggered by an oversized POST body. The router web stack forwards
attacker-controlled request bodies into a CGILua parser before authentication gates matter, and that parser still
exposes an eager body-processing path that can be stressed from the first HTTP transaction even when the body stays
below the visible default maxinput ceiling.
The vulnerability is not a simple missing size check. It is a pre-auth architectural flaw: the router eagerly
reads and parses application/x-www-form-urlencoded input inside the allowed parser budget before
authentication gates matter.
Authentication is not the relevant barrier here because POST parsing happens before login enforcement, and the expensive body-processing path is still reachable below the configured size ceiling.
POST / HTTP/1.1
Host: 192.168.1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 1000000
Array.from({ length: 100000 }, () => String.fromCharCode(65 + Math.floor(Math.random() * 26))).join('');
The browser-console generator shown here uses a 100,000-character body. In some models, a 1,000,000-character body was required, which can be sent using Python.
At time of report this issue was affecting 140K+ devices worldwide across 17+ models. The 140K+ number is the original 2024 exposure estimate. Based on direct testing, almost any version prior to 2022 is affected across this H-series surface.
Two clips from my 2024 lab set: one shows the denial-of-service path, the other shows a model that did not reproduce.
Main clip from my original 2024 validation set showing the denial-of-service path.
This clip shows a model from the same test set that did not reproduce the denial-of-service condition.
The extracted firmware exposes a custom httpd and a Lua web application under
home/httpd. The request parser is implemented in compiled Lua chunks, including
cgilua.lua and cgilua/post.lua. Those chunks were decompiled back into readable Lua source
and then checked against the live validation path from the original report.
This conclusion is not based on reverse engineering alone. The issue was reported to ZTE PSIRT in May 2024 with live validation evidence, ZTE acknowledged receipt, and the later firmware analysis explains the request-body path that matches the reported behavior.
cgilua.lua runs post.parsedata() for every POST before the later login and page-action flow, so the body hits parser code before authentication matters.maxinput of 2097152 bytes into the parser.parsedata() only rejects input when inputsize > maxinput, so any body below that threshold continues into content-type dispatch.x-www-form-urlencoded path:
urlcode.parsequery(read(inputsize), defs.args).
The vulnerability is not a simple missing size check. It is a fundamental logic flaw: the router eagerly reads and
parses the entire POST body before authentication takes place, as long as the payload stays within the visible
parser budget. In practice, that means a large urlencoded body can still reach a full read(inputsize)
plus parsequery(...) operation on the unauthenticated request path.
The H168N firmware provides a definitive look at the vulnerable parser logic. It is the clearest same-surface code comparison in the current workspace, and the decompiled source maps directly to the live exploitation path.
cgilua/post.lua decompiles cleanly from both firmware artifacts compared here.cgilua.lua is also materially unchanged in the caller path that sets _default_maxinput = 2097152 and invokes post.parsedata().The working set combined live validation, firmware extraction, Lua decompilation, and parser comparison. The main tools used across that path were:
Chrome DevTools for live request validation against the H-series test set.UnluacNET for clean decompilation of the router's Lua 5.1 bytecode into readable source.LuaDecompy and LuaPytecode for chunk parsing, structural checks, and disassembly support.7-Zip for extracting firmware payloads and carved filesystem regions.
Only two files matter here. cgilua.lua is the caller that sends every POST body into the parser.
cgilua/post.lua is the parser file where the urlencoded body is actually read and parsed.
local _default_maxinput = 2097152
local _maxinput = _default_maxinput
POST_DESC = {isExceedMaxInput = false, maxFileSize = _maxfilesize}
if requestmethod == "POST" then
post.parsedata({
read = sapi.Request.getpostdata,
discardinput = ap and ap.discard_request_body,
content_type = servervariable("CONTENT_TYPE"),
content_length = servervariable("CONTENT_LENGTH"),
maxinput = _maxinput,
maxfilesize = _maxfilesize,
args = POST,
POST_DESC = POST_DESC
})
end
function parsedata(defs)
assert(type(defs.args) == "table", "field `args' must be a table")
init(defs)
local inputsize = tonumber(defs.content_length) or 0
if inputsize > maxinput then
_G.g_logger:warn("inputsize(" .. inputsize .. ") > maxinput(" .. maxinput .. ")")
defs.POST_DESC.isExceedMaxInput = true
inputsize = 0
return
end
if not content_type then
error("Undefined Media Type")
end
if string.find(content_type, "x-www-form-urlencoded", 1, true) then
urlcode.parsequery(read(inputsize), defs.args)
elseif string.find(content_type, "multipart/form-data", 1, true) then
if inputsize > 0 then
Main(inputsize, defs.args, defs.POST_DESC)
end
elseif string.find(content_type, "application/xml", 1, true)
or string.find(content_type, "text/xml", 1, true)
or string.find(content_type, "text/plain", 1, true) then
tinsert(defs.args, read(inputsize))
else
error("Unsupported Media Type: " .. content_type)
end
end
The bug is the combination of those two snippets: every POST reaches post.parsedata(), and if the body
is still under maxinput, the urlencoded branch performs a full read(inputsize) followed by
urlcode.parsequery(...) before authentication gates matter.
ZTE responded in February 2026 that the issue had been resolved on 2021-03-23 and declined vendor-side CVE assignment. The public record still matters because the issue was reported in 2024, acknowledged by the vendor, escalated through MITRE, and ultimately published as CVE-2026-34473.
ZTE PSIRT received the original report for the unauthenticated H-series POST-body denial of service.
ZTE acknowledged receipt and forwarded the report to the related product team.
MITRE service request 1980204 was opened for CNA-LR processing with evidence bundles and per-issue PDF packages.
ZTE declined vendor-side CVE assignment and stated that this issue had been resolved on 2021-03-23.
MITRE assigned CVE-2026-34473 and asked for a public reference URL containing the minimum publication data.
Service request 2016046 tracked the publication follow-up after the public advisory link was sent and ZTE was contacted again.
MITRE confirmed that CVE-2026-34473 would be published on cve.org within hours, and it was published the same day.