[하루한줄] SLP heap overflow를 통한 VMware ESXi pre-auth RCE 취약점

URL

CVE-2020-3992 & CVE-2021-21974: PRE-AUTH REMOTE CODE EXECUTION IN VMWARE ESXI

Target

  • VMware ESXi
    • 7.0 before ESXi70U1c-17325551
    • 6.7 before ESXi670-202102401-SG
    • 6.5 before ESXi650-202102101-SG

Explain

VMware ESXi에서 pre-authentication remote code execution 취약점이 발견되었습니다. 해당 취약점은 Service Location Protocol(SLP) 서비스에서 발생합니다.

Service Location Protocol(SLP)는 VMware ESXi 기본 설치 시 TCP와 UDP의 427 포트를 리스닝하는 네트워크 서비스입니다. VMware는 OpenSLP 1.0.1을 기반으로 기능을 추가하여 서비스를 구현하였습니다. 이 서비스는 인증 없이 네트워크 input을 구문 분석하고 root 권한으로 실행하므로 ESXi SLP 서비스의 취약점을 통해 pre-auth remote code execution이 가능합니다.

VMware의 SLP 코드의 일부분은 다음과 같습니다.

__int64 __fastcall SLPParseSrvUrl(int srvurllen, const char *srvurl, _QWORD *a3)
{
// ...
  obuf = calloc(1uLL, srvurllen + 53LL);
  if ( !obuf )
    return 12LL;
  v6 = strstr(srvurl, ":/");        // <-- out-of-bounds string search
  if ( !v6 )
  {
    free(obuf);
    return 22LL;
  }
  memcpy((char *)obuf + 41, srvurl, v6 - srvurl);  // <-- heap overflow
// ...
}

srvurl은 네트워크 input을 통해 받는 값이지만 함수에서 strstr()을 사용하기 전에 srvurl의 끝에 NULL 바이트를 추가하지 않습니다. 따라서 범위 밖의 문자열을 검사하여 memcpy 부분에서 heap overflow가 발생합니다.

SLP를 통해 Remote Code Execution을 하기 위해 SLP에서 송수신에 사용되는 struct SLPBuffer의 구조는 다음과 같습니다.

typedef struct _SLPBuffer
{
    SLPListItem listitem;
    size_t  allocated;
    unsigned char*   start;
    unsigned char*   curpos;
    unsigned char*   end;
    // buffer data is appended
}*SLPBuffer;

typedef struct _SLPDSocket
{
    SLPListItem         listitem;
    int                 fd;
    time_t              age;
    int                 state;
// ...
    SLPBuffer           recvbuf; /* Incoming socket stuff */
    SLPBuffer           sendbuf;
// ...
}SLPDSocket;

VMware의 SLP 코드를 통해 Remote Code execution을 트리거하는 과정은 다음과 같습니다.

  1. connection->stateSTREAM_WRITE_FIRST로 덮어씁니다. 메모리를 leak 하기 위해 sendbuf->curpossendbuf->start로 리셋시켜야 합니다.
  2. sendbuf->start의 일부분을 2개의 널 바이트로 덮어씁니다. 연결 후 수신을 기다리면, sendbuf의 주소를 포함한 메모리를 leak 할 수 있습니다.
  3. mmap()으로 할당된 recvbuf를 leak 하기 위해 새로운 연결을 하고 sendbuf->curpos를 덮어씁니다. mmap 된 주소를 통해 libc base 주소를 얻을 수 있습니다.
  4. free_hook의 주소를 설정하기 위해 새로운 연결에서 recvbuf->curpos를 덮어씁니다. 연결 후 전송이 시작되면 free_hook을 덮어쓸 수 있습니다.
  5. 접속을 종료하면 free_hook이 호출되어 ROP 체인이 시작됩니다.