
Investigación original y PoC para un desbordamiento de búfer en la pila previo a la autenticación mediante un conjunto de escaneo sscanf sin límites en el controlador ipFilterList del Netis NC63
sscanf %[^,] en el manejador ipFilterList de skk_set.cgi en Netis NC63Investigador: Özcan Ersan (@ozcanpng)
CVE-2026-76071NC63_V3.0.0.3327/bin/netis.cgiPOST /cgi-bin/skk_set.cgiipFilterList=moddestHostsrcHostEl analizador genérico de MIB/valores en el firmware V3.0.0.3327 del Netis NC63 analiza el
campo destHost de ipFilterList con dos conjuntos de exploración %[^,] pero sin anchos
máximos de campo. Cada conversión escribe en un búfer local de pila de 16 bytes. Por lo tanto, un cliente
HTTP directo puede enviar un componente largo sin comas y sobrescribir los
datos de control guardados de la función.
Para el segundo componente destHost probado dinámicamente, el registro ra guardado está exactamente
a 112 bytes del búfer local. El trazado de QEMU contra el CGI con hash original
confirmó una tercera entrada seleccionada por el atacante en 0x0040f7f4. Una prueba separada
de solo observación redirigió el retorno a la ruta PLT original de system() en 0x00423ab0
conservando los datos de la solicitud controlados por el atacante como el
argumento a0 exacto de MIPS. Un /bin/sh protegido registró el marcador y no ejecutó ningún
comando.
El PoC público contiene deliberadamente solo un patrón B demasiado largo. No
incluye el valor privado de transferencia de control ni la construcción del límite de comando.
El script de RCE mantenido por separado en poc/exploit-db/ está preparado para un
envío a Exploit-DB y está destinado únicamente a pruebas autorizadas de sistemas
afectados.
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

El frontend del proveedor construye srcHost y destHost como tres componentes
separados por comas y los envía con ipFilterList a /cgi-bin/skk_set.cgi:
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
});

La solicitud CGI validada con forma HTTP no contenía ningún encabezado Cookie ni Authorization. /tmp/boa_auth estaba ausente, sin embargo la ejecución continuó hacia la ruta del analizador ipFilterList=mod y destHost. El defecto de corrupción de memoria es el conjunto de escaneo sin ancho en FUN_0040f7f4; el fallo de autorización CGI más amplio es la condición que expone este manejador privilegiado antes de la autenticación.

Unauthenticated HTTP client
|
| POST /cgi-bin/skk_set.cgi
| ipFilterList=mod
| destHost=1,0.0.0.0,<long comma-free component>
v
FUN_004138a0
v
FUN_004134c8 (ipFilterList trigger row)
v
FUN_00410898(request, "ipFilterList")
v
FUN_0040f7f4(request, trigger, mib_table, pMib)
|
| get_request_param("destHost")
v
sscanf(value, "%d,%[^,],%[^,]", ...)
|
| second destination: char[16]
| no maximum scanset width
v
saved fp overwrite -> saved ra overwrite -> controlled PC
Los metadatos originales de libapmib.so asignan tanto srcHost como destHost al tipo 0x0c, lo que selecciona este caso del analizador. La validación dinámica se realizó con destHost; srcHost se incluye como cobertura estática del mismo analizador en lugar de una afirmación dinámica separada.

Pseudocódigo normalizado derivado de Ghidra:
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;

sscanf() no es intrínsecamente la vulnerabilidad. El defecto es que %[^,]
no tiene un ancho máximo de campo, por lo que sscanf no tiene conocimiento de que cada destino
tiene solo 16 bytes. Un formato consciente de la capacidad usaría un ancho como
%15[^,], verificaría que exactamente tres conversiones tuvieron éxito y luego validaría
los valores de dirección analizados. Este es un ejemplo de mitigación, no un parche del proveedor.
FUN_0040f7f4 comienza en 0x0040f7f4 y crea un marco de 0x1d0 bytes:
0040f7f4 addiu sp,sp,-0x1d0
0040f7f8 sw ra,0x1cc(sp)
0040f7fc sw fp,0x1c8(sp)
0040f800 sw s0,0x1c4(sp)
Los destinos de tipo 0x0c están en fp+0x14c y fp+0x15c. El ra guardado está en fp+0x1cc, lo que hace que la distancia exacta desde el segundo búfer sea:
0x1cc - 0x15c = 0x70 = 112 bytes
