Edimax BR-6478AC firmware v2.20 - authenticated command injection

EDB-ID:

52696

CVE:

N/A




Platform:

Hardware

Date:

2026-10-21


Title: Edimax BR-6478AC firmware v2.20 - authenticated command injection 
Author: 0day Rubbish Research Team
Contact: disclosure@0day-rubbish.com
Type: remote
Platform: Hardware
CVE: pending

Description:
Edimax BR-6478AC firmware image BR6478AC_v2.20.bin, the closed-source
dual-band AC1750 SOHO wireless router from Edimax Technology Co., Ltd.
(Taiwan), whose SquashFS root filesystem of roughly 2.3 MB targets MIPS 32-bit
big-endian MIPS-I with a uClibc C library and carries an internal build
timestamp from 2015, contains an OS command injection (CWE-78) in the
WAN-configuration form handler of bin/webs, the embedded GoAhead-style HTTP
server serving the management interface on TCP port 80. That binary is ELF
32-bit MSB MIPS, 762904 bytes, SHA256
ec39c0f9332361e37ab92f1999ac5f00f5742dd6db75f7cb26c0652cfedb32a1, reads
etc/boa/boa.conf, writes /var/run/webs.pid and is kept alive by the boot path
cleanlog.sh:131 invoked from init.sh:1115. The registration
websFormDefine("setWAN", ...) at 0x478484, handler at 0x441cec, name string at
0x4a6250, publishes POST /goform/setWAN. Inside the handler the WAN mode is
taken from the request and dispatched: websGetVar(wp, "wanMode", "") at
0x441d3c, atoi at 0x441d44, apmib_set(0x102, &wanMode) at 0x441d54, then addiu
v1, zero, 2 at 0x441d60 and beq v0, v1, 0x441e74 at 0x441d6c. On the PPP
branch, wanMode 2, the username field is read by websGetVar for pppUserName,
name string at 0x4953f4, copied with strcpy at 0x441e94 into a fixed stack
buffer, measured with strlen at 0x441ea8 and then scanned by an inline byte
loop between 0x441ebc and 0x441ee4 whose only comparison is addiu a0, zero,
0x3b, a semicolon; finding one, the loop writes a NUL and stops. That
single-character scan is the entire sanitisation on the path. The value is
then formatted by sprintf at 0x441ee8 with the format string at 0x49a510, echo
%s > /tmp/UserName, and passed to system(buf2) at 0x441f1c through the PLT
slot at 0x4c6d60, which hands the line to /bin/sh -c. A pipe, an ampersand, a
backtick, a dollar sign with parentheses and an embedded newline are never
inspected, so a username containing no semicolon reaches the shell untouched:
the payload |id > /tmp/marker # builds the command line "echo |id >
/tmp/marker # > /tmp/UserName", in which the shell treats the hash as a
comment swallowing the sink's own redirect and runs id as the right-hand side
of a pipeline. An escape loop replacing a hash with a backslash-escaped hash
does exist between 0x441f4c and 0x441fb0, but the instruction order places it
after system() at 0x441f1c with no branch back to it, and it writes the
configuration buffer at sp+0x18 that apmib_set(0x6d, ...) stores at 0x44200c
rather than the buffer handed to the shell, so the hash character is escaped
for storage and for nowhere near execution. Between websGetVar and system
there is no regex call, no inet_aton, no isdigit, no isalnum, no strchr, no
strcspn, no vendor validation helper and no custom validator; the inet_aton
calls that do exist in the function, at 0x441dd0 and 0x441e14, belong to the
static-IP branch, wanMode 1, reached via the branch at 0x441d8c, which the
wanMode 2 comparison never enters, so a quick read of the decompilation
misleads a reviewer into thinking this function validates addresses. The same
construction appears three times in this handler and one root cause yields
three reachable parameters: pppUserName on wanMode 2 with sink region 0x441e74
to 0x441f1c and configuration object 0x6d written after the sink, pptpUserName
on wanMode 3 from 0x4420d0 onward with configuration object 0xf3, and
L2TPUserName on wanMode 6 from 0x44246c onward with configuration object
0x128, each selected by a value the request supplies and each using the same
immediate-0x3b scan, the same echo format string and the same system() call
shape, so a patch confined to the PPP path leaves the PPTP and L2TP paths
injectable. Reaching the handler requires a valid administrative session: the
gate auth_authorize at 0x404d00 reads configuration object 0x1bc, the
setup-wizard flag, through apmib_get, and once past setup the security store
at 0x4c7004, a single / entry of type Basic registered from the directive Auth
/ /etc/boa/boa.passwd, covers every /goform/ path with HTTP Basic
authentication. The product has no second lower-privileged role, so Privileges
Required is High: one realm entry, one password file, one account, an Allow
directive handler at 0x407688 that is a stub answering "This version of Boa
doesn't support access controls", a type-None string at 0x49070c never passed
to auth_add, and a shipped configuration carrying one Auth directive and no
Allow directive. The route is not one of the four carrying a post-boot uptime
gate, /mp.asp, /goform/mp, /advanced_management.asp and
/goform/formAdvManagement, which redirect to /index.asp once /proc/uptime
exceeds 600 seconds, so it is reachable at any time the device is up. The
calling process is root because etc/boa/boa.conf declares User root, the
server performs no setuid, chroot, namespace or capability drop and writes
/var/run/webs.pid, which requires root on this filesystem, so the landing
identity is uid=0(root) with no escalation step in the chain. This handler is
one instance of a broad construction: a count of the shipped registration
table found 91 /goform/form* handlers of which roughly 85 reach a system() or
popen() call. Two adjacent observations are reported without a score: the
strcpy at 0x441e94 copies attacker-controlled bytes with no length argument
into a fixed stack buffer, for which no overflow was developed and no
exploitability claim is made, and because the semicolon scan writes a NUL at
the first semicolon a legitimate username containing one is silently truncated
before storage, a correctness defect independent of the injection. The
firmware image ships a populated /etc/boa/boa.passwd for the single realm and
the model's published factory login is admin:1234; that is reported separately
as CWE-798, carries no score, is never described as the vector for this
injection, and no cryptographic match against the shipped MD5-crypt digest is
claimed because the digest string captured during analysis was recorded
incompletely. One detail is left unresolved rather than smoothed over: the
single account entry in the image is recorded under the name root while the
model's published management login is admin, and because no device was ever
authenticated against, which user name the realm accepts was not established.
Absence of privilege separation is likewise reported separately as CWE-250. A
static comparison against a second firmware image, BR-6208AC_V2_1.03, whose
webs is a different build, ELF 32-bit MSB MIPS-I, 816568 bytes, SHA256
3ad10a41d11c8cd59185eab6c9ce67b18cd2118942eb2201f48540d7846a0328, found a
setWAN implementation matching instruction for instruction, handler at
0x448710, websGetVar for pppUserName at 0x4488b8, strcpy at 0x4488bc, the same
inline immediate-0x3b scan at 0x4488dc to 0x4488fc, the same sprintf at
0x448920 with format string 0x4a6954, system() at 0x448940, the same three
parameters on the same three wanMode values and the same single-realm Basic
authentication behind the same configuration flag, which points to a shared
source component in the vendor's router software base; that model was not
tested, nothing was executed from that image, no score covers it and it is not
an exploited finding on it. A second independent command injection on the same
image, in the formWlanMP manufacturing-test handler reaching sprintf at
0x462fd0 and system() at 0x46308c through POST /goform/formWlanMP with no
sanitisation across thirty radio-calibration fields, is published as a
companion advisory; it is a different handler, format string, sink address and
sanitiser, so one patch point does not close both. No prior public record was
found whose root cause is command injection in this setWAN handler.

CVSS: 7.2 High (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H) is the single
score published for this finding, from ISS = 1 - 0.44^3 = 0.914816, Impact at
S:U = 6.42 x 0.914816 = 5.873119, Exploitability = 8.22 x 0.85 x 0.77 x 0.27 x
0.85 = 1.234708 and Base = Roundup(5.873119 + 1.234708) = Roundup(7.107826) =
7.2. The provenance of that figure is disclosed because it is a correction:
the research notes carried 8.8 next to a PR:H vector, and 8.8 is the
arithmetic of PR:L, so the score was recomputed from the vector and published
at 7.2, lowering the researchers' own number by 1.6 points. Four further
readings are printed with their arithmetic in the public analysis. Privileges
Required Low is rejected at 8.8 High, Exploitability 2.835255 and Base
Roundup(8.708373), because the product has no basic-user role. Scope Changed
is rejected at 9.1 High, Impact at S:C 6.128026, Exploitability 2.286496 and
Base Roundup(1.08 x 8.414522), because webs is root before the attack and root
after it. Attack Complexity High is rejected at 6.6 High, Exploitability
0.705547 and Base Roundup(6.578666), because success depends on no condition
outside the attacker's control, the only branch to satisfy, wanMode, being
attacker-supplied. An adjacent-vector reading is published as a labelled
alternative at 6.8 High, Exploitability 0.900610 and Base Roundup(6.773729),
and is the honest description of a stock device whose WAN-side input policy
drops new connections, the shipped firewall script dropping WAN-originated SYN
at firewall.sh:180, installing a default DROP input policy at firewall.sh:187
and accepting input on other interfaces at firewall.sh:189; AV:N is retained
because the service is bound to the device's own network stack and addressable
over IP by every host on the segment the router serves, including guest
wireless clients and anything bridged or compromised behind it, and because
remote management on the WAN side or forwarding port 80 are ordinary
documented features. Default internet exposure is not claimed. Two readings at
9.8 Critical, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H from Exploitability
3.887043 and Base Roundup(9.760161), are published only as explicitly labelled
conditionals and never as the headline: the first applies where a deployment
still accepts the factory credential and is reasoned rather than demonstrated,
since no device was authenticated against; the second applies to a device
still in its pre-configuration setup state, where auth_authorize returns
success without a challenge for paths outside /mp.asp, /index.asp and / at
0x4052d0 while /generate_204 answers 204, and that branch is structural
inference only, read from the binary and never exercised, with three shipped
artefacts identifying the state as the setup wizard rather than normal
operation, namely formwizResetDefault at 0x44cdbc displaying Resettowizard,
formWizSetup printing NO - LOGIN-WEB when the flag is 1, and pages such as
web/inter_wan.asp:214 and web/admin_timezone.asp:95 reading the same flag.
That conditional's Scope Changed variant computes to 10.0 and is deliberately
not published, and if the setup window has to be contested rather than found
open the reading is 8.1 High from Exploitability 2.221167 and Base 8.094286.
No version range is claimed: only firmware BR6478AC_v2.20.bin was examined,
and only the wanMode 2 path was replicated, so no claim is made for other
BR6478AC builds, for the separately marketed BR-6478AC V2 hardware revision
whose firmware line is a different little-endian MIPS-II build of webs, for
the pptpUserName and L2TPUserName instances beyond the structural reading of
the same handler, or for any other model.

Impact: arbitrary operating-system command execution as root on the router
from a single authenticated POST indistinguishable from a legitimate WAN
settings submission, since the payload travels in the PPP username field of
the form the product's own wan.asp page posts. Root on this device exposes
every stored secret, including precisely the PPP, PPTP and L2TP credentials
this handler writes into configuration objects 0x6d, 0xf3 and 0x128, so an
attacker who reaches the device can read the ISP or VPN account details the
owner entered, together with the administrator password file, wireless
pre-shared keys and per-station key material, VPN parameters and any
certificate or key on the filesystem. It grants control of the configuration
and routing plane: firewall and NAT rules, the DNS resolver handed out to
clients, DHCP leases and a malicious default gateway, port forwards, wireless
SSIDs and the WAN uplink itself, including rewriting the WAN mode, which is
exactly the configuration path this handler owns. It grants firmware-level
persistence, since root can write the flash-backed configuration area and
install a boot-time startup mechanism, so a compromise survives reboots and
can survive a partial configuration reset, with recovery requiring a full
reflash of a known-good image. It grants availability at will over wireless,
routing, DHCP, DNS and the management service. There is no rate limit on the
handler and no lockout in the Basic realm, so the route is repeatable and
leaves no alert behind. Detection is poor by construction but this sink leaves
one usable anomaly: a submitted WAN configuration produces ordinary behaviour
and the sink's visible effect, a username written into /tmp/UserName and then
into the configuration store, is what a legitimate submission does, so the
indicator is the absence of the expected /tmp/UserName destination on a
submission that should have created it. An injection also corrupts stored
configuration in a way the owner may not notice, the semicolon scan truncating
at the first semicolon and a pipe-and-hash payload storing a username the WAN
link cannot authenticate with, a self-inflicted availability event that
doubles as a fingerprint. One operational caution: the injection is not
side-effect free. A successful submission overwrites the stored WAN username
and can break the uplink, which is what the handler does on any successful
submission, so any demonstration must record and restore the device's original
WAN configuration.

Verification boundary: no physical device was powered on and no end-to-end
HTTP request was ever accepted by the product. The HTTP leg, being request
parsing, the authentication gate, form registration, mode dispatch and PPP
branch selection, is evidenced at instruction level from the shipped binary,
its configuration and its boot scripts. The execution leg is evidenced
dynamically by replication of the sink's semantics rather than by detonation,
because a MIPS big-endian uClibc userspace with a board-specific bootloader
could not be booted end to end in the research environment: the extracted
binaries faulted with SIGBUS under the available user-mode translator and
full-system emulation of the board was not brought up. The question under test
is not the device but whether a shell evaluates a pipeline and honours a
trailing comment in a string built by the vendor's own sprintf and handed to
/bin/sh -c, which is a question about POSIX shell semantics that are
architecture independent, combined with the exact format string read out of
the binary. The string sprintf produces at 0x441ee8 was reconstructed byte for
byte for a request whose pppUserName field is |id > /tmp/marker_sw # and
handed to /bin/sh -c in an isolated POSIX sandbox whose invoking shell ran as
root, producing a 39-byte marker file owned by root:root containing
uid=0(root) gid=0(root) groups=0(root) with shell exit status 0, while the
sink's own /tmp/UserName destination was not created, that absence being the
proof that the trailing hash reached the shell as a comment rather than being
escaped, since a protected sink would have left the file behind. A benign
control submitting an ordinary username created its destination containing
that username and produced no marker. The same replication was repeated on a
workstation under an ordinary unprivileged user account and reproduced the
semantics at that identity, ruling out the alternative explanation that
running as root caused the marker to appear. The harness removes any
pre-existing marker and destination before each run and deletes its scratch
files afterwards. Consequently the on-device landing identity of uid=0(root)
is inferred from the shipped User root directive plus the root-identity
replication and is not an observation from hardware; AV:N is argued from the
product's transport and authentication model rather than measured across a
network; the pptpUserName and L2TPUserName instances are a structural reading
of the same handler rather than a demonstrated result; the sibling-model
comparison is static only with nothing executed from that image and no score
covering it; no setup-state request was sent; no dictionary or credential
attack was attempted; and no overflow was developed against the unbounded
stack copy.

Proof-of-concept and full analysis, published unconditionally:
https://0day-rubbish.com/blog/edimax-br-6478ac-setwan-command-injection-root-rce

Companion advisory, same product and image, different handler:
https://0day-rubbish.com/blog/edimax-br-6478ac-formwlanmp-command-injection-root-rce

PoC location in the public repository:
product/edimax/br-6478ac-v2.20/setwan-command-injection-root-rce/exploit/edimax_br6478ac_setwan_command_injection_root_rce.py
Repository: https://github.com/Exploit-Garbage/0day-Rubbish

The shipped proof of concept is Python 3 using only the standard library, with
no third-party dependency and no hardcoded target, and it is built so that it
cannot be used accidentally against a live network. Its default mode is
replication: it reconstructs the exact string the device's sprintf produces at
0x441ee8 and hands it to /bin/sh -c locally, asserting both that the injected
pipeline ran and that the sink's own redirect target was not created, which is
what proves the trailing hash reached the shell as a comment, then running a
benign username as a negative control and asserting the opposite. That
replication mode, not a device run, produced the evidence above. Arguments
cover target host, Basic realm username and password, the WAN mode to select,
which of the three username fields carries the payload, an option to iterate
all three modes, a single non-destructive command defaulting to id, the marker
path, the submit-url field value defaulting to /wan.asp, a user agent and a
per-request timeout. A send mode additionally POSTs the request to the target
and is refused for any non-loopback target unless an explicit allow-remote
flag is given, and a print-request mode emits the exact HTTP request that
would be transmitted while making no connection at all. Safety rails are
loopback-only by default, one non-destructive marker command, no callback
listener, no reverse shell, no persistence, and removal of every local file
the run created. The tool's own documentation carries the warning that sending
against a real device does change stored WAN configuration, because that is
what the handler does on any successful submission, so the device's WAN
settings must be restored afterwards. The model's published factory password
appears in the tool only as a placeholder default so the request shape is
self-documenting; the injection does not depend on it and the credential is
reported as a separate weakness. Use only against systems you own or are
authorised to test.

--
0day Rubbish Research Team
disclosure@0day-rubbish.com
https://0day-rubbish.com