
Netis NC63 login.cgi의 인증 전 Base64 디코딩 비밀번호 스택 버퍼 오버플로우에 대한 독자적 연구 및 PoC
login.cgi의 Base64 디코딩된 비밀번호를 통한 인증 전 사전 인증 스택 버퍼 오버플로로 인한 RCE연구자: Özcan Ersan (@ozcanpng)
CVE-2026-76070NC63_V3.0.0.3327/bin/netis.cgiPOST /cgi-bin/login.cgipasswordNetis NC63 펌웨어 V3.0.0.3327의 공개 로그인 핸들러는 공격자가 제어하는
password 매개변수를 가져와 사용자 정의 Base64 루틴 FUN_00402bd4로 디코딩한다.
호출자는 64바이트 로컬 스택 버퍼를 제공하지만 그 용량을 디코더에 전달하지 않는다.
디코더는 인코딩된 입력으로부터 작업량을 도출하고 대상 끝을 확인하지 않은 채
디코딩된 바이트를 기록한다.
저장된 MIPS 반환 주소는 디코딩된 버퍼의 시작으로부터 136바이트 떨어져 있다. 원본 해시 프로덕션 CGI에 대한 동적 테스트로 다음이 확인되었다:
B 패턴은 0x42424242에서 폴트를 발생시킨다;ra를 0x0041a2e0으로 교체하면 로그인 핸들러에서 두 번째 진입이
관찰되어 프로그램 카운터 제어가 입증된다; 그리고a0 값으로 원본 바이너리의
직접 system() 호출에 도달한다. 대체된 /bin/sh는 /bin/sh -c NC63_RCE_PROOF를
기록했으며 어떤 명령도 실행하지 않았다.이 저장소의 공개 PoC는 의도적으로 크래시 패턴에서 멈춘다. 여기에는 반환 체인,
셸코드, 명령, 리버스 셸 또는 지속성이 포함되지 않는다. poc/exploit-db/에서
별도로 유지 관리되는 RCE 스크립트는 Exploit-DB 제출용으로 준비되었으며, 영향받는
시스템의 승인된 테스트에만 사용하도록 의도되었다.
193f6a5e2ce65972b1805bf076f8d3521379a8441c8aaeb5ad0ba174bbee0792 netis_NC63_V3.0.0.3327.bin
23faa747b7d2f067aa5431bcc227ceca97a7977cf3e7c372f715cbba57f9209b squashfs-root/bin/boa
eb298774c27070dc595fefcabb4e8c12a46cb5f4fd08f91c3ca92282c3a289a2 squashfs-root/bin/netis.cgi
동적으로 테스트된 /bin/netis.cgi 사본은 벤더에서 추출한 실행 파일과 동일한 SHA-256을 가집니다.

벤더 프론트엔드는 비밀번호를 Base64로 공개 엔드포인트에 전송합니다:
obj.password = base64encode(utf16to8(password));
request({
url: "/cgi-bin/login.cgi",
data: obj
});
HTML 필드는 maxlength="63"을 사용하지만, 이는 브라우저 측 제한일 뿐입니다. 직접 HTTP 클라이언트는 더 큰 인코딩된 값을 제출할 수 있습니다.

login.cgi는 인증 전에 반드시 접근 가능합니다. 안전하지 않은 디코딩은 디코딩된 비밀번호가 설정된 관리자 비밀번호와 비교되기 전에 발생합니다. 유효한 세션, Cookie 헤더, Authorization 헤더 또는 올바른 비밀번호가 필요하지 않습니다.
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)
|
| no destination-capacity argument
| decoded output exceeds 64 bytes
v
saved s8 at decoded offset 132
saved ra at decoded offset 136
|
v
attacker-selected MIPS PC
가독성을 위해 이름을 정규화한 Ghidra 기반 의사코드:
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의 디코더는 목적지 포인터와 소스 포인터만을 받는다.
이 디코더의 루프는 목적지 포인터를 전진시키며, Base64 심볼 네 개마다 최대 세 개의 디코딩된 바이트를 저장한다. 목적지가 decoded + 64를 넘지 않는지 확인하는 비교는 존재하지 않는다.

Base64는 입력 변환일 뿐, 근본적인 결함은 아니다. 근본 원인은 공격자가 제어하는 디코딩된 길이와, 용량이 결코 강제되지 않는 고정 크기 목적지 사이의 불일치이다. 일반적인 패딩 입력의 경우 인코딩된 문자 네 개가 최대 세 개의 디코딩된 바이트를 나타내므로, 서버 측 검사는 쓰기 전에 디코딩된 크기를 계산하고 검증해야 한다.
FUN_0041a2e0은 0x0041a2e0에서 시작하며 0xa8바이트 프레임을 생성한다:
0041a2e0 addiu sp,sp,-168
0041a2e4 sw ra,164(sp)
0041a2e8 sw s8,160(sp)
0041a2ec move s8,sp
디코딩된 목적지는 s8+0x1c에서 시작하며, 저장된 s8과 저장된 ra는
s8+0xa0과 s8+0xa4에 있습니다:
decoded[64] s8+0x1c decoded offset 0
saved s8 s8+0xa0 decoded offset 132
saved ra s8+0xa4 decoded offset 136
정확한 반환 주소 거리는 0xa4 - 0x1c = 0x88, 즉 136바이트입니다.

140바이트로 디코딩된 B 패턴이 4바이트 저장된 반환 주소를 대체했습니다:
--- SIGSEGV {si_signo=SIGSEGV, si_code=1, si_addr=0x42424242} ---
qemu: uncaught target signal 11 (Segmentation fault)

별도의 140바이트 입력 세트가 ra를 0x0041a2e0으로 저장했습니다. QEMU CPU 트레이싱은
평범한 첫 번째 핸들러 진입에 이어 s8=0x41414141 및 ra=0x0041a2e0을 가진
두 번째 진입을 기록했습니다.

원본 바이너리에는 0x0041a3cc에 직접적인 jal system이 포함되어 있습니다. 비공개
격리 검증에서, 기존의 고정 베이스 명령어들이 마커를 a0에 로드하여 해당 호출에
도달했습니다. 정적 관찰 프로그램이 /bin/sh 위에 마운트되었으며, 이 프로그램은
명령 인터프리터 인자를 기록하고 아무것도 실행하지 않았습니다:
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.
이는 격리된 프로덕션 코드 경로에서의 RCE 프리미티브를 보여줍니다. 이는 실제 배포된 커널 및 스택 무작위화 구성 하의 물리적 라우터에서 동일한 익스플로잇 신뢰성을 입증하지는 않습니다.
원본 Boa 구성은 User root, Group root, 그리고 /bin 및 /web/cgi-bin을 포함하는 CGI 경로를 지정합니다. 프로덕션 실행 파일은 고정 베이스(0x00400000)이며, 스택 카나리나 RELRO가 없고, RWX 세그먼트를 가진 실행 가능한 GNU 스택을 선언합니다.


포함된 스크립트는 기본적으로 드라이런 모드로 동작하며, 디코딩 후 140개의 B 바이트를 포함하는 Base64 폼 본문만 생성합니다:
python3 poc/poc.py
전송하려면 명시적으로 승인된 대상과 --send가 필요합니다:
python3 poc/poc.py --target http://192.168.1.1 --send
poc/exploit-db/CVE-2026-76070.py는 Exploit-DB용으로 준비된 RCE 스크립트입니다.
이 스크립트는 문서화된 인증 없는 login.cgi 취약점을 대상으로 하며,
권한이 부여된 영향을 받는 테스트 대상에 사용할 명령 인자를 받습니다:
python3 poc/exploit-db/CVE-2026-76070.py <target> "id"
해당 패턴을 전송하면 CGI 프로세스가 충돌할 수 있습니다. 승인된 일회용 환경에서만 사용하십시오. PoC는 비공개 RCE 검증 체인을 구현하지 않습니다.
성공적인 익스플로잇은 라우터 관리 컨텍스트에서 공격자가 선택한 코드나 명령을 실행할 수 있습니다. 원래 Boa 구성에서는 해당 컨텍스트가 root로 실행됩니다. 잠재적 결과로는 구성 및 비밀 정보 노출, DNS/방화벽/라우팅 조작, 트래픽 리다이렉션, 서비스 중단, 그리고 전체 기기 장악이 포함됩니다.