← back to blog
EN TR

CVE-2026-76071: Netis NC63 Pre-Auth ipFilterList Stack Buffer Overflow via Unbounded sscanf Scansets

On this page

Netis NC63 | CVE-2026-76071 | Pre-Auth ipFilterList Stack Buffer Overflow via Unbounded sscanf Scansets

Zafiyet: Pre-Authentication Stack Buffer OverflowRemote Code Execution Primitive

CVE: CVE-2026-76071

Resmi Başlık: Unauthenticated Pre-Auth Stack Buffer Overflow via sscanf %[^,] in skk_set.cgi ipFilterList Handler in Netis NC63

CVE Durumu: VulnCheck tarafından atandı; yazı hazırlanırken kayıt detayları henüz populate edilmemişti

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


Giriş

CVE-2026-76071, Netis NC63 /cgi-bin/skk_set.cgi endpoint’inin kullandığı generic MIB/value parser içindeki pre-auth stack-based buffer overflow zafiyetidir.

Request ipFilterList=mod seçtiğinde attacker-controlled destHost değeri FUN_0040f7f4 fonksiyonuna ulaşıyor. Type-0x0c parser değeri iki %[^,] conversion ile ayırıyor. İki destination array de 16 byte olmasına rağmen scanset’lerin hiçbirinde maximum field width yok.

Problem firmware’in sadece sscanf() kullanması değil. Asıl root cause capacity bilgisini taşımayan format: %[^,], destination boyutunu bilmeden comma olmayan byte’ları kopyalamaya devam ediyor. Direct HTTP request böylece saved stack state’i overwrite edebiliyor.

Original-hash production CGI üzerinde yapılan dinamik testler saved return-address ve PC control’ü doğruladı. Ayrı bir observation-only doğrulama, guarded /bin/sh argümanları loglayıp hiçbir şey çalıştırmazken attacker-controlled a0 marker’ının original binary’deki system() PLT path’ine ulaştığını gösterdi.


Etkilenen Hedef

AlanDeğer
VendorNetis Systems Co., Ltd.
ÜrünNetis NC63 AC1200 Wireless Dual Band Gigabit MU-MIMO Router
FirmwareNC63_V3.0.0.3327
MimariMIPS32r2 little-endian, o32 ABI, uClibc
CGI binary/bin/netis.cgi
EndpointPOST /cgi-bin/skk_set.cgi
TriggeripFilterList=mod
Dinamik test edilen parametredestHost
Statik aynı-parser coveragesrcHost
AuthenticationValidated path içinde yok
193f6a5e2ce65972b1805bf076f8d3521379a8441c8aaeb5ad0ba174bbee0792  netis_NC63_V3.0.0.3327.bin
eb298774c27070dc595fefcabb4e8c12a46cb5f4fd08f91c3ca92282c3a289a2  squashfs-root/bin/netis.cgi
23faa747b7d2f067aa5431bcc227ceca97a7977cf3e7c372f715cbba57f9209b  squashfs-root/bin/boa
e3fd0ee3013014d59b14a409fb4ee5bb546e7d32413758ab3ad4fb0f0d3dcc47  squashfs-root/lib/libapmib.so

Original production hash'leri


Kısa Özet

Unauthenticated HTTP client
  |
  | POST /cgi-bin/skk_set.cgi
  | ipFilterList=mod
  | destHost=1,0.0.0.0,<attacker-controlled comma-free bytes>
  v
FUN_004138a0
  v
FUN_004134c8
  v
FUN_00410898(request, "ipFilterList")
  v
FUN_0040f7f4(request, trigger, mib_table, pMib)
  |
  | get_request_param("destHost")
  v
sscanf(value, "%d,%[^,],%[^,]", ...)
  |
  | iki char[16] destination
  | maximum scanset width yok
  v
saved fp overwrite -> saved ra overwrite -> controlled MIPS PC

Dinamik olarak test edilen ikinci component ile saved ra arasındaki mesafe tam 112 byte.


Frontend ve External Request Formatı

Extracted vendor frontend srcHost ve destHost değerlerini üç comma-separated field olarak oluşturup ipFilterList ile birlikte gönderiyor:

param.srcHost = $("#src_host").val();
param.srcHost += "," + $("#src_ip_1").val();
param.srcHost += "," + $("#src_ip_2").val();

param.destHost = $("#dest_host").val();
param.destHost += "," + $("#dest_ip_1").val();
param.destHost += "," + $("#dest_ip_2").val();

param.ipFilterList = $("#ip_action").val();

request({
    url: "/cgi-bin/skk_set.cgi",
    data: param
});

Vendor ipFilterList request construction

Browser-side IP validation direct client’ı sınırlamaz. Server kendi destination width kontrolünü yapmak zorunda, ancak bu parser yapmıyor.


Authentication Boundary

HTTP-shaped CGI validation request içinde Cookie veya Authorization header bulunmuyordu. Disposable rootfs içinde /tmp/boa_auth özellikle yoktu ve QEMU şunu kaydetti:

open("/tmp/boa_auth", O_RDONLY) = -1 ENOENT

Buna rağmen execution skk_set.cgi, ipFilterList=mod ve vulnerable destHost conversion üzerinden devam etti.

Unauthenticated path ve production privilege

Broad CGI authorization failure, pre-auth erişimi sağlayan enabling condition’dır. CVE-2026-76071’in bağımsız memory-corruption root cause’u ise FUN_0040f7f4 içindeki widthless type-0x0c scanset parser’dır.


Handler ve Metadata Route

Statik analiz request path’ini şöyle map etti:

POST /cgi-bin/skk_set.cgi
  -> top-level route row at 0x00434080
  -> FUN_004138a0
  -> FUN_004134c8 (trigger: ipFilterList)
  -> FUN_00410898(request, "ipFilterList")
  -> FUN_0040f7f4(request, trigger, mib_table, pMib)

Original libapmib.so metadata içinde:

id=0x8f name='srcHost'  type=0x0c dst=0x21
id=0x90 name='destHost' type=0x0c dst=0x2a

İki isim de aynı vulnerable parser case’i seçiyor. Dynamic validation destHost ile yapıldı; srcHost, ayrı exploit iddiası değil aynı root cause için static coverage olarak sunuluyor.

Handler route ve MIB metadata


Root Cause: Scanset Width Eksikliği

Normalize edilmiş Ghidra pseudocode:

case 0x0c:
    value = get_request_param(request, metadata_name);
    sscanf(value,
           "%d,%[^,],%[^,]",
           &selector,
           first_ip_component,   /* char[16] */
           second_ip_component); /* char[16] */

    *(char *)(destination + field_offset) = selector;
    inet_aton(first_ip_component, destination + field_offset + 1);
    inet_aton(second_ip_component, destination + field_offset + 5);
    break;

Generic parser içindeki widthless scanset'ler

%[^,], comma veya terminator görene kadar comma olmayan bütün byte’ları tüketir. Width belirtilmediği için sscanf, iki array’in de yalnızca 16 byte olduğunu bilemez.

Capacity-aware format kabaca şu şekilde olmalıdır:

"%d,%15[^,],%15[^,]"

Bu değişiklik ayrıca exact return-count ve semantic validation ile birlikte uygulanmalıdır. Buradaki örnek vendor patch’i değil, root cause’u açıklayan mitigation örneğidir.


Stack Corruption Analizi

FUN_0040f7f4, 0x1d0-byte frame oluşturup control state’i frame’in sonuna yakın kaydediyor:

0040f7f4  addiu sp,sp,-0x1d0
0040f7f8  sw    ra,0x1cc(sp)
0040f7fc  sw    fp,0x1c8(sp)
0040f800  sw    s0,0x1c4(sp)

İki scanset destination:

00410550  addiu t8,fp,0x14c   # first char[16]
00410554  addiu v1,fp,0x15c   # second char[16]
00410564  addiu a1,...,0x19d4 # "%d,%[^,],%[^,]"
00410570  jal   sscanf

Saved ra, fp+0x1cc adresinde. İkinci destination ile arasındaki tam mesafe:

0x1cc - 0x15c = 0x70 = 112 byte

Exact stack layout ve saved-ra offset


Dinamik Doğrulama

Kontrollü Program Counter

Isolated proof, 112 padding byte sonrasında 0x0040f7f4 adresinin üç low little-endian byte’ını kullandı. sscanf tarafından eklenen NUL dördüncü byte’ı tamamlayarak f4 f7 40 00 oluşturdu.

QEMU CPU tracing iki normal parser call ve attacker-controlled ra restore edildikten sonra üçüncü entry kaydetti:

parser_entry_hit=3 pc=0x0040f7f4
GPR28: ... s8 41414141 ra 0040f7f4
total_parser_entry_hits=3
PASS: third parser entry is the overwritten saved RA.

Kontrollü üçüncü parser entry

Observation-Only RCE Boundary

İkinci private validation saved ra değerini original production system() PLT path’i 0x00423ab0 adresine yönlendirdi. Request-controlled data return anında exact MIPS a0 argument olarak kaldı. Disposable runtime /bin/sh yerine observation-only program mount etti:

argv[0]=</bin/sh>
argv[1]=<-c>
argv[2]=<NC63_IPFILTER_RCE_PROOF>
CONTROLLED_MARKER_PREFIX_REACHED
PASS: attacker-controlled request data reached system() as exact a0.
PASS: guarded /bin/sh recorded argv and executed no command.

Guard yalnızca final execution boundary’yi değiştiriyor. Overflow, saved ra, PC, a0 ve original system() PLT transfer production CGI içinde gerçekleşiyor.


Runtime Scope ve Limitler

Test edilen /bin/netis.cgi, vendor-extracted production binary ile byte-identical. Physical flash-backed MIB qemu-user altında bulunmadığından disposable rootfs zeroed MIB state allocate eden ve bir packed-field alignment ayarı yapan disclosed lab-only libapmib.so accommodation kullandı.

Bu accommodation netis.cgi, request parser, vulnerable sscanf, local buffer’lar, frame layout, epilogue, saved-return offset veya system() path’i değiştirmedi. Static MIB metadata original vendor library’den alındı.

Production CGI fixed-base, stack canary ve RELRO yok, executable stack ve RWX segment bulunuyor.

Binary hardening durumu

Physical router exploit reliability, deployed-kernel randomization ve default WAN management exposure ayrıca test edilmedi.


Güvenli Public Proof of Concept

Public repository’de malformed destHost için dry-run-by-default generator bulunuyor:

python3 poc/poc.py

Request göndermek explicit authorized target gerektiriyor:

python3 poc/poc.py --target http://192.168.1.1 --send

Public payload 115-byte B component kullanıyor ve CGI process’i crash edebilir. Return-to-system değeri, command, shellcode, reverse shell veya persistence içermiyor. RCE boundary hazır saldırı zinciri olarak değil, non-destructive runtime evidence ile belgeleniyor.


Etki

Management interface’e erişebilen unauthenticated attacker, unbounded destHost scanset’ini exploit ederek privileged router-management context içinde attacker-selected command çalıştırabilir. Original Boa configuration User root ve Group root kullanıyor.

Olası etkiler:

  • tam router compromise
  • router configuration ve stored secret’ların disclosure veya modification’ı
  • trusted configuration ve service’lerin değiştirilmesi
  • DNS, firewall ve routing manipulation
  • traffic interception, modification veya redirection
  • post-compromise erişim üzerinden credential theft
  • olası bir follow-on action olarak persistent compromise
  • management service veya cihaz disruption

Default realistic exposure genellikle router management interface’e adjacent-network access’tir. Remote management aktifse veya interface başka bir şekilde exposed ise attack surface network-reachable hale gelebilir. Persistence ve physical-device exploit reliability dinamik olarak test edilmedi.


Sınıflandırma

Bu yazı hazırlanırken CVE ID atanmış fakat kayıt henüz populate edilmemişti. Aşağıdakiler researcher assessment’tır:

Researcher-assessed CVSS v3.1 (typical adjacent management network): 8.8
CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Conditional routable-management score: 9.8
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
  • CWE-121: Stack-based Buffer Overflow
  • Related: CWE-120 — Buffer Copy without Checking Size of Input

Mitigation

  1. İki scanset de 16-byte destination’a göre sınırlandırılmalı; örneğin %15[^,].
  2. Tam olarak üç conversion başarılı olmadıkça işlem reddedilmeli.
  3. Oversized serialized value parsing öncesinde reddedilmeli.
  4. İki IP değeri de storage öncesinde server-side doğrulanmalı.
  5. Privileged CGI dispatch öncesinde administrator authorization uygulanmalı.
  6. Bütün metadata parser case’leri widthless %s ve %[...] için audit edilmeli.
  7. Binary stack canary, PIE, NX ve RELRO ile yeniden build edilmeli.

Disclosure Timeline

  • 2026-08-16: discovery ve isolated production-binary validation tamamlandı.
  • Ağustos 2026: VulnCheck’e raporlandı.
  • 2026-08-20: VulnCheck CVE-2026-76071 ID’sini atadı ve public disclosure izni verdi.
  • 2026-08-20: public disclosure paketi hazırlandı; remote publication explicit push onayını bekliyor.

Disclosure Note

VulnCheck, CVE-2026-76071 ID’sini 2026-08-20 tarihinde atadı ve public disclosure izni verdi.

Destructive validation bilinçli olarak yapılmadı. Public proof of concept yalnızca non-executable overlong-field pattern kullanıyor. RCE validation observation-only /bin/sh -c boundary’sinde durduruldu ve hiçbir komut çalıştırılmadı.


Referanslar

Physical router flash’lanmadı. Gerçek command, reverse shell, persistence, external connection, credential theft veya destructive operation kullanılmadı.