Mail-SpamAssassin
view release on metacpan or search on metacpan
lib/Mail/SpamAssassin/Plugin/OLEVBMacro.pm view on Meta::CPAN
# See the License for the specific language governing permissions and
# limitations under the License.
# </@LICENSE>
=head1 NAME
Mail::SpamAssassin::Plugin::OLEVBMacro - scan Office documents for evidence of OLE Macros or other exploits
=head1 SYNOPSIS
loadplugin Mail::SpamAssassin::Plugin::OLEVBMacro
ifplugin Mail::SpamAssassin::Plugin::OLEVBMacro
body OLEMACRO eval:check_olemacro()
describe OLEMACRO Attachment has an Office Macro
body OLEOBJ eval:check_oleobject()
describe OLEOBJ Attachment has an Ole Object
body OLERTF eval:check_olertfobject()
describe OLERTF Attachment has an Ole Rtf Object
body OLEMACRO_MALICE eval:check_olemacro_malice()
describe OLEMACRO_MALICE Potentially malicious Office Macro
body OLEMACRO_ENCRYPTED eval:check_olemacro_encrypted()
describe OLEMACRO_ENCRYPTED Has an Office doc that is encrypted
body OLEMACRO_RENAME eval:check_olemacro_renamed()
describe OLEMACRO_RENAME Has an Office doc that has been renamed
body OLEMACRO_ZIP_PW eval:check_olemacro_zip_password()
describe OLEMACRO_ZIP_PW Has an Office doc that is password protected in a zip
body OLEMACRO_CSV eval:check_olemacro_csv()
describe OLEMACRO_CSV Malicious csv file that tries to exec cmd.exe detected
body OLEMACRO_DOWNLOAD_EXE eval:check_olemacro_download_exe()
describe OLEMACRO_DOWNLOAD_EXE Malicious code inside the Office doc that tries to download a .exe file detected
body OLEMACRO_URI_TARGET eval:check_olemacro_redirect_uri()
describe OLEMACRO_URI_TARGET Uri inside an Office doc
body OLEMACRO_MHTML_TARGET eval:check_olemacro_mhtml_uri()
describe OLEMACRO_MHTML_TARGET Exploitable mhtml uri inside an Office doc
endif
=head1 DESCRIPTION
This plugin detects OLE Macros or other exploits inside Office documents
attached to emails. It can detect documents inside zip files as well as
encrypted documents.
=head1 REQUIREMENT
This plugin requires Archive::Zip and IO::String perl modules.
=head1 USER PREFERENCES
The following options can be used in both site-wide (C<local.cf>) and
user-specific (C<user_prefs>) configuration files to customize how
the module handles attached documents
=cut
package Mail::SpamAssassin::Plugin::OLEVBMacro;
use strict;
use warnings;
use Mail::SpamAssassin::Plugin;
use Mail::SpamAssassin::Util qw(compile_regexp get_part_details);
use constant HAS_ARCHIVE_ZIP => eval { require Archive::Zip; };
use constant HAS_IO_STRING => eval { require IO::String; };
BEGIN
{
eval{
Archive::Zip->import(qw( :ERROR_CODES :CONSTANTS ))
};
eval{
IO::String->import
};
}
use re 'taint';
use vars qw(@ISA);
@ISA = qw(Mail::SpamAssassin::Plugin);
our $VERSION = '4.00';
# https://www.openoffice.org/sc/compdocfileformat.pdf
# http://blog.rootshell.be/2015/01/08/searching-for-microsoft-office-files-containing-macro/
my $marker1 = "\xd0\xcf\x11\xe0";
my $marker2 = "\x00\x41\x74\x74\x72\x69\x62\x75\x74\x00";
# Office 2003 embedded ole
my $marker2a = "\x01\x00\x4f\x00\x6c\x00\x65\x00\x31\x00\x30\x00\x4e\x00\x61\x00";
# embedded object in rtf files (https://www.biblioscape.com/rtf15_spec.htm)
my $marker3 = "\x5c\x6f\x62\x6a\x65\x6d\x62";
my $marker4 = "\x5c\x6f\x62\x6a\x64\x61\x74";
my $marker5 = "\x5c\x20\x6f\x62\x6a\x64\x61\x74";
# CVE-2025-21298
my $marker6 = '{\rtf1{\object\objhtml\objw1\objh1\objupdate\rsltpict{\*\objclass ';
# Excel .xlsx encrypted package, thanks to Dan Bagwell for the sample
my $encrypted_marker = "\x45\x00\x6e\x00\x63\x00\x72\x00\x79\x00\x70\x00\x74\x00\x65\x00\x64\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x61\x00\x67\x00\x65";
# Excel .xls marker present only on unencrypted files
my $workbook_marker = "\x57\x00\x6f\x00\x72\x00\x6b\x00\x62\x00\x6f\x00\x6f\x00\x6b\x00";
# .exe file downloaded from external website
my $exe_marker1 = "\x00(https?://[-a-z0-9+&@#/%?=~_|!:,.;]{5,1000}[-a-z0-9+&@#/%=~_|]{5,1000}\.(?:exe|cmd|bat))[\x06|\x00]";
my $exe_marker2 = "URLDownloadToFileA";
# CVE-2021-40444 marker
my $mhtml_marker1 = "^MHTML:HTP:\\1&";
my $mhtml_marker2 = "^(?:mhtml:https?://|file:///)";
# this code burps an ugly message if it fails, but that's redirected elsewhere
# AZ_OK is a constant exported by Archive::Zip
my $az_ok;
eval '$az_ok = AZ_OK';
( run in 0.721 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )