What ?
Once upon a time, when we didn't have virus scanning installed on our
MTA, we decided to filter out Win32 executable attachments, so clueless
users would be somehow protected against the villains out there, and against
other clueless users. We also didn't want our staff or users to spread
viruses to the rest of the world, destroying our sysadmin reputation.
As good BOFHs, we imposed a "no executables in mails" policy
on our users, for inbound and outbound mail through our MTA. After all,
very few people really need to send out executable attachments in mail,
and if they really need to, they can still zip it.
How ?
As PIKT diehards, we of course wanted to use it for this task. We also
wanted PIKT to report mail abuse attemps (executable attachements mostly,
but also relaying attempts and other usual errors reported by the MTA).
So we used PIKT to create the Postfix 'body_checks' file, which is parsed
by postfix at startup. When postfix receives a mail (from MUA or MTA),
it tries to match every single line of mail against the regular expressions
in body_checks.
Preliminary setup
In this document, we consider that postfix is set up to use body_checks
(check the postfix doc). We also assume that postfix servers are members
of the 'postfix_servers' pikt group.
Defining unwanted extensions in attachments
Unwanted extensions are listed in an object file, RejectExtensions. All
mail attached files having this extension will be rejected by postfix.
You can customize this at your convenience, and accroding to you local
policy, to add for instance .doc, .xls, etc... to protect against macro-virus.
#if postfix_servers
RejectExtensions
au
bat
chm
cla
cmd
com
css
dll
dot
exe
hlp
hta
jse
lnk
ocx
pak
pif
pps
scr
sct
shs
src
vbe
vbs
vxd
wsh
#endif //postfix_server
RejectExtensions.cfg
|
Generating /etc/postfix/body_checks from RejectExtensions
We then needed an alarm, run withing an alert run, to
create the body_checks file. Note that in the alarm below, the body_checks
file is hardcoded. change it at will, or customize with a macro.
#if postfix_servers
GenBodyChecks
init
status active
level urgent
task "Generates postfix body_checks rules"
input file "=objdir/RejectExtensions.obj"
dat $ext 1
begin
if #fopen(BC, "/etc/postfix/body_checks", "w") == #err()
output mail "Unable to open /etc/postfix/body_checks"
die "oops!"
endif
do #write(BC, "\#Pass-all rule")
// this rule protects agains PIKT Mail warning rejects
do #write(BC, "/=bypass_tag/ OK")
rule
do #write(BC, "/^begin(-base64)? [0-9]+.*(\\.|\=2E)".
$inline."(\\?\=)?(\\.)?/ REJECT")
do #write(BC, "/^[^<]*(body|filename|name\=).*(\\.|\=2E)".
$inline."(\\?\=)?(\\.)?/ REJECT")
end
do #fclose(BC)
#endif // postfix_servers
GenBodyChecks
|
This alarm will take each line of RejectExtensions object,
and generate two lines body_checks.
The first line handled uuencoded (eventually base64 ones) inlined attachments,
and the second line the more frequent mime type attachments.
/^begin(-base64)? [0-9]+.*(\.|=2E)exe(\?=)?(\.)?/ REJECT
UUEncoded attachments match regexp (dot exe files)
|
/^[^<]*(body|filename|name=).*(\.|=2E)exe(\?=)?(\.)?/ REJECT
MIME attachments match regexp (dot exe files)
|
The alarm also add one special line at the top of body_check.
This line makes the matching process jump out and accept the current
mail message line (the 'OK' at the end of the rule instead of the usual
'REJECT'). This is a pure hack done because PIKT reporting mails (described
below) should match those body_checks regexps. Since PIKT is reporting
executable attachement attemps and giving a lot of details, it's reporting
messages were matched by postfix and bounced back. Rather painful, and
loop prone. To circumvent this just define a 'bypass_tag' macro in your
macro.cfg file. PIKT will thus generate an 'OK' match against that macro
and send postfix attachment denial reports with that tag on the line.
You should make that tag rather secure, since the tag knowledge would
allow an attacker to craft mail messages in such ways that postfix wouldn't
match them. It's appearance in mail messages should also be very unlikely,
especially in mail messages with attachments you want to filter ! So
avoid 'exe' for instance. Also, that tag shouldn't be titanesque, since
it will appear at the front of every line reporting an invalid attachment
attempt.
The value used here is just for the purpose.
|
Pass all rule (choose carefully)
|
At that stage, postfix has all it needs to reject 'bad'
mail.
Now on to reporting stuff.
Reporting mail abnormal events
The alarm below will check the postfix 'maillog' for abnormal
events. Actually, four events are checked for : forbidden attachment
attempt, header_check match, relaying attempt and unknown recipients.
There is also a fifth 'unclassified error' category, which encompass
errors we couldn't categorize above. Note the macros defined below.
PostfixRejects
init
status active
level urgent
task "Check if postfix rejected some mail"
input log "=maillog"
begin
// to ensure input source
exec wait "=touch =maillog"
set #rej_count["Attachements"] = 0
set #rej_count["Recipient"] = 0
set #rej_count["Relay"] = 0
set #rej_count["Header"] = 0
set #rej_count["Unclassified"] = 0
set $rej["Attachements"] = ""
set $rej["Recipient"] = ""
set $rej["Relay"] = ""
set $rej["Header"] = ""
set $rej["Unclassified"] = ""
rule
/// Check for attachements
=postfix_checkfor("Attachements",
".*reject: body .*name=(.*); from=<(.*)> to=<(.*)>",
"=bypass_tag $2 tried to send a suspicious attachement ($1) to $3")
/// Check for unknown recipients
=postfix_checkfor("Recipient",
".*reject: RCPT from (.*): 550 <(.*)>: User unknown; from=<(.*)> to=<(.*)>",
"=bypass_tag $3 tried to mail non-existent $4/$2 from $1")
/// Check for relay attempts
=postfix_checkfor("Relay",
".*reject: RCPT from (.*): 554 <(.*)>:.*from=<(.*)> to=<(.*)>",
"=bypass_tag $3 tried to relay thru =pikthostname to $4/$2 from $1")
/// Check for mail rejected by header rules (/etc/postfix/header_checks
=postfix_checkfor("Header",
".*: reject: header (.*)",
"=bypass_tag header_checks on =pikthostname rejected this mail: $1")
/// check for mail rejectected for some other reason
=postfix_checkfor("Unclassified",
".*: reject: (.*)",
"=bypass_tag =pikthostname rejected the following unclassified mail: $1")
end
// We report problems
=postfix_reportfor("Attachements",
"/!\\".$fixed(#rej_count["Attachements"]).
"Suspicious attachements rejected")
=postfix_reportfor("Recipient",
"/@\\ ".$fixed(#rej_count["Recipient"]).
"Non-existent recipient in enveloppes")
=postfix_reportfor("Relay",
"/!\\ ".$fixed(#rej_count["Relay"]).
"Relay attempts")
=postfix_reportfor("Header",
"/!\\ ".$fixed(#rej_count["Header"]).
"Rejected by headers")
=postfix_reportfor("Unclassified",
"/?\\".$fixed(#rej_count["Unclassified"]).
"Unclassified problems")
#endif // postfix_servers
PostfixRejects : reports postfix rejected mail
|
#if postfix_servers
bypass_tag PASS
postfix_checkfor(T, R, M) // (T) is type,(R) is regexp,(M) is message
if $inlin =~ (R)
set $rej[(T)] .= (M).$newline()
=incr(#rej_count[(T)])
next
endif
postfix_reportfor(T, M) // (T) is type, (M) is message
if $rej[(T)] ne ""
output mail (M).$newline()
output mail $rej[(T)]
endif
#endif //postfix_servers
postfix_* macros
|
Scheduling the stuff
Here are the two alerts used to schedule body_checks generation
and maillog file scruting. Note that GenerateBodyChecks has '=piktnever'
timing, because it is intended to be run manually.
#if postfix_servers
GenerateBodyChecks
timing =piktnever
alarms GenBodyChecks
#endif //postfix_servers
GenerateBodyChecks : executing GenBodyChecks alarm
|
#if postfix_servers
MailAbuse
timing 20% * * * *
mailcmd "=mailx -s 'PIKT Alert: MailAbuse on =pikthostname (PIKT)'
mblanc@erasme.org dlacroix@erasme.org"
alarms
PostfixRejects
#endif
MailAbuse : executing PostfixRejects alarm
|
That's it. Those scripts should solve most of your unwanted
attachments problems. They can be improved a lot, and if you do so,
please send us what you've done (mblanc at erasme dot org).
What you get
Here is the kind of mail you can expect from pikt parsing the log
file.
|
Subject: /!\ MailAbuse on smtp.site.org (PIKT)
Date: Fri, 8 Feb 2002 15:01:08 +0100 (CET)
From: root@smtp.site.org (root)
To: sysadmin@site.org
PIKT ALERT
Fri Feb 8 15:01:08 2002
mail1
URGENT:
PostfixRejects
Check if postfix rejected some mail
/!\ Suspicious attachements rejected
PASS badguy@somewhere.com tried to send a suspicious attachement(test.exe)
to sysadmin@site.org
/@\ Non-existent recipient in enveloppes
001d99c@kylmedia.fi tried to mail non-existent werner@site.org/werner@site.org
from unknown[192.192.192.192]
usager32@site.org tried to mail non-existent usager@site.org/usager@site.org
from poste32.al.lan[192.168.101.32]
/!\ Rejected by headers
header_checks on smtp.site.org rejected this mail: header
Received: from somemta.there.fr
(somemta.there.fr [192.192.192.192])??by smtp.site.org (Postfix)
with ESMTP id 6A8B71700D0??for ; Fri, 8 Feb 2002 14:16:04
+0100 (CET); from= to=
PIKT report about rejected mail
|
Caveats
These scripts may not encompass all your needs, nor even
fit any of them. Read them carefully before using them, especially the
body_checks regexps part. Those regexps might (read : will) match and
bounce legitimate mails, or let in supposed-to-be bounced ones.
It happened here; those regexps are the result of a trial-error loop
process. That process is certainly not complete. Those scripts are not
meant to replace a good anti-virus product, but since viruses are here
to make a living for AV vendors, you might not feel like buying one.
(no, I didn't say AV vendors are themselves creating the viruses they
fight :).
Notes
In the code above, lines may be broken for readability
and convenience.
References
Michel Blanc <mblanc at erasme dot org>
$Revision: 1.4 $ - $Date: 2002/02/19 15:28:24 $
|