← back to blog
EN TR

CVE-2026-73673: Netis NC63 Unauthenticated Firmware Update with Missing Cryptographic Firmware Authentication

On this page

Netis NC63 | CVE-2026-73673 | Unauthenticated Firmware Update with Missing Cryptographic Firmware Authentication

Vulnerability: Unauthenticated Firmware Update -> Missing Cryptographic Firmware Authentication

CVE: CVE-2026-73673

Researcher: Özcan Ersan (@ozcanpng)


Introduction

This post documents CVE-2026-73673, an unauthenticated firmware update with missing cryptographic firmware authentication in the Netis NC63 router firmware V3.0.0.3327.

The affected firmware exposes /cgi-bin/upload_fw.cgi through Boa and netis.cgi. The request can reach the firmware update path without a valid administrator session, and the firmware container validation relies on a forgeable checksum and static product strings instead of a cryptographic signature.

The testing described here was performed against the original vendor firmware binaries in an isolated MIPS32 little-endian runtime. No physical router was flashed, no MTD write was performed, and the proof-of-concept artifact was intentionally non-bootable.


Affected Target

FieldValue
VendorNetis Systems Co., Ltd.
ProductNetis NC63 Wireless AC1200 Router
FirmwareNC63_V3.0.0.3327
ArchitectureMIPS32 little-endian
Web serverBoa/0.94.14rc21
CGI binary/bin/netis.cgi
Firmware updater/bin/fwd
EndpointPOST /cgi-bin/upload_fw.cgi

The tested image matches the vendor’s NC63 download entry:

  • Firmware name: netis_NC63_V3.0.0.3327
  • Published date: 01-02-2024
  • Listed size: 7.02MB
  • Vendor page: Netis NC63 downloads

Original binary hashes


Short Version

The vulnerable chain is:

Unauthenticated HTTP client
  |
  | POST /cgi-bin/upload_fw.cgi
  | multipart/form-data field: update
  v
Boa
  |
  | broad ".cgi" authorization branch
  v
/bin/netis.cgi
  |
  | reads /tmp/boa_auth but does not enforce it before dispatch
  v
FUN_004144dc
  |
  | validates cr6c header, checksum, and NC63/NETISVC product tag
  v
RunSystemCmd("fwd <shmid> <size> ...")
  v
/bin/fwd
  |
  v
open("/dev/mtdblock0", O_RDWR)

The critical issue is not one isolated bug. It is a boundary failure:

  • Boa records an authentication result in /tmp/boa_auth.
  • Boa then allows every path containing .cgi.
  • netis.cgi reads /tmp/boa_auth but does not use it as an authorization gate before invoking the requested handler.
  • The firmware update handler starts /bin/fwd.
  • The update format is not protected by cryptographic firmware authentication.

Root Cause 1: Boa Allows .cgi Requests

The authentication state is first derived from the submitted cookie. For CGI requests, Boa writes that state into /tmp/boa_auth.

However, a later authorization branch treats any pathname containing .cgi as authorized. That means a failed cookie check can still become an allowed CGI request.

The relevant decompiled logic is equivalent to:

auth = 0;

if (cookie_matches_expected_value)
    auth = 1;

if (strstr(request_path, ".cgi") != NULL)
    system("echo <auth> >/tmp/boa_auth");

if (is_public_static_resource(request_path) ||
    strstr(request_path, "config.dat") != NULL ||
    strstr(request_path, ".cgi") != NULL) {
    auth = 1;
}

if (auth == 0)
    redirect_to_login();

Boa CGI authorization branch

Additional Ghidra evidence for the same Boa authorization routine:

Ghidra: FUN_00409a0c Boa authentication branch

This decision happens before CGI execution, so it is not just a frontend visibility problem.


Root Cause 2: netis.cgi Trusts the Boundary

Inside netis.cgi, the authentication state is read from /tmp/boa_auth through FUN_00404c70() and assigned to a global variable.

The important part is what does not happen: the dispatcher does not reject the request before resolving and invoking the CGI handler.

apmib_init();
parse_parameters(&params, argv[1]);
DAT_00439e30 = FUN_00404c70();

if (strstr(cgi_name, ".cgi") != NULL)
    FUN_00405244(cgi_name, &params, output_path);

Auth state read but not enforced

The related Ghidra views show the /tmp/boa_auth reader, netis.cgi main flow, CGI dispatcher, and handler resolver:

Ghidra: FUN_00404c70 reads /tmp/boa_auth

Ghidra: netis.cgi main reads auth state before dispatch

Ghidra: FUN_00405244 CGI dispatcher

Ghidra: FUN_00405300 CGI handler resolver

The upload endpoint is present in the privileged CGI list and is dispatched to the upload handler.


Firmware Update Path

The upload handler for upload_fw.cgi reaches FUN_004144dc. The handler stages uploaded multipart data in shared memory and eventually invokes /bin/fwd.

/cgi-bin/upload_fw.cgi
  -> FUN_004144dc
  -> isUpgrade("upload_fw")
  -> RunSystemCmd("fwd %d %d > /dev/null 2>&1 &", shmid, size)
  -> /bin/fwd

netis.cgi to fwd path

FUN_004144dc parses the shared-memory upload identifiers, prepares the static NC63/NETISVC product marker, attaches the uploaded buffer, and then enters the validation path:

Ghidra: FUN_004144dc upload parameter parsing and product validation

The final handler branch calls the firmware daemon through FUN_004028e0() after the validation branch does not stop the upload:

Ghidra: FUN_004144dc reaches FUN_004028e0 fwd execution

The normal firmware container path checks the cr6c header, an additive checksum, and the static product suffix NC63/NETISVC. I did not identify a digital signature check, trusted certificate, signed manifest, or public-key verification routine protecting the update image.

That distinction matters: a checksum can detect accidental corruption, but it does not prove that firmware came from the vendor.


Non-Destructive Runtime Validation

The request was reproduced through the original Boa, netis.cgi, and fwd binaries in an isolated runtime. The test request did not include a Cookie or Authorization header.

Minimal request shape:

POST /cgi-bin/upload_fw.cgi HTTP/1.0
Host: 127.0.0.1:28081
Content-Type: multipart/form-data; boundary=BOUNDARY
Content-Length: ...

--BOUNDARY
Content-Disposition: form-data; name="update"; filename="probe.bin"
Content-Type: application/octet-stream

<harmless cr6c validation container>
--BOUNDARY--

The runtime returned:

HTTP/1.0 200 OK
Set-Cookie: path=/ ;Max-Age=0

["SUCCESS"]

Unauthenticated upload success

The harmless probe reached the original updater and crossed the firmware validation boundary. The isolated runtime intentionally did not expose MTD devices, so the execution stopped at:

open("/dev/mtdblock0", O_RDWR) = -1 ENOENT

fwd PID and MTD boundary

No flash write in isolated runtime

This was the intended safety boundary: validation and updater reachability were confirmed, but no flash write was possible in the test environment.


Privilege Context

The original Boa configuration runs the web server as root:

User root
Group root

Original Boa root configuration

On a physical router, that privilege context is sufficient for the firmware updater path to access MTD block devices.


Impact

An unauthenticated attacker who can reach the management interface may be able to submit a structurally valid unsigned firmware image.

Potential impact includes:

  • persistent router compromise
  • replacement of trusted firmware with attacker-controlled firmware
  • traffic interception or modification
  • credential theft
  • botnet enrollment
  • device bricking

The default realistic exposure is usually adjacent-network access to the router management interface. If remote management is enabled or the interface is otherwise exposed, the attack surface can become network-reachable.


Classification

Suggested primary weaknesses:

  • CWE-306: Missing Authentication for Critical Function
  • CWE-494: Download of Code Without Integrity Check

CWE-306 captures the missing authentication boundary protecting the firmware-update function, while CWE-494 captures the lack of sufficient cryptographic firmware authentication for firmware supplied to the updater.

Suggested CVSS v3.1:

8.8 (High)
CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

This score assumes the typical deployment in which the router management interface is reachable from the adjacent local network. Deployments exposing the management interface over a routed or WAN-accessible network may warrant a different Attack Vector assessment.


Disclosure Note

VulnCheck provided CVE-2026-73673 for this finding in disclosure correspondence. The CVE record may not be populated immediately at the time this post is published.

I intentionally avoided destructive validation. The proof-of-concept used for reporting was a non-bootable validation container designed to stop before any real flash write.


References


Final Thoughts

This bug is a good example of why embedded authentication boundaries need to be verified at the binary level. The frontend may look protected, the web server may calculate an authentication result, and the CGI process may even read that result. None of that matters if the privileged handler dispatch still happens unconditionally.

In this firmware, the result is a critical update path reachable without authentication and without cryptographic firmware authentication.