[Mimedefang] survey: dropping password protected file
Clayton, Nik [IT]
nik.clayton at citigroup.com
Fri Mar 5 09:42:27 EST 2004
In re using Archive::Zip for zip file scanning.
> Just started doing this here (well, I'll be testing it next
> week). I might be able to send you proof of concept code if
> I can get the necessary sign off.
I got the necessary sign off today, so here are the changes I've
got planned. Comments welcomed.
--- snippets of mimedefang-filter
# Near the top of the filter
use File::Path;
use File::Temp qw(tempdir);
use Archive::Zip qw(:ERROR_CODES);
# Archive::Zip defaults to being verbose about error reporting, which
# we don't need
Archive::Zip::setErrorHandler( sub { return; } );
[...]
# Pull the list of nasty filenames and extensions out in to global
# variables, because now we need to access them from multiple subs.
#
# While I'm here, use /x to make the regexes more readable.
my $BAD_FILENAMES = qr/(?: # Non capturing grouping
application| # These are all nasty .zip files
document|
message|
movie|
myphoto|
photos|
pp-app|
readnow|
screensaver|
wendy|
your_details)\.zip
/x;
my $BAD_EXTS = qr/(?: # Non capturing grouping
\{[^\}]+\}| # CLSID attacks
ade|adp|
bas|bat|
chm|cmd|com|
cpl|crt|
dll|
exe|
hlp|hta|
inf|ini|ins|isp|
jse?|
lib|lnk|
mdb|mde|mht|
mid|mp3|msc|
msi|msp|mst|
nws|
ocx|
pcd|pif?|
reg|rm|
scr|sct|shb|shs|
swf|sys|
url|
vb|vbe|vbs|vi|
vxd|
wma|ws|wsc|wsf|
wsh|
zi
)/x;
my $BAD_RE = qr/(?:$BAD_FILENAMES)| # Any of the bad filenames, or
(?:\.$BAD_EXTS) # any of the bad extensions
\.* # followed by zero or more dots
(?: # and either
[^-A-Za-z0-9_.,]| # a non-alphanumeric, or
$ # the end of the string
)
/x;
# filter_bad_filename() seems like a reasonable place to hook in the .zip
# file scanning.
#
# In my ideal world, filter_bad_filename (and filter_zip) would call
# action_bounce() (or the other action_* routines) themselves, instead of
# letting their call do it.
sub filter_bad_filename ($) {
my($entity) = @_;
# If it matches a banned extension, fail
return 1 if re_match($entity, $BAD_RE);
# If it looks like it contains a .zip file, check inside it for
# banned extensions and other nastiness
if(re_match($entity, qe/\.zip/i)) {
return 1 if filter_zip($entity);
}
# Play it safe -- continue blocking .zip files for the moment
return 1 if re_match($entitity, qr/\.zip\.*(?:[^-A-Za-z0-9_.,]|$)/);
# All looks good, return 0
return 0;
}
# Filter zip files. Look through them, seeing if any of the files they
# contain (or any files in any zip files they contain) should be blocked.
#
# Returns true if we should block the file
my $ZIP_SCRATCH = 10 * 1024 * 1024; # 10MB scratch space when recursing
my $MEMBER_LIMIT = 25 * 1024 * 1024; # Compressed files should be no
bigger
# than this when uncompressed
sub filter_zip {
my $entity = shift;
my $csize = shift || 0; # Cumulative size seen so far
my $chain = shift || 'MSG'; # Chain of filenames for logging
my $r = undef; # Return value from recursion
my $zip = Archive::Zip->new();
if($zip->readFromFileHandle($entity->open('r')) == AZ_OK) {
# Look for EOCD offset problems, generally indicates a virus
if($zip->eocdOffset()) {
md_graphdefang_log("filter_zip: '$chain' non-zero EOCD offset");
return 1;
}
my @members = $zip->members();
foreach my $member (@members) {
my $file = $member->fileName();
my $size = $member->uncompressedSize();
if($member->isEncrypted()) {
md_graphdefang_log("filter_zip: '$chain:$file' encrypted zip
contents not allowed");
return 1;
}
if(lc($file) =~ /\.zip/) {
if($csize + $size >= $ZIP_SCRATCH) {
# Recursing in to this .zip file would exceed our
scratch
# space limit
md_graphdefang_log("filter_zip: '$chain:$file' space
limit exceeded");
return 1;
}
# Create a temp directory, extract the .zip file in to it,
# recurse, and clean up.
my $tmpdir = tempdir(CLEANUP => 1);
if($zip->extractMember($member, "$tmpdir/tmp.zip") == AZ_OK)
{
$r = filter_zip("$tmpdir/tmp.zip", $csize + $size,
"$chain:$file");
} else {
md_graphdefang_log("filter_zip: '$chain:$file'
decompression failed");
$r = 1;
}
rmtree($tmpdir, 0, 0);
} else {
# Fail if the file is over the per-member limit
if($size >= $MEMBER_LIMIT) {
md_graphdefang_log("filter_zip: '$chain:$file'
per-member limit exceeded, file is $size");
return 1;
}
# Fail if the file fails the regex checks
if($file =~ /$BAD_RE/i) {
md_graphdefang_log("filter_zip: '$chain:$file' fails
regex checks");
return 1;
}
}
return $r if $r;
}
} else {
# Couldn't open the .zip file, fail
md_graphdefang_log("filter_zip: '$chain' readFromFileHandle()
failed");
return 1;
}
return 0;
}
More information about the MIMEDefang
mailing list