← 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

Zafiyet: Unauthenticated Firmware Update -> Missing Cryptographic Firmware Authentication

CVE: CVE-2026-73673

Araştırmacı: Özcan Ersan (@ozcanpng)


Giriş

Bu yazıda Netis NC63 router firmware’i V3.0.0.3327 üzerinde tespit edilen CVE-2026-73673 bulgusunu, yani unauthenticated firmware update with missing cryptographic firmware authentication problemini anlatıyorum.

Firmware, Boa ve netis.cgi üzerinden /cgi-bin/upload_fw.cgi endpoint’ini expose ediyor. Bu endpoint geçerli bir admin session olmadan firmware update path’ine ulaşabiliyor. Firmware container validation ise cryptographic signature yerine forge edilebilir bir checksum ve statik product string’lerine dayanıyor.

Bu çalışmadaki testler orijinal vendor firmware binary’leri ile izole bir MIPS32 little-endian runtime içinde yapıldı. Fiziksel router flash’lanmadı, MTD write yapılmadı ve kullanılan proof-of-concept dosyası özellikle non-bootable olacak şekilde hazırlandı.


Affected Target

AlanDeğer
VendorNetis Systems Co., Ltd.
ÜrünNetis NC63 Wireless AC1200 Router
FirmwareNC63_V3.0.0.3327
MimariMIPS32 little-endian
Web serverBoa/0.94.14rc21
CGI binary/bin/netis.cgi
Firmware updater/bin/fwd
EndpointPOST /cgi-bin/upload_fw.cgi

Test edilen image, vendor’ın NC63 download sayfasındaki kayıt ile eşleşiyor:

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

Original binary hashes


Kısa Özet

Vulnerability chain şu şekilde:

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

Buradaki kritik problem tek bir küçük bug değil; authentication boundary’nin kırılması:

  • Boa, authentication result’ı /tmp/boa_auth içine yazıyor.
  • Boa daha sonra içinde .cgi geçen tüm path’leri authorized kabul ediyor.
  • netis.cgi, /tmp/boa_auth değerini okuyor ama handler dispatch öncesinde authorization gate olarak kullanmıyor.
  • Firmware update handler, /bin/fwd çalıştırıyor.
  • Update formatında cryptographic firmware authentication bulunmuyor.

Root Cause 1: Boa .cgi Requests Are Allowed

Authentication state önce gönderilen cookie üzerinden hesaplanıyor. CGI request’lerinde Boa bu state’i /tmp/boa_auth içine yazıyor.

Fakat daha sonraki authorization branch, path içinde .cgi geçiyorsa request’i authorized kabul ediyor. Yani cookie check başarısız olsa bile CGI request çalıştırılabiliyor.

Decompile edilmiş mantık şu şekilde özetlenebilir:

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

Same Boa authorization routine için ek Ghidra evidence:

Ghidra: FUN_00409a0c Boa authentication branch

Bu karar CGI execution öncesinde veriliyor. Dolayısıyla konu sadece frontend’de menünün gizlenmesi değil, gerçek authorization flow’unun bozulması.


Root Cause 2: netis.cgi Trusts the Boundary

netis.cgi içinde authentication state, FUN_00404c70() ile /tmp/boa_auth dosyasından okunup global variable’a atanıyor.

Önemli nokta şu: dispatcher bu değeri kontrol edip request’i reject etmiyor. CGI handler resolve edilip doğrudan çağrılıyor.

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 is read but not enforced

İlgili Ghidra ekranları /tmp/boa_auth reader’ı, netis.cgi main flow’u, CGI dispatcher’ı ve handler resolver’ı gösteriyor:

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

Upload endpoint’i privileged CGI listesinde bulunuyor ve upload handler’a dispatch ediliyor.


Firmware Update Path

upload_fw.cgi handler’ı FUN_004144dc fonksiyonuna ulaşıyor. Handler, multipart upload data’sını shared memory içine alıyor ve sonunda /bin/fwd binary’sini çalıştırıyor.

/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, shared-memory upload identifier’larını parse ediyor, statik NC63/NETISVC product marker’ını hazırlıyor, uploaded buffer’ı attach ediyor ve ardından validation path’e giriyor:

Ghidra: FUN_004144dc upload parameter parsing and product validation

Validation branch upload’ı durdurmadığında final handler branch FUN_004028e0() üzerinden firmware daemon’ı çalıştırıyor:

Ghidra: FUN_004144dc reaches FUN_004028e0 fwd execution

Normal firmware container path’i cr6c header’ını, additive checksum’u ve statik NC63/NETISVC product suffix’ini kontrol ediyor. Update image’ını koruyan digital signature check, trusted certificate, signed manifest veya public-key verification routine tespit etmedim.

Bu ayrım önemli: checksum accidental corruption yakalayabilir, fakat firmware’in vendor tarafından üretildiğini kanıtlamaz.


Non-Destructive Runtime Validation

Request, orijinal Boa, netis.cgi ve fwd binary’leriyle izole runtime içinde tekrarlandı. Test request’inde Cookie veya Authorization header’ı yoktu.

Minimal request formatı:

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--

Runtime response:

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

["SUCCESS"]

Unauthenticated upload success

Harmless probe, orijinal updater’a ulaştı ve firmware validation boundary’yi geçti. İzole runtime içinde MTD device expose edilmediği için execution şu noktada durdu:

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

fwd PID and MTD boundary

No flash write in isolated runtime

Bu bilinçli bir safety boundary idi: validation ve updater reachability doğrulandı, fakat test ortamında flash write mümkün değildi.


Privilege Context

Orijinal Boa config dosyasında web server root olarak çalışıyor:

User root
Group root

Original Boa root config

Fiziksel cihaz üzerinde bu privilege context, firmware updater path’inin MTD block device’lara erişmesi için yeterli.


Impact

Management interface’e erişebilen unauthenticated attacker, structurally valid fakat unsigned bir firmware image gönderebilir.

Olası etkiler:

  • persistent router compromise
  • trusted firmware’in attacker-controlled firmware ile değiştirilmesi
  • traffic interception veya modification
  • credential theft
  • botnet enrollment
  • cihazın brick olması

Realistic default exposure genellikle router management interface’e adjacent-network access’tir. Remote management aktifse veya interface internete açılmışsa attack surface network-reachable hale gelebilir.


Classification

Suggested primary weaknesses:

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

CWE-306, firmware-update path’ini koruması gereken authentication boundary’nin eksikliğini kapsar. CWE-494 ise updater’a verilen firmware için yeterli cryptographic firmware authentication bulunmamasını kapsar.

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

Bu CVSS score, router management interface’in typical deployment’ta adjacent local network üzerinden reachable olduğu varsayımına dayanır. Management interface routed veya WAN-accessible bir network üzerinden exposed ise Attack Vector assessment farklı yapılabilir.


Disclosure Note

VulnCheck disclosure yazışmasında bu bulgu için CVE-2026-73673 ID’sini sağladı. Bu yazı yayınlandığı anda CVE kaydı henüz tamamen populate edilmemiş olabilir.

Destructive validation bilinçli olarak yapılmadı. Reporting için kullanılan PoC, gerçek flash write öncesinde duracak şekilde hazırlanmış non-bootable validation container’dı.


References


Final Thoughts

Bu bug, embedded sistemlerde authentication boundary’nin binary seviyesinde doğrulanması gerektiğini iyi gösteriyor. Frontend protected görünebilir, web server authentication result hesaplıyor olabilir, CGI process bu sonucu okuyor olabilir. Fakat privileged handler dispatch koşulsuz devam ediyorsa bunların hiçbiri yeterli değil.

Bu firmware’de sonuç, authentication gerektirmeden erişilebilen ve cryptographic firmware authentication içermeyen critical update path.