[하루한줄] CVE-2025-0282: Ivanti Connect Secure의 Stack-based Buffer Overflow 취약점
URL
Target
- Ivanti Connect Secure < 22.7R2.5
Explain
Ivanti Connect Secure는 기업 네트워크에 대한 안전한 원격 액세스를 제공하는 VPN 솔루션으로 CVE-2025-0282는 여기서 발생한 Stack-based Buffer Overflow로 인한 RCE 취약점입니다.
취약점은 Ivanti Connect Secure가 TNC 메시지 전달에 사용되는 transport layer 프로토콜인 IF-T 패킷을 처리하는 과정에서 발생합니다.
먼저 아래의 코드는 clientCapabilities
키를 처리하는 부분을 간략화한 것입니다.
int __cdecl ift_handle_1(int a1, IftTlsHeader *a2, char *a3)
{
int v18;
int v19;
char dest[256]; // [esp+120h] [ebp-8ECh] BYREF
char object_to_be_freed[4]; // [esp+220h] [ebp-7ECh] BYREF
void *ptr; // [esp+224h] [ebp-7E8h]
int v20; // [esp+228h] [ebp-7E4h]
int v21; // [esp+22Ch] [ebp-7E0h]
int v22; // [esp+230h] [ebp-7DCh]
char v23; // [esp+234h] [ebp-7D8h]
char v24; // [esp+235h] [ebp-7D7h]
void *v25; // [esp+23Ch] [ebp-7D0h]
_DWORD v26[499]; // [esp+240h] [ebp-7CCh] BYREF
[..SNIP..]
clientCapabilities = getKey(req, "clientCapabilities");//[1]
if ( clientCapabilities != NULL )
{
clientCapabilitiesLength = strlen(clientCapabilities);
if ( clientCapabilitiesLength != 0 )
connInfo->clientCapabilities = clientCapabilities;
}
}
memset(dest, 0, sizeof(dest));
strncpy(dest, connInfo->clientCapabilities, clientCapabilitiesLength);//[2]
v24 = 46;
v25 = &v57;
if ( ((unsigned __int8)&v57 & 2) != 0 )
{
LOBYTE(v24) = 44;
v57 = 0;
v25 = (__int16 *)&v58;
}
memset(v25, 0, 4 * (v24 >> 2));
v26 = &v25[2 * (v24 >> 2)];
if ( (v24 & 2) != 0 )
*v26 = 0;
na = 46;
(*(void (__cdecl **)(int, __int16 *))(*(_DWORD *)a1 + 0x48))(a1, &v22);//[3]
isValid = 1;
EPMessage::~EPMessage((EPMessage *)v18);
DSUtilMemPool::~DSUtilMemPool((DSUtilMemPool *)object_to_be_freed);//[4]
return isValid;
}
[1]
에서는 패킷에 key=value
형태로 저장된 데이터 중에서도 clientCapabilities
의 값을 가져오고 [2]
에서는 이를 dest
로 복사하고 이때 strcpy
가 아닌 strncpy
를 사용해서 복사될 문자열의 최대 길이를 제한하고 있습니다.
언뜻 보면 안전한 코드처럼 보이지만 strncpy
의 길이를 데이터가 복사될 버퍼인 dest
의 길이인 256이 아니라 복사될 데이터의 길이인 clientCapabilitiesLength
로 사용했기 때문에 clientCapabilities
의 길이가 256보다 크다면 Stack-based Buffer Overflow가 발생합니다.
이때의 스택의 구조는 아래와 같습니다
+---------------------+ <- esp
~ ~
+---------------------+
| v18 (int) |
+---------------------+
| v19 (int) |
+---------------------+
| dest[256] | <- 256 bytes
+---------------------+
| object_to_be_freed | <- 4 bytes
+---------------------+
| ptr (void *) |
+---------------------+
| v20 (int) |
+---------------------+
| v21 (int) |
+---------------------+
| v22 (int) |
+---------------------+
| v23 (char) |
+---------------------+
| v24 (char) |
+---------------------+
| v25 (void *) |
+---------------------+
| v26[499] | <- 499 DWORDs (4 bytes each)
+---------------------+
| Return Address |
+---------------------+
| int a1 |
+---------------------+
| IftTlsHeader *a2 |
+---------------------+
취약점이 발생한 바이너리인 /home/bin/web
은 stack canary가 적용되어 있지 않지만 Overflow가 발생하는 버퍼인 dest
바로 이후에 위치한 object_to_be_freed
가 위 함수의 에필로그 직전([4]
)에 free
되기 때문에 해당 부분을 유효한 값으로 전달하지 못하면 변조된 return address로 ret
하기 전에 크래시가 발생합니다.
따라서 스택에 존재하는 a1
을 덮어쓰고 [3]
에서 변조된 a1
의 vftable을 통해 발생하는 간접호출을 이용해야합니다.
위 스택에서 a1
을 덮어쓰면 [3]
에서 해당 주소의 4바이트를 vftable의 주소로 가져오고 다시 해당 주소에 0x48을 더한 주소에서 가져온 함수를 호출합니다. 이를 통해 add esp
처럼 esp가 dest
이후를 가리키게 만들 stack pivot 가젯을 호출하고 ROP Chain을 실행하는 것이 가능합니다. 하지만 바이너리에는 ASLR과 PIE가 적용되어 있기 때문에 성공적인 익스플로잇에는 브루트 포싱이 필요로 합니다.
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.