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